diff --git a/.jshintignore b/.jshintignore index ca83cd332..cac1da8f3 100644 --- a/.jshintignore +++ b/.jshintignore @@ -37,3 +37,4 @@ www/debug/chainpad.dist.js www/pad/mathjax/ www/code/mermaid*.js +www/code/orgmode.js diff --git a/www/code/inner.js b/www/code/inner.js index 7f304e6bc..05356a972 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -10,7 +10,6 @@ define([ '/common/common-util.js', '/common/common-hash.js', '/code/markers.js', - '/common/modes.js', '/common/visible.js', '/common/TypingTests.js', '/customize/messages.js', @@ -57,7 +56,6 @@ define([ Util, Hash, Markers, - Modes, Visible, TypingTest, Messages, diff --git a/www/code/orgmode.js b/www/code/orgmode.js index f36ffe9d6..c2af1bd5e 100644 --- a/www/code/orgmode.js +++ b/www/code/orgmode.js @@ -4,10 +4,40 @@ define([ ], function (CodeMirror) { CodeMirror.__mode = 'orgmode'; + var isEmpty = function (el, idx) { + if (idx < 2) { return true; } + return !Boolean(el); + }; + var onLevelOne = function (matches) { + // If all the elements starting from index 2 are empty, remove them + // because it means it's an empty header for now and it may break codemirror + if (matches && matches.length > 2 && matches.every(isEmpty)) { + matches.splice(2, (matches.length-2)); + } + return ["header level1 org-level-star","header level1 org-todo","header level1 org-done", "header level1 org-priority", "header level1", "header level1 void", "header level1 comment"]; + }; + // Dirty hack to make the function also work as an array + onLevelOne().forEach(function (str, i) { onLevelOne[i] = str; }); + + var onLevelStar = function (matches) { + // If all the elements starting from index 2 are empty, remove them + // because it means it's an empty header for now and it may break codemirror + if (matches && matches.length > 2 && matches.every(isEmpty)) { + matches.splice(2, (matches.length-2)); + } + return ["header org-level-star","header org-todo","header org-done", "header org-priority", "header", "header void", "header comment"]; + }; + // Dirty hack to make the function also work as an array + onLevelStar().forEach(function (str, i) { onLevelStar[i] = str; }); + CodeMirror.defineSimpleMode("orgmode", { start: [ + {regex: /(\*\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: onLevelOne}, + {regex: /(\*{1,}\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/g, sol: true, token: onLevelStar}, + /* {regex: /(\*\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header level1 org-level-star","header level1 org-todo","header level1 org-done", "header level1 org-priority", "header level1", "header level1 void", "header level1 comment"]}, - {regex: /(\*{1,}\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header org-level-star","header org-todo","header org-done", "header org-priority", "header", "header void", "header comment"]}, + {regex: /(\*{1,}\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/g, sol: true, token: ["header org-level-star","header org-todo","header org-done", "header org-priority", "header", "header void", "header comment"]}, + */ {regex: /(\+[^\+]+\+)/, token: ["strikethrough"]}, {regex: /(\*[^\*]+\*)/, token: ["strong"]}, {regex: /(\/[^\/]+\/)/, token: ["em"]}, @@ -93,6 +123,203 @@ define([ }; }); + CodeMirror.registerHelper("orgmode", "init", function (editor) { + editor.setOption("extraKeys", { + "Tab": function(cm) { org_cycle(cm); }, + "Shift-Tab": function(cm){ org_shifttab(cm); }, + "Alt-Left": function(cm){ org_metaleft(cm); }, + "Alt-Right": function(cm){ org_metaright(cm); }, + "Alt-Enter": function(cm){ org_meta_return(cm); }, + "Alt-Up": function(cm){ org_metaup(cm); }, + "Alt-Down": function(cm){ org_metadown(cm); }, + "Shift-Alt-Left": function(cm){ org_shiftmetaleft(cm); }, + "Shift-Alt-Right": function(cm){ org_shiftmetaright(cm); }, + "Shift-Alt-Enter": function(cm){ org_insert_todo_heading(cm); }, + "Shift-Left": function(cm){ org_shiftleft(cm); }, + "Shift-Right": function(cm){ org_shiftright(cm); } + }); + + editor.on('mousedown', toggleHandler); + editor.on('touchstart', toggleHandler); + editor.on('gutterClick', foldLine); + + // fold everything except headers by default + editor.operation(function() { + for (var i = 0; i < editor.lineCount() ; i++) { + if(/header/.test(editor.getTokenTypeAt(CodeMirror.Pos(i, 0))) === false){ + fold(editor, CodeMirror.Pos(i, 0)); + } + } + }); + return CodeMirror.orgmode.destroy.bind(this, editor); + }); + + CodeMirror.registerHelper("orgmode", "destroy", function (editor) { + editor.off('mousedown', toggleHandler); + editor.off('touchstart', toggleHandler); + editor.off('gutterClick', foldLine); + + // Restore CryptPad shortcuts + if (typeof (editor.updateSettings) === "function") { editor.updateSettings(); } + }); + + function foldLine (cm, line){ + var cursor = {line: line, ch: 0}; + isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor); + } + + + var widgets = []; + function toggleHandler (cm, e){ + var position = cm.coordsChar({ + left: e.clientX || (e.targetTouches && e.targetTouches[0].clientX), + top: e.clientY || (e.targetTouches && e.targetTouches[0].clientY) + }, "page"), + token = cm.getTokenAt(position); + + _disableSelection(); + if(/org-level-star/.test(token.type)){ + _preventIfShould(); + _foldHeadline(); + _disableSelection(); + }else if(/org-toggle/.test(token.type)){ + _preventIfShould(); + _toggleCheckbox(); + _disableSelection(); + }else if(/org-todo/.test(token.type)){ + _preventIfShould(); + _toggleTodo(); + _disableSelection(); + }else if(/org-done/.test(token.type)){ + _preventIfShould(); + _toggleDone(); + _disableSelection(); + }else if(/org-priority/.test(token.type)){ + _preventIfShould(); + _togglePriority(); + _disableSelection(); + }else if(/org-url/.test(token.type)){ + _disableSelection(); + _navigateLink(); + }else if(/org-image/.test(token.type)){ + _disableSelection(); + _toggleImageWidget(); + } + + function _preventIfShould(){ + if('ontouchstart' in window) e.preventDefault(); + } + function _disableSelection(){ + cm.on('beforeSelectionChange', _onSelectionChangeHandler); + function _onSelectionChangeHandler(cm, obj){ + obj.update([{ + anchor: position, + head: position + }]); + cm.off('beforeSelectionChange', _onSelectionChangeHandler); + } + } + + function _foldHeadline(){ + var line = position.line; + if(line >= 0){ + var cursor = {line: line, ch: 0}; + isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor); + } + } + + function _toggleCheckbox(){ + var line = position.line; + var content = cm.getRange({line: line, ch: token.start}, {line: line, ch: token.end}); + var new_content = content === "[X]" || content === "[x]" ? "[ ]" : "[X]"; + cm.replaceRange(new_content, {line: line, ch: token.start}, {line: line, ch: token.end}); + } + + function _toggleTodo(){ + var line = position.line; + cm.replaceRange("DONE", {line: line, ch: token.start}, {line: line, ch: token.end}); + } + + function _toggleDone(){ + var line = position.line; + cm.replaceRange("TODO", {line: line, ch: token.start}, {line: line, ch: token.end}); + } + + function _togglePriority(){ + var PRIORITIES = [" [#A] ", " [#B] ", " [#C] ", " [#A] "]; + var line = position.line; + var content = cm.getRange({line: line, ch: token.start}, {line: line, ch: token.end}); + var new_content = PRIORITIES[PRIORITIES.indexOf(content) + 1]; + cm.replaceRange(new_content, {line: line, ch: token.start}, {line: line, ch: token.end}); + } + + function _toggleImageWidget(){ + var exist = !!widgets + .filter(function (line) { return line === position.line; })[0]; + + if(exist === false){ + if(!token.string.match(/\[\[(.*)\]\]/)) return null; + var $node = _buildImage(RegExp.$1); + var widget = cm.addLineWidget(position.line, $node, {coverGutter: false}); + widgets.push(position.line); + $node.addEventListener('click', closeWidget); + + function closeWidget(){ + widget.clear(); + $node.removeEventListener('click', closeWidget); + widgets = widgets.filter(function (line) { return line !== position.line; }); + } + } + function _buildImage(src){ + var $el = document.createElement("div"); + var $img = document.createElement("img"); + + if(/^https?\:\/\//.test(src)){ + $img.src = src; + }else{ + var root_path = dirname(window.location.pathname.replace(/^\/view/, '')); + var img_path = src; + $img.src = "/api/files/cat?path="+encodeURIComponent(pathBuilder(root_path, img_path)); + } + $el.appendChild($img); + return $el; + } + return null; + } + + function _navigateLink(){ + token.string.match(/\[\[(.*?)\]\[/); + var link = RegExp.$1; + if(!link) return; + + if(/^https?\:\/\//.test(link)){ + window.open(link); + }else{ + var root_path = dirname(window.location.pathname.replace(/^\/view/, '')); + var link_path = link; + window.open("/view"+pathBuilder(root_path, link_path)); + } + } + } + + CodeMirror.defineMIME("text/org", "org"); + + function fold(cm, start){ + cm.foldCode(start, null, "fold"); + } + function unfold(cm, start){ + cm.foldCode(start, null, "unfold"); + } + function isFold(cm, start){ + var line = start.line; + var marks = cm.findMarks(CodeMirror.Pos(line, 0), CodeMirror.Pos(line + 1, 0)); + for (var i = 0; i < marks.length; ++i) { + if (marks[i].__isFold && marks[i].find().from.line === line) { return marks[i]; } + } + return false; + } + +/* CodeMirror.afterInit = function(editor){ function fold(cm, start){ cm.foldCode(start, null, "fold"); @@ -154,5 +381,662 @@ define([ } }); }; +*/ + + + +var org_cycle = function (cm) { + var pos = cm.getCursor(); + isFold(cm, pos) ? unfold(cm, pos) : fold(cm, pos); +}; + + +var state = { + stab: 'CONTENT' +}; +var org_set_fold = function (cm) { + var cursor = cm.getCursor(); + set_folding_mode(cm, state.stab); + cm.setCursor(cursor); + return state.stab; +}; +/* + * DONE: Global visibility cycling + * TODO: or move to previous table field. + */ +var org_shifttab = function (cm) { + if(state.stab === "SHOW_ALL"){ + state.stab = 'OVERVIEW'; + }else if(state.stab === "OVERVIEW"){ + state.stab = 'CONTENT'; + }else if(state.stab === "CONTENT"){ + state.stab = 'SHOW_ALL'; + } + set_folding_mode(cm, state.stab); + return state.stab; +}; + + +function set_folding_mode(cm, mode){ + if(mode === "OVERVIEW"){ + folding_mode_overview(cm); + }else if(mode === "SHOW_ALL"){ + folding_mode_all(cm); + }else if(mode === "CONTENT"){ + folding_mode_content(cm); + } + cm.refresh(); + + function folding_mode_overview(cm){ + cm.operation(function() { + for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){ + fold(cm, CodeMirror.Pos(i, 0)); + } + }); + } + function folding_mode_content(cm){ + cm.operation(function() { + var previous_header = null; + for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){ + fold(cm, CodeMirror.Pos(i, 0)); + if(/header/.test(cm.getTokenTypeAt(CodeMirror.Pos(i, 0))) === true){ + var level = cm.getLine(i).replace(/^(\*+).*/, "$1").length; + if(previous_header && level > previous_header.level){ + unfold(cm, CodeMirror.Pos(previous_header.line, 0)); + } + previous_header = { + line: i, + level: level + }; + } + } + }); + } + function folding_mode_all(cm){ + cm.operation(function() { + for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){ + if(/header/.test(cm.getTokenTypeAt(CodeMirror.Pos(i, 0))) === true){ + unfold(cm, CodeMirror.Pos(i, 0)); + } + } + }); + } +} + + +/* + * Promote heading or move table column to left. + */ +var org_metaleft = function (cm) { + var line = cm.getCursor().line; + _metaleft(cm, line); +}; +function _metaleft(cm, line){ + var p = null; + if(p = isTitle(cm, line)){ + if(p['level'] > 1) cm.replaceRange('', {line: p.start, ch: 0}, {line: p.start, ch: 1}); + }else if(p = isItemList(cm, line)){ + for(var i=p.start; i<=p.end; i++){ + if(p['level'] > 0) cm.replaceRange('', {line: i, ch: 0}, {line: i, ch: 2}); + } + }else if(p = isNumberedList(cm, line)){ + for(var i=p.start; i<=p.end; i++){ + if(p['level'] > 0) cm.replaceRange('', {line: i, ch: 0}, {line: i, ch: 3}); + } + rearrange_list(cm, line); + } +} + +/* + * Demote a subtree, a list item or move table column to right. + * In front of a drawer or a block keyword, indent it correctly. + */ +var org_metaright = function (cm){ + var line = cm.getCursor().line; + _metaright(cm, line); +}; + +function _metaright(cm, line) { + var p = null, tmp = null; + if(p = isTitle(cm, line)){ + cm.replaceRange('*', {line: p.start, ch: 0}); + }else if(p = isItemList(cm, line)){ + if(tmp = isItemList(cm, p.start - 1)){ + if(p.level < tmp.level + 1){ + for(var i=p.start; i<=p.end; i++){ + cm.replaceRange(' ', {line: i, ch: 0}); + } + } + } + }else if(p = isNumberedList(cm, line)){ + if(tmp = isNumberedList(cm, p.start - 1)){ + if(p.level < tmp.level + 1){ + for(var i=p.start; i<=p.end; i++){ + cm.replaceRange(' ', {line: i, ch: 0}); + } + rearrange_list(cm, p.start); + } + } + } +} + +/* + * Insert a new heading or wrap a region in a table + */ +var org_meta_return = function (cm) { + var line = cm.getCursor().line, + content = cm.getLine(line); + var p = null; + + if(p = isItemList(cm, line)){ + var level = p.level; + cm.replaceRange('\n'+" ".repeat(level*2)+'- ', {line: p.end, ch: cm.getLine(p.end).length}); + cm.setCursor({line: p.end+1, ch: level*2+2}); + }else if(p = isNumberedList(cm, line)){ + var level = p.level; + cm.replaceRange('\n'+" ".repeat(level*3)+(p.n+1)+'. ', {line: p.end, ch: cm.getLine(p.end).length}); + cm.setCursor({line: p.end+1, ch: level*3+3}); + rearrange_list(cm, line); + }else if(p = isTitle(cm, line)){ + var tmp = previousOfType(cm, 'title', line); + var level = tmp && tmp.level || 1; + cm.replaceRange('\n'+'*'.repeat(level)+' ', {line: line, ch: content.length}); + cm.setCursor({line: line+1, ch: level+1}); + }else if(content.trim() === ""){ + cm.replaceRange('* ', {line: line, ch: 0}); + cm.setCursor({line: line, ch: 2}); + }else{ + cm.replaceRange('\n\n* ', {line: line, ch: content.length}); + cm.setCursor({line: line + 2, ch: 2}); + } +}; + + +var TODO_CYCLES = ["TODO", "DONE", ""]; +/* + * Cycle the thing at point or in the current line, depending on context. + * Depending on context, this does one of the following: + * - TODO: switch a timestamp at point one day into the past + * - DONE: on a headline, switch to the previous TODO keyword. + * - TODO: on an item, switch entire list to the previous bulvar type + * - TODO: on a property line, switch to the previous allowed value + * - TODO: on a clocktable definition line, move time block into the past + */ +var org_shiftleft = function (cm) { + var cycles = [].concat(TODO_CYCLES.slice(0).reverse(), TODO_CYCLES.slice(-1)), + line = cm.getCursor().line, + content = cm.getLine(line), + params = isTitle(cm, line); + + if(params === null) return; + params['status'] = cycles[cycles.indexOf(params['status']) + 1]; + cm.replaceRange(makeTitle(params), {line: line, ch: 0}, {line: line, ch: content.length}); +}; +/* + * Cycle the thing at point or in the current line, depending on context. + * Depending on context, this does one of the following: + * - TODO: switch a timestamp at point one day into the future + * - DONE: on a headline, switch to the next TODO keyword. + * - TODO: on an item, switch entire list to the next bulvar type + * - TODO: on a property line, switch to the next allowed value + * - TODO: on a clocktable definition line, move time block into the future + */ +var org_shiftright = function (cm) { + cm.operation(function () { + var cycles = [].concat(TODO_CYCLES, [TODO_CYCLES[0]]), + line = cm.getCursor().line, + content = cm.getLine(line), + params = isTitle(cm, line); + + if(params === null) return; + params['status'] = cycles[cycles.indexOf(params['status']) + 1]; + cm.replaceRange(makeTitle(params), {line: line, ch: 0}, {line: line, ch: content.length}); + }); +}; + +var org_insert_todo_heading = function (cm) { + cm.operation(function () { + var line = cm.getCursor().line, + content = cm.getLine(line); + + var p = null; + if(p = isItemList(cm, line)){ + var level = p.level; + cm.replaceRange('\n'+" ".repeat(level*2)+'- [ ] ', {line: p.end, ch: cm.getLine(p.end).length}); + cm.setCursor({line: line+1, ch: 6+level*2}); + }else if(p = isNumberedList(cm, line)){ + var level = p.level; + cm.replaceRange('\n'+" ".repeat(level*3)+(p.n+1)+'. [ ] ', {line: p.end, ch: cm.getLine(p.end).length}); + cm.setCursor({line: p.end+1, ch: level*3+7}); + rearrange_list(cm, line); + }else if(p = isTitle(cm, line)){ + var level = p && p.level || 1; + cm.replaceRange('\n'+"*".repeat(level)+' TODO ', {line: line, ch: content.length}); + cm.setCursor({line: line+1, ch: level+6}); + }else if(content.trim() === ""){ + cm.replaceRange('* TODO ', {line: line, ch: 0}); + cm.setCursor({line: line, ch: 7}); + }else{ + cm.replaceRange('\n\n* TODO ', {line: line, ch: content.length}); + cm.setCursor({line: line + 2, ch: 7}); + } + }); +} + + +/* + * Move subtree up or move table row up. + * Calls ‘org-move-subtree-up’ or ‘org-table-move-row’ or + * ‘org-move-item-up’, depending on context + */ +var org_metaup = function (cm) { + cm.operation(function () { + var line = cm.getCursor().line; + var p = null; + + if(p = isItemList(cm, line)){ + var a = isItemList(cm, p.start - 1); + if(a){ + swap(cm, [p.start, p.end], [a.start, a.end]); + rearrange_list(cm, line); + } + }else if(p = isNumberedList(cm, line)){ + var a = isNumberedList(cm, p.start - 1); + if(a){ + swap(cm, [p.start, p.end], [a.start, a.end]); + rearrange_list(cm, line); + } + }else if(p = isTitle(cm, line)){ + var _line = line, + a; + do{ + _line -= 1; + if(a = isTitle(cm, _line, p.level)){ + break; + } + }while(_line > 0); + + if(a){ + swap(cm, [p.start, p.end], [a.start, a.end]); + org_set_fold(cm); + } + } + }); +} + +/* + * Move subtree down or move table row down. + * Calls ‘org-move-subtree-down’ or ‘org-table-move-row’ or + * ‘org-move-item-down’, depending on context + */ +var org_metadown = function (cm) { + cm.operation(function () { + var line = cm.getCursor().line; + var p = null; + + if(p = isItemList(cm, line)){ + var a = isItemList(cm, p.end + 1); + if(a){ + swap(cm, [p.start, p.end], [a.start, a.end]); + } + }else if(p = isNumberedList(cm, line)){ + var a = isNumberedList(cm, p.end + 1); + if(a){ + swap(cm, [p.start, p.end], [a.start, a.end]); + } + rearrange_list(cm, line); + }else if(p = isTitle(cm, line)){ + var a = isTitle(cm, p.end + 1, p.level); + if(a){ + swap(cm, [p.start, p.end], [a.start, a.end]); + org_set_fold(cm); + } + } + }); +} + + + +var org_shiftmetaright = function(cm) { + cm.operation(function () { + var line = cm.getCursor().line; + var p = null; + if(p = isTitle(cm, line)){ + _metaright(cm, line); + for(var i=p.start + 1; i<=p.end; i++){ + if(isTitle(cm, i)){ + _metaright(cm, i); + } + } + } + }); +}; + +var org_shiftmetaleft = function(cm){ + cm.operation(function () { + var line = cm.getCursor().line; + var p = null; + if(p = isTitle(cm, line)){ + if(p.level === 1) return; + _metaleft(cm, line); + for(var i=p.start + 1; i<=p.end; i++){ + if(isTitle(cm, i)){ + _metaleft(cm, i); + } + } + } + }); +}; + + + +function makeTitle(p){ + var content = "*".repeat(p['level'])+" "; + if(p['status']){ + content += p['status']+" "; + } + content += p['content']; + return content; +} + +function previousOfType(cm, type, line){ + var content, tmp, i; + for(i=line - 1; i>0; i--){ + if(type === 'list' || type === null){ + tmp = isItemList(cm, line); + }else if(type === 'numbered' || type === null){ + tmp = isNumberedList(cm, line); + }else if(type === 'title' || type === null){ + tmp = isTitle(cm, line); + } + if(tmp !== null){ + return tmp; + } + } + return null; +} + +function isItemList(cm, line){ + var rootLineItem = findRootLine(cm, line); + if(rootLineItem === null) return null; + line = rootLineItem; + var content = cm.getLine(line); + + if(content && (content.trimLeft()[0] !== "-" || content.trimLeft()[1] !== " ")) return null; + var padding = content.replace(/^(\s*).*$/, "$1").length; + if(padding % 2 !== 0) return null; + return { + type: 'list', + level: padding / 2, + content: content.trimLeft().replace(/^\s*\-\s(.*)$/, '$1'), + start: line, + end: function(_cm, _line){ + var line_candidate = _line, + content = null; + do{ + _line += 1; + content = _cm.getLine(_line); + if(content === undefined || content.trimLeft()[0] === "-"){ + break; + }else if(/^\s+/.test(content)){ + line_candidate = _line; + continue; + }else{ + break; + } + }while(_line <= _cm.lineCount()) + return line_candidate; + }(cm, line) + }; + + function findRootLine(_cm, _line){ + var content; + do{ + content = _cm.getLine(_line); + if(/^\s*\-/.test(content)) return _line; + else if(/^\s+/.test(content) === false){ + break; + } + _line -= 1; + }while(_line >= 0); + return null; + } + +} +function isNumberedList(cm, line){ + var rootLineItem = findRootLine(cm, line); + if(rootLineItem === null) return null; + line = rootLineItem; + var content = cm.getLine(line); + + if(/^[0-9]+[\.\)]\s.*$/.test(content && content.trimLeft()) === false) return null; + var padding = content.replace(/^(\s*)[0-9]+.*$/, "$1").length; + if(padding % 3 !== 0) return null; + return { + type: 'numbered', + level: padding / 3, + content: content.trimLeft().replace(/^[0-9]+[\.\)]\s(.*)$/, '$1'), + start: line, + end: function(_cm, _line){ + var line_candidate = _line, + content = null; + do{ + _line += 1; + content = _cm.getLine(_line); + if(content === undefined || /^[0-9]+[\.\)]/.test(content.trimLeft())){ + break; + }else if(/^\s+/.test(content)){ + line_candidate = _line; + continue; + }else{ + break; + } + }while(_line <= _cm.lineCount()) + return line_candidate; + }(cm, line), + // specific + n: parseInt(content.trimLeft().replace(/^([0-9]+).*$/, "$1")), + separator: content.trimLeft().replace(/^[0-9]+([\.\)]).*$/, '$1') + }; + + + function findRootLine(_cm, _line){ + var content; + do{ + content = _cm.getLine(_line); + if(/^\s*[0-9]+[\.\)]\s/.test(content)) return _line; + else if(/^\s+/.test(content) === false){ + break; + } + _line -= 1; + }while(_line >= 0); + return null; + } +} +function isTitle(cm, line, level){ + var content = cm.getLine(line); + if(/^\*+\s/.test(content) === false) return null; + var match = content.match(/^(\*+)([\sA-Z]*)\s(.*)$/); + var reference_level = match[1].length; + if(level !== undefined && level !== reference_level){ return null; } + if(match === null) return null; + return { + type: 'title', + level: reference_level, + content: match[3], + start: line, + end: function(_cm, _line){ + var line_candidate = _line, + content = null; + do{ + _line += 1; + content = _cm.getLine(_line); + if(content === undefined) break; + var match = content.match(/^(\*+)\s.*/); + if(match && match[1] && ( match[1].length === reference_level || match[1].length < reference_level)){ + break; + }else{ + line_candidate = _line; + continue; + } + }while(_line <= _cm.lineCount()) + return line_candidate; + }(cm, line), + // specific + status: match[2].trim() + }; +} + +function rearrange_list(cm, line){ + var line_inferior = find_limit_inferior(cm, line); + var line_superior = find_limit_superior(cm, line); + + var last_p = null, p; + + for(var i=line_inferior; i<=line_superior; i++){ + if(p = isNumberedList(cm, i)){ + // rearrange numbers on the numbered list + if(last_p){ + if(p.level === last_p.level){ + var tmp = findLastAtLevel(cm, p.start, line_inferior, p.level); + if(tmp && p.n !== tmp.n + 1) setNumber(cm, p.start, tmp.n + 1); + }else if(p.level > last_p.level){ + if(p.n !== 1){ + setNumber(cm, p.start, 1); + } + }else if(p.level < last_p.level){ + var tmp = findLastAtLevel(cm, p.start, line_inferior, p.level); + if(tmp && p.n !== tmp.n + 1) setNumber(cm, p.start, tmp.n + 1); + } + }else{ + if(p.n !== 1){ setNumber(cm, p.start, 1); } + } + } + + + if(p = (isNumberedList(cm, i) || isItemList(cm, i))){ + // rearrange spacing levels in list + if(last_p){ + if(p.level > last_p.level){ + if(p.level !== last_p.level + 1){ + setLevel(cm, [p.start, p.end], last_p.level + 1, p.type); + } + } + }else{ + if(p.level !== 0){ + setLevel(cm, [p.start, p.end], 0, p.type); + } + } + } + + + last_p = p; + // we can process content block instead of line + if(p){ + i += (p.end - p.start); + } + } + + function findLastAtLevel(_cm, line, line_limit_inf, level){ + var p; + do{ + line -= 1; + if((p = isNumberedList(_cm, line)) && p.level === level) + return p; + }while(line > line_limit_inf); + + return null; + } + + function setLevel(_cm, range, level, type){ + var content, i; + for(i=range[0]; i<=range[1]; i++){ + content = cm.getLine(i).trimLeft(); + var n_spaces = function(_level, _line, _type){ + var spaces = _level * 3; + if(_line > 0){ + spaces += _type === 'numbered' ? 3 : 2; + } + return spaces; + }(level, i - range[0], type) + + content = " ".repeat(n_spaces) + content; + cm.replaceRange(content, {line: i, ch: 0}, {line: i, ch: _cm.getLine(i).length}); + } + } + + function setNumber(_cm, line, level){ + var content = _cm.getLine(line); + var new_content = content.replace(/[0-9]+\./, level+"."); + cm.replaceRange(new_content, {line: line, ch: 0}, {line: line, ch: content.length}); + } + + function find_limit_inferior(_cm, _line){ + var content, p, match, line_candidate = _line; + do{ + content = _cm.getLine(_line); + p = isNumberedList(_cm, _line); + match = /(\s+).*$/.exec(content); + if(p){ line_candidate = _line;} + if(!p || !match) break; + _line -= 1; + }while(_line >= 0); + return line_candidate; + } + function find_limit_superior(_cm, _line){ + var content, p, match, line_candidate = _line; + do{ + content = _cm.getLine(_line); + p = isNumberedList(_cm, _line); + match = /(\s+).*$/.exec(content); + if(p){ line_candidate = _line;} + if(!p || !match) break; + _line += 1; + }while(_line < _cm.lineCount()); + return line_candidate; + } +} + +function swap(cm, from, to){ + var from_content = cm.getRange({line: from[0], ch: 0}, {line: from[1], ch: cm.getLine(from[1]).length}), + to_content = cm.getRange({line: to[0], ch: 0}, {line: to[1], ch: cm.getLine(to[1]).length}), + cursor = cm.getCursor(); + + if(to[0] > from[0]){ + // moving down + cm.replaceRange( + from_content, + {line: to[0], ch:0}, + {line: to[1], ch: cm.getLine(to[1]).length} + ); + cm.replaceRange( + to_content, + {line: from[0], ch:0}, + {line: from[1], ch: cm.getLine(from[1]).length} + ); + cm.setCursor({ + line: cursor.line + (to[1] - to[0] + 1), + ch: cursor.ch + }); + }else{ + // moving up + cm.replaceRange( + to_content, + {line: from[0], ch:0}, + {line: from[1], ch: cm.getLine(from[1]).length} + ); + cm.replaceRange( + from_content, + {line: to[0], ch:0}, + {line: to[1], ch: cm.getLine(to[1]).length} + ); + cm.setCursor({ + line: cursor.line - (to[1] - to[0] + 1), + ch: cursor.ch + }); + } +} + + }); diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 112750a55..9d6529fe1 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -199,6 +199,7 @@ define([ evContentUpdate.fire(newContent, waitFor); oldContent = newContent; } catch (e) { + console.error(e); console.log(e.stack); UI.errorLoadingScreen(e.message); } diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index e04dc9c2e..a1c0958a9 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -147,6 +147,13 @@ define([ editor.setOption('indentWithTabs', useTabs); editor.setOption('spellcheck', spellcheck); editor.setOption('autoCloseBrackets', brackets); + setTimeout(function () { + $('.CodeMirror').css('font-size', fontSize+'px'); + editor.refresh(); + }); + + // orgmode is using its own shortcuts + if (editor.getMode().name === 'orgmode') { return; } editor.setOption("extraKeys", { Tab: function() { if (doc.somethingSelected()) { @@ -160,10 +167,16 @@ define([ "Shift-Tab": function () { editor.execCommand("indentLess"); }, - }); - setTimeout(function () { - $('.CodeMirror').css('font-size', fontSize+'px'); - editor.refresh(); + "Alt-Left": undefined, + "Alt-Right": undefined, + "Alt-Enter": undefined, + "Alt-Up": undefined, + "Alt-Down": undefined, + "Shift-Alt-Left": undefined, + "Shift-Alt-Right": undefined, + "Shift-Alt-Enter": undefined, + "Shift-Alt-Up": undefined, + "Shift-Alt-Down": undefined, }); }; @@ -171,7 +184,7 @@ define([ var useTabsKey = 'indentWithTabs'; var fontKey = 'fontSize'; var spellcheckKey = 'spellcheck'; - var updateIndentSettings = function () { + var updateIndentSettings = editor.updateSettings = function () { if (!metadataMgr) { return; } var data = metadataMgr.getPrivateData().settings; data = data.codemirror || {}; @@ -268,6 +281,17 @@ define([ name = name ? Messages.languageButton + ' ('+name+')' : Messages.languageButton; exp.$language.setValue(mode, name); } + + if (mode === "orgmode") { + if (CodeMirror.orgmode && typeof (CodeMirror.orgmode.init) === "function") { + CodeMirror.orgmode.init(editor); + } + } else { + if (CodeMirror.orgmode && typeof (CodeMirror.orgmode.destroy) === "function") { + CodeMirror.orgmode.destroy(editor); + } + } + if(cb) { cb(mode); } }; @@ -343,7 +367,8 @@ define([ }); $aLanguages.click(function () { isHovering = false; - setMode($(this).attr('data-value'), onModeChanged); + var mode = $(this).attr('data-value'); + setMode(mode, onModeChanged); onLocal(); }); diff --git a/www/kanban/inner.js b/www/kanban/inner.js index cf830b096..9aaa508a0 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -10,7 +10,6 @@ define([ '/common/common-interface.js', '/common/common-ui-elements.js', '/common/inner/common-mediatag.js', - '/common/modes.js', '/customize/messages.js', '/common/hyperscript.js', '/common/text-cursor.js', @@ -48,7 +47,6 @@ define([ UI, UIElements, MT, - Modes, Messages, h, TextCursor,