470 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			470 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| // CodeMirror, copyright (c) by Marijn Haverbeke and others
 | |
| // Distributed under an MIT license: http://codemirror.net/LICENSE
 | |
| 
 | |
| (function(mod) {
 | |
|   if (typeof exports == "object" && typeof module == "object") { // CommonJS
 | |
|     mod(require("../../lib/codemirror"));
 | |
|   } else if (typeof define == "function" && define.amd) { // AMD
 | |
|     define(["../../lib/codemirror"], mod);
 | |
|   } else { // Plain browser env
 | |
|     mod(CodeMirror);
 | |
|   }
 | |
| })(function(CodeMirror) {
 | |
|   "use strict";
 | |
| 
 | |
|   var TOKEN_STYLES = {
 | |
|     addition: "positive",
 | |
|     attributes: "attribute",
 | |
|     bold: "strong",
 | |
|     cite: "keyword",
 | |
|     code: "atom",
 | |
|     definitionList: "number",
 | |
|     deletion: "negative",
 | |
|     div: "punctuation",
 | |
|     em: "em",
 | |
|     footnote: "variable",
 | |
|     footCite: "qualifier",
 | |
|     header: "header",
 | |
|     html: "comment",
 | |
|     image: "string",
 | |
|     italic: "em",
 | |
|     link: "link",
 | |
|     linkDefinition: "link",
 | |
|     list1: "variable-2",
 | |
|     list2: "variable-3",
 | |
|     list3: "keyword",
 | |
|     notextile: "string-2",
 | |
|     pre: "operator",
 | |
|     p: "property",
 | |
|     quote: "bracket",
 | |
|     span: "quote",
 | |
|     specialChar: "tag",
 | |
|     strong: "strong",
 | |
|     sub: "builtin",
 | |
|     sup: "builtin",
 | |
|     table: "variable-3",
 | |
|     tableHeading: "operator"
 | |
|   };
 | |
| 
 | |
|   function startNewLine(stream, state) {
 | |
|     state.mode = Modes.newLayout;
 | |
|     state.tableHeading = false;
 | |
| 
 | |
|     if (state.layoutType === "definitionList" && state.spanningLayout &&
 | |
|         stream.match(RE("definitionListEnd"), false))
 | |
|       state.spanningLayout = false;
 | |
|   }
 | |
| 
 | |
|   function handlePhraseModifier(stream, state, ch) {
 | |
|     if (ch === "_") {
 | |
|       if (stream.eat("_"))
 | |
|         return togglePhraseModifier(stream, state, "italic", /__/, 2);
 | |
|       else
 | |
|         return togglePhraseModifier(stream, state, "em", /_/, 1);
 | |
|     }
 | |
| 
 | |
|     if (ch === "*") {
 | |
|       if (stream.eat("*")) {
 | |
|         return togglePhraseModifier(stream, state, "bold", /\*\*/, 2);
 | |
|       }
 | |
|       return togglePhraseModifier(stream, state, "strong", /\*/, 1);
 | |
|     }
 | |
| 
 | |
|     if (ch === "[") {
 | |
|       if (stream.match(/\d+\]/)) state.footCite = true;
 | |
|       return tokenStyles(state);
 | |
|     }
 | |
| 
 | |
|     if (ch === "(") {
 | |
|       var spec = stream.match(/^(r|tm|c)\)/);
 | |
|       if (spec)
 | |
|         return tokenStylesWith(state, TOKEN_STYLES.specialChar);
 | |
|     }
 | |
| 
 | |
|     if (ch === "<" && stream.match(/(\w+)[^>]+>[^<]+<\/\1>/))
 | |
|       return tokenStylesWith(state, TOKEN_STYLES.html);
 | |
| 
 | |
|     if (ch === "?" && stream.eat("?"))
 | |
|       return togglePhraseModifier(stream, state, "cite", /\?\?/, 2);
 | |
| 
 | |
|     if (ch === "=" && stream.eat("="))
 | |
|       return togglePhraseModifier(stream, state, "notextile", /==/, 2);
 | |
| 
 | |
|     if (ch === "-" && !stream.eat("-"))
 | |
|       return togglePhraseModifier(stream, state, "deletion", /-/, 1);
 | |
| 
 | |
|     if (ch === "+")
 | |
|       return togglePhraseModifier(stream, state, "addition", /\+/, 1);
 | |
| 
 | |
|     if (ch === "~")
 | |
|       return togglePhraseModifier(stream, state, "sub", /~/, 1);
 | |
| 
 | |
|     if (ch === "^")
 | |
|       return togglePhraseModifier(stream, state, "sup", /\^/, 1);
 | |
| 
 | |
|     if (ch === "%")
 | |
|       return togglePhraseModifier(stream, state, "span", /%/, 1);
 | |
| 
 | |
|     if (ch === "@")
 | |
|       return togglePhraseModifier(stream, state, "code", /@/, 1);
 | |
| 
 | |
|     if (ch === "!") {
 | |
|       var type = togglePhraseModifier(stream, state, "image", /(?:\([^\)]+\))?!/, 1);
 | |
|       stream.match(/^:\S+/); // optional Url portion
 | |
|       return type;
 | |
|     }
 | |
|     return tokenStyles(state);
 | |
|   }
 | |
| 
 | |
|   function togglePhraseModifier(stream, state, phraseModifier, closeRE, openSize) {
 | |
|     var charBefore = stream.pos > openSize ? stream.string.charAt(stream.pos - openSize - 1) : null;
 | |
|     var charAfter = stream.peek();
 | |
|     if (state[phraseModifier]) {
 | |
|       if ((!charAfter || /\W/.test(charAfter)) && charBefore && /\S/.test(charBefore)) {
 | |
|         var type = tokenStyles(state);
 | |
|         state[phraseModifier] = false;
 | |
|         return type;
 | |
|       }
 | |
|     } else if ((!charBefore || /\W/.test(charBefore)) && charAfter && /\S/.test(charAfter) &&
 | |
|                stream.match(new RegExp("^.*\\S" + closeRE.source + "(?:\\W|$)"), false)) {
 | |
|       state[phraseModifier] = true;
 | |
|       state.mode = Modes.attributes;
 | |
|     }
 | |
|     return tokenStyles(state);
 | |
|   };
 | |
| 
 | |
|   function tokenStyles(state) {
 | |
|     var disabled = textileDisabled(state);
 | |
|     if (disabled) return disabled;
 | |
| 
 | |
|     var styles = [];
 | |
|     if (state.layoutType) styles.push(TOKEN_STYLES[state.layoutType]);
 | |
| 
 | |
|     styles = styles.concat(activeStyles(
 | |
|       state, "addition", "bold", "cite", "code", "deletion", "em", "footCite",
 | |
|       "image", "italic", "link", "span", "strong", "sub", "sup", "table", "tableHeading"));
 | |
| 
 | |
|     if (state.layoutType === "header")
 | |
|       styles.push(TOKEN_STYLES.header + "-" + state.header);
 | |
| 
 | |
|     return styles.length ? styles.join(" ") : null;
 | |
|   }
 | |
| 
 | |
|   function textileDisabled(state) {
 | |
|     var type = state.layoutType;
 | |
| 
 | |
|     switch(type) {
 | |
|     case "notextile":
 | |
|     case "code":
 | |
|     case "pre":
 | |
|       return TOKEN_STYLES[type];
 | |
|     default:
 | |
|       if (state.notextile)
 | |
|         return TOKEN_STYLES.notextile + (type ? (" " + TOKEN_STYLES[type]) : "");
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function tokenStylesWith(state, extraStyles) {
 | |
|     var disabled = textileDisabled(state);
 | |
|     if (disabled) return disabled;
 | |
| 
 | |
|     var type = tokenStyles(state);
 | |
|     if (extraStyles)
 | |
|       return type ? (type + " " + extraStyles) : extraStyles;
 | |
|     else
 | |
|       return type;
 | |
|   }
 | |
| 
 | |
|   function activeStyles(state) {
 | |
|     var styles = [];
 | |
|     for (var i = 1; i < arguments.length; ++i) {
 | |
|       if (state[arguments[i]])
 | |
|         styles.push(TOKEN_STYLES[arguments[i]]);
 | |
|     }
 | |
|     return styles;
 | |
|   }
 | |
| 
 | |
|   function blankLine(state) {
 | |
|     var spanningLayout = state.spanningLayout, type = state.layoutType;
 | |
| 
 | |
|     for (var key in state) if (state.hasOwnProperty(key))
 | |
|       delete state[key];
 | |
| 
 | |
|     state.mode = Modes.newLayout;
 | |
|     if (spanningLayout) {
 | |
|       state.layoutType = type;
 | |
|       state.spanningLayout = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   var REs = {
 | |
|     cache: {},
 | |
|     single: {
 | |
|       bc: "bc",
 | |
|       bq: "bq",
 | |
|       definitionList: /- [^(?::=)]+:=+/,
 | |
|       definitionListEnd: /.*=:\s*$/,
 | |
|       div: "div",
 | |
|       drawTable: /\|.*\|/,
 | |
|       foot: /fn\d+/,
 | |
|       header: /h[1-6]/,
 | |
|       html: /\s*<(?:\/)?(\w+)(?:[^>]+)?>(?:[^<]+<\/\1>)?/,
 | |
|       link: /[^"]+":\S/,
 | |
|       linkDefinition: /\[[^\s\]]+\]\S+/,
 | |
|       list: /(?:#+|\*+)/,
 | |
|       notextile: "notextile",
 | |
|       para: "p",
 | |
|       pre: "pre",
 | |
|       table: "table",
 | |
|       tableCellAttributes: /[\/\\]\d+/,
 | |
|       tableHeading: /\|_\./,
 | |
|       tableText: /[^"_\*\[\(\?\+~\^%@|-]+/,
 | |
|       text: /[^!"_=\*\[\(<\?\+~\^%@-]+/
 | |
|     },
 | |
|     attributes: {
 | |
|       align: /(?:<>|<|>|=)/,
 | |
|       selector: /\([^\(][^\)]+\)/,
 | |
|       lang: /\[[^\[\]]+\]/,
 | |
|       pad: /(?:\(+|\)+){1,2}/,
 | |
|       css: /\{[^\}]+\}/
 | |
|     },
 | |
|     createRe: function(name) {
 | |
|       switch (name) {
 | |
|       case "drawTable":
 | |
|         return REs.makeRe("^", REs.single.drawTable, "$");
 | |
|       case "html":
 | |
|         return REs.makeRe("^", REs.single.html, "(?:", REs.single.html, ")*", "$");
 | |
|       case "linkDefinition":
 | |
|         return REs.makeRe("^", REs.single.linkDefinition, "$");
 | |
|       case "listLayout":
 | |
|         return REs.makeRe("^", REs.single.list, RE("allAttributes"), "*\\s+");
 | |
|       case "tableCellAttributes":
 | |
|         return REs.makeRe("^", REs.choiceRe(REs.single.tableCellAttributes,
 | |
|                                             RE("allAttributes")), "+\\.");
 | |
|       case "type":
 | |
|         return REs.makeRe("^", RE("allTypes"));
 | |
|       case "typeLayout":
 | |
|         return REs.makeRe("^", RE("allTypes"), RE("allAttributes"),
 | |
|                           "*\\.\\.?", "(\\s+|$)");
 | |
|       case "attributes":
 | |
|         return REs.makeRe("^", RE("allAttributes"), "+");
 | |
| 
 | |
|       case "allTypes":
 | |
|         return REs.choiceRe(REs.single.div, REs.single.foot,
 | |
|                             REs.single.header, REs.single.bc, REs.single.bq,
 | |
|                             REs.single.notextile, REs.single.pre, REs.single.table,
 | |
|                             REs.single.para);
 | |
| 
 | |
|       case "allAttributes":
 | |
|         return REs.choiceRe(REs.attributes.selector, REs.attributes.css,
 | |
|                             REs.attributes.lang, REs.attributes.align, REs.attributes.pad);
 | |
| 
 | |
|       default:
 | |
|         return REs.makeRe("^", REs.single[name]);
 | |
|       }
 | |
|     },
 | |
|     makeRe: function() {
 | |
|       var pattern = "";
 | |
|       for (var i = 0; i < arguments.length; ++i) {
 | |
|         var arg = arguments[i];
 | |
|         pattern += (typeof arg === "string") ? arg : arg.source;
 | |
|       }
 | |
|       return new RegExp(pattern);
 | |
|     },
 | |
|     choiceRe: function() {
 | |
|       var parts = [arguments[0]];
 | |
|       for (var i = 1; i < arguments.length; ++i) {
 | |
|         parts[i * 2 - 1] = "|";
 | |
|         parts[i * 2] = arguments[i];
 | |
|       }
 | |
| 
 | |
|       parts.unshift("(?:");
 | |
|       parts.push(")");
 | |
|       return REs.makeRe.apply(null, parts);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   function RE(name) {
 | |
|     return (REs.cache[name] || (REs.cache[name] = REs.createRe(name)));
 | |
|   }
 | |
| 
 | |
|   var Modes = {
 | |
|     newLayout: function(stream, state) {
 | |
|       if (stream.match(RE("typeLayout"), false)) {
 | |
|         state.spanningLayout = false;
 | |
|         return (state.mode = Modes.blockType)(stream, state);
 | |
|       }
 | |
|       var newMode;
 | |
|       if (!textileDisabled(state)) {
 | |
|         if (stream.match(RE("listLayout"), false))
 | |
|           newMode = Modes.list;
 | |
|         else if (stream.match(RE("drawTable"), false))
 | |
|           newMode = Modes.table;
 | |
|         else if (stream.match(RE("linkDefinition"), false))
 | |
|           newMode = Modes.linkDefinition;
 | |
|         else if (stream.match(RE("definitionList")))
 | |
|           newMode = Modes.definitionList;
 | |
|         else if (stream.match(RE("html"), false))
 | |
|           newMode = Modes.html;
 | |
|       }
 | |
|       return (state.mode = (newMode || Modes.text))(stream, state);
 | |
|     },
 | |
| 
 | |
|     blockType: function(stream, state) {
 | |
|       var match, type;
 | |
|       state.layoutType = null;
 | |
| 
 | |
|       if (match = stream.match(RE("type")))
 | |
|         type = match[0];
 | |
|       else
 | |
|         return (state.mode = Modes.text)(stream, state);
 | |
| 
 | |
|       if (match = type.match(RE("header"))) {
 | |
|         state.layoutType = "header";
 | |
|         state.header = parseInt(match[0][1]);
 | |
|       } else if (type.match(RE("bq"))) {
 | |
|         state.layoutType = "quote";
 | |
|       } else if (type.match(RE("bc"))) {
 | |
|         state.layoutType = "code";
 | |
|       } else if (type.match(RE("foot"))) {
 | |
|         state.layoutType = "footnote";
 | |
|       } else if (type.match(RE("notextile"))) {
 | |
|         state.layoutType = "notextile";
 | |
|       } else if (type.match(RE("pre"))) {
 | |
|         state.layoutType = "pre";
 | |
|       } else if (type.match(RE("div"))) {
 | |
|         state.layoutType = "div";
 | |
|       } else if (type.match(RE("table"))) {
 | |
|         state.layoutType = "table";
 | |
|       }
 | |
| 
 | |
|       state.mode = Modes.attributes;
 | |
|       return tokenStyles(state);
 | |
|     },
 | |
| 
 | |
|     text: function(stream, state) {
 | |
|       if (stream.match(RE("text"))) return tokenStyles(state);
 | |
| 
 | |
|       var ch = stream.next();
 | |
|       if (ch === '"')
 | |
|         return (state.mode = Modes.link)(stream, state);
 | |
|       return handlePhraseModifier(stream, state, ch);
 | |
|     },
 | |
| 
 | |
|     attributes: function(stream, state) {
 | |
|       state.mode = Modes.layoutLength;
 | |
| 
 | |
|       if (stream.match(RE("attributes")))
 | |
|         return tokenStylesWith(state, TOKEN_STYLES.attributes);
 | |
|       else
 | |
|         return tokenStyles(state);
 | |
|     },
 | |
| 
 | |
|     layoutLength: function(stream, state) {
 | |
|       if (stream.eat(".") && stream.eat("."))
 | |
|         state.spanningLayout = true;
 | |
| 
 | |
|       state.mode = Modes.text;
 | |
|       return tokenStyles(state);
 | |
|     },
 | |
| 
 | |
|     list: function(stream, state) {
 | |
|       var match = stream.match(RE("list"));
 | |
|       state.listDepth = match[0].length;
 | |
|       var listMod = (state.listDepth - 1) % 3;
 | |
|       if (!listMod)
 | |
|         state.layoutType = "list1";
 | |
|       else if (listMod === 1)
 | |
|         state.layoutType = "list2";
 | |
|       else
 | |
|         state.layoutType = "list3";
 | |
| 
 | |
|       state.mode = Modes.attributes;
 | |
|       return tokenStyles(state);
 | |
|     },
 | |
| 
 | |
|     link: function(stream, state) {
 | |
|       state.mode = Modes.text;
 | |
|       if (stream.match(RE("link"))) {
 | |
|         stream.match(/\S+/);
 | |
|         return tokenStylesWith(state, TOKEN_STYLES.link);
 | |
|       }
 | |
|       return tokenStyles(state);
 | |
|     },
 | |
| 
 | |
|     linkDefinition: function(stream, state) {
 | |
|       stream.skipToEnd();
 | |
|       return tokenStylesWith(state, TOKEN_STYLES.linkDefinition);
 | |
|     },
 | |
| 
 | |
|     definitionList: function(stream, state) {
 | |
|       stream.match(RE("definitionList"));
 | |
| 
 | |
|       state.layoutType = "definitionList";
 | |
| 
 | |
|       if (stream.match(/\s*$/))
 | |
|         state.spanningLayout = true;
 | |
|       else
 | |
|         state.mode = Modes.attributes;
 | |
| 
 | |
|       return tokenStyles(state);
 | |
|     },
 | |
| 
 | |
|     html: function(stream, state) {
 | |
|       stream.skipToEnd();
 | |
|       return tokenStylesWith(state, TOKEN_STYLES.html);
 | |
|     },
 | |
| 
 | |
|     table: function(stream, state) {
 | |
|       state.layoutType = "table";
 | |
|       return (state.mode = Modes.tableCell)(stream, state);
 | |
|     },
 | |
| 
 | |
|     tableCell: function(stream, state) {
 | |
|       if (stream.match(RE("tableHeading")))
 | |
|         state.tableHeading = true;
 | |
|       else
 | |
|         stream.eat("|");
 | |
| 
 | |
|       state.mode = Modes.tableCellAttributes;
 | |
|       return tokenStyles(state);
 | |
|     },
 | |
| 
 | |
|     tableCellAttributes: function(stream, state) {
 | |
|       state.mode = Modes.tableText;
 | |
| 
 | |
|       if (stream.match(RE("tableCellAttributes")))
 | |
|         return tokenStylesWith(state, TOKEN_STYLES.attributes);
 | |
|       else
 | |
|         return tokenStyles(state);
 | |
|     },
 | |
| 
 | |
|     tableText: function(stream, state) {
 | |
|       if (stream.match(RE("tableText")))
 | |
|         return tokenStyles(state);
 | |
| 
 | |
|       if (stream.peek() === "|") { // end of cell
 | |
|         state.mode = Modes.tableCell;
 | |
|         return tokenStyles(state);
 | |
|       }
 | |
|       return handlePhraseModifier(stream, state, stream.next());
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   CodeMirror.defineMode("textile", function() {
 | |
|     return {
 | |
|       startState: function() {
 | |
|         return { mode: Modes.newLayout };
 | |
|       },
 | |
|       token: function(stream, state) {
 | |
|         if (stream.sol()) startNewLine(stream, state);
 | |
|         return state.mode(stream, state);
 | |
|       },
 | |
|       blankLine: blankLine
 | |
|     };
 | |
|   });
 | |
| 
 | |
|   CodeMirror.defineMIME("text/x-textile", "textile");
 | |
| });
 |