diff --git a/bower.json b/bower.json index ad45a0d6..704ca23c 100644 --- a/bower.json +++ b/bower.json @@ -23,6 +23,7 @@ "pagedown-extra": "git@github.com:jmcmanus/pagedown-extra.git#master", "crel": "git@github.com:KoryNunn/crel.git#8dbda04b129fc0aec01a2a080d1cab26816e11c1", "waitForImages": "git@github.com:alexanderdickson/waitForImages.git#~1.4.2", - "to-markdown": "git@github.com:benweet/to-markdown.git#jquery" + "to-markdown": "git@github.com:benweet/to-markdown.git#jquery", + "js-yaml": "~2.1.0" } } diff --git a/res/core.js b/res/core.js index e5fa4003..44bc2b48 100644 --- a/res/core.js +++ b/res/core.js @@ -18,6 +18,7 @@ define([ 'libs/ace_mode', 'ace/requirejs/text!ace/css/editor.css', 'ace/requirejs/text!ace/theme/textmate.css', + 'ace/ext/spellcheck', ], function($, _, crel, ace, utils, settings, eventMgr, mousetrap, bodyIndexHTML, bodyViewerHTML, settingsTemplateTooltipHTML, settingsUserCustomExtensionTooltipHTML) { @@ -206,6 +207,8 @@ define([ return; } aceEditor = ace.edit("wmd-input"); + require('ace/ext/spellcheck'); + aceEditor.setOption("spellcheck", true); aceEditor.renderer.setShowGutter(false); aceEditor.renderer.setPrintMarginColumn(false); aceEditor.renderer.setPadding(EDITOR_DEFAULT_PADDING); @@ -361,13 +364,15 @@ define([ // north resizer is very small // var $previewButtonsContainerElt = $('
'); + $resizerDecorator = $('
'); $previewButtonsElt = $('
'); $editorButtonsElt = $('
'); if(viewerMode || settings.layoutOrientation == "horizontal") { - $('.ui-layout-resizer-north').append($previewButtonsElt); + $('.ui-layout-resizer-north').append($resizerDecorator).append($previewButtonsElt); $('.ui-layout-resizer-east').append($northTogglerElt).append($editorButtonsElt); } else { + $('.ui-layout-resizer-north').append($resizerDecorator); $('.ui-layout-resizer-south').append($previewButtonsElt).append($editorButtonsElt).append($northTogglerElt); } @@ -435,27 +440,6 @@ define([ // Create the converter and the editor var converter = new Markdown.Converter(); - // Parse MD sections for extensions - converter.hooks.chain("preConversion", function(text) { - eventMgr.previewStartTime = new Date(); - var tmpText = text + "\n\n"; - var sectionList = [], offset = 0; - // Look for titles (excluding gfm blocs) - tmpText.replace(/^```.*\n[\s\S]*?\n```|(^.+[ \t]*\n=+[ \t]*\n+|^.+[ \t]*\n-+[ \t]*\n+|^\#{1,6}[ \t]*.+?[ \t]*\#*\n+)/gm, function(match, title, matchOffset) { - if(title) { - // We just found a title which means end of the previous - // section - // Exclude last \n of the section - sectionList.push(tmpText.substring(offset, matchOffset)); - offset = matchOffset; - } - return ""; - }); - // Last section - sectionList.push(tmpText.substring(offset, text.length)); - eventMgr.onSectionsCreated(sectionList); - return text; - }); function checkDocumentChanges() { var newDocumentContent = $editorElt.val(); diff --git a/res/eventMgr.js b/res/eventMgr.js index f8339c50..f5a56792 100644 --- a/res/eventMgr.js +++ b/res/eventMgr.js @@ -6,6 +6,8 @@ define([ "classes/Extension", "settings", "text!html/settingsExtensionsAccordion.html", +// "extensions/yamlFrontMatterParser", + "extensions/markdownSectionParser", "extensions/partialRendering", "extensions/buttonMarkdownSyntax", "extensions/googleAnalytics", @@ -178,6 +180,8 @@ define([ // Operations on PageDown addEventHook("onPagedownConfigure"); addEventHook("onSectionsCreated"); + addEventHook("onMarkdownTrim"); + addEventHook("onExtraExtensions"); // Operation on ACE addEventHook("onAceCreated"); diff --git a/res/extensions/markdownExtra.js b/res/extensions/markdownExtra.js index 4548d369..41e2f6f5 100644 --- a/res/extensions/markdownExtra.js +++ b/res/extensions/markdownExtra.js @@ -44,6 +44,11 @@ define([ newConfig.highlighter = utils.getInputValue("#input-markdownextra-highlighter"); }; + var eventMgr = undefined; + markdownExtra.onEventMgrCreated = function(eventMgrParameter) { + eventMgr = eventMgrParameter; + }; + markdownExtra.onPagedownConfigure = function(editor) { var converter = editor.getConverter(); var options = { @@ -64,8 +69,8 @@ define([ } Markdown.Extra.init(converter, options); - // Store extensions list in converter for partialRendering - converter.setExtraExtension && converter.setExtraExtension(markdownExtra.config.extensions); + // Send extensions list to other extensions + eventMgr.onExtraExtensions(markdownExtra.config.extensions); }; return markdownExtra; diff --git a/res/extensions/markdownSectionParser.js b/res/extensions/markdownSectionParser.js new file mode 100644 index 00000000..0002f97b --- /dev/null +++ b/res/extensions/markdownSectionParser.js @@ -0,0 +1,37 @@ +define([ + "classes/Extension" +], function(Extension) { + + var markdownSectionParser = new Extension("markdownSectionParser", "Markdown section parser"); + + var eventMgr = undefined; + markdownSectionParser.onEventMgrCreated = function(eventMgrParameter) { + eventMgr = eventMgrParameter; + }; + + markdownSectionParser.onPagedownConfigure = function(editor) { + var converter = editor.getConverter(); + converter.hooks.chain("preConversion", function(text) { + eventMgr.previewStartTime = new Date(); + var tmpText = text + "\n\n"; + var sectionList = [], offset = 0; + // Look for titles (excluding gfm blocs) + tmpText.replace(/^```.*\n[\s\S]*?\n```|(^.+[ \t]*\n=+[ \t]*\n+|^.+[ \t]*\n-+[ \t]*\n+|^\#{1,6}[ \t]*.+?[ \t]*\#*\n+)/gm, function(match, title, matchOffset) { + if(title) { + // We just found a title which means end of the previous + // section + // Exclude last \n of the section + sectionList.push(tmpText.substring(offset, matchOffset)); + offset = matchOffset; + } + return ""; + }); + // Last section + sectionList.push(tmpText.substring(offset, text.length)); + eventMgr.onSectionsCreated(sectionList); + return text; + }); + }; + + return markdownSectionParser; +}); \ No newline at end of file diff --git a/res/extensions/partialRendering.js b/res/extensions/partialRendering.js index d2e1b4eb..a93b2b3e 100644 --- a/res/extensions/partialRendering.js +++ b/res/extensions/partialRendering.js @@ -189,13 +189,14 @@ define([ editor.hooks.chain("onPreviewRefresh", function() { refreshSections(); }); - converter.setExtraExtension = function(extraExtensions) { - doFootnotes = _.some(extraExtensions, function(extension) { - return extension == "footnotes"; - }); - }; }; + partialRendering.onExtraExtensions = function(extraExtensions) { + doFootnotes = _.some(extraExtensions, function(extension) { + return extension == "footnotes"; + }); + }; + partialRendering.onReady = function() { footnoteContainerElt = crel('div', { id: 'wmd-preview-section-footnotes', diff --git a/res/extensions/scrollLink.js b/res/extensions/scrollLink.js index 07796c74..b95fb8dd 100644 --- a/res/extensions/scrollLink.js +++ b/res/extensions/scrollLink.js @@ -17,6 +17,11 @@ define([ scrollLink.onSectionsCreated = function(sectionListParam) { sectionList = sectionListParam; }; + + var offsetBegin = 0; + scrollLink.onMarkdownTrim = function(offsetBeginParam) { + offsetBegin = offsetBeginParam; + }; var $previewElt = undefined; var mdSectionList = []; @@ -31,8 +36,10 @@ define([ mdSectionList = []; var mdTextOffset = 0; var mdSectionOffset = 0; + var firstSectionOffset = offsetBegin; _.each(sectionList, function(sectionText) { - mdTextOffset += sectionText.length; + mdTextOffset += sectionText.length + firstSectionOffset; + firstSectionOffset = 0; var documentPosition = aceEditor.session.doc.indexToPosition(mdTextOffset); var screenPosition = aceEditor.session.documentToScreenPosition(documentPosition.row, documentPosition.column); var newSectionOffset = screenPosition.row * aceEditor.renderer.lineHeight; diff --git a/res/extensions/yamlFrontMatterParser.js b/res/extensions/yamlFrontMatterParser.js new file mode 100644 index 00000000..536e24d1 --- /dev/null +++ b/res/extensions/yamlFrontMatterParser.js @@ -0,0 +1,35 @@ +define([ + "classes/Extension", + "text!html/yamlFrontMatterParserSettingsBlock.html", + "js-yaml", +], function(Extension, yamlFrontMatterParserSettingsBlock) { + + var yamlFrontMatterParser = new Extension("yamlFrontMatterParser", "YAML front matter", true); + yamlFrontMatterParser.settingsBlock = yamlFrontMatterParserSettingsBlock; + + var eventMgr = undefined; + yamlFrontMatterParser.onEventMgrCreated = function(eventMgrParameter) { + eventMgr = eventMgrParameter; + }; + + yamlFrontMatterParser.onPagedownConfigure = function(editor) { + var converter = editor.getConverter(); + converter.hooks.chain("preConversion", function(text) { + try { + var re = /^(\s*-{3}\s*\n([\w\W]+?)\n\s*-{3}\s*\n)?([\w\W]*)*/, results = re.exec(text), conf = {}, yaml; + + if((yaml = results[2])) { + conf = jsyaml.load(yaml); + console.log(conf); + } + eventMgr.onMarkdownTrim(results[1].length); + return results[3]; + } + catch(e) { + return text; + } + }); + }; + + return yamlFrontMatterParser; +}); \ No newline at end of file diff --git a/res/html/yamlFrontMatterParserSettingsBlock.html b/res/html/yamlFrontMatterParserSettingsBlock.html new file mode 100644 index 00000000..e4ffbf5b --- /dev/null +++ b/res/html/yamlFrontMatterParserSettingsBlock.html @@ -0,0 +1,3 @@ +

Parses YAML front matter block at the begining of the document.

+
Interpreted variables: title, published, category/categories/tags
+More info \ No newline at end of file diff --git a/res/img/loader-school.gif b/res/img/loader-school.gif index 0d59379e..1f35fc28 100644 Binary files a/res/img/loader-school.gif and b/res/img/loader-school.gif differ diff --git a/res/img/loader-school2x.gif b/res/img/loader-school2x.gif index 3c20e3c8..35d7b220 100644 Binary files a/res/img/loader-school2x.gif and b/res/img/loader-school2x.gif differ diff --git a/res/img/school-line.png b/res/img/school-line.png index 54db81b6..cef43c77 100644 Binary files a/res/img/school-line.png and b/res/img/school-line.png differ diff --git a/res/main.js b/res/main.js index cf0e3412..3b2e543d 100644 --- a/res/main.js +++ b/res/main.js @@ -49,7 +49,8 @@ requirejs.config({ 'pagedown-ace': 'bower-libs/pagedown-ace/Markdown.Editor', 'pagedown-extra': 'bower-libs/pagedown-extra/Markdown.Extra', 'ace/requirejs/text': 'libs/ace_text', - 'ace/commands/default_commands': 'libs/ace_commands' + 'ace/commands/default_commands': 'libs/ace_commands', + 'js-yaml': 'bower-libs/js-yaml/js-yaml' }, shim: { underscore: { diff --git a/res/providers/gdriveProvider.js b/res/providers/gdriveProvider.js index 005bd640..c17c35be 100644 --- a/res/providers/gdriveProvider.js +++ b/res/providers/gdriveProvider.js @@ -285,10 +285,8 @@ define([ aceEditor = aceEditorParam; // Listen to editor's changes aceEditor.session.on('change', function(e) { - if(realtimeString !== undefined) { - // Update the real time model - realtimeString.setText(aceEditor.getValue()); - } + // Update the real time model if any + realtimeString && realtimeString.setText(aceEditor.getValue()); }); }); @@ -308,18 +306,18 @@ define([ logger.log("Starting Google Drive realtime synchronization"); realtimeDocument = doc; var model = realtimeDocument.getModel(); - realtimeString = model.getRoot().get('content'); + var realtimeStringLocal = model.getRoot().get('content'); // Saves model content checksum function updateContentState() { - syncAttributes.contentCRC = utils.crc32(realtimeString.getText()); + syncAttributes.contentCRC = utils.crc32(realtimeStringLocal.getText()); utils.storeAttributes(syncAttributes); } var debouncedRefreshPreview = _.debounce(pagedownEditor.refreshPreview, 100); // Listen to insert text events - realtimeString.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, function(e) { + realtimeStringLocal.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, function(e) { if(aceEditor !== undefined && (isAceUpToDate === false || e.isLocal === false)) { // Update ACE editor var position = aceEditor.session.doc.indexToPosition(e.index); @@ -334,7 +332,7 @@ define([ } }); // Listen to delete text events - realtimeString.addEventListener(gapi.drive.realtime.EventType.TEXT_DELETED, function(e) { + realtimeStringLocal.addEventListener(gapi.drive.realtime.EventType.TEXT_DELETED, function(e) { if(aceEditor !== undefined && (isAceUpToDate === false || e.isLocal === false)) { // Update ACE editor var range = (function(posStart, posEnd) { @@ -361,7 +359,7 @@ define([ // Try to merge offline modifications var localContent = fileDesc.content; var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); - var remoteContent = realtimeString.getText(); + var remoteContent = realtimeStringLocal.getText(); var remoteContentCRC = utils.crc32(remoteContent); var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC; var fileContentChanged = localContent != remoteContent; @@ -373,13 +371,13 @@ define([ } else { // Add local modifications if no collaborators change - realtimeString.setText(localContent); + realtimeStringLocal.setText(localContent); } } if(aceEditor === undefined) { // Binds model with textarea - realtimeBinding = gapi.drive.realtime.databinding.bindString(realtimeString, document.getElementById("wmd-input")); + realtimeBinding = gapi.drive.realtime.databinding.bindString(realtimeStringLocal, document.getElementById("wmd-input")); } // Update content state according to collaborators changes @@ -389,8 +387,11 @@ define([ updateContentState(); aceEditor === undefined && debouncedRefreshPreview(); } - + if(aceEditor !== undefined) { + // Tell ACE to update realtime string on each change + realtimeString = realtimeStringLocal; + // Save undo/redo buttons actions undoExecute = pagedownEditor.uiManager.buttons.undo.execute; redoExecute = pagedownEditor.uiManager.buttons.redo.execute; diff --git a/res/styles/main.less b/res/styles/main.less index 52528703..20c87e5e 100644 --- a/res/styles/main.less +++ b/res/styles/main.less @@ -67,7 +67,7 @@ @btn-success-color: @primary-color-light; @btn-success-bg: @navbar-default-bg; @btn-success-border: @transparent; -@btn-info-bg: @body-bg; +@btn-info-bg: @transparent; @btn-info-border: @transparent; @gray-lighter: @body-bg; @modal-header-border-color: @primary-bg-light; @@ -1140,6 +1140,10 @@ div.dropdown-menu textarea { z-index: 1050 !important; } +.picker-dialog-bg { + z-index: 1040 !important; +} + .action-import-image-gplus { float: left; } diff --git a/res/themes/school.less b/res/themes/school.less index 7f07b1b0..19d56317 100644 --- a/res/themes/school.less +++ b/res/themes/school.less @@ -1,18 +1,24 @@ @import "../styles/main.less"; -@navbar-default-bg: #315A4B; -@bg-navbar-hover: lighten(@navbar-default-bg, 8%); -@primary-color: #174d80; +@board-color: #385E50; +@board-border-color: #B9AA9F; +@blue-ink: #284F72; +@black-ink: #444; +@red-ink: #B56E85; +@green-ink: #6c9c7f; +@navbar-default-bg: darken(@board-color, 2%); +@bg-navbar-hover: lighten(@board-color, 4%); +@primary-color: @blue-ink; @primary-color-light: lighten(@primary-color, 13%); @primary-color-lighter: lighten(@primary-color, 20%); @primary-color-lightest: lighten(@primary-color, 35%); -@primary-bg: darken(@primary-bg-light, 4%); -@primary-bg-light: #E2DEDE; -@primary-bg-lighter: lighten(@primary-bg-light, 4%); +@primary-bg: lighten(@board-border-color, 16%); +@primary-bg-light: lighten(@board-border-color, 20%); +@primary-bg-lighter: lighten(@board-border-color, 24%); @btn-success-color: #eee; +@btn-success-bg: @transparent; @title-base-size: 18px; -@resizer-bg: #B1A19A; -@panel-button-color: mix(#444, @resizer-bg, 50%); +@panel-button-color: #666; @font-face { @@ -26,67 +32,58 @@ font-style: normal; } -.ui-layout-resizer-north { - background-color: @resizer-bg; +.navbar { + #gradient.vertical(@board-color; darken(@board-color, 2%)); +} + +.ui-layout-resizer-north .resizer-decorator { + position: absolute; + width: 100%; + height: 7px; + background-color: @board-border-color; + .box-shadow(0 -1px 15px rgba(0,0,0,.3)); + z-index: 10; + display: block !important; } .extension-preview-buttons { - margin-top: 12px; + //margin-top: 4px; +} + +.ui-layout-resizer-east { + z-index: 1 !important; } .ace-tm { .ace_content { background-image: url("../img/school-line.png"); background-repeat: repeat; - background-color: transparent; } .ace_marker-layer .ace_active-line { - background-color: fade(#a2bace, 20%); + background-color: fade(@blue-ink, 5%); } .ace_print-margin { - background-color: fade(#b56e80, 50%); + background-color: fade(@red-ink, 50%); } .ace_markup.ace_heading { - color: #444; - font-weight: bold; - } - - .ace_markup.ace_list { - color: @primary-color-lightest; - } - - .ace_strong { - font-weight: bold; - } - - .ace_emphasis { - color: @primary-color; - font-style: italic; + color: @black-ink; } .ace_blockquote { - color: #b56e80; - font-style: italic; + color: @red-ink; } .ace_code { - font-family: Menlo, Consolas, "Courier New", monospace; - color: #66a26d; - background-color: fade(@secondary-bg, 10%); + color: @green-ink; } .ace_code_block { - font-family: Menlo, Consolas, "Courier New", monospace; - color: #66a26d; - } - - .ace_description { - color: @primary-color-lighter; + color: @green-ink; } } -.wmd-title { +h1, h2, h3, h4, h5, h6 { font-family: 'cursive_standardregular'; }