Implement remember document position

This commit is contained in:
benweet 2013-06-03 23:19:52 +01:00
parent 693072218e
commit c99f927205
18 changed files with 394 additions and 197 deletions

View File

@ -598,7 +598,7 @@ dt, dd {
} }
dd { dd {
margin-left: 40px; margin-left: 40px;
} }
/* Table style */ /* Table style */

View File

@ -736,6 +736,9 @@
<dd> <dd>
<a target="_blank" href="http://jquery.com/">jQuery</a> <a target="_blank" href="http://jquery.com/">jQuery</a>
</dd> </dd>
<dd>
<a target="_blank" href="https://github.com/brandonaaron/jquery-mousewheel">jQuery Mouse Wheel Plugin</a>
</dd>
<dd> <dd>
<a target="_blank" href="https://code.google.com/p/pagedown/">PageDown</a> <a target="_blank" href="https://code.google.com/p/pagedown/">PageDown</a>
/ <a target="_blank" / <a target="_blank"

View File

@ -200,15 +200,31 @@ define([
// Create the PageDown editor // Create the PageDown editor
var editor = undefined; var editor = undefined;
var fileDesc = undefined;
var documentContent = undefined; var documentContent = undefined;
core.createEditor = function(onTextChange) { var undoManager = undefined;
core.createEditor = function(fileDescParam) {
fileDesc = fileDescParam;
documentContent = undefined; documentContent = undefined;
$("#wmd-input, .preview-container").scrollTop(0); var initDocumentContent = fileDesc.content;
$("#wmd-input").val(initDocumentContent);
if(editor !== undefined) { if(editor !== undefined) {
// Only restart if the editor is already created // If the editor is already created
editor.restart(); undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
editor.refreshPreview();
return; return;
} }
// Store scrollTop on scroll event
$("#wmd-input").scroll(function() {
if(documentContent !== undefined) {
fileDesc.editorScrollTop = $(this).scrollTop();
}
});
$(".preview-container").scroll(function() {
if(documentContent !== undefined) {
fileDesc.previewScrollTop = $(this).scrollTop();
}
});
var converter = new Markdown.Converter(); var converter = new Markdown.Converter();
editor = new Markdown.Editor(converter); editor = new Markdown.Editor(converter);
// Custom insert link dialog // Custom insert link dialog
@ -229,7 +245,7 @@ define([
function checkDocumentChanges() { function checkDocumentChanges() {
var newDocumentContent = $("#wmd-input").val(); var newDocumentContent = $("#wmd-input").val();
if(documentContent !== undefined && documentContent != newDocumentContent) { if(documentContent !== undefined && documentContent != newDocumentContent) {
onTextChange(); fileDesc.content = newDocumentContent;
} }
documentContent = newDocumentContent; documentContent = newDocumentContent;
} }
@ -240,6 +256,8 @@ define([
return function() { return function() {
if(documentContent === undefined) { if(documentContent === undefined) {
makePreview(); makePreview();
$("#wmd-input").scrollTop(fileDesc.editorScrollTop);
$(".preview-container").scrollTop(fileDesc.previewScrollTop);
} }
else { else {
debouncedMakePreview(); debouncedMakePreview();
@ -251,14 +269,22 @@ define([
else { else {
previewWrapper = function(makePreview) { previewWrapper = function(makePreview) {
return function() { return function() {
checkDocumentChanges();
makePreview(); makePreview();
if(documentContent === undefined) {
$(".preview-container").scrollTop(fileDesc.previewScrollTop);
}
checkDocumentChanges();
}; };
}; };
} }
extensionMgr.onEditorConfigure(editor); extensionMgr.onEditorConfigure(editor);
editor.hooks.chain("onPreviewRefresh", extensionMgr.onAsyncPreview); editor.hooks.chain("onPreviewRefresh", extensionMgr.onAsyncPreview);
editor.run(previewWrapper); undoManager = editor.run(previewWrapper);
undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
$("#wmd-input").bind("keydown click focus", function(event) {
fileDesc.editorStart = this.selectionStart;
fileDesc.editorEnd = this.selectionEnd;
});
// Hide default buttons // Hide default buttons
$(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)").addClass("btn").css("left", 0).find("span").hide(); $(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)").addClass("btn").css("left", 0).find("span").hide();
@ -482,11 +508,6 @@ define([
checkOnline(); checkOnline();
} }
}, 1000); }, 1000);
// Focus on the editor at startup
_.defer(function() {
$("#wmd-input").focus();
});
}); });
return core; return core;

View File

@ -7,6 +7,11 @@ define([
var documentSelector = { var documentSelector = {
extensionId: "documentSelector", extensionId: "documentSelector",
extensionName: "Document selector", extensionName: "Document selector",
/*
defaultConfig: {
keyShortcut: 223
},
*/
settingsBloc: '<p>Builds the "Open document" dropdown menu.</p>' settingsBloc: '<p>Builds the "Open document" dropdown menu.</p>'
}; };
@ -56,7 +61,8 @@ define([
$("#file-selector li:not(.stick)").removeClass("disabled"); $("#file-selector li:not(.stick)").removeClass("disabled");
var li = liMap[fileDesc.fileIndex]; var li = liMap[fileDesc.fileIndex];
if(li === undefined) { if(li === undefined) {
// It means that we are showing a temporary file (not in the selector) // It means that we are showing a temporary file (not in the
// selector)
return; return;
} }
liMap[fileDesc.fileIndex].addClass("disabled"); liMap[fileDesc.fileIndex].addClass("disabled");
@ -104,6 +110,12 @@ define([
}).click(function(event) { }).click(function(event) {
event.stopPropagation(); event.stopPropagation();
}); });
/*
$("#wmd-input").keydown(function(event) {
if(event.ctrlKey && event.keyCode == documentSelector.config.keyShortcut) {
console.log(event.keyCode);
}
});*/
}; };
return documentSelector; return documentSelector;

View File

@ -1,7 +1,8 @@
define([ define([
"jquery", "jquery",
"underscore", "underscore",
"libs/css_browser_selector" "libs/css_browser_selector",
"libs/jquery.mousewheel"
], function($, _) { ], function($, _) {
var scrollLink = { var scrollLink = {
@ -95,13 +96,14 @@ define([
// apply Scroll Link // apply Scroll Link
lastEditorScrollTop = -10; lastEditorScrollTop = -10;
isScrollPreview = false; lastPreviewScrollTop = -10;
runScrollLink(); runScrollLink();
}, 500); }, 500);
// -10 to be sure the gap is > 9 // -10 to be sure the gap is more than 9px
var lastEditorScrollTop = -10; var lastEditorScrollTop = -10;
var lastPreviewScrollTop = -10; var lastPreviewScrollTop = -10;
var isScrollEditor = false;
var isScrollPreview = false; var isScrollPreview = false;
var runScrollLink = _.debounce(function() { var runScrollLink = _.debounce(function() {
if(mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) { if(mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) {
@ -120,7 +122,8 @@ define([
}); });
if(srcSection === undefined) { if(srcSection === undefined) {
// Something wrong in the algorithm... // Something wrong in the algorithm...
return -10; callback(-10);
return;
} }
var posInSection = (srcScrollTop - srcSection.startOffset) / srcSection.height; var posInSection = (srcScrollTop - srcSection.startOffset) / srcSection.height;
var destSection = destSectionList[sectionIndex]; var destSection = destSectionList[sectionIndex];
@ -131,50 +134,58 @@ define([
]); ]);
if(Math.abs(destScrollTop - lastDestScrollTop) < 9) { if(Math.abs(destScrollTop - lastDestScrollTop) < 9) {
// Skip the animation in case it's not necessary // Skip the animation in case it's not necessary
callback(lastDestScrollTop);
return; return;
} }
destElt.animate({ destElt.animate({
scrollTop: destScrollTop scrollTop: destScrollTop
}, 600, function() { }, 500, function() {
callback(destScrollTop); callback(destScrollTop);
}); });
} }
// Perform the animation if diff > 9px // Perform the animation if diff > 9px
if(isScrollPreview === false && Math.abs(editorScrollTop - lastEditorScrollTop) > 9) { if(isScrollEditor === true && Math.abs(editorScrollTop - lastEditorScrollTop) > 9) {
isScrollEditor = false;
// Animate the preview // Animate the preview
lastEditorScrollTop = editorScrollTop; lastEditorScrollTop = editorScrollTop;
animate(editorScrollTop, mdSectionList, previewElt, htmlSectionList, lastPreviewScrollTop, function(destScrollTop) { animate(editorScrollTop, mdSectionList, previewElt, htmlSectionList, lastPreviewScrollTop, function(destScrollTop) {
lastPreviewScrollTop = destScrollTop; lastPreviewScrollTop = destScrollTop;
}); });
} }
else if(Math.abs(previewScrollTop - lastPreviewScrollTop) > 9) { else if(isScrollPreview === true && Math.abs(previewScrollTop - lastPreviewScrollTop) > 9) {
isScrollPreview = false;
// Animate the editor // Animate the editor
lastPreviewScrollTop = previewScrollTop; lastPreviewScrollTop = previewScrollTop;
animate(previewScrollTop, htmlSectionList, editorElt, mdSectionList, lastEditorScrollTop, function(destScrollTop) { animate(previewScrollTop, htmlSectionList, editorElt, mdSectionList, lastEditorScrollTop, function(destScrollTop) {
lastEditorScrollTop = destScrollTop; lastEditorScrollTop = destScrollTop;
}); });
} }
}, 600); }, 500);
scrollLink.onLayoutConfigure = function(layoutConfig) { scrollLink.onLayoutConfigure = function(layoutConfig) {
layoutConfig.onresize = buildSections; layoutConfig.onresize = function() {
isScrollEditor = true;
buildSections();
};
}; };
scrollLink.onLayoutCreated = function() { scrollLink.onLayoutCreated = function() {
$(".preview-container").scroll(function() { $(".preview-container").bind("keydown click focus mousewheel", function() {
isScrollPreview = true; isScrollPreview = true;
isScrollEditor = false;
runScrollLink(); runScrollLink();
}); });
$("#wmd-input").scroll(function() { $("#wmd-input").bind("keydown click focus mousewheel", function() {
isScrollEditor = true;
isScrollPreview = false; isScrollPreview = false;
runScrollLink(); runScrollLink();
}); });
}; };
scrollLink.onEditorConfigure = function(editor) { scrollLink.onEditorConfigure = function(editor) {
lastPreviewScrollTop = 0;
editor.getConverter().hooks.chain("postConversion", function(text) { editor.getConverter().hooks.chain("postConversion", function(text) {
// To avoid losing scrolling position before elements are fully loaded // To avoid losing scrolling position before elements are fully
// loaded
$("#wmd-preview").height($("#wmd-preview").height()); $("#wmd-preview").height($("#wmd-preview").height());
return text; return text;
}); });
@ -183,11 +194,8 @@ define([
scrollLink.onPreviewFinished = function() { scrollLink.onPreviewFinished = function() {
// Now set the correct height // Now set the correct height
$("#wmd-preview").height("auto"); $("#wmd-preview").height("auto");
_.defer(function() { isScrollEditor = true;
// Modify scroll position of the preview not the editor buildSections();
lastEditorScrollTop = -10;
buildSections();
});
}; };
return scrollLink; return scrollLink;

View File

@ -11,10 +11,16 @@ define([
var fileMgr = {}; var fileMgr = {};
// Defines a file descriptor in the file system (fileDesc objects) // Defines a file descriptor (fileDesc objects)
function FileDescriptor(fileIndex, title, syncLocations, publishLocations) { function FileDescriptor(fileIndex, title, syncLocations, publishLocations) {
this.fileIndex = fileIndex; this.fileIndex = fileIndex;
this._title = title; this._title = title;
this._editorScrollTop = parseInt(localStorage[fileIndex + ".editorScrollTop"]) || 0;
this._editorStart = parseInt(localStorage[fileIndex + ".editorStart"]) || 0;
this._editorEnd = parseInt(localStorage[fileIndex + ".editorEnd"]) || 0;
this._previewScrollTop = parseInt(localStorage[fileIndex + ".previewScrollTop"]) || 0;
this.syncLocations = syncLocations || {};
this.publishLocations = publishLocations || {};
this.__defineGetter__("title", function() { this.__defineGetter__("title", function() {
return this._title; return this._title;
}); });
@ -30,12 +36,38 @@ define([
localStorage[this.fileIndex + ".content"] = content; localStorage[this.fileIndex + ".content"] = content;
extensionMgr.onContentChanged(this); extensionMgr.onContentChanged(this);
}); });
this.syncLocations = syncLocations || {}; this.__defineGetter__("editorScrollTop", function() {
this.publishLocations = publishLocations || {}; return this._editorScrollTop;
});
this.__defineSetter__("editorScrollTop", function(editorScrollTop) {
this._editorScrollTop = editorScrollTop;
localStorage[this.fileIndex + ".editorScrollTop"] = editorScrollTop;
});
this.__defineGetter__("editorStart", function() {
return this._editorStart;
});
this.__defineSetter__("editorStart", function(editorStart) {
this._editorStart = editorStart;
localStorage[this.fileIndex + ".editorStart"] = editorStart;
});
this.__defineGetter__("editorEnd", function() {
return this._editorEnd;
});
this.__defineSetter__("editorEnd", function(editorEnd) {
this._editorEnd = editorEnd;
localStorage[this.fileIndex + ".editorEnd"] = editorEnd;
});
this.__defineGetter__("previewScrollTop", function() {
return this._previewScrollTop;
});
this.__defineSetter__("previewScrollTop", function(previewScrollTop) {
this._previewScrollTop = previewScrollTop;
localStorage[this.fileIndex + ".previewScrollTop"] = previewScrollTop;
});
} }
// Load file descriptors from localStorage // Retrieve file descriptors from localStorage and populate fileSystem
_.chain(localStorage["file.list"].split(";")).compact().each(function(fileIndex) { _.each(utils.retrieveIndexArray("file.list"), function(fileIndex) {
fileSystem[fileIndex] = new FileDescriptor(fileIndex, localStorage[fileIndex + ".title"]); fileSystem[fileIndex] = new FileDescriptor(fileIndex, localStorage[fileIndex + ".title"]);
}); });
@ -57,7 +89,6 @@ define([
} }
}; };
// Caution: this function recreates the editor (reset undo operations)
fileMgr.selectFile = function(fileDesc) { fileMgr.selectFile = function(fileDesc) {
fileDesc = fileDesc || fileMgr.getCurrentFile(); fileDesc = fileDesc || fileMgr.getCurrentFile();
@ -92,12 +123,8 @@ define([
} }
} }
// Recreate the editor // Refresh the editor
$("#wmd-input").val(fileDesc.content); core.createEditor(fileDesc);
core.createEditor(function() {
// Callback to save content when textarea changes
fileMgr.saveFile();
});
}; };
fileMgr.createFile = function(title, content, syncLocations, isTemporary) { fileMgr.createFile = function(title, content, syncLocations, isTemporary) {
@ -137,7 +164,7 @@ define([
// Add the index to the file list // Add the index to the file list
if(!isTemporary) { if(!isTemporary) {
localStorage["file.list"] += fileIndex + ";"; utils.appendIndexToArray("file.list", fileIndex);
fileSystem[fileIndex] = fileDesc; fileSystem[fileIndex] = fileDesc;
extensionMgr.onFileCreated(fileDesc); extensionMgr.onFileCreated(fileDesc);
} }
@ -148,7 +175,7 @@ define([
fileDesc = fileDesc || fileMgr.getCurrentFile(); fileDesc = fileDesc || fileMgr.getCurrentFile();
// Remove the index from the file list // Remove the index from the file list
localStorage["file.list"] = localStorage["file.list"].replace(";" + fileDesc.fileIndex + ";", ";"); utils.removeIndexFromArray("file.list", fileDesc.fileIndex);
delete fileSystem[fileDesc.fileIndex]; delete fileSystem[fileDesc.fileIndex];
if(fileMgr.isCurrentFile(fileDesc) === true) { if(fileMgr.isCurrentFile(fileDesc) === true) {
@ -160,12 +187,12 @@ define([
// Remove synchronized locations // Remove synchronized locations
_.each(fileDesc.syncLocations, function(syncAttributes) { _.each(fileDesc.syncLocations, function(syncAttributes) {
fileMgr.removeSync(syncAttributes, true); fileMgr.removeSync(syncAttributes);
}); });
// Remove publish locations // Remove publish locations
_.each(fileDesc.publishLocations, function(publishAttributes) { _.each(fileDesc.publishLocations, function(publishAttributes) {
fileMgr.removePublish(publishAttributes, true); fileMgr.removePublish(publishAttributes);
}); });
localStorage.removeItem(fileDesc.fileIndex + ".title"); localStorage.removeItem(fileDesc.fileIndex + ".title");
@ -176,32 +203,24 @@ define([
extensionMgr.onFileDeleted(fileDesc); extensionMgr.onFileDeleted(fileDesc);
}; };
// Save current file in localStorage
fileMgr.saveFile = function() {
var fileDesc = fileMgr.getCurrentFile();
fileDesc.content = $("#wmd-input").val();
};
// Add a synchronized location to a file // Add a synchronized location to a file
fileMgr.addSync = function(fileDesc, syncAttributes) { fileMgr.addSync = function(fileDesc, syncAttributes) {
localStorage[fileDesc.fileIndex + ".sync"] += syncAttributes.syncIndex + ";"; utils.appendIndexToArray(fileDesc.fileIndex + ".sync", syncAttributes.syncIndex);
fileDesc.syncLocations[syncAttributes.syncIndex] = syncAttributes; fileDesc.syncLocations[syncAttributes.syncIndex] = syncAttributes;
// addSync is only used for export, not for import // addSync is only used for export, not for import
extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes); extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes);
}; };
// Remove a synchronized location // Remove a synchronized location
fileMgr.removeSync = function(syncAttributes, skipExtensions) { fileMgr.removeSync = function(syncAttributes) {
var fileDesc = fileMgr.getFileFromSyncIndex(syncAttributes.syncIndex); var fileDesc = fileMgr.getFileFromSyncIndex(syncAttributes.syncIndex);
if(fileDesc !== undefined) { if(fileDesc !== undefined) {
localStorage[fileDesc.fileIndex + ".sync"] = localStorage[fileDesc.fileIndex + ".sync"].replace(";" + syncAttributes.syncIndex + ";", ";"); utils.removeIndexFromArray(fileDesc.fileIndex + ".sync", syncAttributes.syncIndex);
} delete fileDesc.syncLocations[syncAttributes.syncIndex];
// Remove sync attributes
localStorage.removeItem(syncAttributes.syncIndex);
delete fileDesc.syncLocations[syncAttributes.syncIndex];
if(!skipExtensions) {
extensionMgr.onSyncRemoved(fileDesc, syncAttributes); extensionMgr.onSyncRemoved(fileDesc, syncAttributes);
} }
// Remove sync attributes from localStorage
localStorage.removeItem(syncAttributes.syncIndex);
}; };
// Get the file descriptor associated to a syncIndex // Get the file descriptor associated to a syncIndex
@ -228,23 +247,21 @@ define([
// Add a publishIndex (publish location) to a file // Add a publishIndex (publish location) to a file
fileMgr.addPublish = function(fileDesc, publishAttributes) { fileMgr.addPublish = function(fileDesc, publishAttributes) {
localStorage[fileDesc.fileIndex + ".publish"] += publishAttributes.publishIndex + ";"; utils.appendIndexToArray(fileDesc.fileIndex + ".publish", publishAttributes.publishIndex);
fileDesc.publishLocations[publishAttributes.publishIndex] = publishAttributes; fileDesc.publishLocations[publishAttributes.publishIndex] = publishAttributes;
extensionMgr.onNewPublishSuccess(fileDesc, publishAttributes); extensionMgr.onNewPublishSuccess(fileDesc, publishAttributes);
}; };
// Remove a publishIndex (publish location) // Remove a publishIndex (publish location)
fileMgr.removePublish = function(publishAttributes, skipExtensions) { fileMgr.removePublish = function(publishAttributes) {
var fileDesc = fileMgr.getFileFromPublishIndex(publishAttributes.publishIndex); var fileDesc = fileMgr.getFileFromPublishIndex(publishAttributes.publishIndex);
if(fileDesc !== undefined) { if(fileDesc !== undefined) {
localStorage[fileDesc.fileIndex + ".publish"] = localStorage[fileDesc.fileIndex + ".publish"].replace(";" + publishAttributes.publishIndex + ";", ";"); utils.removeIndexFromArray(fileDesc.fileIndex + ".publish", publishAttributes.publishIndex);
} delete fileDesc.publishLocations[publishAttributes.publishIndex];
// Remove publish attributes
localStorage.removeItem(publishAttributes.publishIndex);
delete fileDesc.publishLocations[publishAttributes.publishIndex];
if(!skipExtensions) {
extensionMgr.onPublishRemoved(fileDesc, publishAttributes); extensionMgr.onPublishRemoved(fileDesc, publishAttributes);
} }
// Remove publish attributes from localStorage
localStorage.removeItem(publishAttributes.publishIndex);
}; };
// Get the file descriptor associated to a publishIndex // Get the file descriptor associated to a publishIndex

View File

@ -362,7 +362,7 @@ define([
task.retry(new Error(errorMsg), 1); task.retry(new Error(errorMsg), 1);
return; return;
} }
else if(error.code <= 0) { else if(error.code === 0 || error.code === -1) {
connected = false; connected = false;
authenticated = false; authenticated = false;
core.setOffline(); core.setOffline();

View File

@ -111,14 +111,13 @@
* its own image insertion dialog, this hook should return true, and the callback should be called with the chosen * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen
* image url (or null if the user cancelled). If this hook returns false, the default dialog will be used. * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used.
*/ */
hooks.addFalse("insertLinkDialog"); // benweet hooks.addFalse("insertLinkDialog");
this.getConverter = function () { return markdownConverter; } this.getConverter = function () { return markdownConverter; }
var that = this, var that = this,
panels; panels;
// benweet
var undoManager; var undoManager;
this.run = function (previewWrapper) { this.run = function (previewWrapper) {
if (panels) if (panels)
@ -126,7 +125,7 @@
panels = new PanelCollection(idPostfix); panels = new PanelCollection(idPostfix);
var commandManager = new CommandManager(hooks, getString); var commandManager = new CommandManager(hooks, getString);
var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); }, previewWrapper); // benweet var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); }, previewWrapper);
var uiManager; var uiManager;
if (!/\?noundo/.test(doc.location.href)) { if (!/\?noundo/.test(doc.location.href)) {
@ -147,13 +146,9 @@
var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); }; var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); };
forceRefresh(); //Not necessary
}; //forceRefresh();
return undoManager;
// benweet
this.restart = function() {
undoManager.reinit();
that.refreshPreview();
}; };
} }
@ -681,18 +676,21 @@
var init = function () { var init = function () {
setEventHandlers(); setEventHandlers();
refreshState(true); refreshState(true);
saveState(); //Not necessary
//saveState();
}; };
// benweet this.reinit = function(content, start, end, scrollTop) {
this.reinit = function() {
undoStack = []; undoStack = [];
stackPtr = 0; stackPtr = 0;
mode = "none"; mode = "none";
lastState = undefined; lastState = undefined;
timer = undefined; timer = undefined;
inputStateObj = undefined;
refreshState(); refreshState();
inputStateObj.text = content;
inputStateObj.start = start;
inputStateObj.end = end;
inputStateObj.scrollTop = scrollTop;
inputStateObj.setInputAreaSelection(); inputStateObj.setInputAreaSelection();
saveState(); saveState();
}; };
@ -842,7 +840,7 @@
this.init(); this.init();
}; };
function PreviewManager(converter, panels, previewRefreshCallback, previewWrapper) { // benweet function PreviewManager(converter, panels, previewRefreshCallback, previewWrapper) {
var managerObj = this; var managerObj = this;
var timeout; var timeout;
@ -908,7 +906,7 @@
pushPreviewHtml(text); pushPreviewHtml(text);
}; };
if(previewWrapper !== undefined) { // benweet if(previewWrapper !== undefined) {
makePreviewHtml = previewWrapper(makePreviewHtml); makePreviewHtml = previewWrapper(makePreviewHtml);
} }
@ -1030,7 +1028,8 @@
var init = function () { var init = function () {
setupEvents(panels.input, applyTimeout); setupEvents(panels.input, applyTimeout);
makePreviewHtml(); //Not necessary
//makePreviewHtml();
if (panels.preview) { if (panels.preview) {
panels.preview.scrollTop = 0; panels.preview.scrollTop = 0;
@ -1437,12 +1436,12 @@
return false; return false;
} }
} }
button.className = button.className.replace(/ disabled/g, ""); // benweet button.className = button.className.replace(/ disabled/g, "");
} }
else { else {
image.style.backgroundPosition = button.XShift + " " + disabledYShift; image.style.backgroundPosition = button.XShift + " " + disabledYShift;
button.onmouseover = button.onmouseout = button.onclick = function () { }; button.onmouseover = button.onmouseout = button.onclick = function () { };
button.className += " disabled"; // benweet button.className += " disabled";
} }
} }
@ -1807,7 +1806,7 @@
ui.prompt(this.getString("imagedialog"), imageDefaultText, linkEnteredCallback); ui.prompt(this.getString("imagedialog"), imageDefaultText, linkEnteredCallback);
} }
else { else {
if (!this.hooks.insertLinkDialog(linkEnteredCallback)) // benweet if (!this.hooks.insertLinkDialog(linkEnteredCallback))
ui.prompt(this.getString("linkdialog"), linkDefaultText, linkEnteredCallback); ui.prompt(this.getString("linkdialog"), linkDefaultText, linkEnteredCallback);
} }
return true; return true;

View File

@ -24,7 +24,7 @@
} }
} }
return -1; return -1;
} };
} }
function trim(str) { function trim(str) {
@ -113,21 +113,17 @@
// Duplicated from PageDown converter // Duplicated from PageDown converter
function unescapeSpecialChars(text) { function unescapeSpecialChars(text) {
//
// Swap back in all the special characters we've hidden. // Swap back in all the special characters we've hidden.
// text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) {
text = text.replace(/~E(\d+)E/g, var charCodeToReplace = parseInt(m1);
function (wholeMatch, m1) { return String.fromCharCode(charCodeToReplace);
var charCodeToReplace = parseInt(m1); });
return String.fromCharCode(charCodeToReplace);
}
);
return text; return text;
} }
/****************************************************************** /*****************************************************************************
* Markdown.Extra * * Markdown.Extra *
*****************************************************************/ ****************************************************************************/
Markdown.Extra = function() { Markdown.Extra = function() {
// For converting internal markdown (in tables for instance). // For converting internal markdown (in tables for instance).
@ -159,36 +155,47 @@
var extra = new Markdown.Extra(); var extra = new Markdown.Extra();
var postNormalizationTransformations = []; var postNormalizationTransformations = [];
var preBlockGamutTransformations = []; var preBlockGamutTransformations = [];
var postConversionTransformations = ["unHashExtraBlocks"];
options = options || {}; options = options || {};
options.extensions = options.extensions || ["all"]; options.extensions = options.extensions || ["all"];
if (contains(options.extensions, "all")) if (contains(options.extensions, "all")) {
options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list"]; options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list"];
if (contains(options.extensions, "tables")) }
preBlockGamutTransformations.push("tables"); if (contains(options.extensions, "attr_list")) {
if (contains(options.extensions, "fenced_code_gfm")) postNormalizationTransformations.push("hashFcbAttributeBlocks");
preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
postConversionTransformations.push("applyAttributeBlocks");
extra.attributeBlocks = true;
}
if (contains(options.extensions, "tables")) {
preBlockGamutTransformations.push("tables");
}
if (contains(options.extensions, "fenced_code_gfm")) {
postNormalizationTransformations.push("fencedCodeBlocks"); postNormalizationTransformations.push("fencedCodeBlocks");
if (contains(options.extensions, "def_list")) }
preBlockGamutTransformations.push("definitionLists"); if (contains(options.extensions, "def_list")) {
if (contains(options.extensions, "attr_list")) preBlockGamutTransformations.push("definitionLists");
extra.attributeBlocks = true; }
converter.hooks.chain("postNormalization", function(text) { converter.hooks.chain("postNormalization", function(text) {
return extra.doTransform(postNormalizationTransformations, text); return extra.doTransform(postNormalizationTransformations, text) + '\n';
}); });
// preBlockGamut also gives us access to a hook so we can run the
// block gamut recursively, however we don't need it at this point
converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) { converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) {
// Keep a reference to the block gamut callback to run recursively
extra.blockGamutHookCallback = blockGamutHookCallback; extra.blockGamutHookCallback = blockGamutHookCallback;
return extra.doConversion(preBlockGamutTransformations, text); text = processEscapes(text);
return extra.doTransform(preBlockGamutTransformations, text) + '\n';
}); });
// Keep a reference to the hook chain running before finishConversion to apply on hashed extra blocks // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks
extra.previousPostConversion = converter.hooks.postConversion; extra.previousPostConversion = converter.hooks.postConversion;
converter.hooks.chain("postConversion", function(text) { converter.hooks.chain("postConversion", function(text) {
return extra.finishConversion(text); text = extra.doTransform(postConversionTransformations, text);
// Clear state vars that may use unnecessary memory
this.hashBlocks = [];
return text;
}); });
if ("highlighter" in options) { if ("highlighter" in options) {
@ -208,35 +215,9 @@
// Do transformations // Do transformations
Markdown.Extra.prototype.doTransform = function(transformations, text) { Markdown.Extra.prototype.doTransform = function(transformations, text) {
if (this.attributeBlocks)
text = this.hashFcbAttributeBlocks(text);
for(var i = 0; i < transformations.length; i++) for(var i = 0; i < transformations.length; i++)
text = this[transformations[i]](text); text = this[transformations[i]](text);
return text;
return text + '\n';
};
// Setup state vars, do conversion
Markdown.Extra.prototype.doConversion = function(transformations, text) {
text = processEscapes(text);
if (this.attributeBlocks)
text = this.hashHeaderAttributeBlocks(text);
return this.doTransform(transformations, text);
};
// Clear state vars that may use unnecessary memory. Unhash blocks we
// stored, apply attribute blocks if necessary, and return converted text.
Markdown.Extra.prototype.finishConversion = function(text) {
text = this.unHashExtraBlocks(text);
if (this.attributeBlocks)
text = this.applyAttributeBlocks(text);
this.hashBlocks = [];
return text;
}; };
// Return a placeholder containing a key, which is the block's index in the // Return a placeholder containing a key, which is the block's index in the
@ -466,6 +447,9 @@
code = code.replace(/&/g, "&amp;"); code = code.replace(/&/g, "&amp;");
code = code.replace(/</g, "&lt;"); code = code.replace(/</g, "&lt;");
code = code.replace(/>/g, "&gt;"); code = code.replace(/>/g, "&gt;");
// These were escaped by PageDown before postNormalization
code = code.replace(/~D/g, "$$");
code = code.replace(/~T/g, "~");
return code; return code;
} }

View File

@ -0,0 +1,117 @@
/*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net)
* Licensed under the MIT License (LICENSE.txt).
*
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
* Thanks to: Seamus Leahy for adding deltaX and deltaY
*
* Version: 3.1.3
*
* Requires: 1.2.2+
*/
(function (factory) {
if ( typeof define === 'function' && define.amd ) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS style for Browserify
module.exports = factory;
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
var lowestDelta, lowestDeltaXY;
if ( $.event.fixHooks ) {
for ( var i = toFix.length; i; ) {
$.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
}
}
$.event.special.mousewheel = {
setup: function() {
if ( this.addEventListener ) {
for ( var i = toBind.length; i; ) {
this.addEventListener( toBind[--i], handler, false );
}
} else {
this.onmousewheel = handler;
}
},
teardown: function() {
if ( this.removeEventListener ) {
for ( var i = toBind.length; i; ) {
this.removeEventListener( toBind[--i], handler, false );
}
} else {
this.onmousewheel = null;
}
}
};
$.fn.extend({
mousewheel: function(fn) {
return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
},
unmousewheel: function(fn) {
return this.unbind("mousewheel", fn);
}
});
function handler(event) {
var orgEvent = event || window.event,
args = [].slice.call(arguments, 1),
delta = 0,
deltaX = 0,
deltaY = 0,
absDelta = 0,
absDeltaXY = 0,
fn;
event = $.event.fix(orgEvent);
event.type = "mousewheel";
// Old school scrollwheel delta
if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
if ( orgEvent.detail ) { delta = orgEvent.detail * -1; }
// New school wheel delta (wheel event)
if ( orgEvent.deltaY ) {
deltaY = orgEvent.deltaY * -1;
delta = deltaY;
}
if ( orgEvent.deltaX ) {
deltaX = orgEvent.deltaX;
delta = deltaX * -1;
}
// Webkit
if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX * -1; }
// Look for lowest delta to normalize the delta values
absDelta = Math.abs(delta);
if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
// Get a whole value for the deltas
fn = delta > 0 ? 'floor' : 'ceil';
delta = Math[fn](delta / lowestDelta);
deltaX = Math[fn](deltaX / lowestDeltaXY);
deltaY = Math[fn](deltaY / lowestDeltaXY);
// Add event and delta to the front of the arguments
args.unshift(event, delta, deltaX, deltaY);
return ($.event.dispatch || $.event.handle).apply(this, args);
}
}));

View File

@ -26,6 +26,9 @@ requirejs.config({
'libs/jquery.waitforimages': [ 'libs/jquery.waitforimages': [
'jquery' 'jquery'
], ],
'libs/jquery.mousewheel': [
'jquery'
],
'libs/layout': [ 'libs/layout': [
'libs/jquery-ui' 'libs/jquery-ui'
], ],

View File

@ -244,12 +244,11 @@ define([
}; };
core.onReady(function() { core.onReady(function() {
var state = localStorage[PROVIDER_GDRIVE + ".state"]; var state = utils.retrieveIgnoreError(PROVIDER_GDRIVE + ".state");
if(state === undefined) { if(state === undefined) {
return; return;
} }
localStorage.removeItem(PROVIDER_GDRIVE + ".state"); localStorage.removeItem(PROVIDER_GDRIVE + ".state");
state = JSON.parse(state);
if(state.action == "create") { if(state.action == "create") {
googleHelper.upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE, settings.defaultContent, undefined, function(error, file) { googleHelper.upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE, settings.defaultContent, undefined, function(error, file) {
if(error) { if(error) {

View File

@ -28,6 +28,7 @@ define([
var importImageCallback = undefined; var importImageCallback = undefined;
var imageDoc = undefined; var imageDoc = undefined;
var importImagePreferences = utils.retrieveIgnoreError(PROVIDER_GPLUS + ".importImagePreferences");
gplusProvider.importImage = function(callback) { gplusProvider.importImage = function(callback) {
importImageCallback = callback; importImageCallback = callback;
googleHelper.picker(function(error, docs) { googleHelper.picker(function(error, docs) {
@ -46,9 +47,7 @@ define([
utils.setInputValue("#input-import-image-title", imageDoc.name); utils.setInputValue("#input-import-image-title", imageDoc.name);
// Load preferences // Load preferences
var serializedPreferences = localStorage[PROVIDER_GPLUS + ".importImagePreferences"]; if(importImagePreferences) {
if(serializedPreferences) {
var importImagePreferences = JSON.parse(serializedPreferences);
utils.setInputValue("#input-import-image-size", importImagePreferences.size); utils.setInputValue("#input-import-image-size", importImagePreferences.size);
} }
@ -67,7 +66,7 @@ define([
importImageCallback(undefined, image); importImageCallback(undefined, image);
// Store import preferences for next time // Store import preferences for next time
var importImagePreferences = {}; importImagePreferences = {};
if(size) { if(size) {
importImagePreferences.size = size; importImagePreferences.size = size;
} }

View File

@ -30,13 +30,22 @@ define([
// Retrieve publish locations from localStorage // Retrieve publish locations from localStorage
_.each(fileSystem, function(fileDesc) { _.each(fileSystem, function(fileDesc) {
_.chain(localStorage[fileDesc.fileIndex + ".publish"].split(";")).compact().each(function(publishIndex) { _.each(utils.retrieveIndexArray(fileDesc.fileIndex + ".publish"), function(publishIndex) {
var publishAttributes = JSON.parse(localStorage[publishIndex]); try {
// Store publishIndex var publishAttributes = JSON.parse(localStorage[publishIndex]);
publishAttributes.publishIndex = publishIndex; // Store publishIndex
// Replace provider ID by provider module in attributes publishAttributes.publishIndex = publishIndex;
publishAttributes.provider = providerMap[publishAttributes.provider]; // Replace provider ID by provider module in attributes
fileDesc.publishLocations[publishIndex] = publishAttributes; publishAttributes.provider = providerMap[publishAttributes.provider];
fileDesc.publishLocations[publishIndex] = publishAttributes;
}
catch(e) {
// localStorage can be corrupted
extensionMgr.onError(e);
// Remove publish location
utils.removeIndexFromArray(fileDesc.fileIndex + ".publish", publishIndex);
localStorage.removeItem(publishIndex);
}
}); });
}); });
@ -151,9 +160,8 @@ define([
$("input:radio[name=radio-publish-format][value=" + defaultPublishFormat + "]").prop("checked", true); $("input:radio[name=radio-publish-format][value=" + defaultPublishFormat + "]").prop("checked", true);
// Load preferences // Load preferences
var serializedPreferences = localStorage[provider.providerId + ".publishPreferences"]; var publishPreferences = utils.retrieveIgnoreError(provider.providerId + ".publishPreferences");
if(serializedPreferences) { if(publishPreferences) {
var publishPreferences = JSON.parse(serializedPreferences);
_.each(provider.publishPreferencesInputIds, function(inputId) { _.each(provider.publishPreferencesInputIds, function(inputId) {
utils.setInputValue("#input-publish-" + inputId, publishPreferences[inputId]); utils.setInputValue("#input-publish-" + inputId, publishPreferences[inputId]);
}); });
@ -195,18 +203,6 @@ define([
localStorage[provider.providerId + ".publishPreferences"] = JSON.stringify(publishPreferences); localStorage[provider.providerId + ".publishPreferences"] = JSON.stringify(publishPreferences);
} }
// Retrieve file's publish locations from localStorage
publisher.populatePublishLocations = function(fileDesc) {
_.chain(localStorage[fileDesc.fileIndex + ".publish"].split(";")).compact().each(function(publishIndex) {
var publishAttributes = JSON.parse(localStorage[publishIndex]);
// Store publishIndex
publishAttributes.publishIndex = publishIndex;
// Replace provider ID by provider module in attributes
publishAttributes.provider = providerMap[publishAttributes.provider];
fileDesc.publishLocations[publishIndex] = publishAttributes;
});
};
core.onReady(function() { core.onReady(function() {
// Add every provider // Add every provider
var publishMenu = $("#publish-menu"); var publishMenu = $("#publish-menu");

View File

@ -26,9 +26,12 @@ define([
extensionSettings: {} extensionSettings: {}
}; };
if(_.has(localStorage, "settings")) { try {
_.extend(settings, JSON.parse(localStorage.settings)); _.extend(settings, JSON.parse(localStorage.settings));
} }
catch(e) {
// Ignore parsing error
}
return settings; return settings;
}); });

View File

@ -1,13 +1,10 @@
// Setup an empty localStorage or upgrade an existing one // Setup an empty localStorage or upgrade an existing one
define([ define([
"underscore" "underscore",
], function(_) { "utils"
], function(_, utils) {
// Create the file system if not exist var fileIndexList = utils.retrieveIndexArray("file.list");
if(localStorage["file.list"] === undefined) {
localStorage["file.list"] = ";";
}
var fileIndexList = _.compact(localStorage["file.list"].split(";"));
// localStorage versioning // localStorage versioning
var version = localStorage["version"]; var version = localStorage["version"];
@ -22,7 +19,7 @@ define([
_.each(fileIndexList, function(fileIndex) { _.each(fileIndexList, function(fileIndex) {
localStorage[fileIndex + ".publish"] = ";"; localStorage[fileIndex + ".publish"] = ";";
var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); var syncIndexList = utils.retrieveIndexArray(fileIndex + ".sync");
_.each(syncIndexList, function(syncIndex) { _.each(syncIndexList, function(syncIndex) {
localStorage[syncIndex + ".contentCRC"] = "0"; localStorage[syncIndex + ".contentCRC"] = "0";
// We store title CRC only for Google Drive synchronization // We store title CRC only for Google Drive synchronization
@ -52,7 +49,7 @@ define([
var SYNC_PROVIDER_GDRIVE = "sync." + PROVIDER_GDRIVE + "."; var SYNC_PROVIDER_GDRIVE = "sync." + PROVIDER_GDRIVE + ".";
var SYNC_PROVIDER_DROPBOX = "sync." + PROVIDER_DROPBOX + "."; var SYNC_PROVIDER_DROPBOX = "sync." + PROVIDER_DROPBOX + ".";
_.each(fileIndexList, function(fileIndex) { _.each(fileIndexList, function(fileIndex) {
var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); var syncIndexList = utils.retrieveIndexArray(fileIndex + ".sync");
_.each(syncIndexList, function(syncIndex) { _.each(syncIndexList, function(syncIndex) {
var syncAttributes = {}; var syncAttributes = {};
if(syncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { if(syncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
@ -85,7 +82,7 @@ define([
localStorage.removeItem(fileIndex + ".title"); localStorage.removeItem(fileIndex + ".title");
localStorage.removeItem(fileIndex + ".publish"); localStorage.removeItem(fileIndex + ".publish");
localStorage.removeItem(fileIndex + ".content"); localStorage.removeItem(fileIndex + ".content");
localStorage["file.list"] = localStorage["file.list"].replace(";" + fileIndex + ";", ";"); utils.removeIndexFromArray("file.list", fileIndex);
} }
}); });
version = "v3"; version = "v3";
@ -110,7 +107,7 @@ define([
// Upgrade from v5 to v6 // Upgrade from v5 to v6
if(version == "v5") { if(version == "v5") {
_.each(fileIndexList, function(fileIndex) { _.each(fileIndexList, function(fileIndex) {
var publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";")); var publishIndexList = utils.retrieveIndexArray(fileIndex + ".publish");
_.each(publishIndexList, function(publishIndex) { _.each(publishIndexList, function(publishIndex) {
var publishAttributes = JSON.parse(localStorage[publishIndex]); var publishAttributes = JSON.parse(localStorage[publishIndex]);
if(publishAttributes.provider == "gdrive") { if(publishAttributes.provider == "gdrive") {

View File

@ -22,13 +22,22 @@ define([
// Retrieve sync locations from localStorage // Retrieve sync locations from localStorage
_.each(fileSystem, function(fileDesc) { _.each(fileSystem, function(fileDesc) {
_.chain(localStorage[fileDesc.fileIndex + ".sync"].split(";")).compact().each(function(syncIndex) { _.each(utils.retrieveIndexArray(fileDesc.fileIndex + ".sync"), function(syncIndex) {
var syncAttributes = JSON.parse(localStorage[syncIndex]); try {
// Store syncIndex var syncAttributes = JSON.parse(localStorage[syncIndex]);
syncAttributes.syncIndex = syncIndex; // Store syncIndex
// Replace provider ID by provider module in attributes syncAttributes.syncIndex = syncIndex;
syncAttributes.provider = providerMap[syncAttributes.provider]; // Replace provider ID by provider module in attributes
fileDesc.syncLocations[syncIndex] = syncAttributes; syncAttributes.provider = providerMap[syncAttributes.provider];
fileDesc.syncLocations[syncIndex] = syncAttributes;
}
catch(e) {
// localStorage can be corrupted
extensionMgr.onError(e);
// Remove sync location
utils.removeIndexFromArray(fileDesc.fileIndex + ".sync", syncIndex);
localStorage.removeItem(syncIndex);
}
}); });
}); });
@ -191,9 +200,8 @@ define([
utils.resetModalInputs(); utils.resetModalInputs();
// Load preferences // Load preferences
var serializedPreferences = localStorage[provider.providerId + ".exportPreferences"]; var exportPreferences = utils.retrieveIgnoreError(provider.providerId + ".exportPreferences");
if(serializedPreferences) { if(exportPreferences) {
var exportPreferences = JSON.parse(serializedPreferences);
_.each(provider.exportPreferencesInputIds, function(inputId) { _.each(provider.exportPreferencesInputIds, function(inputId) {
utils.setInputValue("#input-sync-export-" + inputId, exportPreferences[inputId]); utils.setInputValue("#input-sync-export-" + inputId, exportPreferences[inputId]);
}); });

View File

@ -196,7 +196,7 @@ define([
}; };
utils.updateCurrentTime(); utils.updateCurrentTime();
// Serialize sync/publish attributes and store it in the fileStorage // Serialize sync/publish attributes and store it in the localStorage
utils.storeAttributes = function(attributes) { utils.storeAttributes = function(attributes) {
var storeIndex = attributes.syncIndex || attributes.publishIndex; var storeIndex = attributes.syncIndex || attributes.publishIndex;
// Don't store sync/publish index // Don't store sync/publish index
@ -206,6 +206,37 @@ define([
localStorage[storeIndex] = JSON.stringify(attributes); localStorage[storeIndex] = JSON.stringify(attributes);
}; };
// Retrieve/parse an index array from localStorage
utils.retrieveIndexArray = function(storeIndex) {
try {
return _.compact(localStorage[storeIndex].split(";"));
}
catch(e) {
localStorage[storeIndex] = ";";
return [];
}
};
// Append an index to an array in localStorage
utils.appendIndexToArray = function(storeIndex, index) {
localStorage[storeIndex] += index + ";";
};
// Remove an index from an array in localStorage
utils.removeIndexFromArray = function(storeIndex, index) {
localStorage[storeIndex] = localStorage[storeIndex].replace(";" + index + ";", ";");
};
// Retrieve/parse an object from localStorage. Returns undefined if error.
utils.retrieveIgnoreError = function(storeIndex) {
try {
return JSON.parse(localStorage[storeIndex]);
}
catch(e) {
return undefined;
}
};
// Base64 conversion // Base64 conversion
utils.encodeBase64 = function(str) { utils.encodeBase64 = function(str) {
if(str.length === 0) { if(str.length === 0) {