Skip to content
Browse files

bug 805793 - Syntax highlighting for the knowledge base editor

  • Loading branch information...
1 parent 8a5e5c3 commit cb94df44f267755a5d6423d5e64b10226c2c2d45 @tobbi tobbi committed with rlr Oct 30, 2012
View
3 .gitmodules
@@ -133,3 +133,6 @@
[submodule "vendor/src/django-eadred"]
path = vendor/src/django-eadred
url = git://github.com/willkg/django-eadred.git
+[submodule "media/js/libs/ace"]
+ path = media/js/libs/ace
+ url = git://github.com/ajaxorg/ace-builds.git
View
2 apps/wiki/templates/wiki/edit.html
@@ -11,7 +11,7 @@
{% set crumbs = [(document.get_absolute_url(), document.title),
(None, _('Edit Article'))] %}
{% set classes = 'edit' %}
-{% set scripts = ('wiki', 'wiki.diff') %}
+{% set scripts = ('wiki', 'wiki.diff', 'wiki.editor') %}
{% block content %}
<div class="grid_9">
View
1 apps/wiki/templates/wiki/new_document.html
@@ -8,6 +8,7 @@
{# TODO: Change KB url to landing page when we have one #}
{% set crumbs = [(None, _('New Article'))] %}
{% set classes = 'new' %}
+{% set scripts = ('wiki', 'wiki.editor') %}
{% block content %}
<div class="grid_9">
View
2 apps/wiki/templates/wiki/translate.html
@@ -9,7 +9,7 @@
{% set crumbs = [(parent.get_absolute_url(), parent.title),
(None, _('Translate Article'))] %}
{% set classes = 'translate' %}
-{% set scripts = ('wiki', 'wiki.diff') %}
+{% set scripts = ('wiki', 'wiki.diff', 'wiki.editor') %}
{% block content %}
{% set language = settings.LANGUAGES[locale.lower()] %}
1 media/js/libs/ace
@@ -0,0 +1 @@
+Subproject commit 6149ca6b148e878d4c1341d4675ca3597d78dbdd
View
246 media/js/libs/ace.mode-sumo.js
@@ -0,0 +1,246 @@
+define('ace/mode/sumo', function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextMode = require("ace/mode/text").Mode;
+var Tokenizer = require("ace/tokenizer").Tokenizer;
+var SUMOHighlightRules = require("ace/mode/sumo_highlight_rules").SUMOHighlightRules;
+var SUMOBehaviour = require("ace/mode/behaviour/sumo").SUMOBehaviour;
+
+var Mode = function() {
+ this.$tokenizer = new Tokenizer(new SUMOHighlightRules().getRules());
+ this.$behaviour = new SUMOBehaviour();
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+ // Extra logic goes here. (see below)
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
+
+define('ace/mode/sumo_highlight_rules', function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+
+var SUMOHighlightRules = function() {
+ // see: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects
+ var keywordMapper = this.createKeywordMapper({
+ "variable.language":
+ "__TOC__",
+ }, "identifier");
+ // TODO: Unicode escape sequences
+ var identifierRe = "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*\\b";
+
+ this.$rules = {
+ "start" : [{
+ token : keywordMapper,
+ regex : identifierRe
+ },
+ {
+ token: ["meta.tag", null],
+ regex: /({\/?(note|warning)})/,
+ },
+ {
+ token: "meta.tag",
+ regex: /<\/?br>/
+ },
+ {
+ token: ["variable.language", "meta.tag", "variable.language", "variable.language"],
+ regex: /({\/?)(for)(.*?)(})/
+ },
+ {
+ token: ["variable.language", "meta.tag", "identifier", "variable.language"],
+ regex: /({)(filepath|key|menu|button|pref)(.*?)(})/
+ },
+ {
+ token: ["markup.other.link", "markup.other.link", "markup.other.link", "markup.other.link"],
+ regex: /(\[{2})([^\]]*?)(\|.*?)(\]{2})/
+ },
+ {
+ token: ["markup.other.link", "markup.other.link", "markup.other.link", "markup.other.link"],
+ regex: /(\[{2})(\S*:)(.*?)(\]{2})/
+ },
+ {
+ token: ["markup.other.link", "markup.other.link", "markup.other.link"],
+ regex: /(\[{2})(.*?)(\]{2})/
+ },
+ {
+ token: ["markup.other.link", "markup.other.link", "markup.other.link", "markup.other.link"],
+ regex: /(\[)(\S*)(.*?)(\])/
+ },
+ {
+ token: ["variable.language", "markup.bold", "variable.language"],
+ regex: /(''')(.*?)(''')/
+ },
+ {
+ token: ["variable.language", "markup.italic", "variable.language"],
+ regex: /('')(.*?)('')/
+ },
+ {
+ token: ["variable.language", "markup.underline", "variable.language"],
+ regex: /(<u>)(.*?)(<\/u>)/
+ },
+ {
+ token: ["variable.language", "markup.strikethrough", "variable.language"],
+ regex: /(<s>)(.*?)(<\/s>)/
+ },
+ {
+ token: ["variable.language", "markup.strikethrough", "variable.language"],
+ regex: /(<del>)(.*?)(<\/del>)/
+ },
+ {
+ token: ["variable.language", "comment"],
+ regex: /(<nowiki>)(.*?)/,
+ next: "nowiki"
+ },
+ {
+ token: ["variable.language", "comment"],
+ regex: /(<code>)(.*?)/,
+ next: "code"
+ },
+ {
+ token: ["variable.language", "comment"],
+ regex: /(<pre>)(.*?)/,
+ next: "pre"
+ },
+ {
+ token: "markup.quote",
+ regex: /^\s+.*/
+ },
+ {
+ token: "variable.language",
+ regex: /^[\*#]*\*/
+ },
+ {
+ token: "variable.language",
+ regex: /^[\*#]*#/
+ },
+ {
+ token: ["markup.bold", "markup.bold", "markup.bold"],
+ regex: /^(={1,6})(.*?)(\1)$/
+ },
+ {
+ token: "variable.language",
+ regex: /^[-]{4}/
+ },
+ {
+ token: "variable.language",
+ regex: /^[;:]/
+ },
+ {
+ token: "variable.language",
+ regex: /^{?[\|!][+-]?}?/,
+ },
+ {
+ token : "comment",
+ merge : true,
+ regex : "<\\!--",
+ next : "comment"
+ }
+ ],
+ comment: [{
+ token : "comment",
+ regex : ".*?-->",
+ next : "start"
+ }, {
+ token : "comment",
+ merge : true,
+ regex : ".+"
+ }],
+ nowiki: [{
+ token: ["variable.language"],
+ regex: /(<\/nowiki>)/,
+ next: "start"
+ }],
+ code: [{
+ token: ["variable.language"],
+ regex: /(<\/code>)/,
+ next: "start"
+ },
+ {
+ token: ["variable.language"],
+ regex: /(<\/?nowiki>)/
+ }],
+ pre: [{
+ token: ["variable.language"],
+ regex: /(<\/pre>)/,
+ next: "start"
+ },
+ {
+ token: ["variable.language"],
+ regex: /(<\/?nowiki>)/
+ }]
+ };
+};
+
+oop.inherits(SUMOHighlightRules, TextHighlightRules);
+
+exports.SUMOHighlightRules = SUMOHighlightRules;
+});
+
+define('ace/mode/behaviour/sumo', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/behaviour', 'ace/token_iterator'], function(require, exports, module) {
+
+var oop = require("../../lib/oop");
+var Behaviour = require("../behaviour").Behaviour;
+var TokenIterator = require("../../token_iterator").TokenIterator;
+
+function hasType(token, type) {
+ var hasType = true;
+ var typeList = token.type.split('.');
+ var needleList = type.split('.');
+ needleList.forEach(function(needle){
+ if (typeList.indexOf(needle) == -1) {
+ hasType = false;
+ return false;
+ }
+ });
+ return hasType;
+}
+
+var SUMOBehaviour = function () {
+ this.add("autoclosing", "insertion", function (state, action, editor, session, text) {
+ if (text == '}') {
+ var position = editor.getCursorPosition();
+ var iterator = new TokenIterator(session, position.row, position.column);
+ var token = iterator.getCurrentToken();
+ var closeable = ["for", "note", "warning"];
+ var notCloseable = ["key", "menu", "button", "pref"];
+ var tag = token.value;
+ while(closeable.indexOf(tag) == -1) {
+ if(notCloseable.indexOf(tag) != -1 ||
+ tag == "{") {
+ return;
+ }
+ tag = iterator.stepBackward().value;
+ }
+
+ return {
+ text: '}' + '{/' + tag + '}',
+ selection: [1, 1]
+ }
+ }
+ if (text == "=")
+ return {
+ text: text + "=",
+ selection: [1, 1]
+ }
+
+ if (text == "[")
+ return {
+ text: text + "]",
+ selection: [1,1]
+ }
+
+ if(text == "(")
+ return {
+ text: text + ")",
+ selection: [1,1]
+ }
+ });
+}
+oop.inherits(SUMOBehaviour, Behaviour);
+
+exports.SUMOBehaviour = SUMOBehaviour;
+});
View
38 media/js/markup.js
@@ -147,6 +147,11 @@ Marky.SimpleButton.prototype = {
// Get selected text
getSelectedText: function() {
var selText = '';
+ if(window.ace && window.highlighting.isEnabled()) {
+ var editor = window.highlighting.editor;
+ return window.highlighting.session.getTextRange(editor.getSelectionRange());
+ }
+
if(document.selection && document.selection.createRange) {
// IE/Opera
selText = document.selection.createRange().text;
@@ -160,9 +165,38 @@ Marky.SimpleButton.prototype = {
},
// Handles the button click.
handleClick: function(e) {
- var selText, selStart, selEnd, splitText, range,
+ var selText, selRange, selStart, selEnd, splitText, range,
+ selStartColumn,
textarea = this.textarea,
- scrollTop = $(textarea).scrollTop();
+ scrollTop = $(textarea).scrollTop(),
+ session = window.highlighting.session,
+ editor = window.highlighting.editor;
+
+ if(window.ace && window.highlighting.isEnabled()) {
+ selRange = editor.getSelectionRange();
+ selText = session.getTextRange(selRange);
+ selStartColumn = selRange.start.column + this.openTag.length;
+ if(!selText.length) {
+ selText = this.defaultText;
+ }
+ editor.insert(this.openTag + selText + this.closeTag);
+ if(selText == this.defaultText) {
+ session.selection.setSelectionRange({
+ start: {
+ row: selRange.start.row,
+ column: selStartColumn
+ },
+ end: {
+ row: selRange.end.row,
+ column: selStartColumn + selText.length
+ }
+ });
+ }
+ editor.focus();
+ e.preventDefault();
+ return false;
+ }
+
textarea.focus();
if (document.selection && document.selection.createRange) {
View
56 media/js/wiki.js
@@ -52,6 +52,7 @@
initNeedsChange();
initSummaryCount();
initFormLock();
+ initAceEditor();
$('img.lazy').loadnow();
@@ -534,6 +535,61 @@
}
});
}
+
+ function initAceEditor() {
+ window.highlighting = {};
+
+ var editor = $("<div id='editor'></div>");
+ var editor_wrapper = $("<div id='editor_wrapper'></div>");
+
+ var updateHighlightingEditor = function() {
+ var session = window.highlighting.session;
+ if(!session)
+ return;
+
+ var content = $("#id_content").val();
+ session.setValue(content);
+ };
+ window.highlighting.updateEditor = updateHighlightingEditor;
+
+ var switch_link = $("<a></a>")
+ .text(gettext("Toggle syntax highlighting"))
+ .css({cssFloat: "right", cursor: "pointer"})
+ .toggle(function() {
+ editor_wrapper.css("display", "none");
+ $("#id_content").css("display", "block");
+ }, function() {
+ updateHighlightingEditor();
+ editor_wrapper.css("display", "block");
+ $("#id_content").css("display", "none");
+ });
+
+ var highlightingEnabled = function() {
+ return editor_wrapper.css("display") == 'block';
+ };
+ window.highlighting.isEnabled = highlightingEnabled;
+
+ editor_wrapper.append(editor);
+ $("#id_content").after(switch_link).after(editor_wrapper).hide();
+
+ window.addEventListener("load", function() {
+ var ace_editor = ace.edit("editor");
+ window.highlighting.editor = ace_editor;
+ var session = ace_editor.getSession();
+ window.highlighting.session = session;
+ session.setMode("ace/mode/sumo");
+ session.setUseWrapMode(true);
+
+ $("#id_content").bind("keyup", updateHighlightingEditor);
+ updateHighlightingEditor();
+
+ session.on('change', function(e) {
+ if(!highlightingEnabled())
+ return;
+ $("#id_content").val(session.getValue());
+ });
+ }, false);
+ }
function initFormLock() {
var $doc = $('#edit-document');
View
63 media/less/wiki.less
@@ -1146,3 +1146,66 @@ div.video .kbox-container {
text-decoration: none;
}
}
+
+/* Ace syntax custom CSS */
+#editor {
+ border: 2px solid #F7F7F7;
+ border-radius: 5px;
+ bottom: 0;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+}
+
+#editor_wrapper {
+ height: 500px;
+ position: relative;
+}
+
+.ace_quote {
+ background-color: black;
+ color: white;
+}
+
+.ace_variable,
+.ace_tag {
+ color: #d98484 !important;
+}
+
+.ace_other.ace_link {
+ color: #5757c2 !important;
+}
+
+/* Variable font-spacing support rules */
+/* Un-comment the following rules
+ when https://github.com/ajaxorg/ace/issues/460 is fixed: */
+/*
+.ace_variable,
+.ace_tag {
+ font-family: 'Monaco','Menlo','Ubuntu Mono','Droid Sans Mono','Consolas',monospace;
+}
+
+.ace_tag {
+ font-weight: bold;
+}
+
+.ace_editor {
+ font-family: 'OpenSans', Arial, Helvetica, sans-serif !important;
+}
+*/
+/* Remove the following rules
+ when https://github.com/ajaxorg/ace/issues/460 is fixed: */
+.ace_bold {
+ font-weight: normal !important;
+}
+
+.ace_strikethrough {
+ text-decoration: line-through;
+}
+
+.ace_editor .ace_identifier,
+.ace_editor .ace_italic,
+.ace_editor .ace_underline {
+ color: #666666 !important;
+}
View
4 settings.py
@@ -644,6 +644,10 @@ def JINJA_CONFIG():
'js/libs/diff_match_patch_uncompressed.js',
'js/diff.js',
),
+ 'wiki.editor': (
+ 'js/libs/ace/src-min/ace.js',
+ 'js/libs/ace.mode-sumo.js',
+ ),
'dashboard.helpful': (
'js/helpfuldashcharts.js',
),

0 comments on commit cb94df4

Please sign in to comment.
Something went wrong with that request. Please try again.