tiki.js 8.3 KB

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