From 43d46b4cfdce275747e67433f8c791b9915ec611 Mon Sep 17 00:00:00 2001 From: wwebfor Date: Fri, 25 Dec 2015 18:56:48 +0600 Subject: [PATCH 01/25] Switch to Codemirror Why: * It probably better supports Markdown syntax. * It is easier to extend. --- app/scripts/modules/codemirror/controller.js | 566 ++++++++++++++++++ app/scripts/modules/codemirror/module.js | 50 ++ .../modules/codemirror/templates/editor.html | 69 +++ .../modules/codemirror/views/editor.js | 55 ++ 4 files changed, 740 insertions(+) create mode 100644 app/scripts/modules/codemirror/controller.js create mode 100644 app/scripts/modules/codemirror/module.js create mode 100644 app/scripts/modules/codemirror/templates/editor.html create mode 100644 app/scripts/modules/codemirror/views/editor.js diff --git a/app/scripts/modules/codemirror/controller.js b/app/scripts/modules/codemirror/controller.js new file mode 100644 index 000000000..5633a8ec3 --- /dev/null +++ b/app/scripts/modules/codemirror/controller.js @@ -0,0 +1,566 @@ +/** + * Copyright (C) 2015 Laverna project Authors. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* global define */ +define([ + 'underscore', + 'marionette', + 'backbone.radio', + 'codemirror/lib/codemirror', + 'modules/codemirror/views/editor', + 'codemirror/mode/gfm/gfm', + 'codemirror/mode/markdown/markdown', + 'codemirror/addon/edit/continuelist', + 'codemirror/addon/mode/overlay', +], function(_, Marionette, Radio, CodeMirror, View) { + 'use strict'; + + /** + * Codemirror module. + * Regex and WYSIWG button functions are based on simplemde-markdown-editor: + * https://github.com/NextStepWebs/simplemde-markdown-editor + */ + var Controller = Marionette.Object.extend({ + + marks: { + strong: { + tag : ['**', '__'], + start : /(\*\*|__)(?![\s\S]*(\*\*|__))/, + end : /(\*\*|__)/, + }, + em: { + tag : ['*', '_'], + start : /(\*|_)(?![\s\S]*(\*|_))/, + end : /(\*|_)/, + }, + strikethrough: { + tag : ['~~'], + start : /(\*\*|~~)(?![\s\S]*(\*\*|~~))/, + end : /(\*\*|~~)/, + }, + 'code': { + tag : '```\r\n', + tagEnd : '\r\n```', + }, + 'unordered-list': { + replace : /^(\s*)(\*|\-|\+)\s+/, + tag : '* ', + }, + 'ordered-list': { + replace : /^(\s*)\d+\.\s+/, + tag : '1. ', + }, + }, + + initialize: function() { + _.bindAll(this, 'onChange', 'onScroll', 'onCursor', 'boldAction', 'italicAction', 'linkAction', 'headingAction', 'attachmentAction', 'codeAction', 'hrAction', 'listAction', 'numberedListAction'); + + // Get configs + this.configs = Radio.request('configs', 'get:object'); + + // Initialize the view + this.view = new View({ + model : Radio.request('notesForm', 'model'), + configs : this.configs + }); + + this.view.once('dom:refresh', this.initEditor, this); + + // Events + this.listenTo(this.view, 'editor:action', this.onViewAction); + + // Show the view and render Pagedown editor + Radio.request('notesForm', 'show:editor', this.view); + + Radio.reply('editor', { + 'get:data' : this.getData, + 'generate:link' : this.generateLink, + 'generate:image': this.generateImage + }, this); + }, + + onDestroy: function() { + Radio.stopReplying('editor', 'get:data'); + }, + + initEditor: function() { + this.editor = CodeMirror.fromTextArea(document.getElementById('editor--input'), { + mode : { + name : 'gfm', + gitHubSpice : false + }, + lineNumbers : false, + matchBrackets : true, + lineWrapping : true, + extraKeys : { + 'Cmd-B' : this.boldAction, + 'Ctrl-B' : this.boldAction, + + 'Cmd-I' : this.italicAction, + 'Ctrl-I' : this.italicAction, + + 'Cmd-H' : this.headingAction, + 'Ctrl-H' : this.headingAction, + + 'Cmd-L' : this.linkAction, + 'Ctrl-L' : this.linkAction, + + 'Cmd-K' : this.codeAction, + 'Ctrl-K' : this.codeAction, + + 'Cmd-O' : this.numberedListAction, + 'Ctrl-O' : this.numberedListAction, + + 'Cmd-U' : this.listAction, + 'Ctrl-U' : this.listAction, + + // Ctrl+d - divider + 'Cmd-G' : this.attachmentAction, + 'Ctrl-G' : this.attachmentAction, + + // Ctrl+d - divider + 'Cmd-D' : this.hrAction, + 'Ctrl-D' : this.hrAction, + + 'Enter': 'newlineAndIndentContinueMarkdownList' + } + }); + + window.dispatchEvent(new Event('resize')); + this.editor.on('change', this.onChange); + this.editor.on('scroll', this.onScroll); + this.editor.on('cursorActivity', this.onCursor); + + // Show the preview + this.updatePreview(); + }, + + /** + * Update the preview. + */ + updatePreview: function() { + var self = this; + + return Radio.request('markdown', 'render', this.editor.getValue()) + .then(function(content) { + self.view.trigger('editor:change', content); + }); + }, + + /** + * Text in the editor changed. + */ + onChange: function() { + + // Update the preview + this.updatePreview(); + + // Trigger autosave + this.autoSave(); + }, + + /** + * Editor's cursor position changed. + */ + onCursor: function() { + var state = this.getState(); + this.$btns = this.$btns || $('.editor--btns .btn'); + + // Make a specific button active depending on the type of the element under cursor + this.$btns.removeClass('btn-primary'); + for (var i = 0; i < state.length; i++) { + this['$btn' + state[i]] = this['$btn' + state[i]] || $('.editor--btns [data-state="' + state[i] + '"]'); + this['$btn' + state[i]].addClass('btn-primary'); + } + }, + + /** + * Trigger 'save:auto' event. + */ + autoSave: _.debounce(function() { + Radio.trigger('notesForm', 'save:auto'); + }, 1000), + + /** + * Synchronize the editor's scroll position with the preview's. + */ + onScroll: _.debounce(function(e) { + + // Don't do any computations + if (!e.doc.scrollTop) { + this.view.ui.preview.scrollTop(0); + return; + } + + var info = this.editor.getScrollInfo(), + lineNumber = this.editor.lineAtHeight(info.top, 'local'), + range = this.editor.getRange({line: 0, ch: null}, {line: lineNumber, ch: null}), + self = this, + fragment, + temp, + lines, + els; + + Radio.request('markdown', 'render', range) + .then(function(html) { + + // Create a fragment and attach rendered HTML + fragment = document.createDocumentFragment(); + temp = document.createElement('div'); + temp.innerHTML = html; + fragment.appendChild(temp); + + // Get all elements in both the fragment and the preview + lines = temp.children; + els = self.view.ui.preview[0].children; + + // Get from the preview the last visible element of the editor + var newPos = els[lines.length].offsetTop; + + /** + * If the scroll position is on the same element, + * change it according to the difference of scroll positions in the editor. + */ + if (self.scrollTop && self.scrollPos === newPos) { + self.view.ui.preview.scrollTop(self.view.ui.preview.scrollTop() + (e.doc.scrollTop - self.scrollTop)); + self.scrollTop = e.doc.scrollTop; + return; + } + + // Scroll to the last visible element's position + self.view.ui.preview.animate({ + scrollTop: newPos + }, 70, 'swing'); + + self.scrollPos = newPos; + self.scrollTop = e.doc.scrollTop; + }); + }, 10), + + /** + * If the view triggered some action event, call a suitable function. + * For instance, when action='bold', call boldAction method. + */ + onViewAction: function(action) { + action = action + 'Action'; + + if (this[action]) { + this[action](); + } + }, + + /** + * Return data from the editor. + */ + getData: function() { + var content = this.editor.getValue(); + + return Radio.request('markdown', 'render', content) + .then(function() { + return { + content: content, + tags: [], + tasks: [] + }; + }); + }, + + /** + * Return state of the element under the cursor. + */ + getState: function(pos) { + pos = pos || this.editor.getCursor('start'); + var stat = this.editor.getTokenAt(pos); + + if (!stat.type) { + return []; + } + + stat.type = stat.type.split(' '); + + if (_.indexOf(stat.type, 'variable-2') !== -1) { + if (/^\s*\d+\.\s/.test(this.editor.getLine(pos.line))) { + stat.type.push('ordered-list'); + } + else { + stat.type.push('unordered-list'); + } + } + + + return stat.type; + }, + + /** + * Toggle Markdown block. + */ + toggleBlock: function(type) { + var stat = this.getState(), + start = this.editor.getCursor('start'), + end = this.editor.getCursor('end'), + text, + startText, + endText; + + // Text is already [strong|italic|etc] + if (_.indexOf(stat, type) !== -1) { + text = this.editor.getLine(start.line); + startText = text.slice(0, start.ch); + endText = text.slice(start.ch); + + // Remove Markdown tags from the text + startText = startText.replace(this.marks[type].start, ''); + endText = endText.replace(this.marks[type].end, ''); + + this.replaceRange(startText + endText, start.line); + + start.ch -= this.marks[type].tag[0].length; + end.ch -= this.marks[type].tag[0].length; + } + else { + text = this.editor.getSelection(); + + for (var i = 0; i < this.marks[type].tag.length - 1; i++) { + text = text.split(this.marks[type].tag[i]).join(''); + } + + this.editor.replaceSelection(this.marks[type].tag[0] + text + this.marks[type].tag[0]); + + start.ch += this.marks[type].tag[0].length; + end.ch = start.ch + text.length; + } + + this.editor.setSelection(start, end); + this.editor.focus(); + }, + + /** + * Make selected text strong. + */ + boldAction: function() { + this.toggleBlock('strong'); + }, + + /** + * Make selected text italicized. + */ + italicAction: function() { + this.toggleBlock('em'); + }, + + /** + * Create headings. + */ + headingAction: function() { + var start = this.editor.getCursor('start'), + end = this.editor.getCursor('end'); + + for (var i = start.line; i <= end.line; i++) { + this.toggleHeading(i); + } + }, + + /** + * Show a dialog to attach images or files. + */ + attachmentAction: function() { + var self = this, + dialog = Radio.request('editor', 'show:attachment', this.view.model); + + if (!dialog) { + return; + } + + dialog.then(function(text) { + if (!text || !text.length) { + return; + } + + self.editor.replaceSelection(text, true); + self.editor.focus(); + }); + }, + + /** + * Show a link dialog. + */ + linkAction: function() { + var self = this, + dialog = Radio.request('editor', 'show:link'); + + if (!dialog) { + return; + } + + dialog.then(function(link) { + if (!link || !link.length) { + return; + } + + var cursor = self.editor.getCursor('start'), + text = self.editor.getSelection() || 'Link'; + + self.editor.replaceSelection('[' + text + '](' + link + ')'); + self.editor.setSelection( + {line: cursor.line, ch: cursor.ch + 1}, + {line: cursor.line, ch: cursor.ch + text.length + 1} + ); + self.editor.focus(); + }); + }, + + /** + * Create a divider. + */ + hrAction: function() { + var start = this.editor.getCursor('start'); + this.editor.replaceSelection('\r\r-----\r\r'); + + start.line += 4; + start.ch = 0; + this.editor.setSelection( start, start ); + this.editor.focus(); + }, + + /** + * Create a code block. + */ + codeAction: function() { + var state = this.getState(), + start = this.editor.getCursor('start'), + end = this.editor.getCursor('end'), + text; + + if (_.indexOf(state, 'code') !== -1) { + return; + } + else { + text = this.editor.getSelection(); + this.editor.replaceSelection(this.marks.code.tag + text + this.marks.code.tagEnd); + } + this.editor.setSelection({line: start.line + 1, ch: start.ch}, {line: end.line + 1, ch: end.ch}); + this.editor.focus(); + }, + + replaceRange: function(text, line) { + this.editor.replaceRange(text, { + line : line, + ch : 0 + }, { + line : line, + ch : 99999999999999 + }); + this.editor.focus(); + }, + + /** + * Convert a line to a headline. + */ + toggleHeading: function(i) { + var text = this.editor.getLine(i), + headingLvl = text.search(/[^#]/); + + // Create a default headline + if (headingLvl === -1) { + text = '# Heading'; + + this.replaceRange(text, i); + return this.editor.setSelection( + {line: i, ch: 2}, + {line: i, ch: 9} + ); + } + + // Increase headline level up to 6th + if (headingLvl < 6) { + text = headingLvl > 0 ? text.substr(headingLvl + 1) : text; + text = new Array(headingLvl + 2).join('#') + ' ' + text; + } + else { + text = text.substr(headingLvl + 1); + } + + this.replaceRange(text, i); + }, + + /** + * Convert selected text to unordered list. + */ + listAction: function() { + this.toggleLists('unordered-list'); + }, + + /** + * Convert selected text to ordered list. + */ + numberedListAction: function() { + this.toggleLists('ordered-list', 1); + }, + + /** + * Convert several selected lines to ordered or unordered lists. + */ + toggleLists: function(type, order) { + var state = this.getState(), + start = this.editor.getCursor('start'), + end = this.editor.getCursor('end'); + + // Convert each line to list + _.each(new Array(end.line - start.line + 1), function(val, i) { + this.toggleList(type, start.line + i, state, order); + if (order) { + order++; + } + }, this); + }, + + /** + * Convert selected text to an ordered or unordered list. + */ + toggleList: function(name, line, state, order) { + var text = this.editor.getLine(line); + + // If it is a list, convert it to normal text + if (_.indexOf(state, name) !== -1) { + text = text.replace(this.marks[name].replace, '$1'); + } + else if (order) { + text = order + '. ' + text; + } + else { + text = this.marks[name].tag + text; + } + + this.replaceRange(text, line); + }, + + /** + * Redo the last action in Codemirror. + */ + redoAction: function() { + this.editor.redo(); + }, + + /** + * Undo the last action in Codemirror. + */ + undoAction: function() { + this.editor.undo(); + }, + + generateLink: function(data) { + return '[' + data.text + ']' + '(' + data.url + ')'; + }, + + generateImage: function(data) { + return '!' + this.generateLink(data); + }, + + }); + + return Controller; +}); diff --git a/app/scripts/modules/codemirror/module.js b/app/scripts/modules/codemirror/module.js new file mode 100644 index 000000000..9621131cb --- /dev/null +++ b/app/scripts/modules/codemirror/module.js @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2015 Laverna project Authors. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* global define */ +define([ + 'underscore', + 'backbone.radio', + 'marionette', + 'modules', + 'modules/codemirror/controller' +], function(_, Radio, Marionette, Modules, Controller) { + 'use strict'; + + /** + * Codemirror module. + */ + var Module = Modules.module('Codemirror', {}); + + /** + * Initializers & finalizers of the module + */ + Module.on('start', function() { + console.info('Codemirror module has started'); + Module.controller = new Controller(); + }); + + Module.on('stop', function() { + console.info('Codemirror module has stoped'); + + Module.controller.destroy(); + Module.controller = null; + }); + + // Add a global module initializer + Radio.request('init', 'add', 'module', function() { + console.info('Codemirror module has been initialized'); + + Radio.channel('notesForm') + .on('view:ready', Module.start, Module) + .on('view:destroy', Module.stop, Module); + + }); + + return Module; + +}); diff --git a/app/scripts/modules/codemirror/templates/editor.html b/app/scripts/modules/codemirror/templates/editor.html new file mode 100644 index 000000000..472a256bb --- /dev/null +++ b/app/scripts/modules/codemirror/templates/editor.html @@ -0,0 +1,69 @@ + + + + +
+
+ +
+ +
+
+
+
diff --git a/app/scripts/modules/codemirror/views/editor.js b/app/scripts/modules/codemirror/views/editor.js new file mode 100644 index 000000000..be56ec281 --- /dev/null +++ b/app/scripts/modules/codemirror/views/editor.js @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2015 Laverna project Authors. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* global define */ +define([ + 'underscore', + 'marionette', + 'backbone.radio', + 'text!modules/codemirror/templates/editor.html' +], function(_, Marionette, Radio, Tmpl) { + 'use strict'; + + /** + * Codemirror view. + */ + var View = Marionette.ItemView.extend({ + template: _.template(Tmpl), + className: 'layout--body container-fluid', + + ui: { + preview : '#wmd-preview', + }, + + events: { + 'click .editor--btns .btn': 'triggerAction' + }, + + initialize: function() { + this.listenTo(this, 'editor:change', this.onEditorChange); + }, + + serializeData: function() { + return {content: _.escape(this.model.get('content'))}; + }, + + onEditorChange: function(content) { + this.ui.preview.html(content); + }, + + triggerAction: function(e) { + e.preventDefault(); + var action = $(e.currentTarget).attr('data-action'); + + if (action) { + this.trigger('editor:action', action); + } + }, + }); + + return View; +}); From 99d871baa8ed9b11ad7fd1dfaf539e8c604dfb84 Mon Sep 17 00:00:00 2001 From: wwwredfish Date: Fri, 25 Dec 2015 19:00:38 +0600 Subject: [PATCH 02/25] Switch from Pagedown to MarkdownIt Why: * It is easier to extend * It is more modern * It has a lot of plugins --- app/index.html | 3 + app/scripts/apps/confirm/show/controller.js | 32 ++++-- app/scripts/apps/notes/form/controller.js | 18 ++- .../apps/notes/list/views/noteSidebarItem.js | 4 +- app/scripts/apps/notes/show/controller.js | 14 +++ app/scripts/apps/notes/show/noteView.js | 8 +- app/scripts/init.js | 4 +- app/scripts/main.js | 19 ++++ .../modules/markdown/libs/markdown-it.js | 69 ++++++++++++ app/scripts/modules/markdown/libs/markdown.js | 105 ++++++++++++++++++ app/scripts/modules/markdown/module.js | 29 +++++ .../modules/markdown/workers/markdown.js | 71 ++++++++++++ bower.json | 10 +- gulpfile.js | 22 +++- 14 files changed, 382 insertions(+), 26 deletions(-) create mode 100644 app/scripts/modules/markdown/libs/markdown-it.js create mode 100644 app/scripts/modules/markdown/libs/markdown.js create mode 100644 app/scripts/modules/markdown/module.js create mode 100644 app/scripts/modules/markdown/workers/markdown.js diff --git a/app/index.html b/app/index.html index afb96b238..cf4bacefb 100644 --- a/app/index.html +++ b/app/index.html @@ -30,6 +30,9 @@ + + + diff --git a/app/scripts/apps/confirm/show/controller.js b/app/scripts/apps/confirm/show/controller.js index 8124e3fd1..f841af0d9 100644 --- a/app/scripts/apps/confirm/show/controller.js +++ b/app/scripts/apps/confirm/show/controller.js @@ -1,6 +1,6 @@ /** * Copyright (C) 2015 Laverna project Authors. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -8,10 +8,11 @@ /* global define */ define([ 'underscore', + 'q', 'marionette', 'backbone.radio', 'apps/confirm/show/view' -], function(_, Marionette, Radio, View) { +], function(_, Q, Marionette, Radio, View) { 'use strict'; /** @@ -24,21 +25,28 @@ define([ var Controller = Marionette.Object.extend({ initialize: function(options) { + var self = this; + if (typeof options === 'string') { options = {content: options}; } - // If instead of text a view was provided, render it - if (typeof options.content === 'object') { - options.content = options.content.render().$el.html(); - } - // Try to make HTML from supposedly Markdown string - else { - options.content = Radio.request('editor', 'content:html', options.content); - } - this.options = options; - this.show(); + new Q((function() { + + // If instead of text a view was provided, render it + if (typeof options.content === 'object') { + return options.content.render().$el.html(); + } + + // Try to make HTML from supposedly Markdown string + return Radio.request('markdown', 'render', options.content); + })()) + .then(function(content) { + self.options.content = content; + return self.show(); + }); + }, onDestroy: function() { diff --git a/app/scripts/apps/notes/form/controller.js b/app/scripts/apps/notes/form/controller.js index 15dc14e12..d7d2e75cb 100644 --- a/app/scripts/apps/notes/form/controller.js +++ b/app/scripts/apps/notes/form/controller.js @@ -105,16 +105,26 @@ define([ }, save: function() { - Radio.request('notes', 'save', this.view.model, this.getContent()) + var self = this; + + return this.getContent() + .then(function(data) { + return Radio.request('notes', 'save', self.view.model, data); + }) .fail(function(e) { console.error('Error', e); }); }, getContent: function() { - return _.extend(Radio.request('editor', 'get:content'), { - title : this.view.ui.title.val().trim(), - notebookId : this.view.notebooks.currentView.ui.notebookId.val().trim(), + var self = this; + + return Radio.request('editor', 'get:data') + .then(function(data) { + return _.extend(data, { + title : self.view.ui.title.val().trim(), + notebookId : self.view.notebooks.currentView.ui.notebookId.val().trim(), + }); }); }, diff --git a/app/scripts/apps/notes/list/views/noteSidebarItem.js b/app/scripts/apps/notes/list/views/noteSidebarItem.js index 5a4879333..4736b80d8 100644 --- a/app/scripts/apps/notes/list/views/noteSidebarItem.js +++ b/app/scripts/apps/notes/list/views/noteSidebarItem.js @@ -1,6 +1,6 @@ /** * Copyright (C) 2015 Laverna project Authors. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -65,7 +65,7 @@ define([ return { // Show only first 50 characters of the content getContent: function() { - return this.content.substring(0, 50); + return _.escape(this.content.substring(0, 50)); }, // Strip from HTML tags the title diff --git a/app/scripts/apps/notes/show/controller.js b/app/scripts/apps/notes/show/controller.js index 0e14f0377..251fc43c2 100644 --- a/app/scripts/apps/notes/show/controller.js +++ b/app/scripts/apps/notes/show/controller.js @@ -27,6 +27,10 @@ define([ * 2. channel: appNote, request: `remove:note` * in order to destroy a model * 3. channel: global, request: `set:title` + * + * Requests: + * 1. channel: markdown, request: render + * it expects to receive HTML. */ var Controller = Marionette.Object.extend({ @@ -45,11 +49,21 @@ define([ }, _show: function(note, notebook) { + var self = this; + + Radio.request('markdown', 'render', note.get('content')) + .then(function(content) { + return self.render(note, content, notebook); + }); + }, + + render: function(note, content, notebook) { // Trigger an event that the model is active Radio.trigger('appNote', 'model:active', note); this.view = new View({ model : note, + content : content, notebook : notebook, args : this.options, files : [], diff --git a/app/scripts/apps/notes/show/noteView.js b/app/scripts/apps/notes/show/noteView.js index cef8be8ba..4c33056c6 100644 --- a/app/scripts/apps/notes/show/noteView.js +++ b/app/scripts/apps/notes/show/noteView.js @@ -27,9 +27,7 @@ define([ * 2. channel: noteView, event: view:destroy * before the view is destroyed. * Requests: - * 1. channel: editor, request: content:html - * it expects to receive HTML. - * 2. channel: global, request: configs + * 1. channel: global, request: configs */ var View = Marionette.ItemView.extend({ template: _.template(Tmpl), @@ -154,9 +152,9 @@ define([ }, serializeData: function() { - var content = Radio.request('editor', 'content:html', this.model); + // var content = Radio.request('markdown', 'render', this.model); return _.extend(this.model.toJSON(), { - content : content || this.model.get('content'), + content : this.options.content || this.model.get('content'), notebook : this.options.notebook.toJSON(), uri : Radio.request('uri', 'link:profile', '/') }); diff --git a/app/scripts/init.js b/app/scripts/init.js index cf16f05c7..785ded95b 100644 --- a/app/scripts/init.js +++ b/app/scripts/init.js @@ -53,7 +53,9 @@ define([ 'apps/help/appHelp', // Optional modules - 'modules/pagedown/module', + // 'modules/pagedown/module', + 'modules/markdown/module', + 'modules/codemirror/module', 'modules/tags/module', 'modules/tasks/module', 'modules/linkDialog/module', diff --git a/app/scripts/main.js b/app/scripts/main.js index f72ed5094..18863e2b1 100644 --- a/app/scripts/main.js +++ b/app/scripts/main.js @@ -21,6 +21,12 @@ requirejs.config({ location : '../bower_components/ace/lib/ace', main : 'ace' }, + // Codemirror editor + { + name : 'codemirror', + location : '../bower_components/codemirror', + main : 'lib/codemirror' + }, // Pagedown editor { name : 'pagedown', @@ -33,6 +39,12 @@ requirejs.config({ location : '../bower_components/pagedown-ace', main : 'Markdown.Editor' }, + // Prismjs + { + name : 'prism', + location : '../bower_components/prism', + main : 'bundle' + }, // Xregexp { name : 'xregexp', @@ -69,12 +81,16 @@ requirejs.config({ dropbox : '../bower_components/dropbox/dropbox', // Markdown + 'markdown-it' : '../bower_components/markdown-it/dist/markdown-it', + 'markdown-it-san' : '../bower_components/markdown-it-sanitizer/dist/markdown-it-sanitizer', + 'markdown-it-hash' : '../bower_components/markdown-it-hashtag/dist/markdown-it-hashtag', 'pagedown-extra' : '../bower_components/pagedown-extra/Markdown.Extra', 'to-markdown' : '../bower_components/to-markdown/src/to-markdown', // Others dompurify : '../bower_components/DOMPurify/src/purify', mathjax : '../bower_components/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML', + hljs : '../bower_components/highlightjs/highlight.pack', prettify : '../bower_components/google-code-prettify/src/prettify', dropzone : '../bower_components/dropzone/dist/dropzone-amd-module', toBlob : '../bower_components/blueimp-canvas-to-blob/js/canvas-to-blob', @@ -169,6 +185,9 @@ requirejs.config({ sjcl: { exports: 'sjcl' }, + 'prism/bundle': { + exports: 'Prism' + }, bootstrap: { deps: ['jquery'] }, diff --git a/app/scripts/modules/markdown/libs/markdown-it.js b/app/scripts/modules/markdown/libs/markdown-it.js new file mode 100644 index 000000000..1b1b7f70f --- /dev/null +++ b/app/scripts/modules/markdown/libs/markdown-it.js @@ -0,0 +1,69 @@ +/* global define */ +define([ + 'underscore', + 'q', + 'markdown-it', + 'prism/bundle', + 'markdown-it-san', + 'markdown-it-hash', +], function(_, Q, MarkdownIt, Prism, sanitizer, hash) { + 'use strict'; + + /** + * Markdown parser helper. + */ + function Markdown() { + + // Initialize MarkdownIt + this.md = new MarkdownIt({ + html : true, + xhtmlOut : true, + breaks : true, + linkify : true, + highlight: function(code, lang) { + if (!Prism.languages[lang]) { + return ''; + } + + return Prism.highlight(code, Prism.languages[lang]); + } + }); + + this.configure(); + } + + _.extend(Markdown.prototype, { + + /** + * Configure MarkdownIt. + */ + configure: function() { + this.md + .use(sanitizer) + .use(hash) + ; + + this.md.renderer.rules.hashtag_open = function(tokens, idx) { // jshint ignore:line + var tagName = tokens[idx].content.toLowerCase(); + return ''; + }; + }, + + /** + * Convert Markdown to HTML. + */ + render: function(content) { + return new Q( + this.md.render(content) + ); + }, + + parse: function(content) { + return new Q( + this.md.parse(content) + ); + }, + }); + + return Markdown; +}); diff --git a/app/scripts/modules/markdown/libs/markdown.js b/app/scripts/modules/markdown/libs/markdown.js new file mode 100644 index 000000000..b2b9e6469 --- /dev/null +++ b/app/scripts/modules/markdown/libs/markdown.js @@ -0,0 +1,105 @@ +/** + * Copyright (C) 2015 Laverna project Authors. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* global define, Modernizr */ +define([ + 'q', + 'underscore', + 'modules/markdown/libs/markdown-it' +], function(Q, _, MarkdownIt) { + 'use strict'; + + /** + * Parse Markdown in WebWorker. + */ + function Markdown() { + var self = this; + this.worker = new Worker('scripts/modules/markdown/workers/markdown.js'); + + // Promise which signifies whether the worker is ready + this.workerPromise = Q.defer(); + + this.worker.onmessage = function(data) { + var msg = data.data; + + switch (msg.msg) { + + // Webworker is ready + case 'ready': + self.workerPromise.resolve(); + break; + + // Request was fullfilled + case 'done': + self.promises[msg.promiseId].resolve(msg.data); + delete self.promises[msg.promiseId]; + break; + + // Request failed with errors + case 'fail': + self.promises[msg.promiseId].reject(msg.data); + delete self.promises[msg.promiseId]; + break; + + default: + } + }; + } + + _.extend(Markdown.prototype, { + promises: [], + + render: function(content) { + return this._emit('render', content); + }, + + /** + * Send a message to the Webworker. + * + * @type string msg + * @type object data + */ + _emit: function(msg, data) { + var self = this; + + // Worker is ready + if (!this.workerPromise.promise.isPending()) { + return this._send(msg, data); + } + + return this.workerPromise.promise + .then(function() { + return self._send(msg, data); + }); + }, + + _send: function(msg, data) { + + // Generate a unique ID for the worker's promise + var promiseId = ((1 + Math.random()) * 0x10000); + this.promises[promiseId] = Q.defer(); + + this.worker.postMessage({ + msg : msg, + promiseId : promiseId, + data : data + }); + + return this.promises[promiseId].promise; + }, + + }); + + + // Parse Markdown without Webworkers + if (!Modernizr.webworkers) { + return MarkdownIt; + } + + return Markdown; + +}); diff --git a/app/scripts/modules/markdown/module.js b/app/scripts/modules/markdown/module.js new file mode 100644 index 000000000..29335b774 --- /dev/null +++ b/app/scripts/modules/markdown/module.js @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2015 Laverna project Authors. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* global define */ +define([ + 'backbone.radio', + 'modules/markdown/libs/markdown' +], function(Radio, Markdown) { + 'use strict'; + + // Add a global module initializer + Radio.request('init', 'add', 'module', function() { + console.info('Markdown module has been initialized'); + var md = new Markdown(); + + Radio.reply('markdown', { + 'render': md.render, + }, md); + + if (md.workerPromise) { + return md.workerPromise.promise; + } + }); + +}); diff --git a/app/scripts/modules/markdown/workers/markdown.js b/app/scripts/modules/markdown/workers/markdown.js new file mode 100644 index 000000000..40c666e88 --- /dev/null +++ b/app/scripts/modules/markdown/workers/markdown.js @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2015 Laverna project Authors. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* global requirejs, importScripts, self */ +'use strict'; +importScripts('../../../../bower_components/requirejs/require.js'); + +requirejs.config({ + baseUrl: '../../../', + packages: [ + // Prismjs + { + name : 'prism', + location : '../bower_components/prism', + main : 'bundle' + }, + ], + paths: { + q : '../bower_components/q/q', + underscore : '../bower_components/underscore/underscore', + 'markdown-it' : '../bower_components/markdown-it/dist/markdown-it', + 'markdown-it-san' : '../bower_components/markdown-it-sanitizer/dist/markdown-it-sanitizer', + 'markdown-it-hash' : '../bower_components/markdown-it-hashtag/dist/markdown-it-hashtag', + }, + shim: { + 'prism/bundle': { + exports: 'Prism' + }, + } +}); + +// Prevent Prismjs from listening to our events +self.addEventListener = undefined; + +requirejs([ + 'modules/markdown/libs/markdown-it' +], function(Markdown) { + var markdown = new Markdown(); + + // Listen to the webworker messages + self.onmessage = function(data) { + var msg = data.data; + + if (markdown[msg.msg]) { + return markdown[msg.msg](msg.data) + .then(function(result) { + self.postMessage({ + msg : 'done', + promiseId : msg.promiseId, + data : result + }); + }) + .fail(function(e) { + self.postMessage({ + msg : 'fail', + promiseId : msg.promiseId, + data : e + }); + }); + } + + console.error('MarkdownIt module:', 'Method doesn\'t exist', msg.msg); + }; + + // Post a message that the worker is ready + self.postMessage({msg: 'ready'}); +}); diff --git a/bower.json b/bower.json index bc5b9d07c..0af29c7b4 100644 --- a/bower.json +++ b/bower.json @@ -42,7 +42,15 @@ "xregexp": "~2.0.0", "jszip": "Stuk/jszip#~2.5.0" }, - "devDependencies": {}, + "devDependencies": { + "ace": "~1.2.2", + "markdown-it": "~5.0.2", + "codemirror": "~5.10.0", + "highlightjs": "~9.0.0", + "prism": "~1.3.0", + "markdown-it-sanitizer": "~0.4.1", + "markdown-it-hashtag": "~0.4.0" + }, "resolutions": { "backbone": "~1.2.3", "underscore": "~1.8.3" diff --git a/gulpfile.js b/gulpfile.js index 4bf4b1aca..28ffe7865 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,7 +6,7 @@ var gulp = require('gulp'), less = require('gulp-less'), rename = require('gulp-rename'), aprefix = require('gulp-autoprefixer'), - // concat = require('gulp-concat'), + concat = require('gulp-concat'), rjs = require('gulp-requirejs-optimize'), merge = require('merge-stream'), uglify = require('gulp-uglify'), @@ -168,6 +168,26 @@ gulp.task('clean:dist', ['test'], function() { .pipe(clean()); }); +gulp.task('build:prism', function() { + /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+abap+actionscript+apacheconf+apl+applescript+aspnet+autoit+autohotkey+bash+basic+batch+c+brainfuck+bison+csharp+cpp+coffeescript+ruby+css-extras+d+dart+diff+docker+eiffel+elixir+erlang+fsharp+fortran+gherkin+git+glsl+go+groovy+handlebars+haskell+haxe+http+icon+inform7+ini+j+jade+java+julia+keyman+kotlin+latex+less+lolcode+lua+makefile+markdown+matlab+mel+mizar+monkey+nasm+nginx+nim+nix+nsis+objectivec+ocaml+oz+parigp+parser+pascal+perl+php+php-extras+powershell+processing+prolog+puppet+pure+python+q+qore+r+jsx+rest+rip+roboconf+crystal+rust+sas+sass+scss+scala+scheme+smalltalk+smarty+sql+stylus+swift+tcl+textile+twig+typescript+verilog+vhdl+vim+wiki+yaml */ + var components = 'markup+css+clike+javascript+abap+actionscript+apacheconf+apl+applescript+aspnet+autoit+autohotkey+bash+basic+batch+c+brainfuck+bison+csharp+cpp+coffeescript+ruby+css-extras+d+dart+diff+docker+eiffel+elixir+erlang+fsharp+fortran+gherkin+git+glsl+go+groovy+handlebars+haskell+haxe+http+icon+inform7+ini+j+jade+java+julia+keyman+kotlin+latex+less+lolcode+lua+makefile+markdown+matlab+mel+mizar+monkey+nasm+nginx+nim+nix+nsis+objectivec+ocaml+oz+parigp+parser+pascal+perl+php+php-extras+powershell+processing+prolog+puppet+pure+python+q+qore+r+jsx+rest+rip+roboconf+crystal+rust+sas+sass+scss+scala+scheme+smalltalk+smarty+sql+stylus+swift+tcl+textile+twig+typescript+verilog+vhdl+vim+wiki+yaml', + files = []; + + components = components.split('+'); + components.unshift('core'); + + components.forEach(function(item) { + files.push( + './app/bower_components/prism/components/prism-' + item + '.js' + ); + }); + + return gulp.src(files) + .pipe(concat('bundle.js')) + // .pipe(uglify()) + .pipe(gulp.dest('./app/bower_components/prism')); +}); + /** * Minify JS files. * Require.js config example: From 0c8b07701601db52ee552dd79045cd64205dcf80 Mon Sep 17 00:00:00 2001 From: wwebfor Date: Fri, 25 Dec 2015 19:02:53 +0600 Subject: [PATCH 03/25] Add Codemirror CSS styles --- app/styles/core/codemirror.less | 344 +++++++++++++++++++++++ app/styles/core/main.less | 1 + app/styles/core/pagedown.less | 2 + app/styles/theme-default/codemirror.less | 18 ++ app/styles/theme-default/main.less | 1 + 5 files changed, 366 insertions(+) create mode 100644 app/styles/core/codemirror.less create mode 100644 app/styles/theme-default/codemirror.less diff --git a/app/styles/core/codemirror.less b/app/styles/core/codemirror.less new file mode 100644 index 000000000..911375ef0 --- /dev/null +++ b/app/styles/core/codemirror.less @@ -0,0 +1,344 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family : @font-family-sans-serif; + height : auto; + position : absolute !important; + top : 0 !important; + bottom : 0 !important; + left : 0 !important; + right : 0 !important; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 15px 10px; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/** + * Default theme + */ +.cm-s-default .cm-header-1 { + &:extend(h1); + line-height: floor((@font-size-h1 * @line-height-base)); +} +.cm-s-default .cm-header-2 { + &:extend(h2); + line-height: floor((@font-size-h2 * @line-height-base)); +} +.cm-s-default .cm-header-3 { + &:extend(h3); + line-height: floor((@font-size-h3 * @line-height-base)); +} +.cm-s-default .cm-header-4 { + &:extend(h4); + line-height: floor((@font-size-h4 * @line-height-base)); +} +.cm-s-default .cm-header-5 { + &:extend(h5); + line-height: floor((@font-size-h5 * @line-height-base)); +} +.cm-s-default .cm-header-6 { + &:extend(h6); + line-height: floor((@font-size-h6 * @line-height-base)); +} + +.cm-s-default .cm-quote { +} + +.cm-negative {} +.cm-positive {} +.cm-header, .cm-strong:extend(strong) {} +.cm-em { + font-style: italic; +} +.cm-link { + text-decoration: underline; +} +.cm-strikethrough {text-decoration: line-through;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of +the editor. You probably shouldn't touch them. */ + +.CodeMirror { + // position: relative; + // overflow: hidden; + // background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling +before actual scrolling happens, thus preventing shaking and +flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + margin-bottom: -30px; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: @font-size-base * 1; + line-height: floor((@font-size-base * 1 * @line-height-base)); + margin: 0; + white-space: pre; + word-wrap: normal; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { position: absolute; } +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } diff --git a/app/styles/core/main.less b/app/styles/core/main.less index e55f40d38..52d4a009c 100644 --- a/app/styles/core/main.less +++ b/app/styles/core/main.less @@ -18,6 +18,7 @@ @import 'fuzzy.less'; // Editor +@import 'codemirror.less'; @import 'pagedown.less'; @import 'editor.less'; diff --git a/app/styles/core/pagedown.less b/app/styles/core/pagedown.less index 49b7d8af6..4fcd76d30 100644 --- a/app/styles/core/pagedown.less +++ b/app/styles/core/pagedown.less @@ -152,6 +152,8 @@ &.-left { left: 0; + padding-left: 0; + padding-right: 0; } &.-right { right: 0; diff --git a/app/styles/theme-default/codemirror.less b/app/styles/theme-default/codemirror.less new file mode 100644 index 000000000..c8356dd71 --- /dev/null +++ b/app/styles/theme-default/codemirror.less @@ -0,0 +1,18 @@ +.CodeMirror { + background : white; +} + +.cm-link { + color: @gray; +} +.cm-url { + color: @gray-light; +} + +.cm-comment { + background-color: lighten(@gray-lighter, 2); +} + +.cm-quote { + color: @gray-light; +} diff --git a/app/styles/theme-default/main.less b/app/styles/theme-default/main.less index 264f30cfb..a6f13906d 100644 --- a/app/styles/theme-default/main.less +++ b/app/styles/theme-default/main.less @@ -27,6 +27,7 @@ // Pagedown editor @import 'editor.less'; +@import 'codemirror.less'; @import 'pagedown.less'; // Google code prettify From 869852db2fc2aba434a4bde03c7612f46361c637 Mon Sep 17 00:00:00 2001 From: wwwredfish Date: Fri, 25 Dec 2015 19:03:46 +0600 Subject: [PATCH 04/25] Fix fileDialog and linkDialog modules --- app/scripts/modules/fileDialog/controller.js | 7 ++-- app/scripts/modules/fileDialog/module.js | 37 +++++++++++++------- app/scripts/modules/linkDialog/module.js | 27 +++++++------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/app/scripts/modules/fileDialog/controller.js b/app/scripts/modules/fileDialog/controller.js index 8ce3ffe0f..5c25ed926 100644 --- a/app/scripts/modules/fileDialog/controller.js +++ b/app/scripts/modules/fileDialog/controller.js @@ -74,9 +74,7 @@ define([ // If there is only 1 file and its type is image if (files.length === 1 && self.isImage(files[0])) { - return self.attachImage( - Radio.request('uri', 'link:file', files[0]) - ); + return self.attachImage(self.generateCode(files[0])); } // Otherwise, we will generate custom Markdown code @@ -106,8 +104,7 @@ define([ }, this); } - Radio.request('editor', 'insert', str); - this.attachImage(null); + this.attachImage(str); }, attachImage: function(url) { diff --git a/app/scripts/modules/fileDialog/module.js b/app/scripts/modules/fileDialog/module.js index bad02985b..1a7225911 100644 --- a/app/scripts/modules/fileDialog/module.js +++ b/app/scripts/modules/fileDialog/module.js @@ -1,6 +1,6 @@ /** * Copyright (C) 2015 Laverna project Authors. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -8,11 +8,12 @@ /* global define */ define([ 'underscore', + 'q', 'modules', 'backbone.radio', 'modules/fileDialog/controller', 'modules/fileDialog/helper' -], function(_, Modules, Radio, Controller, Helper) { +], function(_, Q, Modules, Radio, Controller, Helper) { 'use strict'; /** @@ -46,6 +47,8 @@ define([ }); FileDialog.on('before:stop', function() { + console.info('FileDialog stopped'); + Helper.revokeUrls(); this.stopListening(); FileDialog.controller = null; @@ -70,23 +73,33 @@ define([ }); } - // Show custom dialog on `insertImageDialog` hook. - Radio.request('init', 'add', 'editor:before', function(editor, model) { - editor.hooks.set('insertImageDialog', function(fnc) { - FileDialog.start({callback: fnc, model: model}); - return true; - }); - }); + // Radio.request('init', 'add', 'editor:before', function(editor, model) { + // editor.hooks.set('insertImageDialog', function(fnc) { + // return true; + // }); + // }); Radio.request('init', 'add', 'module', function() { + + // Stop the module when editor is closed. + Radio.on('editor', 'destroy', FileDialog.stop, FileDialog); + + // Show custom dialog on `insertImageDialog` hook. + Radio.reply('editor', 'show:attachment', function(model) { + var defer = Q.defer(); + + FileDialog.start({model: model, callback: function(link) { + defer.resolve(link); + }}); + + return defer.promise; + }); + Radio.reply('editor', 'get:files', Helper.getFileIds, Helper); // When editor converter is initialized, add hooks Radio.on('editor', 'converter:init', addHook); - // Stop the module when editor is closed. - Radio.on('editor', 'destroy', FileDialog.stop, FileDialog); - // Revoke all URLs when a note is closed. Radio.on('noteView', 'view:destroy', Helper.revokeUrls, Helper); }); diff --git a/app/scripts/modules/linkDialog/module.js b/app/scripts/modules/linkDialog/module.js index 5f4d7bf1a..fc9827e20 100644 --- a/app/scripts/modules/linkDialog/module.js +++ b/app/scripts/modules/linkDialog/module.js @@ -8,10 +8,11 @@ /* global define */ define([ 'underscore', + 'q', 'modules', 'backbone.radio', 'modules/linkDialog/controller' -], function(_, Modules, Radio, Controller) { +], function(_, Q, Modules, Radio, Controller) { 'use strict'; /** @@ -34,25 +35,25 @@ define([ }); LinkDialog.on('before:stop', function() { + console.info('LinkDialog stopped'); + this.stopListening(); LinkDialog.controller = null; }); - Radio.request('init', 'add', 'editor:before', function(editor) { - if (!editor.hooks.insertLinkDialog) { - return; - } - - // Register a hook - editor.hooks.set('insertLinkDialog', function(fnc) { - LinkDialog.start({callback: fnc}); - return true; - }); - }); - // Stop the module when editor is closed Radio.request('init', 'add', 'module', function() { Radio.on('editor', 'destroy', LinkDialog.stop, LinkDialog); + + Radio.reply('editor', 'show:link', function() { + var defer = Q.defer(); + + LinkDialog.start({callback: function(link) { + defer.resolve(link); + }}); + + return defer.promise; + }); }); return LinkDialog; From f4e3bf9082245478824b10c650d4cf1c53f956f7 Mon Sep 17 00:00:00 2001 From: wwebfor Date: Mon, 28 Dec 2015 14:18:58 +0600 Subject: [PATCH 05/25] Fix Codemirror's fullscreen and normal modes --- app/scripts/modules/codemirror/controller.js | 5 + .../modules/codemirror/templates/editor.html | 16 +- app/styles/core/codemirror.less | 346 +----------------- app/styles/core/codemirror/core.less | 333 +++++++++++++++++ app/styles/core/codemirror/theme.less | 69 ++++ app/styles/core/editor.less | 159 +++++++- app/styles/core/main.less | 2 +- app/styles/core/pagedown.less | 147 -------- app/styles/theme-default/editor.less | 32 +- app/styles/theme-default/main.less | 2 +- app/styles/theme-default/pagedown.less | 26 -- 11 files changed, 608 insertions(+), 529 deletions(-) create mode 100644 app/styles/core/codemirror/core.less create mode 100644 app/styles/core/codemirror/theme.less diff --git a/app/scripts/modules/codemirror/controller.js b/app/scripts/modules/codemirror/controller.js index 5633a8ec3..664429e0c 100644 --- a/app/scripts/modules/codemirror/controller.js +++ b/app/scripts/modules/codemirror/controller.js @@ -72,6 +72,7 @@ define([ // Events this.listenTo(this.view, 'editor:action', this.onViewAction); + this.listenTo(Radio.channel('notesForm'), 'set:mode', this.changeMode); // Show the view and render Pagedown editor Radio.request('notesForm', 'show:editor', this.view); @@ -139,6 +140,10 @@ define([ this.updatePreview(); }, + changeMode: function() { + window.dispatchEvent(new Event('resize')); + }, + /** * Update the preview. */ diff --git a/app/scripts/modules/codemirror/templates/editor.html b/app/scripts/modules/codemirror/templates/editor.html index 472a256bb..a40ed9712 100644 --- a/app/scripts/modules/codemirror/templates/editor.html +++ b/app/scripts/modules/codemirror/templates/editor.html @@ -1,4 +1,4 @@ -