Import files from disk
This commit is contained in:
parent
d334dfe2f8
commit
e5cc6e907b
192
js/classes/AsyncTask.js
Normal file
192
js/classes/AsyncTask.js
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
define([
|
||||||
|
"underscore",
|
||||||
|
"core",
|
||||||
|
"utils",
|
||||||
|
"extensionMgr",
|
||||||
|
"config",
|
||||||
|
"libs/stacktrace",
|
||||||
|
], function(_, core, utils, extensionMgr) {
|
||||||
|
|
||||||
|
var taskQueue = [];
|
||||||
|
|
||||||
|
function AsyncTask() {
|
||||||
|
this.finished = false;
|
||||||
|
this.timeout = ASYNC_TASK_DEFAULT_TIMEOUT;
|
||||||
|
this.retryCounter = 0;
|
||||||
|
this.callPath = [];
|
||||||
|
this.runCallbacks = [];
|
||||||
|
this.successCallbacks = [];
|
||||||
|
this.errorCallbacks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onRun callbacks are called by chain(). These callbacks have to call
|
||||||
|
* chain() themselves to chain with next onRun callback or error() to
|
||||||
|
* throw an exception or retry() to restart the task.
|
||||||
|
*/
|
||||||
|
AsyncTask.prototype.onRun = function(callback) {
|
||||||
|
this.runCallbacks.push(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onSuccess callbacks are called when every onRun callbacks have
|
||||||
|
* succeed.
|
||||||
|
*/
|
||||||
|
AsyncTask.prototype.onSuccess = function(callback) {
|
||||||
|
this.successCallbacks.push(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onError callbacks are called when error() is called in a onRun
|
||||||
|
* callback.
|
||||||
|
*/
|
||||||
|
AsyncTask.prototype.onError = function(callback) {
|
||||||
|
this.errorCallbacks.push(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* chain() calls the next onRun callback or the onSuccess callbacks when
|
||||||
|
* finished. The optional callback parameter can be used to pass an
|
||||||
|
* onRun callback during execution, bypassing the onRun queue.
|
||||||
|
*/
|
||||||
|
AsyncTask.prototype.chain = function(callback) {
|
||||||
|
this.callPath.unshift(printStackTrace()[5]);
|
||||||
|
if(this.finished === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If first execution
|
||||||
|
if(this.queue === undefined) {
|
||||||
|
// Create a copy of the onRun callbacks
|
||||||
|
this.queue = this.runCallbacks.slice();
|
||||||
|
}
|
||||||
|
// If a callback is passed as a parameter
|
||||||
|
if(callback !== undefined) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If all callbacks have been run
|
||||||
|
if(this.queue.length === 0) {
|
||||||
|
// Run the onSuccess callbacks
|
||||||
|
runSafe(this, this.successCallbacks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Run the next callback
|
||||||
|
var runCallback = this.queue.shift();
|
||||||
|
runCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* error() calls the onError callbacks passing the error parameter and
|
||||||
|
* ends the task by throwing an exception.
|
||||||
|
*/
|
||||||
|
AsyncTask.prototype.error = function(error) {
|
||||||
|
this.callPath.unshift(printStackTrace()[5]);
|
||||||
|
if(this.finished === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
error = error || new Error("Unknown error|\n" + this.callPath.join("\n"));
|
||||||
|
if(error.message) {
|
||||||
|
extensionMgr.onError(error);
|
||||||
|
}
|
||||||
|
runSafe(this, this.errorCallbacks, error);
|
||||||
|
// Exit the current call stack
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retry() can be called in an onRun callback to restart the task
|
||||||
|
*/
|
||||||
|
AsyncTask.prototype.retry = function(error, maxRetryCounter) {
|
||||||
|
if(this.finished === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
maxRetryCounter = maxRetryCounter || 5;
|
||||||
|
this.queue = undefined;
|
||||||
|
if(this.retryCounter >= maxRetryCounter) {
|
||||||
|
this.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Implement an exponential backoff
|
||||||
|
var delay = Math.pow(2, this.retryCounter++) * 1000;
|
||||||
|
currentTaskStartTime = utils.currentTime + delay;
|
||||||
|
currentTaskRunning = false;
|
||||||
|
this.callPath = [];
|
||||||
|
runTask();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* enqueue() has to be called to add the task in the running task queue
|
||||||
|
*/
|
||||||
|
AsyncTask.prototype.enqueue = function() {
|
||||||
|
taskQueue.push(this);
|
||||||
|
runTask();
|
||||||
|
};
|
||||||
|
|
||||||
|
var asyncRunning = false;
|
||||||
|
var currentTask = undefined;
|
||||||
|
var currentTaskRunning = false;
|
||||||
|
var currentTaskStartTime = 0;
|
||||||
|
|
||||||
|
// Run the next task in the queue if any and no other running
|
||||||
|
function runTask() {
|
||||||
|
// Use defer to avoid stack overflow
|
||||||
|
_.defer(function() {
|
||||||
|
|
||||||
|
// If there is a task currently running
|
||||||
|
if(currentTaskRunning === true) {
|
||||||
|
// If the current task takes too long
|
||||||
|
if(currentTaskStartTime + currentTask.timeout < utils.currentTime) {
|
||||||
|
currentTask.error(new Error("A timeout occurred.|\n" + currentTask.callPath.join("\n")));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currentTask === undefined) {
|
||||||
|
// If no task in the queue
|
||||||
|
if(taskQueue.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dequeue an enqueued task
|
||||||
|
currentTask = taskQueue.shift();
|
||||||
|
currentTaskStartTime = utils.currentTime;
|
||||||
|
if(asyncRunning === false) {
|
||||||
|
asyncRunning = true;
|
||||||
|
extensionMgr.onAsyncRunning(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the task
|
||||||
|
if(currentTaskStartTime <= utils.currentTime) {
|
||||||
|
currentTaskRunning = true;
|
||||||
|
currentTask.chain();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Run runTask function periodically
|
||||||
|
core.addPeriodicCallback(runTask);
|
||||||
|
|
||||||
|
function runSafe(task, callbacks, param) {
|
||||||
|
try {
|
||||||
|
_.each(callbacks, function(callback) {
|
||||||
|
callback(param);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
task.finished = true;
|
||||||
|
if(currentTask === task) {
|
||||||
|
currentTask = undefined;
|
||||||
|
currentTaskRunning = false;
|
||||||
|
}
|
||||||
|
if(taskQueue.length === 0) {
|
||||||
|
asyncRunning = false;
|
||||||
|
extensionMgr.onAsyncRunning(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
runTask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsyncTask;
|
||||||
|
});
|
102
js/classes/FileDescriptor.js
Normal file
102
js/classes/FileDescriptor.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
define(["utils"], function(utils) {
|
||||||
|
|
||||||
|
function FileDescriptor(fileIndex, title, syncLocations, publishLocations) {
|
||||||
|
this.fileIndex = fileIndex;
|
||||||
|
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._selectTime = parseInt(localStorage[fileIndex + ".selectTime"]) || 0;
|
||||||
|
this.syncLocations = syncLocations || {};
|
||||||
|
this.publishLocations = publishLocations || {};
|
||||||
|
Object.defineProperty(this, 'title', {
|
||||||
|
get: function() {
|
||||||
|
return this._title;
|
||||||
|
},
|
||||||
|
set: function(title) {
|
||||||
|
this._title = title;
|
||||||
|
localStorage[this.fileIndex + ".title"] = title;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, 'content', {
|
||||||
|
get: function() {
|
||||||
|
return localStorage[this.fileIndex + ".content"];
|
||||||
|
},
|
||||||
|
set: function(content) {
|
||||||
|
localStorage[this.fileIndex + ".content"] = content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, 'editorScrollTop', {
|
||||||
|
get: function() {
|
||||||
|
return this._editorScrollTop;
|
||||||
|
},
|
||||||
|
set: function(editorScrollTop) {
|
||||||
|
this._editorScrollTop = editorScrollTop;
|
||||||
|
localStorage[this.fileIndex + ".editorScrollTop"] = editorScrollTop;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, 'editorStart', {
|
||||||
|
get: function() {
|
||||||
|
return this._editorStart;
|
||||||
|
},
|
||||||
|
set: function(editorStart) {
|
||||||
|
this._editorStart = editorStart;
|
||||||
|
localStorage[this.fileIndex + ".editorStart"] = editorStart;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, 'editorEnd', {
|
||||||
|
get: function() {
|
||||||
|
return this._editorEnd;
|
||||||
|
},
|
||||||
|
set: function(editorEnd) {
|
||||||
|
this._editorEnd = editorEnd;
|
||||||
|
localStorage[this.fileIndex + ".editorEnd"] = editorEnd;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, 'previewScrollTop', {
|
||||||
|
get: function() {
|
||||||
|
return this._previewScrollTop;
|
||||||
|
},
|
||||||
|
set: function(previewScrollTop) {
|
||||||
|
this._previewScrollTop = previewScrollTop;
|
||||||
|
localStorage[this.fileIndex + ".previewScrollTop"] = previewScrollTop;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, 'selectTime', {
|
||||||
|
get: function() {
|
||||||
|
return this._selectTime;
|
||||||
|
},
|
||||||
|
set: function(selectTime) {
|
||||||
|
this._selectTime = selectTime;
|
||||||
|
localStorage[this.fileIndex + ".selectTime"] = selectTime;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
FileDescriptor.prototype.addSyncLocation = function(syncAttributes) {
|
||||||
|
utils.storeAttributes(syncAttributes);
|
||||||
|
utils.appendIndexToArray(this.fileIndex + ".sync", syncAttributes.syncIndex);
|
||||||
|
this.syncLocations[syncAttributes.syncIndex] = syncAttributes;
|
||||||
|
};
|
||||||
|
|
||||||
|
FileDescriptor.prototype.removeSyncLocation = function(syncAttributes) {
|
||||||
|
utils.removeIndexFromArray(this.fileIndex + ".sync", syncAttributes.syncIndex);
|
||||||
|
delete this.syncLocations[syncAttributes.syncIndex];
|
||||||
|
localStorage.removeItem(syncAttributes.syncIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
FileDescriptor.prototype.addPublishLocation = function(publishAttributes) {
|
||||||
|
utils.storeAttributes(publishAttributes);
|
||||||
|
utils.appendIndexToArray(this.fileIndex + ".publish", publishAttributes.publishIndex);
|
||||||
|
this.publishLocations[publishAttributes.publishIndex] = publishAttributes;
|
||||||
|
};
|
||||||
|
|
||||||
|
FileDescriptor.prototype.removePublishLocation = function(publishAttributes) {
|
||||||
|
utils.removeIndexFromArray(this.fileIndex + ".publish", publishAttributes.publishIndex);
|
||||||
|
delete this.publishLocations[publishAttributes.publishIndex];
|
||||||
|
localStorage.removeItem(publishAttributes.publishIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
return FileDescriptor;
|
||||||
|
});
|
94
js/extensions/dialogOpenHarddrive.js
Normal file
94
js/extensions/dialogOpenHarddrive.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
define([
|
||||||
|
"jquery",
|
||||||
|
"underscore",
|
||||||
|
"toMarkdown",
|
||||||
|
"config",
|
||||||
|
], function($, _, toMarkdown) {
|
||||||
|
|
||||||
|
var dialogOpenHarddrive = {
|
||||||
|
extensionId: "dialogOpenHarddrive",
|
||||||
|
extensionName: 'Dialog "Open from"',
|
||||||
|
settingsBloc: '<p>Handles the "Open from hard drive" and the "Convert HTML to Markdown" dialog boxes.</p>'
|
||||||
|
};
|
||||||
|
|
||||||
|
var fileMgr = undefined;
|
||||||
|
dialogOpenHarddrive.onFileMgrCreated = function(fileMgrParameter) {
|
||||||
|
fileMgr = fileMgrParameter;
|
||||||
|
};
|
||||||
|
|
||||||
|
var extensionMgr = undefined;
|
||||||
|
dialogOpenHarddrive.onExtensionMgrCreated = function(extensionMgrParameter) {
|
||||||
|
extensionMgr = extensionMgrParameter;
|
||||||
|
};
|
||||||
|
|
||||||
|
var contentWrapper = undefined;
|
||||||
|
var converter = undefined;
|
||||||
|
var htmlContentWrapper = function(content) {
|
||||||
|
return converter.makeMd(content);
|
||||||
|
};
|
||||||
|
function handleFileImport(evt) {
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
|
var files = (evt.dataTransfer || evt.target).files;
|
||||||
|
$("#modal-import-harddrive-markdown, #modal-import-harddrive-html").modal("hide");
|
||||||
|
_.each(files, function(file) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = (function(importedFile) {
|
||||||
|
return function(e) {
|
||||||
|
var content = e.target.result;
|
||||||
|
if(content.match(/\uFFFD/)) {
|
||||||
|
extensionMgr.onError(importedFile.name + " is a binary file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
content = contentWrapper ? contentWrapper(content) : content;
|
||||||
|
if(content === undefined) {
|
||||||
|
extensionMgr.onError(importedFile.name + " is not a valid HTML file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var title = importedFile.name;
|
||||||
|
var dotPosition = title.lastIndexOf(".");
|
||||||
|
title = dotPosition !== -1 ? title.substring(0, dotPosition) : title;
|
||||||
|
var fileDesc = fileMgr.createFile(title, content);
|
||||||
|
fileMgr.selectFile(fileDesc);
|
||||||
|
};
|
||||||
|
})(file);
|
||||||
|
var blob = file.slice(0, IMPORT_FILE_MAX_CONTENT_SIZE);
|
||||||
|
reader.readAsText(blob);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMarkdownImport(evt) {
|
||||||
|
contentWrapper = undefined;
|
||||||
|
handleFileImport(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHtmlImport(evt) {
|
||||||
|
contentWrapper = htmlContentWrapper;
|
||||||
|
handleFileImport(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragOver(evt) {
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogOpenHarddrive.onReady = function() {
|
||||||
|
// Create toMarkdown converter
|
||||||
|
converter = new toMarkdown.converter();
|
||||||
|
|
||||||
|
$("#input-file-import-harddrive-markdown").change(handleMarkdownImport);
|
||||||
|
$('#dropzone-import-harddrive-markdown').each(function() {
|
||||||
|
this.addEventListener('dragover', handleDragOver, false);
|
||||||
|
this.addEventListener('drop', handleMarkdownImport, false);
|
||||||
|
});
|
||||||
|
$("#input-file-import-harddrive-html").change(handleHtmlImport);
|
||||||
|
$('#dropzone-import-harddrive-html').each(function() {
|
||||||
|
this.addEventListener('dragover', handleDragOver, false);
|
||||||
|
this.addEventListener('drop', handleHtmlImport, false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return dialogOpenHarddrive;
|
||||||
|
|
||||||
|
});
|
248
js/libs/to-markdown.js
Normal file
248
js/libs/to-markdown.js
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* to-markdown - an HTML to Markdown converter
|
||||||
|
*
|
||||||
|
* Copyright 2011-2012, Dom Christie
|
||||||
|
* Licenced under the MIT licence
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var root = this;
|
||||||
|
var toMarkdown = {};
|
||||||
|
var isNode = false;
|
||||||
|
|
||||||
|
if(typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = toMarkdown;
|
||||||
|
root.toMarkdown = toMarkdown;
|
||||||
|
isNode = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
root.toMarkdown = toMarkdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
toMarkdown.converter = function(options) {
|
||||||
|
|
||||||
|
if(options && options.elements && $.isArray(options.elements)) {
|
||||||
|
ELEMENTS = ELEMENTS.concat(options.elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.makeMd = function(input, callback) {
|
||||||
|
var result;
|
||||||
|
if(isNode) {
|
||||||
|
var jsdom = require('jsdom');
|
||||||
|
jsdom.env({
|
||||||
|
html: input,
|
||||||
|
scripts: [
|
||||||
|
'http://code.jquery.com/jquery-1.6.4.min.js'
|
||||||
|
],
|
||||||
|
done: function(errors, window) {
|
||||||
|
if(typeof callback === 'function') {
|
||||||
|
callback(process(input, window.$));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = process(input, $);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var process = function(input, $) {
|
||||||
|
// Escape potential ol triggers
|
||||||
|
// see bottom of lists section: http://daringfireball.net/projects/markdown/syntax#list
|
||||||
|
input = input.replace(/(\d+)\. /g, '$1\\. ');
|
||||||
|
|
||||||
|
// Wrap in containing div
|
||||||
|
var $container = $('<div/>');
|
||||||
|
var $input = $container.html(input);
|
||||||
|
|
||||||
|
// Remove whitespace
|
||||||
|
$input.find('*:not(pre, code)').contents().filter(function() {
|
||||||
|
return this.nodeType === 3 && (/^\s+$/.test(this.nodeValue));
|
||||||
|
}).remove();
|
||||||
|
|
||||||
|
var selectors = [];
|
||||||
|
for(var i = 0, len = ELEMENTS.length; i < len; i++) {
|
||||||
|
selectors.push(ELEMENTS[i].selector);
|
||||||
|
}
|
||||||
|
selectors = selectors.join(',');
|
||||||
|
|
||||||
|
while($input.find(selectors).length) {
|
||||||
|
for(var i = 0, len = ELEMENTS.length; i < len; i++) {
|
||||||
|
|
||||||
|
// Find the innermost elements containing NO children that convert to markdown
|
||||||
|
$matches = $input.find(ELEMENTS[i].selector + ':not(:has("' + selectors + '"))');
|
||||||
|
|
||||||
|
$matches.each(function(j, el) {
|
||||||
|
var $el = $(el);
|
||||||
|
$el.before(ELEMENTS[i].replacement($el.html(), $el)).remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleanUp($input.html());
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============
|
||||||
|
// = Utilities =
|
||||||
|
// =============
|
||||||
|
|
||||||
|
var trimNewLines = function(str) {
|
||||||
|
return str.replace(/^[\n\r\f]+|[\n\r\f]+$/g, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
var decodeHtmlEntities = function(str) {
|
||||||
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
};
|
||||||
|
|
||||||
|
var cleanUp = function(string) {
|
||||||
|
string = string.replace(/^[\t\r\n]+|[\t\r\n]+$/g, ''); // trim leading/trailing whitespace
|
||||||
|
string = string.replace(/\n\s+\n/g, '\n\n');
|
||||||
|
string = string.replace(/\n{3,}/g, '\n\n'); // limit consecutive linebreaks to 2
|
||||||
|
string = decodeHtmlEntities(string);
|
||||||
|
return string;
|
||||||
|
};
|
||||||
|
|
||||||
|
var strongReplacement = function(innerHTML) {
|
||||||
|
innerHTML = trimNewLines(innerHTML);
|
||||||
|
return innerHTML ? '**' + innerHTML + '**' : '';
|
||||||
|
};
|
||||||
|
var emReplacement = function(innerHTML) {
|
||||||
|
innerHTML = trimNewLines(innerHTML);
|
||||||
|
return innerHTML ? '_' + innerHTML + '_' : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============
|
||||||
|
// = Elements =
|
||||||
|
// ============
|
||||||
|
|
||||||
|
var ELEMENTS = [
|
||||||
|
{
|
||||||
|
selector: 'p',
|
||||||
|
replacement: function(innerHTML, el) {
|
||||||
|
innerHTML = $.trim(innerHTML);
|
||||||
|
return innerHTML ? '\n\n' + innerHTML + '\n\n' : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'br',
|
||||||
|
replacement: function(innerHTML, el) {
|
||||||
|
return '\n';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'h1,h2,h3,h4,h5,h6',
|
||||||
|
replacement: function(innerHTML, $el) {
|
||||||
|
innerHTML = $.trim(innerHTML);
|
||||||
|
var hLevel = $el.prop("nodeName").charAt(1),
|
||||||
|
prefix = '';
|
||||||
|
for(var i = 0; i < hLevel; i++) {
|
||||||
|
prefix += '#';
|
||||||
|
}
|
||||||
|
return innerHTML ? '\n\n' + prefix + ' ' + innerHTML + '\n\n' : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'hr',
|
||||||
|
replacement: function(innerHTML, el) {
|
||||||
|
return '\n\n* * *\n\n';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'a[href]',
|
||||||
|
replacement: function(innerHTML, $el) {
|
||||||
|
if(innerHTML) {
|
||||||
|
innerHTML = trimNewLines(innerHTML);
|
||||||
|
var href = $el.attr('href'),
|
||||||
|
title = $el.attr('title') || '';
|
||||||
|
return '[' + innerHTML + ']' + '(' + href + (title ? ' "' + title + '"' : '') + ')';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'b',
|
||||||
|
replacement: strongReplacement
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'strong',
|
||||||
|
replacement: strongReplacement
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'i',
|
||||||
|
replacement: emReplacement
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'em',
|
||||||
|
replacement: emReplacement
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'code',
|
||||||
|
replacement: function(innerHTML, el) {
|
||||||
|
innerHTML = trimNewLines(innerHTML);
|
||||||
|
return innerHTML ? '`' + innerHTML + '`' : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'img',
|
||||||
|
replacement: function(innerHTML, $el) {
|
||||||
|
var alt = $el.attr('alt') || '',
|
||||||
|
src = $el.attr('src') || '',
|
||||||
|
title = $el.attr('title') || '';
|
||||||
|
return '![' + alt + ']' + '(' + src + (title ? ' "' + title + '"' : '') + ')';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'pre',
|
||||||
|
replacement: function(innerHTML, el) {
|
||||||
|
if(/^\s*\`/.test(innerHTML)) {
|
||||||
|
innerHTML = innerHTML.replace(/\`/g, '');
|
||||||
|
return ' ' + innerHTML.replace(/\n/g, '\n ');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'li',
|
||||||
|
replacement: function(innerHTML, $el) {
|
||||||
|
innerHTML = innerHTML.replace(/^\s+|\s+$/, '').replace(/\n/gm, '\n ');
|
||||||
|
var prefix = '* ';
|
||||||
|
var suffix = '';
|
||||||
|
var $parent = $el.parent();
|
||||||
|
var $children = $parent.contents().filter(function() {
|
||||||
|
return (this.nodeType === 1 && this.nodeName === 'LI') || (this.nodeType === 3);
|
||||||
|
});
|
||||||
|
var index = $children.index($el) + 1;
|
||||||
|
|
||||||
|
prefix = $parent.is('ol') ? index + '. ' : '* ';
|
||||||
|
if(index == $children.length) {
|
||||||
|
if(!$el.parents('li').length) {
|
||||||
|
suffix = '\n';
|
||||||
|
}
|
||||||
|
innerHTML = innerHTML.replace(/\s+$/, ''); // Trim
|
||||||
|
$el.unwrap();
|
||||||
|
}
|
||||||
|
return prefix + innerHTML + suffix + '\n';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'blockquote',
|
||||||
|
replacement: function(innerHTML, el) {
|
||||||
|
innerHTML = innerHTML = $.trim(innerHTML).replace(/\n{3,}/g, '\n\n');
|
||||||
|
innerHTML = innerHTML.replace(/\n/g, '\n> ');
|
||||||
|
return "> " + innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var NON_MD_BLOCK_ELEMENTS = ['address', 'article', 'aside', 'audio', 'canvas', 'div', 'dl', 'dd', 'dt',
|
||||||
|
'fieldset', 'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'output',
|
||||||
|
'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'section', 'video'];
|
||||||
|
|
||||||
|
})();
|
Loading…
Reference in New Issue
Block a user