Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'gh-pages' into firefox-back-button-fix

  • Loading branch information...
commit 6f10ca9ec68217d1035d288bef230e4849284c8d 2 parents 386951c + 24a8dbd
@toolness toolness authored
View
24 README.md
@@ -1,5 +1,29 @@
This is a friendly HTML editor that uses [slowparse][] and [hacktionary][]
to provide ultra-friendly real-time help to novice webmakers.
+## Updating CodeMirror
+
+In the `codemirror2` directory is a mini-distribution of [CodeMirror][]
+which contains only the files necessary for HTML editing. It can be updated
+with the following Python script, if it is run from the root directory
+of the repository and the value of `NEW_CODEMIRROR_PATH` is changed:
+
+```python
+import os
+
+NEW_CODEMIRROR_PATH = "/path/to/new/codemirror/version"
+OUR_CODEMIRROR_PATH = os.path.abspath("codemirror2")
+
+for dirpath, dirnames, filenames in os.walk(OUR_CODEMIRROR_PATH):
+ for filename in filenames:
+ ourpath = os.path.join(dirpath, filename)
+ relpath = os.path.relpath(ourpath, OUR_CODEMIRROR_PATH)
+ newpath = os.path.join(NEW_CODEMIRROR_PATH, relpath)
+ if os.path.exists(newpath):
+ print "copying %s" % newpath
+ open(ourpath, "wb").write(open(newpath, "rb").read())
+```
+
[slowparse]: https://github.com/toolness/slowparse
[hacktionary]: https://github.com/toolness/hacktionary
+ [CodeMirror]: http://codemirror.net/
View
63 codemirror2/lib/codemirror.css
@@ -9,6 +9,7 @@
/* This is needed to prevent an IE[67] bug where the scrolled content
is visible outside of the scrolling box. */
position: relative;
+ outline: none;
}
.CodeMirror-gutter {
@@ -27,6 +28,7 @@
}
.CodeMirror-lines {
padding: .4em;
+ white-space: pre;
}
.CodeMirror pre {
@@ -40,38 +42,73 @@
padding: 0; margin: 0;
white-space: pre;
word-wrap: normal;
+ line-height: inherit;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
+ word-break: normal;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror textarea {
- font-family: inherit !important;
- font-size: inherit !important;
+ outline: none !important;
}
-.CodeMirror-cursor {
+.CodeMirror pre.CodeMirror-cursor {
z-index: 10;
position: absolute;
visibility: hidden;
- border-left: 1px solid black !important;
+ border-left: 1px solid black;
+ border-right:none;
+ width:0;
}
-.CodeMirror-focused .CodeMirror-cursor {
+.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
+.CodeMirror-focused pre.CodeMirror-cursor {
visibility: visible;
}
-span.CodeMirror-selected {
- background: #ccc !important;
- color: HighlightText !important;
-}
-.CodeMirror-focused span.CodeMirror-selected {
- background: Highlight !important;
+div.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
+
+.CodeMirror-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
}
-.CodeMirror-matchingbracket {color: #0f0 !important;}
-.CodeMirror-nonmatchingbracket {color: #f22 !important;}
+/* Default theme */
+
+.cm-s-default span.cm-keyword {color: #708;}
+.cm-s-default span.cm-atom {color: #219;}
+.cm-s-default span.cm-number {color: #164;}
+.cm-s-default span.cm-def {color: #00f;}
+.cm-s-default span.cm-variable {color: black;}
+.cm-s-default span.cm-variable-2 {color: #05a;}
+.cm-s-default span.cm-variable-3 {color: #085;}
+.cm-s-default span.cm-property {color: black;}
+.cm-s-default span.cm-operator {color: black;}
+.cm-s-default span.cm-comment {color: #a50;}
+.cm-s-default span.cm-string {color: #a11;}
+.cm-s-default span.cm-string-2 {color: #f50;}
+.cm-s-default span.cm-meta {color: #555;}
+.cm-s-default span.cm-error {color: #f00;}
+.cm-s-default span.cm-qualifier {color: #555;}
+.cm-s-default span.cm-builtin {color: #30a;}
+.cm-s-default span.cm-bracket {color: #cc7;}
+.cm-s-default span.cm-tag {color: #170;}
+.cm-s-default span.cm-attribute {color: #00c;}
+.cm-s-default span.cm-header {color: #a0a;}
+.cm-s-default span.cm-quote {color: #090;}
+.cm-s-default span.cm-hr {color: #999;}
+.cm-s-default span.cm-link {color: #00c;}
+
+span.cm-header, span.cm-strong {font-weight: bold;}
+span.cm-em {font-style: italic;}
+span.cm-emstrong {font-style: italic; font-weight: bold;}
+span.cm-link {text-decoration: underline;}
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
View
1,800 codemirror2/lib/codemirror.js
1,031 additions, 769 deletions not shown
View
2  codemirror2/mode/css/css.js
@@ -92,7 +92,7 @@ CodeMirror.defineMode("css", function(config) {
var style = state.tokenize(stream, state);
var context = state.stack[state.stack.length-1];
- if (type == "hash" && context == "rule") style = "atom";
+ if (type == "hash" && context != "rule") style = "string-2";
else if (style == "variable") {
if (context == "rule") style = "number";
else if (!context || context == "@media{") style = "tag";
View
6 codemirror2/mode/htmlmixed/htmlmixed.js
@@ -28,7 +28,7 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
function javascript(stream, state) {
if (stream.match(/^<\/\s*script\s*>/i, false)) {
state.token = html;
- state.curState = null;
+ state.localState = null;
state.mode = "html";
return html(stream, state);
}
@@ -73,11 +73,13 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
},
compareStates: function(a, b) {
+ if (a.mode != b.mode) return false;
+ if (a.localState) return CodeMirror.Pass;
return htmlMode.compareStates(a.htmlState, b.htmlState);
},
electricChars: "/{}:"
}
-});
+}, "xml", "javascript", "css");
CodeMirror.defineMIME("text/html", "htmlmixed");
View
21 codemirror2/mode/javascript/javascript.js
@@ -69,7 +69,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
else if (state.reAllowed) {
nextUntilUnescaped(stream, "/");
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
- return ret("regexp", "string");
+ return ret("regexp", "string-2");
}
else {
stream.eatWhile(isOperatorChar);
@@ -87,7 +87,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
- return known ? ret(known.type, known.style, word) :
+ return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
@@ -230,7 +230,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(maybeexpression);
- if (type == "(") return cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator);
+ if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
if (type == "operator") return cont(expression);
if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
@@ -243,7 +243,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function maybeoperator(type, value) {
if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
- if (type == "operator") return cont(expression);
+ if (type == "operator" || type == ":") return cont(expression);
if (type == ";") return;
if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
if (type == ".") return cont(property, maybeoperator);
@@ -316,10 +316,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return {
tokenize: jsTokenBase,
reAllowed: true,
+ kwAllowed: true,
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
- localVars: null,
- context: null,
+ localVars: parserConfig.localVars,
+ context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
},
@@ -333,14 +334,16 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
- state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
+ state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
+ state.kwAllowed = type != '.';
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize != jsTokenBase) return 0;
- var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
- type = lexical.type, closing = firstChar == type;
+ var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
+ if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
+ var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + 4;
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
View
148 codemirror2/mode/xml/xml.js
@@ -1,11 +1,44 @@
CodeMirror.defineMode("xml", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var Kludges = parserConfig.htmlMode ? {
- autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
- "meta": true, "col": true, "frame": true, "base": true, "area": true},
- doNotIndent: {"pre": true, "!cdata": true},
- allowUnquoted: true
- } : {autoSelfClosers: {}, doNotIndent: {"!cdata": true}, allowUnquoted: false};
+ autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
+ 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
+ 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
+ 'track': true, 'wbr': true},
+ implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
+ 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
+ 'th': true, 'tr': true},
+ contextGrabbers: {
+ 'dd': {'dd': true, 'dt': true},
+ 'dt': {'dd': true, 'dt': true},
+ 'li': {'li': true},
+ 'option': {'option': true, 'optgroup': true},
+ 'optgroup': {'optgroup': true},
+ 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
+ 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
+ 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
+ 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
+ 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
+ 'rp': {'rp': true, 'rt': true},
+ 'rt': {'rp': true, 'rt': true},
+ 'tbody': {'tbody': true, 'tfoot': true},
+ 'td': {'td': true, 'th': true},
+ 'tfoot': {'tbody': true},
+ 'th': {'td': true, 'th': true},
+ 'thead': {'tbody': true, 'tfoot': true},
+ 'tr': {'tr': true}
+ },
+ doNotIndent: {"pre": true},
+ allowUnquoted: true,
+ allowMissing: false
+ } : {
+ autoSelfClosers: {},
+ implicitlyClosed: {},
+ contextGrabbers: {},
+ doNotIndent: {},
+ allowUnquoted: false,
+ allowMissing: false
+ };
var alignCDATA = parserConfig.alignCDATA;
// Return variables for tokenizers
@@ -27,7 +60,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
else if (stream.match("--")) return chain(inBlock("comment", "-->"));
else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
- return chain(inBlock("meta", ">"));
+ return chain(doctype(1));
}
else return null;
}
@@ -47,9 +80,17 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
}
}
else if (ch == "&") {
- stream.eatWhile(/[^;]/);
- stream.eat(";");
- return "atom";
+ var ok;
+ if (stream.eat("#")) {
+ if (stream.eat("x")) {
+ ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
+ } else {
+ ok = stream.eatWhile(/[\d]/) && stream.eat(";");
+ }
+ } else {
+ ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
+ }
+ return ok ? "atom" : "error";
}
else {
stream.eatWhile(/[^&<]/);
@@ -102,6 +143,26 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
return style;
};
}
+ function doctype(depth) {
+ return function(stream, state) {
+ var ch;
+ while ((ch = stream.next()) != null) {
+ if (ch == "<") {
+ state.tokenize = doctype(depth + 1);
+ return state.tokenize(stream, state);
+ } else if (ch == ">") {
+ if (depth == 1) {
+ state.tokenize = inText;
+ break;
+ } else {
+ state.tokenize = doctype(depth - 1);
+ return state.tokenize(stream, state);
+ }
+ }
+ }
+ return "meta";
+ };
+ }
var curState, setStyle;
function pass() {
@@ -127,30 +188,38 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
}
function element(type) {
- if (type == "openTag") {curState.tagName = tagName; return cont(attributes, endtag(curState.startOfLine));}
- else if (type == "closeTag") {
+ if (type == "openTag") {
+ curState.tagName = tagName;
+ return cont(attributes, endtag(curState.startOfLine));
+ } else if (type == "closeTag") {
var err = false;
if (curState.context) {
- err = curState.context.tagName != tagName;
+ if (curState.context.tagName != tagName) {
+ if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
+ popContext();
+ }
+ err = !curState.context || curState.context.tagName != tagName;
+ }
} else {
err = true;
}
if (err) setStyle = "error";
return cont(endclosetag(err));
}
- else if (type == "string") {
- if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata");
- if (curState.tokenize == inText) popContext();
- return cont();
- }
- else return cont();
+ return cont();
}
function endtag(startOfLine) {
return function(type) {
if (type == "selfcloseTag" ||
- (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase())))
+ (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) {
+ maybePopContext(curState.tagName.toLowerCase());
+ return cont();
+ }
+ if (type == "endTag") {
+ maybePopContext(curState.tagName.toLowerCase());
+ pushContext(curState.tagName, startOfLine);
return cont();
- if (type == "endTag") {pushContext(curState.tagName, startOfLine); return cont();}
+ }
return cont();
};
}
@@ -162,17 +231,37 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
return cont(arguments.callee);
}
}
+ function maybePopContext(nextTagName) {
+ var parentTagName;
+ while (true) {
+ if (!curState.context) {
+ return;
+ }
+ parentTagName = curState.context.tagName.toLowerCase();
+ if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
+ !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
+ return;
+ }
+ popContext();
+ }
+ }
function attributes(type) {
- if (type == "word") {setStyle = "attribute"; return cont(attributes);}
+ if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
+ if (type == "endTag" || type == "selfcloseTag") return pass();
+ setStyle = "error";
+ return cont(attributes);
+ }
+ function attribute(type) {
if (type == "equals") return cont(attvalue, attributes);
- if (type == "string") {setStyle = "error"; return cont(attributes);}
- return pass();
+ if (!Kludges.allowMissing) setStyle = "error";
+ return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
}
function attvalue(type) {
- if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
if (type == "string") return cont(attvaluemaybe);
- return pass();
+ if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
+ setStyle = "error";
+ return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
}
function attvaluemaybe(type) {
if (type == "string") return cont(attvaluemaybe);
@@ -205,9 +294,11 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
return setStyle || style;
},
- indent: function(state, textAfter) {
+ indent: function(state, textAfter, fullLine) {
var context = state.context;
- if (context && context.noIndent) return 0;
+ if ((state.tokenize != inTag && state.tokenize != inText) ||
+ context && context.noIndent)
+ return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
if (context && /^<\//.test(textAfter))
context = context.prev;
@@ -230,4 +321,5 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
});
CodeMirror.defineMIME("application/xml", "xml");
-CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
+if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
+ CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
View
4 default-content.html
@@ -15,14 +15,14 @@
<!-- This section describes what parts of your webpage will look like using a language called CSS, which stands for Cascading Style Sheets. -->
<style>
-<!-- The following block describes style for the body of the page. It's saying that the background should be white and that there should be some space at the top before the content starts. It's also saying that the text should be very dark grey, but it's using a special format called hexadecimal. -->
+/* The following block describes style for the body of the page. It's saying that the background should be white and that there should be some space at the top before the content starts. It's also saying that the text should be very dark grey, but it's using a special format called hexadecimal. */
body {
background-color: white;
color: #333;
padding-top: 1em;
}
-<!-- Each style block corresponds to a piece of HTML above. Can you find the piece of code that this block refers to? Try changing the color below and see what happens. -->
+/* Each style block corresponds to a piece of HTML above. Can you find the piece of code that this block refers to? Try changing the color below and see what happens. */
h1 {
color: #333;
font-family: Georgia;
View
10 js/fc/ui/indexable-codemirror.js
@@ -6,17 +6,9 @@ define(["codemirror"], function(CodeMirror) {
return function IndexableCodeMirror(place, givenOptions) {
var codeMirror = CodeMirror(place, givenOptions);
- // This is the reverse of CodeMirror2's coordsFromIndex() method.
- codeMirror.indexFromCoords = function(pos) {
- var index = pos.ch;
- for (var i = 0; i < pos.line; i++)
- index += codeMirror.getLine(i).length + 1;
- return index;
- };
-
// Returns the character index of the cursor position.
codeMirror.getCursorIndex = function() {
- return codeMirror.indexFromCoords(codeMirror.getCursor());
+ return codeMirror.indexFromPos(codeMirror.getCursor());
};
return codeMirror;
View
4 js/fc/ui/mark-tracker.js
@@ -18,8 +18,8 @@ define(function() {
classNames[className].push(element);
$(element).addClass(className);
}
- start = codeMirror.coordsFromIndex(start);
- end = codeMirror.coordsFromIndex(end);
+ start = codeMirror.posFromIndex(start);
+ end = codeMirror.posFromIndex(end);
marks.push(codeMirror.markText(start, end, className));
},
// Clear all marks made so far and remove the class from any elements
View
11 test/index.html
@@ -1,9 +1,18 @@
<!DOCTYPE html>
<html>
- </head>
+ <head>
<meta charset="utf-8">
<base href="..">
<title>Friendlycode Test Suite</title>
+ <script>
+ // IE9 really wants base hrefs to be absolute URLs, so we'll do that...
+ (function() {
+ var base = document.querySelector("base[href]");
+ var a = document.createElement("a");
+ a.setAttribute("href", "..");
+ base.setAttribute("href", a.href);
+ })();
+ </script>
<link rel="stylesheet" href="test/qunit.css">
</head>
View
6 test/test-indexable-codemirror.js
@@ -17,10 +17,10 @@ require(["fc/ui/indexable-codemirror"], function(IndexableCodeMirror) {
});
}
- icmTest("indexFromCoords() works", function(cm, content) {
- equal(cm.indexFromCoords({line: 0, ch: 0}), 0,
+ icmTest("indexFromPos() works", function(cm, content) {
+ equal(cm.indexFromPos({line: 0, ch: 0}), 0,
"index of line 0, char 0 is 0");
- equal(cm.indexFromCoords({line: 1, ch: 0}), content.indexOf("there"),
+ equal(cm.indexFromPos({line: 1, ch: 0}), content.indexOf("there"),
"index of line 1, char 0 works");
});
Please sign in to comment.
Something went wrong with that request. Please try again.