/* TypoScript parser
- *
+ *
* based on parsejavascript.js by Marijn Haverbeke
*
* A parser that can be plugged into the CodeMirror system has to
lexical.align = true;
}
// Execute actions until one 'consumes' the token and we can
- // return it. Marked is used to
+ // return it. Marked is used to
while (true) {
consume = marked = false;
// Take and execute the topmost action.
cc.push(fs[i]);
}
}
-
+
// cont and pass are used by the action functions to add other
// actions to the stack. cont will cause the current token to be
// consumed, pass will leave it for the next action.
push(arguments);
consume = true;
}
-
+
function pass() {
push(arguments);
consume = false;
}
-
+
// Used to change the style of the current token.
function mark(style) {
marked = style;
}
};
}
-
+
// Pop off the current scope.
function popcontext() {
context = context.prev;
}
-
+
// Register a variable in the current scope.
function register(varname) {
if (context) {
result.lex = true;
return result;
}
-
+
// Pop off the current lexical context.
function poplex() {
lexical = lexical.prev;
}
-
+
poplex.lex = true;
// The 'lex' flag on these actions is used by the 'next' function
// to know they can (and have to) be ran before moving on to the
function expression(type) {
if (atomicTypes.hasOwnProperty(type)) {
cont(maybeoperator);
-
+
} else if (type == "function") {
cont(functiondef);
-
+
} else if (type == "keyword c") {
cont(expression);
-
+
} else if (type == "(") {
cont(pushlex(")"), expression, expect(")"), poplex);
-
+
} else if (type == "operator") {
cont(expression);
-
+
} else if (type == "[") {
cont(pushlex("]"), commasep(expression), expect("]"), poplex);
-
+
} else if (type == "{") {
cont(pushlex("}"), commasep(objprop), expect("}"), poplex);
}
}
-
+
// Called for places where operators, function calls, or
// subscripts are valid. Will skip on to the next action if none
// is found.
function maybeoperator(type) {
if (type == "operator") {
cont(expression);
-
+
} else if (type == "(") {
cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex);
-
+
} else if (type == ".") {
cont(property, maybeoperator);
-
+
} else if (type == "[") {
cont(pushlex("]"), expression, expect("]"), poplex);
- }
+ }
}
-
+
// When a statement starts with a variable name, it might be a
// label. If no colon follows, it's a regular statement.
function maybelabel(type) {
pass(maybeoperator, expect(";"), poplex);
}
}
-
+
// Property names need to have their style adjusted -- the
// tokenizer think they are variables.
function property(type) {
cont();
}
}
-
+
// This parses a property and its value in an object literal.
function objprop(type) {
if (type == "variable") {
// Look for statements until a closing brace is found.
function condition(type) {
- if (type == "]") 7
+ if (type == "]") {
cont();
} else {
pass(statement, block);
cont();
}
}
-
+
function vardef2(type) {
if (type == "operator") {
cont(expression, vardef2);
cont(vardef1);
}
}
-
+
// For loops.
function forspec1(type, value) {
if (type == "var") {
cont(expression, forspec2);
}
}
-
+
function forspec2(type) {
if (type == ",") {
cont(forspec1);
cont(expression, expect(";"), expression);
}
}
-
+
// A function definition creates a new context, and the variables
// in its argument list have to be added to this context.
function functiondef(type, value) {
cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext);
}
}
-
+
function funarg(type, value) {
if (type == "variable") {
register(value);
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
-/* t3editor.js is based on codemirror.js from the Codemirror editor.
+/* t3editor.js is based on codemirror.js from the Codemirror editor.
* See LICENSE file for further informations
*/
} else if (node.nodeName == "BR" && node.childNodes.length == 0) {
result.push(node);
} else {
- // forEach(node.childNodes, simplifyNode);
+ // forEach(node.childNodes, simplifyNode);
$A(node.childNodes).each(simplifyNode);
if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) {
leaving = true;
return function(newnode) {
parent.insertBefore(newnode, next);
};
- }
- else {
+ } else {
return function(newnode) {
parent.appendChild(newnode);
};
// Check whether a node is a normalized <span> element.
function partNode(node) {
- if (node.nodeName == "SPAN"
- && node.childNodes.length == 1
+ if (node.nodeName == "SPAN"
+ && node.childNodes.length == 1
&& node.firstChild.nodeType == 3) {
node.currentText = node.firstChild.nodeValue;
return true;
"class": "t3e_modalOverlay",
"id": "t3e_modalOverlay_wait"
});
-
+
this.modalOverlay.hide();
this.modalOverlay.setStyle(this.outerdiv.getDimensions());
this.modalOverlay.setStyle({
"class": "t3e_modalOverlay",
"id": "t3e_modalOverlay_help"
});
-
+
// TODO: fill with senseful content, make it dynamic
- this.helpOverlay.innerHTML =
+ this.helpOverlay.innerHTML =
"<div class='closedok_icon'>"+
"<a href='javascript:void(0)' onclick='t3e_instances[" + this.index + "].toggleHelp();'>"+
"<img src='"+PATH_t3e+"../t3skin/icons/gfx/closedok.gif' alt='click here to close this help window' title='click here to close this help window' />"+
"</a></div>" +
- "<h2>t3editor</h2>" +
+ "<h2>t3editor</h2>" +
"<p>'t3editor' is a javascript-driven code editor with syntax highlighting for TypoScript</p><br/>" +
- "<p>It's based on the 'Codemirror' editor.</p><br/><br/>" +
- "<p>Hotkeys:</p>" +
- "<p>" +
- "<strong>CTRL-S</strong> save/send code to server<br/>" +
- "<strong>CTRL-F11</strong> toggle fullscreen mode<br/>" +
- "<strong>CTRL-SPACE</strong> auto-complete (based on letters at current cursor-position)<br/>" +
+ "<p>It's based on the 'Codemirror' editor.</p><br/><br/>" +
+ "<p>Hotkeys:</p>" +
+ "<p>" +
+ "<strong>CTRL-S</strong> save/send code to server<br/>" +
+ "<strong>CTRL-F11</strong> toggle fullscreen mode<br/>" +
+ "<strong>CTRL-SPACE</strong> auto-complete (based on letters at current cursor-position)<br/>" +
"</p><br/>" +
"";
this.helpOverlay.hide();
this.outerdiv.appendChild(this.footer_wrap);
// footer item: show help Window
- // TODO make this more flexible! And get rid of inline css and unsed options!
+ // TODO make this more flexible! And get rid of inline css and unsed options!
this.fitem_help = this.createFooterItem('Help', true, "this.toggleHelp()");
this.footer_wrap.appendChild(this.fitem_help);
});
// TODO make this more flexible! And get rid of inline css and unsed options!
- this.fitem_options_overlay.innerHTML = '<ul>' +
- // '<li style="color:grey"><input type="checkbox" disabled="disabled" /> Syntax highlighting</li>'+
- '<li><input type="checkbox" onclick="t3e_instances[' + this.index + '].fitem_options_overlay.hide();t3e_instances[' + this.index + '].toggleAutoComplete();" id="t3e_autocomplete" checked="checked" /><label for="t3e_autocomplete">AutoCompletion</label></li>' +
- '<li><input type="checkbox" onclick="t3e_instances[' + this.index + '].fitem_options_overlay.hide();t3e_instances[' + this.index + '].toggleFullscreen();" id="t3e_fullscreen" /> <label for="t3e_fullscreen">Fullscreen</label></li>' +
+ this.fitem_options_overlay.innerHTML = '<ul>' +
+ // '<li style="color:grey"><input type="checkbox" disabled="disabled" /> Syntax highlighting</li>'+
+ '<li><input type="checkbox" onclick="t3e_instances[' + this.index + '].fitem_options_overlay.hide();t3e_instances[' + this.index + '].toggleAutoComplete();" id="t3e_autocomplete" checked="checked" /><label for="t3e_autocomplete">AutoCompletion</label></li>' +
+ '<li><input type="checkbox" onclick="t3e_instances[' + this.index + '].fitem_options_overlay.hide();t3e_instances[' + this.index + '].toggleFullscreen();" id="t3e_fullscreen" /> <label for="t3e_fullscreen">Fullscreen</label></li>' +
// '<li style="color:grey"><input type="checkbox" disabled="disabled" /> other fancy stuff</li>'+
'</ul>';
this.fitem_options_overlay.hide();
this.doc.open();
this.doc.write(
- "<html><head>" +
- "<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
- t3eOptions.stylesheet +
- "\"/></head>" +
+ "<html><head>" +
+ "<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
+ t3eOptions.stylesheet +
+ "\"/></head>" +
"<body class=\"editbox\" spellcheck=\"false\"></body></html>");
this.doc.close();
this.init(content);
} else {
- // connect(this.iframe, "onload", bind(function(){disconnectAll(this.iframe, "onload"); this.init(content);}, this));
- Event.observe(this.iframe, "load",
+ // connect(this.iframe, "onload", bind(function(){disconnectAll(this.iframe, "onload"); this.init(content);}, this));
+ Event.observe(this.iframe, "load",
function() {
this.init(content);
}.bindAsEventListener(this));
textModified: false,
// editor-content has been modified
saveAjaxEvent: null,
- // Event for save code with ajax
+ // Event for save code with ajax
// Called after we are sure that our frame has a body
init: function(code) {
if (mouseover) {
item.addClassName('t3e_clickable');
- Event.observe(item, "mouseover",
+ Event.observe(item, "mouseover",
function(e) {
Event.element(e).addClassName('t3e_footeritem_active');
});
- Event.observe(item, "mouseout",
+ Event.observe(item, "mouseout",
function(e) {
Event.element(e).removeClassName('t3e_footeritem_active');
});
}
if (typeof clickAction == 'object') {
// display an overlay
- Event.observe(item, "click",
+ Event.observe(item, "click",
function(e) {
clickAction.toggle();
});
} else if (typeof clickAction == 'string' && clickAction != '') {
// execute a method
- Event.observe(item, "click",
+ Event.observe(item, "click",
function(e) {
eval(clickAction);
}.bindAsEventListener(this));
w = this.textarea.getDimensions().width;
} else {
- // turn fullscreen on
+ // turn fullscreen on
this.outerdiv.addClassName('t3e_fullscreen');
h = this.outerdiv.offsetParent.getHeight();
w = this.outerdiv.offsetParent.getWidth();
// make UL list of completation words
var html = '<ul>';
for (i = 0; i < this.words.length; i++) {
- html += '<li style="height:16px;vertical-align:middle;" ' +
+ html += '<li style="height:16px;vertical-align:middle;" ' +
'id="ac_word_' + i + '" ' +
'onclick="t3e_instances[' + this.index + '].clicked=true; ' +
't3e_instances[' + this.index + '].insertCurrWordAtCursor();" ' +
'onmouseover="t3e_instances[' + this.index + '].highlightCurrWord(' + i + ');">' +
- '<span class="word_' + this.words[i].type + '">' +
- this.words[i].word +
+ '<span class="word_' + this.words[i].type + '">' +
+ this.words[i].word +
'</span></li>';
}
html += '</ul>';
-
+
//put HTML and show box
this.autoCompleteBox.innerHTML = html;
this.autoCompleteBox.show();
this.autoCompleteBox.scrollTop = 0;
-
+
// init styles
if (this.words.length > this.options.acWords) {
this.autoCompleteBox.style.overflowY = 'scroll';
width: 'auto'
});
}
-
+
// positioned box to word
this.autoCompleteBox.setStyle({
left: (Position.cumulativeOffset(this.iframe)[0]
+ cursor.start.offsetHeight
- this.container.scrollTop) + 'px'
});
-
- // set flag to 1 - needed for continue typing word.
+
+ // set flag to 1 - needed for continue typing word.
this.ac = 1;
// highlight first word in list
this.highlightCurrWord(0);
}
}
},
-
+
// Get word where cursor focused
getLastWord: function() {
var cursor = new select.Cursor(this.container);
this.autoCompleteBox.hide();
},
-
+
// return words for autocomplete by trigger (part of word)
getCompleteWordsByTrigger: function(trigger) {
result = [];
this.autoCompleteBox.scrollTop = this.ac_up * 16;
}
},
-
+
//move cursor in autocomplete box down
autoCompleteBoxMoveDownCursor: function() {
// if previous position was last word in list - then move cursor to first word if not than position ++
}
// highlight new cursor position
this.highlightCurrWord(id);
-
+
// update id of first and last showing words and scroll box
if (this.currWord > this.ac_down || this.currWord == 0) {
this.ac_down = this.currWord;
this.fitem_status.update(bodyContentLineCount + ' lines');
this.fitem_name.update(
- this.documentname +
+ this.documentname +
(this.textModified ? ' <span alt="document has been modified">*</span>': ''));
},
// DOM compliant
scrOfY = this.doc.body.scrollTop;
scrOfX = this.doc.body.scrollLeft;
- } else if (this.doc.documentElement
- && (this.doc.documentElement.scrollLeft
+ } else if (this.doc.documentElement
+ && (this.doc.documentElement.scrollLeft
|| this.doc.documentElement.scrollTop)) {
// IE6 standards compliant mode
scrOfY = this.doc.documentElement.scrollTop;
if (code == "") {
this.container.appendChild(this.win.document.createElement('SPAN'));
this.container.appendChild(
- (Prototype.Browser.Gecko && !Prototype.Browser.WebKit) ?
- this.win.document.createElement('BR') :
+ (Prototype.Browser.Gecko && !Prototype.Browser.WebKit) ?
+ this.win.document.createElement('BR') :
this.win.document.createElement('SPAN'));
}
}
},
-
+
// mark the node at the cursor dirty when a non-safe key is
// released.
keyUp: function(event) {
this.cursorObj = cursor.start;
// remove current highlights
- Selector.findChildElements(this.doc,
+ Selector.findChildElements(this.doc,
$A(['.highlight-bracket', '.error-bracket'])
).each(function(item) {
item.className = item.className.replace(' highlight-bracket', '');
maybeMatch = maybeMatch.nextSibling;
}
}
-
+
// we have a closing bracket, search backward for an opening bracket
if (cursor.start.className.indexOf('curly-bracket-close') != -1) {
var maybeMatch = cursor.start.previousSibling;
// Sometimes the first character on a line can influence the
// correct indentation, so we retrieve it.
- var firstText = whiteSpace ?
- whiteSpace.nextSibling :
- start ?
- start.nextSibling :
+ var firstText = whiteSpace ?
+ whiteSpace.nextSibling :
+ start ?
+ start.nextSibling :
this.container.firstChild;
- var firstChar = (start && firstText && firstText.currentText) ?
- firstText.currentText.charAt(0) :
+ var firstChar = (start && firstText && firstText.currentText) ?
+ firstText.currentText.charAt(0) :
"";
// Ask the lexical context for the correct indentation, and
if (!container.firstChild) {
return;
}
-
+
// Backtrack to the first node before from that has a partial
// parse stored.
while (from && !from.parserFromHere) {
from = from.previousSibling;
}
-
+
// If we are at the end of the document, do nothing.
if (from && !from.nextSibling) {
return;
}
-
+
// Check whether a part (<span> node) and the corresponding token
// match.
function correctPart(token, part) {
- return ! part.reduced
- && part.currentText == token.value
+ return ! part.reduced
+ && part.currentText == token.value
&& hasClass(part, token.style);
}
// Shorten the text associated with a part by chopping off
part.currentText = part.currentText.substring(minus);
part.reduced = true;
}
-
+
// Create a part corresponding to a given token.
function tokenPart(token) {
var part = new Element('SPAN', {
// Get the token stream. If from is null, we start with a new
// parser from the start of the frame, otherwise a partial parse
// is resumed.
- var parsed = from ?
- from.parserFromHere(multiStringStream(traverseDOM(from.nextSibling))) :
+ var parsed = from ?
+ from.parserFromHere(multiStringStream(traverseDOM(from.nextSibling))) :
this.options.parser(multiStringStream(traverseDOM(container.firstChild)));
// parts is a wrapper that makes it possible to 'delay' going to
var parts = {
current: null,
forward: false,
-
+
// Get the current part.
get: function() {
if (!this.current) {
this.forward = false;
return this.current;
},
-
+
// Advance to the next part (do not fetch it yet).
next: function() {
if (this.forward) {
}
this.forward = true;
},
-
+
// Remove the current part from the DOM tree, and move to the
// next.
remove: function() {
container.removeChild(this.current ? this.current.nextSibling: container.firstChild);
this.forward = true;
},
-
+
// Advance to the next part that is not empty, discarding empty
// parts.
nextNonEmpty: function() {
try {
while (true) {
// stopped by StopIteration
-
+
token = parsed.next();
var part = parts.nextNonEmpty();
-
+
if (token.value == "\n") {
// The idea of the two streams actually staying synchronized
// is such a long shot that we explicitly check.
// everything ready: turn textarea's into fancy editors
if (!Prototype.Browser.MobileSafari) {
- Event.observe(window, 'load',
+ Event.observe(window, 'load',
function() {
$$('textarea.t3editor').each(
function(textarea, i) {
-/* Tokenizer for TypoScript code
- *
+/* Tokenizer for TypoScript code
+ *
* based on tokenizejavascript.js by Marijn Haverbeke
*/
var isDigit = matcher(/[0-9]/);
var isHexDigit = matcher(/[0-9A-Fa-f]/);
var isWordChar = matcher(/[\w\$_]/);
-
+
function isWhiteSpace(ch) {
// Unfortunately, IE's regexp matcher thinks non-breaking spaces
// aren't whitespace. Also, in our scheme newlines are no
source.next();
}
}
-
+
// Advance the stream until the given character (not preceded by a
// backslash) is encountered (or a newline is found).
function nextUntilUnescaped(end) {
nextWhile(isHexDigit);
return result("number", "atom");
}
-
+
function readNumber() {
nextWhile(isDigit);
if (source.peek() == ".") {
source.next();
nextWhile(isDigit);
}
-
+
if (source.peek() == "e" || source.peek() == "E") {
source.next();
if (source.peek() == "-") {
}
return result("number", "atom");
}
-
+
// Read a word, look it up in keywords. If not found, it is a
// variable, otherwise it is a keyword of the type found.
function readWord() {
type: 'keyword',
style: typoscriptWords[word]
};
- return known ?
- result(known.type, known.style, word) :
+ return known ?
+ result(known.type, known.style, word) :
result("variable", "other", word);
}
-
+
function readRegexp() {
nextUntilUnescaped("/");
nextWhile(matcher(/[gi]/));
return result("regexp", "string");
}
-
+
// Mutli-line comments are tricky. We want to return the newlines
// embedded in them as regular newline tokens, and then continue
// returning a comment token for every line of the comment. So
}
maybeEnd = (next == "*");
}
-
+
return result("comment", "ts-comment");
}
style: "whitespace",
value: source.get()
};
-
+
} else if (this.inComment) {
token = readMultilineComment.call(this, ch);
-
+
} else if (this.inValue) {
token = nextUntilUnescaped(null) || {
type: "value",
value: source.get()
};
this.inValue = false;
-
+
} else if (isWhiteSpace(ch)) {
token = nextWhile(isWhiteSpace) || result("whitespace", "whitespace");
-
+
} else if (ch == "\"" || ch == "'") {
token = nextUntilUnescaped(ch) || result("string", "string");
} else if (ch == "/") {
next = source.peek();
-
+
if (next == "*") {
token = readMultilineComment.call(this, ch);
-
+
} else if (next == "/") {
token = nextUntilUnescaped(null) || result("comment", "ts-comment");
} else {
token = nextWhile(isOperatorChar) || result("operator", "ts-operator");
}
-
+
} else if (ch == "#") {
token = nextUntilUnescaped(null) || result("comment", "ts-comment");
} else {
token = readWord();
}
-
+
// JavaScript's syntax rules for when a slash might be the start
// of a regexp and when it is just a division operator are kind
// of non-obvious. This decides, based on the current token,