diff --git a/client/pages/viewerpage/editor.js b/client/pages/viewerpage/editor.js index a4172c1a..721e037e 100644 --- a/client/pages/viewerpage/editor.js +++ b/client/pages/viewerpage/editor.js @@ -32,9 +32,15 @@ export class Editor extends React.Component { editor: null, filename: this.props.filename }; + this._refresh = this._refresh.bind(this); + } + + _refresh(){ + if(this.state.editor) this.state.editor.refresh(); } componentDidMount(){ + window.addEventListener('resize', this._refresh); this.setState({loading: null, error: false}, () => { window.setTimeout(() => { if(this.state.loading === null) this.setState({loading: true}); @@ -60,6 +66,7 @@ export class Editor extends React.Component { this.props.onFoldChange( org_shifttab(this.state.editor) ); + this.state.editor.refresh(); } }); }); @@ -70,17 +77,15 @@ export class Editor extends React.Component { const size_small = 500; let editor = CodeMirror(document.getElementById('editor'), { value: this.props.content, - lineNumbers: document.body.offsetWidth > size_small ? true : false, + lineNumbers: true, mode: mode, keyMap: config.god_editor_mode ? "emacs" : "default", lineWrapping: true, - foldGutter: { - minFoldSize: 1 - }, foldOptions: { widget: "..." } }); + window._editor = editor; if(!('ontouchstart' in window)) editor.focus(); @@ -112,6 +117,7 @@ export class Editor extends React.Component { } componentWillUnmount(){ + window.removeEventListener('resize', this._refresh); this.state.editor.clearHistory(); } diff --git a/client/pages/viewerpage/editor.scss b/client/pages/viewerpage/editor.scss index 9f95eac5..dcbe045b 100644 --- a/client/pages/viewerpage/editor.scss +++ b/client/pages/viewerpage/editor.scss @@ -35,6 +35,15 @@ padding: 5px; } +/* HIDE LINE NUMBERS ON MOBILE */ +// this hack is important as we rely on the dom to provide code folding for org mode +@media screen and (max-width: 400px) { + .CodeMirror-sizer{ margin-left: 0!important; } + .CodeMirror-gutters{ display: none; } + .CodeMirror-gutter-wrapper{ display: none; } +} + + /* SEARCH */ .CodeMirror-dialog { position: fixed; @@ -82,6 +91,9 @@ .cm-s-default .cm-header.cm-org-level-star{ color: #6f6f6f; vertical-align: text-bottom; + display: inline-block; + padding-left: 4px; + margin-left: -4px; } .cm-s-default .cm-header.cm-org-todo{color: #FF8355; font-weight: normal;} .cm-s-default .cm-header.cm-org-done{color: #3BB27C; font-weight: normal;} diff --git a/client/pages/viewerpage/editor/orgmode.js b/client/pages/viewerpage/editor/orgmode.js index 20a91a3b..6ffae9ed 100644 --- a/client/pages/viewerpage/editor/orgmode.js +++ b/client/pages/viewerpage/editor/orgmode.js @@ -17,7 +17,7 @@ CodeMirror.defineSimpleMode("orgmode", { {regex: /(\~[^\~]+\~)/, token: ["comment"]}, {regex: /(\=[^\=]+\=)/, token: ["comment"]}, {regex: /\[\[[^\[\]]*\]\[[^\[\]]*\]\]/, token: "url"}, // links - {regex: /\[[xX\s]?\]/, token: 'qualifier'}, // checkbox + {regex: /\[[xX\s\-]?\]/, token: 'qualifier org-toggle'}, // checkbox {regex: /\#\+BEGIN_[A-Z]*/, token: "comment", next: "env"}, // comments {regex: /:?[A-Z_]+\:.*/, token: "comment"}, // property drawers {regex: /(\#\+[A-Z_]*)(\:.*)/, token: ["keyword", 'qualifier']}, // environments @@ -120,12 +120,57 @@ CodeMirror.afterInit = function(editor, fn){ } }); - editor.on('touchstart', function(cm, e){ - setTimeout(() => { - isFold(cm, cm.getCursor()) ? unfold(cm, cm.getCursor()) : fold(cm, cm.getCursor()) - }, 150); + + // Toggle headline on org mode by clicking on the heading ;) + editor.on('mousedown', toggleHandler); + editor.on('touchstart', toggleHandler); + function toggleHandler(cm, e){ + const className = e.target.getAttribute('class'); + if(/cm-org-level-star/.test(className) === true){ + _foldHeadline(cm, e); + }else if(/cm-org-toggle/.test(className) === true){ + _toggleCheckbox(cm, e); + } + + function _foldHeadline(){ + const line = _init(e); + if(line >= 0){ + const cursor = {line: line, ch: 0}; + isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor); + } + } + function _toggleCheckbox(){ + const line = _init(e), + reg = /\[(x|X|\s|\-)]/; + + if(line > 0 && reg.test(e.target.innerHTML)){ + const old = cm.getLine(line), + cursor = cm.getCursor(), + content = RegExp.$1.toLowerCase() === "x" ? old.replace(reg, "[ ]") : old.replace(reg, "[X]"); + + cm.replaceRange(content, {line: line, ch:0}, {line: line, ch: old.length}); + cm.setCursor(cursor); + } + } + function _init(e){ + if('ontouchstart' in window) e.preventDefault(); + + // yes it's dirty but well code mirror doesn't let us the choice + let line = parseInt( + e.target + .parentElement.parentElement.parentElement + .firstElementChild.firstElementChild.textContent + ) - 1; + return line; + } + } + editor.on('gutterClick', function(cm, line){ + const cursor = {line: line, ch: 0}; + isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor); }); + + // fold everything except headers by default editor.operation(function() { for (var i = 0; i < editor.lineCount() ; i++) {