File modules/editor/codemirror/mode/textile/textile.js
Last commit: Sun Dec 17 01:14:09 2017 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 // Distributed under an MIT license: http://codemirror.net/LICENSE 3 4 (function(mod) { 5 if (typeof exports == "object" && typeof module == "object") { // CommonJS 6 mod(require("../../lib/codemirror")); 7 } else if (typeof define == "function" && define.amd) { // AMD 8 define(["../../lib/codemirror"], mod); 9 } else { // Plain browser env 10 mod(CodeMirror); 11 } 12 })(function(CodeMirror) { 13 "use strict"; 14 15 var TOKEN_STYLES = { 16 addition: "positive", 17 attributes: "attribute", 18 bold: "strong", 19 cite: "keyword", 20 code: "atom", 21 definitionList: "number", 22 deletion: "negative", 23 div: "punctuation", 24 em: "em", 25 footnote: "variable", 26 footCite: "qualifier", 27 header: "header", 28 html: "comment", 29 image: "string", 30 italic: "em", 31 link: "link", 32 linkDefinition: "link", 33 list1: "variable-2", 34 list2: "variable-3", 35 list3: "keyword", 36 notextile: "string-2", 37 pre: "operator", 38 p: "property", 39 quote: "bracket", 40 span: "quote", 41 specialChar: "tag", 42 strong: "strong", 43 sub: "builtin", 44 sup: "builtin", 45 table: "variable-3", 46 tableHeading: "operator" 47 }; 48 49 function startNewLine(stream, state) { 50 state.mode = Modes.newLayout; 51 state.tableHeading = false; 52 53 if (state.layoutType === "definitionList" && state.spanningLayout && 54 stream.match(RE("definitionListEnd"), false)) 55 state.spanningLayout = false; 56 } 57 58 function handlePhraseModifier(stream, state, ch) { 59 if (ch === "_") { 60 if (stream.eat("_")) 61 return togglePhraseModifier(stream, state, "italic", /__/, 2); 62 else 63 return togglePhraseModifier(stream, state, "em", /_/, 1); 64 } 65 66 if (ch === "*") { 67 if (stream.eat("*")) { 68 return togglePhraseModifier(stream, state, "bold", /\*\*/, 2); 69 } 70 return togglePhraseModifier(stream, state, "strong", /\*/, 1); 71 } 72 73 if (ch === "[") { 74 if (stream.match(/\d+\]/)) state.footCite = true; 75 return tokenStyles(state); 76 } 77 78 if (ch === "(") { 79 var spec = stream.match(/^(r|tm|c)\)/); 80 if (spec) 81 return tokenStylesWith(state, TOKEN_STYLES.specialChar); 82 } 83 84 if (ch === "<" && stream.match(/(\w+)[^>]+>[^<]+<\/\1>/)) 85 return tokenStylesWith(state, TOKEN_STYLES.html); 86 87 if (ch === "?" && stream.eat("?")) 88 return togglePhraseModifier(stream, state, "cite", /\?\?/, 2); 89 90 if (ch === "=" && stream.eat("=")) 91 return togglePhraseModifier(stream, state, "notextile", /==/, 2); 92 93 if (ch === "-" && !stream.eat("-")) 94 return togglePhraseModifier(stream, state, "deletion", /-/, 1); 95 96 if (ch === "+") 97 return togglePhraseModifier(stream, state, "addition", /\+/, 1); 98 99 if (ch === "~") 100 return togglePhraseModifier(stream, state, "sub", /~/, 1); 101 102 if (ch === "^") 103 return togglePhraseModifier(stream, state, "sup", /\^/, 1); 104 105 if (ch === "%") 106 return togglePhraseModifier(stream, state, "span", /%/, 1); 107 108 if (ch === "@") 109 return togglePhraseModifier(stream, state, "code", /@/, 1); 110 111 if (ch === "!") { 112 var type = togglePhraseModifier(stream, state, "image", /(?:\([^\)]+\))?!/, 1); 113 stream.match(/^:\S+/); // optional Url portion 114 return type; 115 } 116 return tokenStyles(state); 117 } 118 119 function togglePhraseModifier(stream, state, phraseModifier, closeRE, openSize) { 120 var charBefore = stream.pos > openSize ? stream.string.charAt(stream.pos - openSize - 1) : null; 121 var charAfter = stream.peek(); 122 if (state[phraseModifier]) { 123 if ((!charAfter || /\W/.test(charAfter)) && charBefore && /\S/.test(charBefore)) { 124 var type = tokenStyles(state); 125 state[phraseModifier] = false; 126 return type; 127 } 128 } else if ((!charBefore || /\W/.test(charBefore)) && charAfter && /\S/.test(charAfter) && 129 stream.match(new RegExp("^.*\\S" + closeRE.source + "(?:\\W|$)"), false)) { 130 state[phraseModifier] = true; 131 state.mode = Modes.attributes; 132 } 133 return tokenStyles(state); 134 }; 135 136 function tokenStyles(state) { 137 var disabled = textileDisabled(state); 138 if (disabled) return disabled; 139 140 var styles = []; 141 if (state.layoutType) styles.push(TOKEN_STYLES[state.layoutType]); 142 143 styles = styles.concat(activeStyles( 144 state, "addition", "bold", "cite", "code", "deletion", "em", "footCite", 145 "image", "italic", "link", "span", "strong", "sub", "sup", "table", "tableHeading")); 146 147 if (state.layoutType === "header") 148 styles.push(TOKEN_STYLES.header + "-" + state.header); 149 150 return styles.length ? styles.join(" ") : null; 151 } 152 153 function textileDisabled(state) { 154 var type = state.layoutType; 155 156 switch(type) { 157 case "notextile": 158 case "code": 159 case "pre": 160 return TOKEN_STYLES[type]; 161 default: 162 if (state.notextile) 163 return TOKEN_STYLES.notextile + (type ? (" " + TOKEN_STYLES[type]) : ""); 164 return null; 165 } 166 } 167 168 function tokenStylesWith(state, extraStyles) { 169 var disabled = textileDisabled(state); 170 if (disabled) return disabled; 171 172 var type = tokenStyles(state); 173 if (extraStyles) 174 return type ? (type + " " + extraStyles) : extraStyles; 175 else 176 return type; 177 } 178 179 function activeStyles(state) { 180 var styles = []; 181 for (var i = 1; i < arguments.length; ++i) { 182 if (state[arguments[i]]) 183 styles.push(TOKEN_STYLES[arguments[i]]); 184 } 185 return styles; 186 } 187 188 function blankLine(state) { 189 var spanningLayout = state.spanningLayout, type = state.layoutType; 190 191 for (var key in state) if (state.hasOwnProperty(key)) 192 delete state[key]; 193 194 state.mode = Modes.newLayout; 195 if (spanningLayout) { 196 state.layoutType = type; 197 state.spanningLayout = true; 198 } 199 } 200 201 var REs = { 202 cache: {}, 203 single: { 204 bc: "bc", 205 bq: "bq", 206 definitionList: /- .*?:=+/, 207 definitionListEnd: /.*=:\s*$/, 208 div: "div", 209 drawTable: /\|.*\|/, 210 foot: /fn\d+/, 211 header: /h[1-6]/, 212 html: /\s*<(?:\/)?(\w+)(?:[^>]+)?>(?:[^<]+<\/\1>)?/, 213 link: /[^"]+":\S/, 214 linkDefinition: /\[[^\s\]]+\]\S+/, 215 list: /(?:#+|\*+)/, 216 notextile: "notextile", 217 para: "p", 218 pre: "pre", 219 table: "table", 220 tableCellAttributes: /[\/\\]\d+/, 221 tableHeading: /\|_\./, 222 tableText: /[^"_\*\[\(\?\+~\^%@|-]+/, 223 text: /[^!"_=\*\[\(<\?\+~\^%@-]+/ 224 }, 225 attributes: { 226 align: /(?:<>|<|>|=)/, 227 selector: /\([^\(][^\)]+\)/, 228 lang: /\[[^\[\]]+\]/, 229 pad: /(?:\(+|\)+){1,2}/, 230 css: /\{[^\}]+\}/ 231 }, 232 createRe: function(name) { 233 switch (name) { 234 case "drawTable": 235 return REs.makeRe("^", REs.single.drawTable, "$"); 236 case "html": 237 return REs.makeRe("^", REs.single.html, "(?:", REs.single.html, ")*", "$"); 238 case "linkDefinition": 239 return REs.makeRe("^", REs.single.linkDefinition, "$"); 240 case "listLayout": 241 return REs.makeRe("^", REs.single.list, RE("allAttributes"), "*\\s+"); 242 case "tableCellAttributes": 243 return REs.makeRe("^", REs.choiceRe(REs.single.tableCellAttributes, 244 RE("allAttributes")), "+\\."); 245 case "type": 246 return REs.makeRe("^", RE("allTypes")); 247 case "typeLayout": 248 return REs.makeRe("^", RE("allTypes"), RE("allAttributes"), 249 "*\\.\\.?", "(\\s+|$)"); 250 case "attributes": 251 return REs.makeRe("^", RE("allAttributes"), "+"); 252 253 case "allTypes": 254 return REs.choiceRe(REs.single.div, REs.single.foot, 255 REs.single.header, REs.single.bc, REs.single.bq, 256 REs.single.notextile, REs.single.pre, REs.single.table, 257 REs.single.para); 258 259 case "allAttributes": 260 return REs.choiceRe(REs.attributes.selector, REs.attributes.css, 261 REs.attributes.lang, REs.attributes.align, REs.attributes.pad); 262 263 default: 264 return REs.makeRe("^", REs.single[name]); 265 } 266 }, 267 makeRe: function() { 268 var pattern = ""; 269 for (var i = 0; i < arguments.length; ++i) { 270 var arg = arguments[i]; 271 pattern += (typeof arg === "string") ? arg : arg.source; 272 } 273 return new RegExp(pattern); 274 }, 275 choiceRe: function() { 276 var parts = [arguments[0]]; 277 for (var i = 1; i < arguments.length; ++i) { 278 parts[i * 2 - 1] = "|"; 279 parts[i * 2] = arguments[i]; 280 } 281 282 parts.unshift("(?:"); 283 parts.push(")"); 284 return REs.makeRe.apply(null, parts); 285 } 286 }; 287 288 function RE(name) { 289 return (REs.cache[name] || (REs.cache[name] = REs.createRe(name))); 290 } 291 292 var Modes = { 293 newLayout: function(stream, state) { 294 if (stream.match(RE("typeLayout"), false)) { 295 state.spanningLayout = false; 296 return (state.mode = Modes.blockType)(stream, state); 297 } 298 var newMode; 299 if (!textileDisabled(state)) { 300 if (stream.match(RE("listLayout"), false)) 301 newMode = Modes.list; 302 else if (stream.match(RE("drawTable"), false)) 303 newMode = Modes.table; 304 else if (stream.match(RE("linkDefinition"), false)) 305 newMode = Modes.linkDefinition; 306 else if (stream.match(RE("definitionList"))) 307 newMode = Modes.definitionList; 308 else if (stream.match(RE("html"), false)) 309 newMode = Modes.html; 310 } 311 return (state.mode = (newMode || Modes.text))(stream, state); 312 }, 313 314 blockType: function(stream, state) { 315 var match, type; 316 state.layoutType = null; 317 318 if (match = stream.match(RE("type"))) 319 type = match[0]; 320 else 321 return (state.mode = Modes.text)(stream, state); 322 323 if (match = type.match(RE("header"))) { 324 state.layoutType = "header"; 325 state.header = parseInt(match[0][1]); 326 } else if (type.match(RE("bq"))) { 327 state.layoutType = "quote"; 328 } else if (type.match(RE("bc"))) { 329 state.layoutType = "code"; 330 } else if (type.match(RE("foot"))) { 331 state.layoutType = "footnote"; 332 } else if (type.match(RE("notextile"))) { 333 state.layoutType = "notextile"; 334 } else if (type.match(RE("pre"))) { 335 state.layoutType = "pre"; 336 } else if (type.match(RE("div"))) { 337 state.layoutType = "div"; 338 } else if (type.match(RE("table"))) { 339 state.layoutType = "table"; 340 } 341 342 state.mode = Modes.attributes; 343 return tokenStyles(state); 344 }, 345 346 text: function(stream, state) { 347 if (stream.match(RE("text"))) return tokenStyles(state); 348 349 var ch = stream.next(); 350 if (ch === '"') 351 return (state.mode = Modes.link)(stream, state); 352 return handlePhraseModifier(stream, state, ch); 353 }, 354 355 attributes: function(stream, state) { 356 state.mode = Modes.layoutLength; 357 358 if (stream.match(RE("attributes"))) 359 return tokenStylesWith(state, TOKEN_STYLES.attributes); 360 else 361 return tokenStyles(state); 362 }, 363 364 layoutLength: function(stream, state) { 365 if (stream.eat(".") && stream.eat(".")) 366 state.spanningLayout = true; 367 368 state.mode = Modes.text; 369 return tokenStyles(state); 370 }, 371 372 list: function(stream, state) { 373 var match = stream.match(RE("list")); 374 state.listDepth = match[0].length; 375 var listMod = (state.listDepth - 1) % 3; 376 if (!listMod) 377 state.layoutType = "list1"; 378 else if (listMod === 1) 379 state.layoutType = "list2"; 380 else 381 state.layoutType = "list3"; 382 383 state.mode = Modes.attributes; 384 return tokenStyles(state); 385 }, 386 387 link: function(stream, state) { 388 state.mode = Modes.text; 389 if (stream.match(RE("link"))) { 390 stream.match(/\S+/); 391 return tokenStylesWith(state, TOKEN_STYLES.link); 392 } 393 return tokenStyles(state); 394 }, 395 396 linkDefinition: function(stream, state) { 397 stream.skipToEnd(); 398 return tokenStylesWith(state, TOKEN_STYLES.linkDefinition); 399 }, 400 401 definitionList: function(stream, state) { 402 stream.match(RE("definitionList")); 403 404 state.layoutType = "definitionList"; 405 406 if (stream.match(/\s*$/)) 407 state.spanningLayout = true; 408 else 409 state.mode = Modes.attributes; 410 411 return tokenStyles(state); 412 }, 413 414 html: function(stream, state) { 415 stream.skipToEnd(); 416 return tokenStylesWith(state, TOKEN_STYLES.html); 417 }, 418 419 table: function(stream, state) { 420 state.layoutType = "table"; 421 return (state.mode = Modes.tableCell)(stream, state); 422 }, 423 424 tableCell: function(stream, state) { 425 if (stream.match(RE("tableHeading"))) 426 state.tableHeading = true; 427 else 428 stream.eat("|"); 429 430 state.mode = Modes.tableCellAttributes; 431 return tokenStyles(state); 432 }, 433 434 tableCellAttributes: function(stream, state) { 435 state.mode = Modes.tableText; 436 437 if (stream.match(RE("tableCellAttributes"))) 438 return tokenStylesWith(state, TOKEN_STYLES.attributes); 439 else 440 return tokenStyles(state); 441 }, 442 443 tableText: function(stream, state) { 444 if (stream.match(RE("tableText"))) 445 return tokenStyles(state); 446 447 if (stream.peek() === "|") { // end of cell 448 state.mode = Modes.tableCell; 449 return tokenStyles(state); 450 } 451 return handlePhraseModifier(stream, state, stream.next()); 452 } 453 }; 454 455 CodeMirror.defineMode("textile", function() { 456 return { 457 startState: function() { 458 return { mode: Modes.newLayout }; 459 }, 460 token: function(stream, state) { 461 if (stream.sol()) startNewLine(stream, state); 462 return state.mode(stream, state); 463 }, 464 blankLine: blankLine 465 }; 466 }); 467 468 CodeMirror.defineMIME("text/x-textile", "textile"); 469 });
Downloadmodules/editor/codemirror/mode/textile/textile.js
History Sun, 17 Dec 2017 01:14:09 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.