Fix Google Drive Realtime document import

This commit is contained in:
benweet 2013-09-19 20:48:21 +01:00
parent 91964c1240
commit d78b446722
16 changed files with 166 additions and 86 deletions

View File

@ -23,6 +23,7 @@
"pagedown-extra": "git@github.com:jmcmanus/pagedown-extra.git#master", "pagedown-extra": "git@github.com:jmcmanus/pagedown-extra.git#master",
"crel": "git@github.com:KoryNunn/crel.git#8dbda04b129fc0aec01a2a080d1cab26816e11c1", "crel": "git@github.com:KoryNunn/crel.git#8dbda04b129fc0aec01a2a080d1cab26816e11c1",
"waitForImages": "git@github.com:alexanderdickson/waitForImages.git#~1.4.2", "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"
} }
} }

View File

@ -18,6 +18,7 @@ define([
'libs/ace_mode', 'libs/ace_mode',
'ace/requirejs/text!ace/css/editor.css', 'ace/requirejs/text!ace/css/editor.css',
'ace/requirejs/text!ace/theme/textmate.css', 'ace/requirejs/text!ace/theme/textmate.css',
'ace/ext/spellcheck',
], function($, _, crel, ace, utils, settings, eventMgr, mousetrap, bodyIndexHTML, bodyViewerHTML, settingsTemplateTooltipHTML, settingsUserCustomExtensionTooltipHTML) { ], function($, _, crel, ace, utils, settings, eventMgr, mousetrap, bodyIndexHTML, bodyViewerHTML, settingsTemplateTooltipHTML, settingsUserCustomExtensionTooltipHTML) {
@ -206,6 +207,8 @@ define([
return; return;
} }
aceEditor = ace.edit("wmd-input"); aceEditor = ace.edit("wmd-input");
require('ace/ext/spellcheck');
aceEditor.setOption("spellcheck", true);
aceEditor.renderer.setShowGutter(false); aceEditor.renderer.setShowGutter(false);
aceEditor.renderer.setPrintMarginColumn(false); aceEditor.renderer.setPrintMarginColumn(false);
aceEditor.renderer.setPadding(EDITOR_DEFAULT_PADDING); aceEditor.renderer.setPadding(EDITOR_DEFAULT_PADDING);
@ -361,13 +364,15 @@ define([
// north resizer is very small // north resizer is very small
// var $previewButtonsContainerElt = $('<div // var $previewButtonsContainerElt = $('<div
// class="preview-button-container">'); // class="preview-button-container">');
$resizerDecorator = $('<div class="resizer-decorator">');
$previewButtonsElt = $('<div class="extension-preview-buttons">'); $previewButtonsElt = $('<div class="extension-preview-buttons">');
$editorButtonsElt = $('<div class="extension-editor-buttons">'); $editorButtonsElt = $('<div class="extension-editor-buttons">');
if(viewerMode || settings.layoutOrientation == "horizontal") { 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); $('.ui-layout-resizer-east').append($northTogglerElt).append($editorButtonsElt);
} }
else { else {
$('.ui-layout-resizer-north').append($resizerDecorator);
$('.ui-layout-resizer-south').append($previewButtonsElt).append($editorButtonsElt).append($northTogglerElt); $('.ui-layout-resizer-south').append($previewButtonsElt).append($editorButtonsElt).append($northTogglerElt);
} }
@ -435,27 +440,6 @@ define([
// Create the converter and the editor // Create the converter and the editor
var converter = new Markdown.Converter(); 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() { function checkDocumentChanges() {
var newDocumentContent = $editorElt.val(); var newDocumentContent = $editorElt.val();

View File

@ -6,6 +6,8 @@ define([
"classes/Extension", "classes/Extension",
"settings", "settings",
"text!html/settingsExtensionsAccordion.html", "text!html/settingsExtensionsAccordion.html",
// "extensions/yamlFrontMatterParser",
"extensions/markdownSectionParser",
"extensions/partialRendering", "extensions/partialRendering",
"extensions/buttonMarkdownSyntax", "extensions/buttonMarkdownSyntax",
"extensions/googleAnalytics", "extensions/googleAnalytics",
@ -178,6 +180,8 @@ define([
// Operations on PageDown // Operations on PageDown
addEventHook("onPagedownConfigure"); addEventHook("onPagedownConfigure");
addEventHook("onSectionsCreated"); addEventHook("onSectionsCreated");
addEventHook("onMarkdownTrim");
addEventHook("onExtraExtensions");
// Operation on ACE // Operation on ACE
addEventHook("onAceCreated"); addEventHook("onAceCreated");

View File

@ -44,6 +44,11 @@ define([
newConfig.highlighter = utils.getInputValue("#input-markdownextra-highlighter"); newConfig.highlighter = utils.getInputValue("#input-markdownextra-highlighter");
}; };
var eventMgr = undefined;
markdownExtra.onEventMgrCreated = function(eventMgrParameter) {
eventMgr = eventMgrParameter;
};
markdownExtra.onPagedownConfigure = function(editor) { markdownExtra.onPagedownConfigure = function(editor) {
var converter = editor.getConverter(); var converter = editor.getConverter();
var options = { var options = {
@ -64,8 +69,8 @@ define([
} }
Markdown.Extra.init(converter, options); Markdown.Extra.init(converter, options);
// Store extensions list in converter for partialRendering // Send extensions list to other extensions
converter.setExtraExtension && converter.setExtraExtension(markdownExtra.config.extensions); eventMgr.onExtraExtensions(markdownExtra.config.extensions);
}; };
return markdownExtra; return markdownExtra;

View File

@ -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;
});

View File

@ -189,13 +189,14 @@ define([
editor.hooks.chain("onPreviewRefresh", function() { editor.hooks.chain("onPreviewRefresh", function() {
refreshSections(); 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() { partialRendering.onReady = function() {
footnoteContainerElt = crel('div', { footnoteContainerElt = crel('div', {
id: 'wmd-preview-section-footnotes', id: 'wmd-preview-section-footnotes',

View File

@ -17,6 +17,11 @@ define([
scrollLink.onSectionsCreated = function(sectionListParam) { scrollLink.onSectionsCreated = function(sectionListParam) {
sectionList = sectionListParam; sectionList = sectionListParam;
}; };
var offsetBegin = 0;
scrollLink.onMarkdownTrim = function(offsetBeginParam) {
offsetBegin = offsetBeginParam;
};
var $previewElt = undefined; var $previewElt = undefined;
var mdSectionList = []; var mdSectionList = [];
@ -31,8 +36,10 @@ define([
mdSectionList = []; mdSectionList = [];
var mdTextOffset = 0; var mdTextOffset = 0;
var mdSectionOffset = 0; var mdSectionOffset = 0;
var firstSectionOffset = offsetBegin;
_.each(sectionList, function(sectionText) { _.each(sectionList, function(sectionText) {
mdTextOffset += sectionText.length; mdTextOffset += sectionText.length + firstSectionOffset;
firstSectionOffset = 0;
var documentPosition = aceEditor.session.doc.indexToPosition(mdTextOffset); var documentPosition = aceEditor.session.doc.indexToPosition(mdTextOffset);
var screenPosition = aceEditor.session.documentToScreenPosition(documentPosition.row, documentPosition.column); var screenPosition = aceEditor.session.documentToScreenPosition(documentPosition.row, documentPosition.column);
var newSectionOffset = screenPosition.row * aceEditor.renderer.lineHeight; var newSectionOffset = screenPosition.row * aceEditor.renderer.lineHeight;

View File

@ -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;
});

View File

@ -0,0 +1,3 @@
<p>Parses YAML front matter block at the begining of the document.</p>
<blockquote><b>Interpreted variables:</b> <i>title, published, category/categories/tags</i></blockquote>
<span class="help-block pull-right"><a target="_blank" href="http://jekyllrb.com/docs/frontmatter/">More info</a></span>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -49,7 +49,8 @@ requirejs.config({
'pagedown-ace': 'bower-libs/pagedown-ace/Markdown.Editor', 'pagedown-ace': 'bower-libs/pagedown-ace/Markdown.Editor',
'pagedown-extra': 'bower-libs/pagedown-extra/Markdown.Extra', 'pagedown-extra': 'bower-libs/pagedown-extra/Markdown.Extra',
'ace/requirejs/text': 'libs/ace_text', '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: { shim: {
underscore: { underscore: {

View File

@ -285,10 +285,8 @@ define([
aceEditor = aceEditorParam; aceEditor = aceEditorParam;
// Listen to editor's changes // Listen to editor's changes
aceEditor.session.on('change', function(e) { aceEditor.session.on('change', function(e) {
if(realtimeString !== undefined) { // Update the real time model if any
// Update the real time model realtimeString && realtimeString.setText(aceEditor.getValue());
realtimeString.setText(aceEditor.getValue());
}
}); });
}); });
@ -308,18 +306,18 @@ define([
logger.log("Starting Google Drive realtime synchronization"); logger.log("Starting Google Drive realtime synchronization");
realtimeDocument = doc; realtimeDocument = doc;
var model = realtimeDocument.getModel(); var model = realtimeDocument.getModel();
realtimeString = model.getRoot().get('content'); var realtimeStringLocal = model.getRoot().get('content');
// Saves model content checksum // Saves model content checksum
function updateContentState() { function updateContentState() {
syncAttributes.contentCRC = utils.crc32(realtimeString.getText()); syncAttributes.contentCRC = utils.crc32(realtimeStringLocal.getText());
utils.storeAttributes(syncAttributes); utils.storeAttributes(syncAttributes);
} }
var debouncedRefreshPreview = _.debounce(pagedownEditor.refreshPreview, 100); var debouncedRefreshPreview = _.debounce(pagedownEditor.refreshPreview, 100);
// Listen to insert text events // 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)) { if(aceEditor !== undefined && (isAceUpToDate === false || e.isLocal === false)) {
// Update ACE editor // Update ACE editor
var position = aceEditor.session.doc.indexToPosition(e.index); var position = aceEditor.session.doc.indexToPosition(e.index);
@ -334,7 +332,7 @@ define([
} }
}); });
// Listen to delete text events // 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)) { if(aceEditor !== undefined && (isAceUpToDate === false || e.isLocal === false)) {
// Update ACE editor // Update ACE editor
var range = (function(posStart, posEnd) { var range = (function(posStart, posEnd) {
@ -361,7 +359,7 @@ define([
// Try to merge offline modifications // Try to merge offline modifications
var localContent = fileDesc.content; var localContent = fileDesc.content;
var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent);
var remoteContent = realtimeString.getText(); var remoteContent = realtimeStringLocal.getText();
var remoteContentCRC = utils.crc32(remoteContent); var remoteContentCRC = utils.crc32(remoteContent);
var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC; var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC;
var fileContentChanged = localContent != remoteContent; var fileContentChanged = localContent != remoteContent;
@ -373,13 +371,13 @@ define([
} }
else { else {
// Add local modifications if no collaborators change // Add local modifications if no collaborators change
realtimeString.setText(localContent); realtimeStringLocal.setText(localContent);
} }
} }
if(aceEditor === undefined) { if(aceEditor === undefined) {
// Binds model with textarea // 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 // Update content state according to collaborators changes
@ -389,8 +387,11 @@ define([
updateContentState(); updateContentState();
aceEditor === undefined && debouncedRefreshPreview(); aceEditor === undefined && debouncedRefreshPreview();
} }
if(aceEditor !== undefined) { if(aceEditor !== undefined) {
// Tell ACE to update realtime string on each change
realtimeString = realtimeStringLocal;
// Save undo/redo buttons actions // Save undo/redo buttons actions
undoExecute = pagedownEditor.uiManager.buttons.undo.execute; undoExecute = pagedownEditor.uiManager.buttons.undo.execute;
redoExecute = pagedownEditor.uiManager.buttons.redo.execute; redoExecute = pagedownEditor.uiManager.buttons.redo.execute;

View File

@ -67,7 +67,7 @@
@btn-success-color: @primary-color-light; @btn-success-color: @primary-color-light;
@btn-success-bg: @navbar-default-bg; @btn-success-bg: @navbar-default-bg;
@btn-success-border: @transparent; @btn-success-border: @transparent;
@btn-info-bg: @body-bg; @btn-info-bg: @transparent;
@btn-info-border: @transparent; @btn-info-border: @transparent;
@gray-lighter: @body-bg; @gray-lighter: @body-bg;
@modal-header-border-color: @primary-bg-light; @modal-header-border-color: @primary-bg-light;
@ -1140,6 +1140,10 @@ div.dropdown-menu textarea {
z-index: 1050 !important; z-index: 1050 !important;
} }
.picker-dialog-bg {
z-index: 1040 !important;
}
.action-import-image-gplus { .action-import-image-gplus {
float: left; float: left;
} }

View File

@ -1,18 +1,24 @@
@import "../styles/main.less"; @import "../styles/main.less";
@navbar-default-bg: #315A4B; @board-color: #385E50;
@bg-navbar-hover: lighten(@navbar-default-bg, 8%); @board-border-color: #B9AA9F;
@primary-color: #174d80; @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-light: lighten(@primary-color, 13%);
@primary-color-lighter: lighten(@primary-color, 20%); @primary-color-lighter: lighten(@primary-color, 20%);
@primary-color-lightest: lighten(@primary-color, 35%); @primary-color-lightest: lighten(@primary-color, 35%);
@primary-bg: darken(@primary-bg-light, 4%); @primary-bg: lighten(@board-border-color, 16%);
@primary-bg-light: #E2DEDE; @primary-bg-light: lighten(@board-border-color, 20%);
@primary-bg-lighter: lighten(@primary-bg-light, 4%); @primary-bg-lighter: lighten(@board-border-color, 24%);
@btn-success-color: #eee; @btn-success-color: #eee;
@btn-success-bg: @transparent;
@title-base-size: 18px; @title-base-size: 18px;
@resizer-bg: #B1A19A; @panel-button-color: #666;
@panel-button-color: mix(#444, @resizer-bg, 50%);
@font-face { @font-face {
@ -26,67 +32,58 @@
font-style: normal; font-style: normal;
} }
.ui-layout-resizer-north { .navbar {
background-color: @resizer-bg; #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 { .extension-preview-buttons {
margin-top: 12px; //margin-top: 4px;
}
.ui-layout-resizer-east {
z-index: 1 !important;
} }
.ace-tm { .ace-tm {
.ace_content { .ace_content {
background-image: url("../img/school-line.png"); background-image: url("../img/school-line.png");
background-repeat: repeat; background-repeat: repeat;
background-color: transparent;
} }
.ace_marker-layer .ace_active-line { .ace_marker-layer .ace_active-line {
background-color: fade(#a2bace, 20%); background-color: fade(@blue-ink, 5%);
} }
.ace_print-margin { .ace_print-margin {
background-color: fade(#b56e80, 50%); background-color: fade(@red-ink, 50%);
} }
.ace_markup.ace_heading { .ace_markup.ace_heading {
color: #444; color: @black-ink;
font-weight: bold;
}
.ace_markup.ace_list {
color: @primary-color-lightest;
}
.ace_strong {
font-weight: bold;
}
.ace_emphasis {
color: @primary-color;
font-style: italic;
} }
.ace_blockquote { .ace_blockquote {
color: #b56e80; color: @red-ink;
font-style: italic;
} }
.ace_code { .ace_code {
font-family: Menlo, Consolas, "Courier New", monospace; color: @green-ink;
color: #66a26d;
background-color: fade(@secondary-bg, 10%);
} }
.ace_code_block { .ace_code_block {
font-family: Menlo, Consolas, "Courier New", monospace; color: @green-ink;
color: #66a26d;
}
.ace_description {
color: @primary-color-lighter;
} }
} }
.wmd-title { h1, h2, h3, h4, h5, h6 {
font-family: 'cursive_standardregular'; font-family: 'cursive_standardregular';
} }