tiki.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. ;(function (mod) {
  4. if (typeof exports == 'object' && typeof module == 'object')
  5. // CommonJS
  6. mod(require('../../lib/codemirror'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. CodeMirror.defineMode('tiki', function (config) {
  15. function inBlock(style, terminator, returnTokenizer) {
  16. return function (stream, state) {
  17. while (!stream.eol()) {
  18. if (stream.match(terminator)) {
  19. state.tokenize = inText
  20. break
  21. }
  22. stream.next()
  23. }
  24. if (returnTokenizer) state.tokenize = returnTokenizer
  25. return style
  26. }
  27. }
  28. function inLine(style) {
  29. return function (stream, state) {
  30. while (!stream.eol()) {
  31. stream.next()
  32. }
  33. state.tokenize = inText
  34. return style
  35. }
  36. }
  37. function inText(stream, state) {
  38. function chain(parser) {
  39. state.tokenize = parser
  40. return parser(stream, state)
  41. }
  42. var sol = stream.sol()
  43. var ch = stream.next()
  44. //non start of line
  45. switch (
  46. ch //switch is generally much faster than if, so it is used here
  47. ) {
  48. case '{': //plugin
  49. stream.eat('/')
  50. stream.eatSpace()
  51. stream.eatWhile(/[^\s\u00a0=\"\'\/?(}]/)
  52. state.tokenize = inPlugin
  53. return 'tag'
  54. case '_': //bold
  55. if (stream.eat('_')) return chain(inBlock('strong', '__', inText))
  56. break
  57. case "'": //italics
  58. if (stream.eat("'")) return chain(inBlock('em', "''", inText))
  59. break
  60. case '(': // Wiki Link
  61. if (stream.eat('(')) return chain(inBlock('variable-2', '))', inText))
  62. break
  63. case '[': // Weblink
  64. return chain(inBlock('variable-3', ']', inText))
  65. break
  66. case '|': //table
  67. if (stream.eat('|')) return chain(inBlock('comment', '||'))
  68. break
  69. case '-':
  70. if (stream.eat('=')) {
  71. //titleBar
  72. return chain(inBlock('header string', '=-', inText))
  73. } else if (stream.eat('-')) {
  74. //deleted
  75. return chain(inBlock('error tw-deleted', '--', inText))
  76. }
  77. break
  78. case '=': //underline
  79. if (stream.match('==')) return chain(inBlock('tw-underline', '===', inText))
  80. break
  81. case ':':
  82. if (stream.eat(':')) return chain(inBlock('comment', '::'))
  83. break
  84. case '^': //box
  85. return chain(inBlock('tw-box', '^'))
  86. break
  87. case '~': //np
  88. if (stream.match('np~')) return chain(inBlock('meta', '~/np~'))
  89. break
  90. }
  91. //start of line types
  92. if (sol) {
  93. switch (ch) {
  94. case '!': //header at start of line
  95. if (stream.match('!!!!!')) {
  96. return chain(inLine('header string'))
  97. } else if (stream.match('!!!!')) {
  98. return chain(inLine('header string'))
  99. } else if (stream.match('!!!')) {
  100. return chain(inLine('header string'))
  101. } else if (stream.match('!!')) {
  102. return chain(inLine('header string'))
  103. } else {
  104. return chain(inLine('header string'))
  105. }
  106. break
  107. case '*': //unordered list line item, or <li /> at start of line
  108. case '#': //ordered list line item, or <li /> at start of line
  109. case '+': //ordered list line item, or <li /> at start of line
  110. return chain(inLine('tw-listitem bracket'))
  111. break
  112. }
  113. }
  114. //stream.eatWhile(/[&{]/); was eating up plugins, turned off to act less like html and more like tiki
  115. return null
  116. }
  117. var indentUnit = config.indentUnit
  118. // Return variables for tokenizers
  119. var pluginName, type
  120. function inPlugin(stream, state) {
  121. var ch = stream.next()
  122. var peek = stream.peek()
  123. if (ch == '}') {
  124. state.tokenize = inText
  125. //type = ch == ")" ? "endPlugin" : "selfclosePlugin"; inPlugin
  126. return 'tag'
  127. } else if (ch == '(' || ch == ')') {
  128. return 'bracket'
  129. } else if (ch == '=') {
  130. type = 'equals'
  131. if (peek == '>') {
  132. stream.next()
  133. peek = stream.peek()
  134. }
  135. //here we detect values directly after equal character with no quotes
  136. if (!/[\'\"]/.test(peek)) {
  137. state.tokenize = inAttributeNoQuote()
  138. }
  139. //end detect values
  140. return 'operator'
  141. } else if (/[\'\"]/.test(ch)) {
  142. state.tokenize = inAttribute(ch)
  143. return state.tokenize(stream, state)
  144. } else {
  145. stream.eatWhile(/[^\s\u00a0=\"\'\/?]/)
  146. return 'keyword'
  147. }
  148. }
  149. function inAttribute(quote) {
  150. return function (stream, state) {
  151. while (!stream.eol()) {
  152. if (stream.next() == quote) {
  153. state.tokenize = inPlugin
  154. break
  155. }
  156. }
  157. return 'string'
  158. }
  159. }
  160. function inAttributeNoQuote() {
  161. return function (stream, state) {
  162. while (!stream.eol()) {
  163. var ch = stream.next()
  164. var peek = stream.peek()
  165. if (ch == ' ' || ch == ',' || /[ )}]/.test(peek)) {
  166. state.tokenize = inPlugin
  167. break
  168. }
  169. }
  170. return 'string'
  171. }
  172. }
  173. var curState, setStyle
  174. function pass() {
  175. for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i])
  176. }
  177. function cont() {
  178. pass.apply(null, arguments)
  179. return true
  180. }
  181. function pushContext(pluginName, startOfLine) {
  182. var noIndent = curState.context && curState.context.noIndent
  183. curState.context = {
  184. prev: curState.context,
  185. pluginName: pluginName,
  186. indent: curState.indented,
  187. startOfLine: startOfLine,
  188. noIndent: noIndent,
  189. }
  190. }
  191. function popContext() {
  192. if (curState.context) curState.context = curState.context.prev
  193. }
  194. function element(type) {
  195. if (type == 'openPlugin') {
  196. curState.pluginName = pluginName
  197. return cont(attributes, endplugin(curState.startOfLine))
  198. } else if (type == 'closePlugin') {
  199. var err = false
  200. if (curState.context) {
  201. err = curState.context.pluginName != pluginName
  202. popContext()
  203. } else {
  204. err = true
  205. }
  206. if (err) setStyle = 'error'
  207. return cont(endcloseplugin(err))
  208. } else if (type == 'string') {
  209. if (!curState.context || curState.context.name != '!cdata') pushContext('!cdata')
  210. if (curState.tokenize == inText) popContext()
  211. return cont()
  212. } else return cont()
  213. }
  214. function endplugin(startOfLine) {
  215. return function (type) {
  216. if (type == 'selfclosePlugin' || type == 'endPlugin') return cont()
  217. if (type == 'endPlugin') {
  218. pushContext(curState.pluginName, startOfLine)
  219. return cont()
  220. }
  221. return cont()
  222. }
  223. }
  224. function endcloseplugin(err) {
  225. return function (type) {
  226. if (err) setStyle = 'error'
  227. if (type == 'endPlugin') return cont()
  228. return pass()
  229. }
  230. }
  231. function attributes(type) {
  232. if (type == 'keyword') {
  233. setStyle = 'attribute'
  234. return cont(attributes)
  235. }
  236. if (type == 'equals') return cont(attvalue, attributes)
  237. return pass()
  238. }
  239. function attvalue(type) {
  240. if (type == 'keyword') {
  241. setStyle = 'string'
  242. return cont()
  243. }
  244. if (type == 'string') return cont(attvaluemaybe)
  245. return pass()
  246. }
  247. function attvaluemaybe(type) {
  248. if (type == 'string') return cont(attvaluemaybe)
  249. else return pass()
  250. }
  251. return {
  252. startState: function () {
  253. return { tokenize: inText, cc: [], indented: 0, startOfLine: true, pluginName: null, context: null }
  254. },
  255. token: function (stream, state) {
  256. if (stream.sol()) {
  257. state.startOfLine = true
  258. state.indented = stream.indentation()
  259. }
  260. if (stream.eatSpace()) return null
  261. setStyle = type = pluginName = null
  262. var style = state.tokenize(stream, state)
  263. if ((style || type) && style != 'comment') {
  264. curState = state
  265. while (true) {
  266. var comb = state.cc.pop() || element
  267. if (comb(type || style)) break
  268. }
  269. }
  270. state.startOfLine = false
  271. return setStyle || style
  272. },
  273. indent: function (state, textAfter) {
  274. var context = state.context
  275. if (context && context.noIndent) return 0
  276. if (context && /^{\//.test(textAfter)) context = context.prev
  277. while (context && !context.startOfLine) context = context.prev
  278. if (context) return context.indent + indentUnit
  279. else return 0
  280. },
  281. electricChars: '/',
  282. }
  283. })
  284. CodeMirror.defineMIME('text/tiki', 'tiki')
  285. })