mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-22 03:05:19 -04:00
First commit, version 0.2.7
This commit is contained in:
parent
61eb11d23c
commit
4b0ca55eb7
1379 changed files with 173000 additions and 0 deletions
311
public/js/cover.js
Normal file
311
public/js/cover.js
Normal file
|
@ -0,0 +1,311 @@
|
|||
$(".masthead-nav li").click(function () {
|
||||
$(this).siblings().removeClass("active");
|
||||
$(this).addClass("active");
|
||||
});
|
||||
|
||||
$(".ui-home").click(function () {
|
||||
$(".section").hide();
|
||||
$("#home").fadeIn();
|
||||
});
|
||||
|
||||
$(".ui-history").click(function () {
|
||||
$(".section").hide();
|
||||
$("#history").fadeIn();
|
||||
});
|
||||
|
||||
$(".ui-releasenotes").click(function () {
|
||||
$(".section").hide();
|
||||
$("#releasenotes").fadeIn();
|
||||
});
|
||||
|
||||
function checkHistoryList() {
|
||||
if ($("#history-list").children().length > 0)
|
||||
$(".ui-nohistory").hide();
|
||||
else if ($("#history-list").children().length == 0) {
|
||||
$(".ui-nohistory").slideDown();
|
||||
var cookienotehistory = JSON.parse($.cookie('notehistory'));
|
||||
if (login && cookienotehistory && cookienotehistory.length > 0) {
|
||||
$(".ui-import-from-cookie").slideDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseHistoryCallback() {
|
||||
checkHistoryList();
|
||||
$(".ui-history-close").click(function (e) {
|
||||
e.preventDefault();
|
||||
var id = $(this).closest("a").attr("href").split('/')[1];
|
||||
getHistory(function (notehistory) {
|
||||
var newnotehistory = removeHistory(id, notehistory);
|
||||
saveHistory(newnotehistory);
|
||||
});
|
||||
$(this).closest("li").remove();
|
||||
checkHistoryList();
|
||||
});
|
||||
}
|
||||
|
||||
var login = false;
|
||||
|
||||
checkIfAuth(
|
||||
function (data) {
|
||||
$('.ui-signin').hide();
|
||||
$('.ui-or').hide();
|
||||
$('.ui-welcome').show();
|
||||
$('.ui-name').html(data.name);
|
||||
$('.ui-signout').show();
|
||||
$(".ui-history").click();
|
||||
login = true;
|
||||
},
|
||||
function () {
|
||||
$('.ui-signin').slideDown();
|
||||
$('.ui-or').slideDown();
|
||||
login = false;
|
||||
}
|
||||
);
|
||||
|
||||
parseHistory(parseHistoryCallback);
|
||||
|
||||
$(".ui-import-from-cookie").click(function () {
|
||||
saveCookieHistoryToServer(function() {
|
||||
parseCookieToHistory(parseHistoryCallback);
|
||||
$(".ui-import-from-cookie").hide();
|
||||
});
|
||||
});
|
||||
|
||||
var source = $("#template").html();
|
||||
var template = Handlebars.compile(source);
|
||||
var context = {
|
||||
release: [
|
||||
{
|
||||
version: "0.2.7",
|
||||
tag: "fuel",
|
||||
date: moment("201505031200", 'YYYYMMDDhhmm').fromNow(),
|
||||
detail: [
|
||||
{
|
||||
title: "Features",
|
||||
item: [
|
||||
"+ Support facebook, twitter, github, dropbox login",
|
||||
"+ Support own history"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Enhancements",
|
||||
item: [
|
||||
"* Adjust history ui",
|
||||
"* Upgrade realtime package",
|
||||
"* Upgrade editor package, now support composition input better"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Fixes",
|
||||
item: [
|
||||
"* Partial update might not render properly",
|
||||
"* Cursor focus might not at correct position"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "0.2.6",
|
||||
tag: "zippo",
|
||||
date: moment("201504241600", 'YYYYMMDDhhmm').fromNow(),
|
||||
detail: [
|
||||
{
|
||||
title: "Features",
|
||||
item: [
|
||||
"+ Support sync scroll",
|
||||
"+ Support partial update"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Enhancements",
|
||||
item: [
|
||||
"* Added feedback ui",
|
||||
"* Adjust animations and delays",
|
||||
"* Adjust editor viewportMargin for performance",
|
||||
"* Adjust emit refresh event occasion",
|
||||
"* Added editor fallback fonts",
|
||||
"* Index page auto focus at history if valid"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Fixes",
|
||||
item: [
|
||||
"* Server might not disconnect client properly",
|
||||
"* Resume connection might restore wrong info"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "0.2.5",
|
||||
tag: "lightning",
|
||||
date: moment("201504142110", 'YYYYMMDDhhmm').fromNow(),
|
||||
detail: [
|
||||
{
|
||||
title: "Features",
|
||||
item: [
|
||||
"+ Support import from dropbox and clipboard",
|
||||
"+ Support more code highlighting",
|
||||
"+ Support mathjax, sequence diagram and flow chart"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Enhancements",
|
||||
item: [
|
||||
"* Adjust toolbar and layout style",
|
||||
"* Adjust mobile layout style",
|
||||
"* Adjust history layout style",
|
||||
"* Server using heartbeat to gain accuracy of online users"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Fixes",
|
||||
item: [
|
||||
"* Virtual keyboard might broken the navbar",
|
||||
"* Adjust editor viewportMargin for preloading content"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "0.2.4",
|
||||
tag: "flint",
|
||||
date: moment("201504101240", 'YYYYMMDDhhmm').fromNow(),
|
||||
detail: [
|
||||
{
|
||||
title: "Features",
|
||||
item: [
|
||||
"+ Support save to dropbox",
|
||||
"+ Show other users' cursor with light color"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Enhancements",
|
||||
item: [
|
||||
"* Adjust toolbar layout style for future"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Fixes",
|
||||
item: [
|
||||
"* Title might not render properly",
|
||||
"* Code border style might not show properly",
|
||||
"* Server might not connect concurrent client properly"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "0.2.3",
|
||||
tag: "light",
|
||||
date: moment("201504062030", 'YYYYMMDDhhmm').fromNow(),
|
||||
detail: [
|
||||
{
|
||||
title: "Features",
|
||||
item: [
|
||||
"+ Support youtube, vimeo",
|
||||
"+ Support gist",
|
||||
"+ Added quick link in pretty",
|
||||
"+ Added font-smoothing style"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Enhancements",
|
||||
item: [
|
||||
"* Change the rendering engine to remarkable",
|
||||
"* Adjust view, todo list layout style for UX",
|
||||
"+ Added responsive layout check",
|
||||
"+ Auto reload if client version mismatch",
|
||||
"+ Keep history stack after reconnect if nothing changed",
|
||||
"+ Added features page"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Fixes",
|
||||
item: [
|
||||
"* Closetags auto input might not have proper origin",
|
||||
"* Autofocus on editor only if it's on desktop",
|
||||
"+ Prevent using real script and iframe tags",
|
||||
"* Sorting in history by time not percise"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "0.2.2",
|
||||
tag: "fire",
|
||||
date: moment("201503272110", 'YYYYMMDDhhmm').fromNow(),
|
||||
detail: [
|
||||
{
|
||||
title: "Features",
|
||||
item: [
|
||||
"+ Support smartLists, smartypants",
|
||||
"+ Support line number on code block",
|
||||
"+ Support tags and search or sort history"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Enhancements",
|
||||
item: [
|
||||
"+ Added delay on socket change",
|
||||
"+ Updated markdown-body width to match github style",
|
||||
"+ Socket changes now won't add to editor's history",
|
||||
"* Reduce redundant server events"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Fixes",
|
||||
item: [
|
||||
"* Toolbar links might get wrong",
|
||||
"* Wrong action redirections"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "0.2.1",
|
||||
tag: "spark",
|
||||
date: moment("201503171340", 'YYYYMMDDhhmm').fromNow(),
|
||||
detail: [
|
||||
{
|
||||
title: "Features",
|
||||
item: [
|
||||
"+ Support github-like todo-list",
|
||||
"+ Support emoji"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Enhancements",
|
||||
item: [
|
||||
"+ Added more effects on transition",
|
||||
"+ Reduced rendering delay",
|
||||
"+ Auto close and match brackets",
|
||||
"+ Auto close and match tags",
|
||||
"+ Added code fold and fold gutters",
|
||||
"+ Added continue listing of markdown"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "0.2.0",
|
||||
tag: "launch-day",
|
||||
date: moment("201503142020", 'YYYYMMDDhhmm').fromNow(),
|
||||
detail: [
|
||||
{
|
||||
title: "Features",
|
||||
item: [
|
||||
"+ Markdown editor",
|
||||
"+ Preview html",
|
||||
"+ Realtime collaborate",
|
||||
"+ Cross-platformed",
|
||||
"+ Recently used history"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
var html = template(context);
|
||||
$("#releasenotes").html(html);
|
278
public/js/extra.js
Normal file
278
public/js/extra.js
Normal file
|
@ -0,0 +1,278 @@
|
|||
//get title
|
||||
function getTitle(view) {
|
||||
var h1s = view.find("h1");
|
||||
var title = "";
|
||||
if (h1s.length > 0) {
|
||||
title = h1s.first().text();
|
||||
} else {
|
||||
title = null;
|
||||
}
|
||||
return title;
|
||||
}
|
||||
//render title
|
||||
function renderTitle(view) {
|
||||
var title = getTitle(view);
|
||||
if (title) {
|
||||
title += ' - HackMD';
|
||||
} else {
|
||||
title = 'HackMD - Collaborative notes';
|
||||
}
|
||||
return title;
|
||||
}
|
||||
//render filename
|
||||
function renderFilename(view) {
|
||||
var filename = getTitle(view);
|
||||
if (!filename) {
|
||||
filename = 'Untitled';
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
//dynamic event or object binding here
|
||||
function finishView(view) {
|
||||
//youtube
|
||||
view.find(".youtube").click(function () {
|
||||
imgPlayiframe(this, '//www.youtube.com/embed/');
|
||||
});
|
||||
//vimeo
|
||||
view.find(".vimeo")
|
||||
.click(function () {
|
||||
imgPlayiframe(this, '//player.vimeo.com/video/');
|
||||
})
|
||||
.each(function (key, value) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'http://vimeo.com/api/v2/video/' + $(value).attr('videoid') + '.json',
|
||||
jsonp: 'callback',
|
||||
dataType: 'jsonp',
|
||||
success: function (data) {
|
||||
var thumbnail_src = data[0].thumbnail_large;
|
||||
$(value).css('background-image', 'url(' + thumbnail_src + ')');
|
||||
}
|
||||
});
|
||||
});
|
||||
//gist
|
||||
view.find("code[data-gist-id]").each(function(key, value) {
|
||||
if($(value).children().length == 0)
|
||||
$(value).gist();
|
||||
});
|
||||
//emojify
|
||||
emojify.run(view[0]);
|
||||
//mathjax
|
||||
var mathjaxdivs = view.find('.mathjax').toArray();
|
||||
try {
|
||||
for (var i = 0; i < mathjaxdivs.length; i++) {
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs[i].innerHTML]);
|
||||
$(mathjaxdivs[i]).removeClass("mathjax");
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
//sequence diagram
|
||||
var sequence = view.find(".sequence-diagram");
|
||||
try {
|
||||
sequence.sequenceDiagram({
|
||||
theme: 'simple'
|
||||
});
|
||||
sequence.parent().parent().replaceWith(sequence);
|
||||
sequence.removeClass("sequence-diagram");
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
//flowchart
|
||||
var flow = view.find(".flow-chart");
|
||||
flow.each(function (key, value) {
|
||||
try {
|
||||
var chart = flowchart.parse($(value).text());
|
||||
$(value).html('');
|
||||
chart.drawSVG(value, {
|
||||
'line-width': 2,
|
||||
'fill': 'none',
|
||||
'font-size': '16px',
|
||||
'font-family': "'Andale Mono', monospace"
|
||||
});
|
||||
$(value).parent().parent().replaceWith(value);
|
||||
$(value).removeClass("flow-chart");
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
//render title
|
||||
document.title = renderTitle(view);
|
||||
}
|
||||
//only static transform should be here
|
||||
function postProcess(code) {
|
||||
var result = $('<div>' + code + '</div>');
|
||||
//prevent XSS
|
||||
result.find("script").replaceWith(function () {
|
||||
return "<noscript>" + $(this).html() + "</noscript>"
|
||||
});
|
||||
result.find("iframe").replaceWith(function () {
|
||||
return "<noiframe>" + $(this).html() + "</noiframe>"
|
||||
});
|
||||
//todo list
|
||||
var lis = result[0].getElementsByTagName('li');
|
||||
for (var i = 0; i < lis.length; i++) {
|
||||
var html = lis[i].innerHTML;
|
||||
if (/^\s*\[[x ]\]\s+/.test(html)) {
|
||||
lis[i].innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled>')
|
||||
.replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked disabled>');
|
||||
lis[i].setAttribute('class', 'task-list-item');
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function setSizebyAttr(element, target) {
|
||||
var width = $(element).attr("width") ? $(element).attr("width") : '100%';
|
||||
var height = $(element).attr("height") ? $(element).attr("height") : '360px';
|
||||
$(target).width(width);
|
||||
$(target).height(height);
|
||||
}
|
||||
|
||||
function imgPlayiframe(element, src) {
|
||||
if (!$(element).attr("videoid")) return;
|
||||
var iframe = $("<iframe frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>");
|
||||
$(iframe).attr("src", src + $(element).attr("videoid") + '?autoplay=1');
|
||||
setSizebyAttr(element, iframe);
|
||||
$(element).html(iframe);
|
||||
}
|
||||
|
||||
var anchorForId = function (id) {
|
||||
var anchor = document.createElement("a");
|
||||
anchor.className = "header-link";
|
||||
anchor.href = "#" + id;
|
||||
anchor.innerHTML = "<span class=\"sr-only\">Permalink</span><i class=\"fa fa-link\"></i>";
|
||||
anchor.title = "Permalink";
|
||||
return anchor;
|
||||
};
|
||||
|
||||
var linkifyAnchors = function (level, containingElement) {
|
||||
var headers = containingElement.getElementsByTagName("h" + level);
|
||||
for (var h = 0; h < headers.length; h++) {
|
||||
var header = headers[h];
|
||||
|
||||
if (typeof header.id == "undefined" || header.id == "") {
|
||||
var id = S(header.innerHTML.toLowerCase()).trim().stripTags().dasherize().s;
|
||||
header.id = encodeURIComponent(id);
|
||||
}
|
||||
header.appendChild(anchorForId(header.id));
|
||||
}
|
||||
};
|
||||
|
||||
function autoLinkify(view) {
|
||||
var contentBlock = view[0];
|
||||
if (!contentBlock) {
|
||||
return;
|
||||
}
|
||||
for (var level = 1; level <= 6; level++) {
|
||||
linkifyAnchors(level, contentBlock);
|
||||
}
|
||||
};
|
||||
|
||||
function scrollToHash() {
|
||||
var hash = location.hash;
|
||||
location.hash = "";
|
||||
location.hash = hash;
|
||||
}
|
||||
|
||||
function highlightRender(code, lang) {
|
||||
if (!lang || /no(-?)highlight|plain|text/.test(lang))
|
||||
return;
|
||||
if(lang == 'sequence') {
|
||||
return '<div class="sequence-diagram">' + code + '</div>';
|
||||
} else if(lang == 'flow') {
|
||||
return '<div class="flow-chart">' + code + '</div>';
|
||||
}
|
||||
var reallang = lang.replace('=', '');
|
||||
var languages = hljs.listLanguages();
|
||||
if (languages.indexOf(reallang) == -1) {
|
||||
var result = hljs.highlightAuto(code);
|
||||
} else {
|
||||
var result = hljs.highlight(reallang, code);
|
||||
}
|
||||
if (/\=$/.test(lang)) {
|
||||
var lines = result.value.split('\n');
|
||||
var linenumbers = [];
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
linenumbers[i] = "<div class='linenumber'>" + (i + 1) + "</div>";
|
||||
}
|
||||
var linegutter = "<div class='gutter'>" + linenumbers.join('\n') + "</div>";
|
||||
result.value = "<div class='wrapper'>" + linegutter + "<div class='code'>" + result.value + "</div></div>";
|
||||
}
|
||||
return result.value;
|
||||
}
|
||||
|
||||
emojify.setConfig({
|
||||
img_dir: '/vendor/emojify/images',
|
||||
ignore_emoticons: true
|
||||
});
|
||||
|
||||
var md = new Remarkable('full', {
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
highlight: highlightRender
|
||||
});
|
||||
//youtube
|
||||
var youtubePlugin = new Plugin(
|
||||
// regexp to match
|
||||
/{%youtube\s*([\d\D]*?)\s*%}/,
|
||||
|
||||
// this function will be called when something matches
|
||||
function (match, utils) {
|
||||
var videoid = match[1];
|
||||
if (!videoid) return;
|
||||
var div = $('<div class="youtube"></div>');
|
||||
setSizebyAttr(div, div);
|
||||
div.attr('videoid', videoid);
|
||||
var icon = '<i class="icon fa fa-youtube-play fa-5x"></i>';
|
||||
div.append(icon);
|
||||
var thumbnail_src = '//img.youtube.com/vi/' + videoid + '/hqdefault.jpg';
|
||||
div.css('background-image', 'url(' + thumbnail_src + ')');
|
||||
return div[0].outerHTML;
|
||||
}
|
||||
);
|
||||
//vimeo
|
||||
var vimeoPlugin = new Plugin(
|
||||
// regexp to match
|
||||
/{%vimeo\s*([\d\D]*?)\s*%}/,
|
||||
|
||||
// this function will be called when something matches
|
||||
function (match, utils) {
|
||||
var videoid = match[1];
|
||||
if (!videoid) return;
|
||||
var div = $('<div class="vimeo"></div>');
|
||||
setSizebyAttr(div, div);
|
||||
div.attr('videoid', videoid);
|
||||
var icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>';
|
||||
div.append(icon);
|
||||
return div[0].outerHTML;
|
||||
}
|
||||
);
|
||||
//gist
|
||||
var gistPlugin = new Plugin(
|
||||
// regexp to match
|
||||
/{%gist\s*([\d\D]*?)\s*%}/,
|
||||
|
||||
// this function will be called when something matches
|
||||
function (match, utils) {
|
||||
var gistid = match[1];
|
||||
var code = '<code data-gist-id="' + gistid + '"/>';
|
||||
return code;
|
||||
}
|
||||
);
|
||||
//mathjax
|
||||
var mathjaxPlugin = new Plugin(
|
||||
// regexp to match
|
||||
/^\$\$\n([\d\D]*?)\n\$\$$|\$([\d\D]*?)\$/,
|
||||
|
||||
// this function will be called when something matches
|
||||
function (match, utils) {
|
||||
//var code = $(match).text();
|
||||
return '<span class="mathjax">' + match[0] + '</span>';
|
||||
}
|
||||
);
|
||||
md.use(youtubePlugin);
|
||||
md.use(vimeoPlugin);
|
||||
md.use(gistPlugin);
|
||||
md.use(mathjaxPlugin);
|
14
public/js/ga.js
Normal file
14
public/js/ga.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
ga('create', 'get your self one', 'auto');
|
||||
ga('send', 'pageview');
|
256
public/js/history.js
Normal file
256
public/js/history.js
Normal file
|
@ -0,0 +1,256 @@
|
|||
//common
|
||||
function checkIfAuth(yesCallback, noCallback) {
|
||||
$.get('/me')
|
||||
.done(function (data) {
|
||||
if (data && data.status == 'ok') {
|
||||
yesCallback(data);
|
||||
} else {
|
||||
noCallback();
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
noCallback();
|
||||
});
|
||||
}
|
||||
|
||||
function saveHistory(notehistory) {
|
||||
checkIfAuth(
|
||||
function () {
|
||||
saveHistoryToServer(notehistory);
|
||||
},
|
||||
function () {
|
||||
saveHistoryToCookie(notehistory);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function saveHistoryToCookie(notehistory) {
|
||||
$.cookie('notehistory', JSON.stringify(notehistory), {
|
||||
expires: 365
|
||||
});
|
||||
}
|
||||
|
||||
function saveHistoryToServer(notehistory) {
|
||||
$.post('/history', {
|
||||
history: JSON.stringify(notehistory)
|
||||
});
|
||||
}
|
||||
|
||||
function saveCookieHistoryToServer(callback) {
|
||||
$.post('/history', {
|
||||
history: $.cookie('notehistory')
|
||||
})
|
||||
.done(function (data) {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function clearDuplicatedHistory(notehistory) {
|
||||
var newnotehistory = [];
|
||||
for (var i = 0; i < notehistory.length; i++) {
|
||||
var found = false;
|
||||
for (var j = 0; j < newnotehistory.length; j++) {
|
||||
if (notehistory[i].id == newnotehistory[j].id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
newnotehistory.push(notehistory[i]);
|
||||
}
|
||||
return notehistory;
|
||||
}
|
||||
|
||||
function addHistory(id, text, time, tags, notehistory) {
|
||||
notehistory.push({
|
||||
id: id,
|
||||
text: text,
|
||||
time: time,
|
||||
tags: tags
|
||||
});
|
||||
return notehistory;
|
||||
}
|
||||
|
||||
function removeHistory(id, notehistory) {
|
||||
for (var i = 0; i < notehistory.length; i++) {
|
||||
if (notehistory[i].id == id)
|
||||
notehistory.splice(i, 1);
|
||||
}
|
||||
return notehistory;
|
||||
}
|
||||
|
||||
//used for inner
|
||||
function writeHistory(view) {
|
||||
checkIfAuth(
|
||||
function () {
|
||||
writeHistoryToServer(view);
|
||||
},
|
||||
function () {
|
||||
writeHistoryToCookie(view);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function writeHistoryToServer(view) {
|
||||
$.get('/history')
|
||||
.done(function (data) {
|
||||
try {
|
||||
if (data.history) {
|
||||
var notehistory = data.history;
|
||||
} else {
|
||||
var notehistory = [];
|
||||
}
|
||||
} catch (err) {
|
||||
var notehistory = [];
|
||||
}
|
||||
var newnotehistory = generateHistory(view, notehistory);
|
||||
saveHistoryToServer(newnotehistory);
|
||||
})
|
||||
.fail(function () {
|
||||
writeHistoryToCookie(view);
|
||||
});
|
||||
}
|
||||
|
||||
function writeHistoryToCookie(view) {
|
||||
try {
|
||||
var notehistory = JSON.parse($.cookie('notehistory'));
|
||||
} catch (err) {
|
||||
var notehistory = [];
|
||||
}
|
||||
|
||||
var newnotehistory = generateHistory(view, notehistory);
|
||||
saveHistoryToCookie(newnotehistory);
|
||||
}
|
||||
|
||||
function renderHistory(view) {
|
||||
var title = renderFilename(view);
|
||||
|
||||
var tags = [];
|
||||
var rawtags = [];
|
||||
view.find('h6').each(function (key, value) {
|
||||
if (/^tags/gmi.test($(value).text())) {
|
||||
var codes = $(value).find("code");
|
||||
for (var i = 0; i < codes.length; i++)
|
||||
rawtags.push(codes[i]);
|
||||
}
|
||||
});
|
||||
for (var i = 0; i < rawtags.length; i++) {
|
||||
var found = false;
|
||||
for (var j = 0; j < tags.length; j++) {
|
||||
if (tags[j] == rawtags[i].innerHTML) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
tags.push(rawtags[i].innerHTML);
|
||||
}
|
||||
//console.debug(tags);
|
||||
return {
|
||||
id: location.pathname.split('/')[1],
|
||||
text: title,
|
||||
time: moment().format('MMMM Do YYYY, h:mm:ss a'),
|
||||
tags: tags
|
||||
};
|
||||
}
|
||||
|
||||
function generateHistory(view, notehistory) {
|
||||
var info = renderHistory(view);
|
||||
notehistory = clearDuplicatedHistory(notehistory);
|
||||
notehistory = removeHistory(info.id, notehistory);
|
||||
notehistory = addHistory(info.id, info.text, info.time, info.tags, notehistory);
|
||||
return notehistory;
|
||||
}
|
||||
|
||||
//used for outer
|
||||
function getHistory(callback) {
|
||||
checkIfAuth(
|
||||
function () {
|
||||
getServerHistory(callback);
|
||||
},
|
||||
function () {
|
||||
getCookieHistory(callback);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getServerHistory(callback) {
|
||||
$.get('/history')
|
||||
.done(function (data) {
|
||||
if (data.history) {
|
||||
callback(data.history);
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
getCookieHistory(callback);
|
||||
});
|
||||
}
|
||||
|
||||
function getCookieHistory(callback) {
|
||||
callback(JSON.parse($.cookie('notehistory')));
|
||||
}
|
||||
|
||||
function parseHistory(callback) {
|
||||
checkIfAuth(
|
||||
function () {
|
||||
parseServerToHistory(callback);
|
||||
},
|
||||
function () {
|
||||
parseCookieToHistory(callback);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function parseServerToHistory(callback) {
|
||||
$.get('/history')
|
||||
.done(function (data) {
|
||||
if (data.history) {
|
||||
//console.log(data.history);
|
||||
parseToHistory(data.history, callback);
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
parseCookieToHistory(callback);
|
||||
});
|
||||
}
|
||||
|
||||
function parseCookieToHistory(callback) {
|
||||
var notehistory = JSON.parse($.cookie('notehistory'));
|
||||
parseToHistory(notehistory, callback);
|
||||
}
|
||||
|
||||
function parseToHistory(notehistory, callback) {
|
||||
if (notehistory && notehistory.length > 0) {
|
||||
//console.log(notehistory);
|
||||
for (var i = 0; i < notehistory.length; i++) {
|
||||
notehistory[i].timestamp = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').unix();
|
||||
notehistory[i].fromNow = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').fromNow();
|
||||
}
|
||||
$(notehistory).each(function (key, value) {
|
||||
var close = "<div class='ui-history-close fa fa-close fa-fw'></div>";
|
||||
var text = "<h4 class='text'>" + value.text + "</h2>";
|
||||
var timestamp = "<i class='timestamp' style='display:none;'>" + value.timestamp + "</i>";
|
||||
var fromNow = "<i class='fromNow'><i class='fa fa-clock-o'></i> " + value.fromNow + "</i>";
|
||||
var time = "<i class='time'>" + value.time + "</i>";
|
||||
var tags = "";
|
||||
if (value.tags) {
|
||||
var labels = [];
|
||||
for (var j = 0; j < value.tags.length; j++)
|
||||
labels.push("<span class='label label-default'>" + value.tags[j] + "</span>");
|
||||
tags = "<p class='tags'>" + labels.join(" ") + "</p>";
|
||||
}
|
||||
var li = "<li class='col-xs-12 col-sm-6 col-md-6 col-lg-6'><a href='" + "./" + value.id + "'><div class='item'>" + close + text + '<p>' + fromNow + '<br>' + timestamp + time + '</p>' + tags + "</div></a></li>"
|
||||
//console.debug(li);
|
||||
$("#history-list").append(li);
|
||||
});
|
||||
}
|
||||
|
||||
var options = {
|
||||
valueNames: ['text', 'timestamp', 'fromNow', 'time', 'tags']
|
||||
};
|
||||
var historyList = new List('history', options);
|
||||
historyList.sort('timestamp', {
|
||||
order: "desc"
|
||||
});
|
||||
callback();
|
||||
}
|
940
public/js/index.js
Normal file
940
public/js/index.js
Normal file
|
@ -0,0 +1,940 @@
|
|||
//constant vars
|
||||
//settings
|
||||
var debug = false;
|
||||
var version = '0.2.7';
|
||||
var doneTypingDelay = 400;
|
||||
var finishChangeDelay = 400;
|
||||
var cursorActivityDelay = 50;
|
||||
var syncScrollDelay = 50;
|
||||
var scrollAnimatePeriod = 100;
|
||||
var cursorAnimatePeriod = 100;
|
||||
var modeType = {
|
||||
edit: {},
|
||||
view: {},
|
||||
both: {}
|
||||
}
|
||||
var statusType = {
|
||||
connected: {
|
||||
msg: "CONNECTED",
|
||||
label: "label-warning",
|
||||
fa: "fa-wifi"
|
||||
},
|
||||
online: {
|
||||
msg: "ONLINE: ",
|
||||
label: "label-primary",
|
||||
fa: "fa-users"
|
||||
},
|
||||
offline: {
|
||||
msg: "OFFLINE",
|
||||
label: "label-danger",
|
||||
fa: "fa-plug"
|
||||
}
|
||||
}
|
||||
var defaultMode = modeType.both;
|
||||
|
||||
//global vars
|
||||
var loaded = false;
|
||||
var isDirty = false;
|
||||
var editShown = false;
|
||||
var visibleXS = false;
|
||||
var visibleSM = false;
|
||||
var visibleMD = false;
|
||||
var visibleLG = false;
|
||||
var isTouchDevice = 'ontouchstart' in document.documentElement;
|
||||
var currentMode = defaultMode;
|
||||
var currentStatus = statusType.offline;
|
||||
var lastInfo = {
|
||||
needRestore: false,
|
||||
cursor: null,
|
||||
scroll: null,
|
||||
edit: {
|
||||
scroll: {
|
||||
left: null,
|
||||
top: null
|
||||
},
|
||||
cursor: {
|
||||
line: null,
|
||||
ch: null
|
||||
}
|
||||
},
|
||||
view: {
|
||||
scroll: {
|
||||
left: null,
|
||||
top: null
|
||||
}
|
||||
},
|
||||
history: null
|
||||
};
|
||||
|
||||
//editor settings
|
||||
var editor = CodeMirror.fromTextArea(document.getElementById("textit"), {
|
||||
mode: 'gfm',
|
||||
viewportMargin: 20,
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
theme: "monokai",
|
||||
autofocus: true,
|
||||
inputStyle: "textarea",
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
matchTags: {
|
||||
bothTags: true
|
||||
},
|
||||
autoCloseTags: true,
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
extraKeys: {
|
||||
"Enter": "newlineAndIndentContinueMarkdownList"
|
||||
},
|
||||
readOnly: true
|
||||
});
|
||||
|
||||
//ui vars
|
||||
var ui = {
|
||||
spinner: $(".ui-spinner"),
|
||||
content: $(".ui-content"),
|
||||
toolbar: {
|
||||
shortStatus: $(".ui-short-status"),
|
||||
status: $(".ui-status"),
|
||||
new: $(".ui-new"),
|
||||
pretty: $(".ui-pretty"),
|
||||
download: {
|
||||
markdown: $(".ui-download-markdown")
|
||||
},
|
||||
save: {
|
||||
dropbox: $(".ui-save-dropbox")
|
||||
},
|
||||
import: {
|
||||
dropbox: $(".ui-import-dropbox"),
|
||||
clipboard: $(".ui-import-clipboard")
|
||||
},
|
||||
mode: $(".ui-mode"),
|
||||
edit: $(".ui-edit"),
|
||||
view: $(".ui-view"),
|
||||
both: $(".ui-both")
|
||||
},
|
||||
area: {
|
||||
edit: $(".ui-edit-area"),
|
||||
view: $(".ui-view-area"),
|
||||
codemirror: $(".ui-edit-area .CodeMirror"),
|
||||
markdown: $(".ui-view-area .markdown-body")
|
||||
}
|
||||
};
|
||||
|
||||
//page actions
|
||||
var opts = {
|
||||
lines: 11, // The number of lines to draw
|
||||
length: 20, // The length of each line
|
||||
width: 2, // The line thickness
|
||||
radius: 30, // The radius of the inner circle
|
||||
corners: 0, // Corner roundness (0..1)
|
||||
rotate: 0, // The rotation offset
|
||||
direction: 1, // 1: clockwise, -1: counterclockwise
|
||||
color: '#000', // #rgb or #rrggbb or array of colors
|
||||
speed: 1.1, // Rounds per second
|
||||
trail: 60, // Afterglow percentage
|
||||
shadow: false, // Whether to render a shadow
|
||||
hwaccel: true, // Whether to use hardware acceleration
|
||||
className: 'spinner', // The CSS class to assign to the spinner
|
||||
zIndex: 2e9, // The z-index (defaults to 2000000000)
|
||||
top: '50%', // Top position relative to parent
|
||||
left: '50%' // Left position relative to parent
|
||||
};
|
||||
var spinner = new Spinner(opts).spin(ui.spinner[0]);
|
||||
//when page ready
|
||||
$(document).ready(function () {
|
||||
checkResponsive();
|
||||
changeMode(currentMode);
|
||||
/* we need this only on touch devices */
|
||||
if (isTouchDevice) {
|
||||
/* cache dom references */
|
||||
var $body = jQuery('body');
|
||||
|
||||
/* bind events */
|
||||
$(document)
|
||||
.on('focus', 'textarea, input', function() {
|
||||
$body.addClass('fixfixed');
|
||||
})
|
||||
.on('blur', 'textarea, input', function() {
|
||||
$body.removeClass('fixfixed');
|
||||
});
|
||||
}
|
||||
});
|
||||
//when page resize
|
||||
$(window).resize(function () {
|
||||
checkResponsive();
|
||||
});
|
||||
//768-792px have a gap
|
||||
function checkResponsive() {
|
||||
visibleXS = $(".visible-xs").is(":visible");
|
||||
visibleSM = $(".visible-sm").is(":visible");
|
||||
visibleMD = $(".visible-md").is(":visible");
|
||||
visibleLG = $(".visible-lg").is(":visible");
|
||||
if (visibleXS && currentMode == modeType.both)
|
||||
if (editor.hasFocus())
|
||||
changeMode(modeType.edit);
|
||||
else
|
||||
changeMode(modeType.view);
|
||||
}
|
||||
|
||||
function showStatus(type, num) {
|
||||
currentStatus = type;
|
||||
var shortStatus = ui.toolbar.shortStatus;
|
||||
var status = ui.toolbar.status;
|
||||
var label = $('<span class="label"></span>');
|
||||
var fa = $('<i class="fa"></i>');
|
||||
var msg = "";
|
||||
var shortMsg = "";
|
||||
|
||||
shortStatus.html("");
|
||||
status.html("");
|
||||
|
||||
switch (currentStatus) {
|
||||
case statusType.connected:
|
||||
label.addClass(statusType.connected.label);
|
||||
fa.addClass(statusType.connected.fa);
|
||||
msg = statusType.connected.msg;
|
||||
break;
|
||||
case statusType.online:
|
||||
label.addClass(statusType.online.label);
|
||||
fa.addClass(statusType.online.fa);
|
||||
shortMsg = " " + num;
|
||||
msg = statusType.online.msg + num;
|
||||
break;
|
||||
case statusType.offline:
|
||||
label.addClass(statusType.offline.label);
|
||||
fa.addClass(statusType.offline.fa);
|
||||
msg = statusType.offline.msg;
|
||||
break;
|
||||
}
|
||||
|
||||
label.append(fa);
|
||||
var shortLabel = label.clone();
|
||||
|
||||
shortLabel.append(" " + shortMsg);
|
||||
shortStatus.append(shortLabel);
|
||||
|
||||
label.append(" " + msg);
|
||||
status.append(label);
|
||||
}
|
||||
|
||||
function toggleMode() {
|
||||
switch(currentMode) {
|
||||
case modeType.edit:
|
||||
changeMode(modeType.view);
|
||||
break;
|
||||
case modeType.view:
|
||||
changeMode(modeType.edit);
|
||||
break;
|
||||
case modeType.both:
|
||||
changeMode(modeType.view);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function changeMode(type) {
|
||||
saveInfo();
|
||||
if (type)
|
||||
currentMode = type;
|
||||
var responsiveClass = "col-lg-6 col-md-6 col-sm-6";
|
||||
var scrollClass = "ui-scrollable";
|
||||
ui.area.codemirror.removeClass(scrollClass);
|
||||
ui.area.edit.removeClass(responsiveClass);
|
||||
ui.area.view.removeClass(scrollClass);
|
||||
ui.area.view.removeClass(responsiveClass);
|
||||
switch (currentMode) {
|
||||
case modeType.edit:
|
||||
ui.area.edit.show();
|
||||
ui.area.view.hide();
|
||||
if (!editShown) {
|
||||
editor.refresh();
|
||||
editShown = true;
|
||||
}
|
||||
break;
|
||||
case modeType.view:
|
||||
ui.area.edit.hide();
|
||||
ui.area.view.show();
|
||||
break;
|
||||
case modeType.both:
|
||||
ui.area.codemirror.addClass(scrollClass);
|
||||
ui.area.edit.addClass(responsiveClass).show();
|
||||
ui.area.view.addClass(scrollClass);
|
||||
ui.area.view.addClass(responsiveClass).show();
|
||||
break;
|
||||
}
|
||||
if (currentMode != modeType.view && visibleLG) {
|
||||
editor.focus();
|
||||
editor.refresh();
|
||||
} else {
|
||||
editor.getInputField().blur();
|
||||
}
|
||||
if (changeMode != modeType.edit)
|
||||
updateView();
|
||||
restoreInfo();
|
||||
|
||||
ui.toolbar.both.removeClass("active");
|
||||
ui.toolbar.edit.removeClass("active");
|
||||
ui.toolbar.view.removeClass("active");
|
||||
var modeIcon = ui.toolbar.mode.find('i');
|
||||
modeIcon.removeClass('fa-toggle-on').removeClass('fa-toggle-off');
|
||||
if (ui.area.edit.is(":visible") && ui.area.view.is(":visible")) { //both
|
||||
ui.toolbar.both.addClass("active");
|
||||
modeIcon.addClass('fa-eye');
|
||||
} else if (ui.area.edit.is(":visible")) { //edit
|
||||
ui.toolbar.edit.addClass("active");
|
||||
modeIcon.addClass('fa-toggle-off');
|
||||
} else if (ui.area.view.is(":visible")) { //view
|
||||
ui.toolbar.view.addClass("active");
|
||||
modeIcon.addClass('fa-toggle-on');
|
||||
}
|
||||
}
|
||||
|
||||
//button actions
|
||||
var noteId = window.location.pathname.split('/')[1];
|
||||
var url = window.location.origin + '/' + noteId;
|
||||
//pretty
|
||||
ui.toolbar.pretty.attr("href", url + "/pretty");
|
||||
//download
|
||||
//markdown
|
||||
ui.toolbar.download.markdown.click(function() {
|
||||
var filename = renderFilename(ui.area.markdown) + '.md';
|
||||
var markdown = editor.getValue();
|
||||
var blob = new Blob([markdown], {type: "text/markdown;charset=utf-8"});
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
//save to dropbox
|
||||
ui.toolbar.save.dropbox.click(function() {
|
||||
var filename = renderFilename(ui.area.markdown) + '.md';
|
||||
var options = {
|
||||
files: [
|
||||
{'url': url + "/download", 'filename': filename}
|
||||
]
|
||||
};
|
||||
Dropbox.save(options);
|
||||
});
|
||||
//import from dropbox
|
||||
ui.toolbar.import.dropbox.click(function() {
|
||||
var options = {
|
||||
success: function(files) {
|
||||
ui.spinner.show();
|
||||
var url = files[0].link;
|
||||
importFromUrl(url);
|
||||
},
|
||||
linkType: "direct",
|
||||
multiselect: false,
|
||||
extensions: ['.md', '.html']
|
||||
};
|
||||
Dropbox.choose(options);
|
||||
});
|
||||
//import from clipboard
|
||||
ui.toolbar.import.clipboard.click(function() {
|
||||
//na
|
||||
});
|
||||
//fix for wrong autofocus
|
||||
$('#clipboardModal').on('shown.bs.modal', function() {
|
||||
$('#clipboardModal').blur();
|
||||
});
|
||||
$("#clipboardModalClear").click(function() {
|
||||
$("#clipboardModalContent").html('');
|
||||
});
|
||||
$("#clipboardModalConfirm").click(function() {
|
||||
var data = $("#clipboardModalContent").html();
|
||||
if(data) {
|
||||
parseToEditor(data);
|
||||
$('#clipboardModal').modal('hide');
|
||||
$("#clipboardModalContent").html('');
|
||||
}
|
||||
});
|
||||
function parseToEditor(data) {
|
||||
var parsed = toMarkdown(data);
|
||||
if(parsed)
|
||||
editor.replaceRange(parsed, {line:0, ch:0}, {line:editor.lastLine(), ch:editor.lastLine().length}, '+input');
|
||||
}
|
||||
function importFromUrl(url) {
|
||||
//console.log(url);
|
||||
if(url == null) return;
|
||||
if(!isValidURL(url)) {
|
||||
alert('Not valid URL :(');
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: url,
|
||||
success: function(data) {
|
||||
parseToEditor(data);
|
||||
},
|
||||
error: function() {
|
||||
alert('Import failed :(');
|
||||
},
|
||||
complete: function() {
|
||||
ui.spinner.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
function isValidURL(str) {
|
||||
var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
|
||||
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
|
||||
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
|
||||
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
|
||||
'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
|
||||
if(!pattern.test(str)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//mode
|
||||
ui.toolbar.mode.click(function () {
|
||||
toggleMode();
|
||||
});
|
||||
//edit
|
||||
ui.toolbar.edit.click(function () {
|
||||
changeMode(modeType.edit);
|
||||
});
|
||||
//view
|
||||
ui.toolbar.view.click(function () {
|
||||
changeMode(modeType.view);
|
||||
});
|
||||
//both
|
||||
ui.toolbar.both.click(function () {
|
||||
changeMode(modeType.both);
|
||||
});
|
||||
|
||||
//socket.io actions
|
||||
var socket = io.connect();
|
||||
socket.on('info', function (data) {
|
||||
console.error(data);
|
||||
location.href = "./404.html";
|
||||
});
|
||||
socket.on('disconnect', function (data) {
|
||||
showStatus(statusType.offline);
|
||||
if (loaded) {
|
||||
saveInfo();
|
||||
lastInfo.history = editor.getHistory();
|
||||
}
|
||||
if (!editor.getOption('readOnly'))
|
||||
editor.setOption('readOnly', true);
|
||||
});
|
||||
socket.on('connect', function (data) {
|
||||
showStatus(statusType.connected);
|
||||
socket.emit('version');
|
||||
});
|
||||
socket.on('version', function (data) {
|
||||
if (data != version)
|
||||
location.reload(true);
|
||||
});
|
||||
socket.on('refresh', function (data) {
|
||||
saveInfo();
|
||||
|
||||
var body = data.body;
|
||||
body = LZString.decompressFromBase64(body);
|
||||
if (body)
|
||||
editor.setValue(body);
|
||||
else
|
||||
editor.setValue("");
|
||||
|
||||
if (!loaded) {
|
||||
editor.clearHistory();
|
||||
ui.spinner.hide();
|
||||
ui.content.fadeIn();
|
||||
changeMode();
|
||||
loaded = true;
|
||||
} else {
|
||||
if (LZString.compressToBase64(editor.getValue()) !== data.body)
|
||||
editor.clearHistory();
|
||||
else {
|
||||
if (lastInfo.history)
|
||||
editor.setHistory(lastInfo.history);
|
||||
}
|
||||
lastInfo.history = null;
|
||||
}
|
||||
|
||||
updateView();
|
||||
|
||||
if (editor.getOption('readOnly'))
|
||||
editor.setOption('readOnly', false);
|
||||
|
||||
restoreInfo();
|
||||
});
|
||||
socket.on('change', function (data) {
|
||||
data = LZString.decompressFromBase64(data);
|
||||
data = JSON.parse(data);
|
||||
editor.replaceRange(data.text, data.from, data.to, "ignoreHistory");
|
||||
isDirty = true;
|
||||
clearTimeout(finishChangeTimer);
|
||||
finishChangeTimer = setTimeout(finishChange, finishChangeDelay);
|
||||
});
|
||||
socket.on('online users', function (data) {
|
||||
if (debug)
|
||||
console.debug(data);
|
||||
showStatus(statusType.online, data.count);
|
||||
$('.other-cursors').html('');
|
||||
for(var i = 0; i < data.users.length; i++) {
|
||||
var user = data.users[i];
|
||||
if(user.id != socket.id)
|
||||
buildCursor(user.id, user.color, user.cursor);
|
||||
}
|
||||
});
|
||||
socket.on('cursor focus', function (data) {
|
||||
if(debug)
|
||||
console.debug(data);
|
||||
var cursor = $('#' + data.id);
|
||||
if(cursor.length > 0) {
|
||||
cursor.fadeIn();
|
||||
} else {
|
||||
if(data.id != socket.id)
|
||||
buildCursor(data.id, data.color, data.cursor);
|
||||
}
|
||||
});
|
||||
socket.on('cursor activity', function (data) {
|
||||
if(debug)
|
||||
console.debug(data);
|
||||
if(data.id != socket.id)
|
||||
buildCursor(data.id, data.color, data.cursor);
|
||||
});
|
||||
socket.on('cursor blur', function (data) {
|
||||
if(debug)
|
||||
console.debug(data);
|
||||
var cursor = $('#' + data.id);
|
||||
if(cursor.length > 0) {
|
||||
cursor.fadeOut();
|
||||
}
|
||||
});
|
||||
function emitUserStatus() {
|
||||
checkIfAuth(
|
||||
function (data) {
|
||||
socket.emit('user status', {login:true});
|
||||
},
|
||||
function () {
|
||||
socket.emit('user status', {login:false});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function buildCursor(id, color, pos) {
|
||||
if(!pos) return;
|
||||
if ($('.other-cursors').length <= 0) {
|
||||
$("<div class='other-cursors'>").insertAfter('.CodeMirror-cursors');
|
||||
}
|
||||
if ($('#' + id).length <= 0) {
|
||||
var cursor = $('<div id="' + id + '" class="other-cursor"> </div>');
|
||||
//console.debug(pos);
|
||||
cursor.attr('data-line', pos.line);
|
||||
cursor.attr('data-ch', pos.ch);
|
||||
var coord = editor.charCoords(pos, 'windows');
|
||||
cursor[0].style.left = coord.left + 'px';
|
||||
cursor[0].style.top = coord.top + 'px';
|
||||
cursor[0].style.height = '18px';
|
||||
cursor[0].style.borderLeft = '2px solid ' + color;
|
||||
$('.other-cursors').append(cursor);
|
||||
cursor.hide().fadeIn();
|
||||
} else {
|
||||
var cursor = $('#' + id);
|
||||
cursor.attr('data-line', pos.line);
|
||||
cursor.attr('data-ch', pos.ch);
|
||||
var coord = editor.charCoords(pos, 'windows');
|
||||
cursor.stop(true).css('opacity', 1).animate({"left":coord.left, "top":coord.top}, cursorAnimatePeriod);
|
||||
//cursor[0].style.left = coord.left + 'px';
|
||||
//cursor[0].style.top = coord.top + 'px';
|
||||
cursor[0].style.height = '18px';
|
||||
cursor[0].style.borderLeft = '2px solid ' + color;
|
||||
}
|
||||
}
|
||||
|
||||
//editor actions
|
||||
editor.on('beforeChange', function (cm, change) {
|
||||
if (debug)
|
||||
console.debug(change);
|
||||
});
|
||||
editor.on('change', function (i, op) {
|
||||
if (debug)
|
||||
console.debug(op);
|
||||
if (op.origin != 'setValue' && op.origin != 'ignoreHistory') {
|
||||
socket.emit('change', LZString.compressToBase64(JSON.stringify(op)));
|
||||
}
|
||||
isDirty = true;
|
||||
clearTimeout(doneTypingTimer);
|
||||
doneTypingTimer = setTimeout(doneTyping, doneTypingDelay);
|
||||
});
|
||||
editor.on('focus', function (cm) {
|
||||
socket.emit('cursor focus', editor.getCursor());
|
||||
});
|
||||
var cursorActivityTimer = null;
|
||||
editor.on('cursorActivity', function (cm) {
|
||||
clearTimeout(cursorActivityTimer);
|
||||
cursorActivityTimer = setTimeout(cursorActivity, cursorActivityDelay);
|
||||
});
|
||||
function cursorActivity() {
|
||||
socket.emit('cursor activity', editor.getCursor());
|
||||
}
|
||||
editor.on('blur', function (cm) {
|
||||
socket.emit('cursor blur');
|
||||
});
|
||||
|
||||
function saveInfo() {
|
||||
var left = $(document.body).scrollLeft();
|
||||
var top = $(document.body).scrollTop();
|
||||
switch (currentMode) {
|
||||
case modeType.edit:
|
||||
lastInfo.edit.scroll.left = left;
|
||||
lastInfo.edit.scroll.top = top;
|
||||
break;
|
||||
case modeType.view:
|
||||
lastInfo.view.scroll.left = left;
|
||||
lastInfo.view.scroll.top = top;
|
||||
break;
|
||||
case modeType.both:
|
||||
lastInfo.edit.scroll = editor.getScrollInfo();
|
||||
lastInfo.view.scroll.left = ui.area.view.scrollLeft();
|
||||
lastInfo.view.scroll.top = ui.area.view.scrollTop();
|
||||
break;
|
||||
}
|
||||
lastInfo.edit.cursor = editor.getCursor();
|
||||
lastInfo.needRestore = true;
|
||||
}
|
||||
|
||||
function restoreInfo() {
|
||||
if (lastInfo.needRestore) {
|
||||
var line = lastInfo.edit.cursor.line;
|
||||
var ch = lastInfo.edit.cursor.ch;
|
||||
editor.setCursor(line, ch);
|
||||
|
||||
switch (currentMode) {
|
||||
case modeType.edit:
|
||||
$(document.body).scrollLeft(lastInfo.edit.scroll.left);
|
||||
$(document.body).scrollTop(lastInfo.edit.scroll.top);
|
||||
break;
|
||||
case modeType.view:
|
||||
$(document.body).scrollLeft(lastInfo.view.scroll.left);
|
||||
$(document.body).scrollTop(lastInfo.view.scroll.top);
|
||||
break;
|
||||
case modeType.both:
|
||||
var left = lastInfo.edit.scroll.left;
|
||||
var top = lastInfo.edit.scroll.top;
|
||||
editor.scrollIntoView();
|
||||
editor.scrollTo(left, top);
|
||||
ui.area.view.scrollLeft(lastInfo.view.scroll.left);
|
||||
ui.area.view.scrollTop(lastInfo.view.scroll.top);
|
||||
break;
|
||||
}
|
||||
|
||||
lastInfo.needRestore = false;
|
||||
}
|
||||
}
|
||||
|
||||
//view actions
|
||||
var doneTypingTimer = null;
|
||||
var finishChangeTimer = null;
|
||||
var input = editor.getInputField();
|
||||
//user is "finished typing," do something
|
||||
function doneTyping() {
|
||||
updateView();
|
||||
var value = editor.getValue();
|
||||
socket.emit('refresh', LZString.compressToBase64(value));
|
||||
}
|
||||
|
||||
function finishChange() {
|
||||
updateView();
|
||||
}
|
||||
|
||||
var lastResult = null;
|
||||
|
||||
function updateView() {
|
||||
if (currentMode == modeType.edit || !isDirty) return;
|
||||
var value = editor.getValue();
|
||||
var result = postProcess(md.render(value)).children().toArray();
|
||||
//ui.area.markdown.html(result);
|
||||
//finishView(ui.area.markdown);
|
||||
partialUpdate(result, lastResult, ui.area.markdown.children().toArray());
|
||||
lastResult = $(result).clone(true);
|
||||
finishView(ui.area.view);
|
||||
writeHistory(ui.area.markdown);
|
||||
isDirty = false;
|
||||
// reset lines mapping cache on content update
|
||||
scrollMap = null;
|
||||
emitUserStatus();
|
||||
}
|
||||
|
||||
function partialUpdate(src, tar, des) {
|
||||
if (!src || src.length == 0 || !tar || tar.length == 0 || !des || des.length == 0) {
|
||||
ui.area.markdown.html(src);
|
||||
return;
|
||||
}
|
||||
if (src.length == tar.length) { //same length
|
||||
for (var i = 0; i < src.length; i++) {
|
||||
copyAttribute(src[i], des[i], 'data-startline');
|
||||
copyAttribute(src[i], des[i], 'data-endline');
|
||||
var rawSrc = cloneAndRemoveDataAttr(src[i]);
|
||||
var rawTar = cloneAndRemoveDataAttr(tar[i]);
|
||||
if (rawSrc.outerHTML != rawTar.outerHTML) {
|
||||
//console.log(rawSrc);
|
||||
//console.log(rawTar);
|
||||
$(des[i]).replaceWith(src[i]);
|
||||
}
|
||||
}
|
||||
} else { //diff length
|
||||
var start = 0;
|
||||
var end = 0;
|
||||
//find diff start position
|
||||
for (var i = 0; i < tar.length; i++) {
|
||||
copyAttribute(src[i], des[i], 'data-startline');
|
||||
copyAttribute(src[i], des[i], 'data-endline');
|
||||
var rawSrc = cloneAndRemoveDataAttr(src[i]);
|
||||
var rawTar = cloneAndRemoveDataAttr(tar[i]);
|
||||
if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
|
||||
start = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//find diff end position
|
||||
var srcEnd = 0;
|
||||
var tarEnd = 0;
|
||||
for (var i = 0; i < src.length; i++) {
|
||||
copyAttribute(src[i], des[i], 'data-startline');
|
||||
copyAttribute(src[i], des[i], 'data-endline');
|
||||
var rawSrc = cloneAndRemoveDataAttr(src[i]);
|
||||
var rawTar = cloneAndRemoveDataAttr(tar[i]);
|
||||
if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
|
||||
start = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//tar end
|
||||
for (var i = 1; i <= tar.length; i++) {
|
||||
var srcLength = src.length;
|
||||
var tarLength = tar.length;
|
||||
copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
|
||||
copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
|
||||
var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]);
|
||||
var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]);
|
||||
if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
|
||||
tarEnd = tar.length - i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//src end
|
||||
for (var i = 1; i <= src.length; i++) {
|
||||
var srcLength = src.length;
|
||||
var tarLength = tar.length;
|
||||
copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
|
||||
copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
|
||||
var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]);
|
||||
var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]);
|
||||
if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
|
||||
srcEnd = src.length - i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//check if tar end overlap tar start
|
||||
var overlap = 0;
|
||||
for (var i = start; i >= 0; i--) {
|
||||
var rawTarStart = cloneAndRemoveDataAttr(tar[i-1]);
|
||||
var rawTarEnd = cloneAndRemoveDataAttr(tar[tarEnd+1+start-i]);
|
||||
if(rawTarStart && rawTarEnd && rawTarStart.outerHTML == rawTarEnd.outerHTML)
|
||||
overlap++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
if(debug)
|
||||
console.log('overlap:' + overlap);
|
||||
//show diff content
|
||||
if(debug) {
|
||||
console.log('start:' + start);
|
||||
console.log('tarEnd:' + tarEnd);
|
||||
console.log('srcEnd:' + srcEnd);
|
||||
console.log('des[start]:' + des[start]);
|
||||
}
|
||||
tarEnd += overlap;
|
||||
srcEnd += overlap;
|
||||
//add new element
|
||||
var newElements = "";
|
||||
for (var j = start; j <= srcEnd; j++) {
|
||||
if(debug)
|
||||
srcChanged += src[j].outerHTML;
|
||||
newElements += src[j].outerHTML;
|
||||
}
|
||||
if(newElements && des[start]) {
|
||||
$(newElements).insertBefore(des[start]);
|
||||
} else {
|
||||
$(newElements).insertAfter(des[des.length-1]);
|
||||
}
|
||||
if(debug)
|
||||
console.log(srcChanged);
|
||||
//remove old element
|
||||
if(debug)
|
||||
var tarChanged = "";
|
||||
for (var j = start; j <= tarEnd; j++) {
|
||||
if(debug)
|
||||
tarChanged += tar[j].outerHTML;
|
||||
if(des[j])
|
||||
des[j].remove();
|
||||
}
|
||||
if(debug) {
|
||||
console.log(tarChanged);
|
||||
var srcChanged = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cloneAndRemoveDataAttr(el) {
|
||||
if(!el) return;
|
||||
var rawEl = $(el).clone(true)[0];
|
||||
rawEl.removeAttribute('data-startline');
|
||||
rawEl.removeAttribute('data-endline');
|
||||
return rawEl;
|
||||
}
|
||||
|
||||
function copyAttribute(src, des, attr) {
|
||||
if (src && src.getAttribute(attr) && des)
|
||||
des.setAttribute(attr, src.getAttribute(attr));
|
||||
}
|
||||
|
||||
//
|
||||
// Inject line numbers for sync scroll. Notes:
|
||||
//
|
||||
// - We track only headings and paragraphs on first level. That's enougth.
|
||||
// - Footnotes content causes jumps. Level limit filter it automatically.
|
||||
//
|
||||
md.renderer.rules.paragraph_open = function (tokens, idx) {
|
||||
var line;
|
||||
if (tokens[idx].lines && tokens[idx].level === 0) {
|
||||
var startline = tokens[idx].lines[0] + 1;
|
||||
var endline = tokens[idx].lines[1];
|
||||
return '<p class="part" data-startline="' + startline + '" data-endline="' + endline + '">';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
md.renderer.rules.heading_open = function (tokens, idx) {
|
||||
var line;
|
||||
if (tokens[idx].lines && tokens[idx].level === 0) {
|
||||
var startline = tokens[idx].lines[0] + 1;
|
||||
var endline = tokens[idx].lines[1];
|
||||
return '<h' + tokens[idx].hLevel + ' class="part" data-startline="' + startline + '" data-endline="' + endline + '">';
|
||||
}
|
||||
return '<h' + tokens[idx].hLevel + '>';
|
||||
};
|
||||
|
||||
editor.on('scroll', _.debounce(syncScrollToView, syncScrollDelay));
|
||||
//ui.area.view.on('scroll', _.debounce(syncScrollToEdit, 50));
|
||||
var scrollMap;
|
||||
// Build offsets for each line (lines can be wrapped)
|
||||
// That's a bit dirty to process each line everytime, but ok for demo.
|
||||
// Optimizations are required only for big texts.
|
||||
function buildScrollMap() {
|
||||
var i, offset, nonEmptyList, pos, a, b, lineHeightMap, linesCount,
|
||||
acc, sourceLikeDiv, textarea = ui.area.codemirror,
|
||||
_scrollMap;
|
||||
|
||||
sourceLikeDiv = $('<div />').css({
|
||||
position: 'absolute',
|
||||
visibility: 'hidden',
|
||||
height: 'auto',
|
||||
width: editor.getScrollInfo().clientWidth,
|
||||
'font-size': textarea.css('font-size'),
|
||||
'font-family': textarea.css('font-family'),
|
||||
'line-height': textarea.css('line-height'),
|
||||
'white-space': textarea.css('white-space')
|
||||
}).appendTo('body');
|
||||
|
||||
offset = ui.area.view.scrollTop() - ui.area.view.offset().top;
|
||||
_scrollMap = [];
|
||||
nonEmptyList = [];
|
||||
lineHeightMap = [];
|
||||
|
||||
acc = 0;
|
||||
editor.getValue().split('\n').forEach(function (str) {
|
||||
var h, lh;
|
||||
|
||||
lineHeightMap.push(acc);
|
||||
|
||||
if (str.length === 0) {
|
||||
acc++;
|
||||
return;
|
||||
}
|
||||
|
||||
sourceLikeDiv.text(str);
|
||||
h = parseFloat(sourceLikeDiv.css('height'));
|
||||
lh = parseFloat(sourceLikeDiv.css('line-height'));
|
||||
acc += Math.round(h / lh);
|
||||
});
|
||||
sourceLikeDiv.remove();
|
||||
lineHeightMap.push(acc);
|
||||
linesCount = acc;
|
||||
|
||||
for (i = 0; i < linesCount; i++) {
|
||||
_scrollMap.push(-1);
|
||||
}
|
||||
|
||||
nonEmptyList.push(0);
|
||||
_scrollMap[0] = 0;
|
||||
|
||||
ui.area.markdown.find('.part').each(function (n, el) {
|
||||
var $el = $(el),
|
||||
t = $el.data('startline');
|
||||
if (t === '') {
|
||||
return;
|
||||
}
|
||||
t = lineHeightMap[t];
|
||||
if (t !== 0) {
|
||||
nonEmptyList.push(t);
|
||||
}
|
||||
_scrollMap[t] = Math.round($el.offset().top + offset);
|
||||
});
|
||||
|
||||
nonEmptyList.push(linesCount);
|
||||
_scrollMap[linesCount] = ui.area.view[0].scrollHeight;
|
||||
|
||||
pos = 0;
|
||||
for (i = 1; i < linesCount; i++) {
|
||||
if (_scrollMap[i] !== -1) {
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
a = nonEmptyList[pos];
|
||||
b = nonEmptyList[pos + 1];
|
||||
_scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a));
|
||||
}
|
||||
|
||||
return _scrollMap;
|
||||
}
|
||||
|
||||
function syncScrollToView() {
|
||||
var lineNo, posTo;
|
||||
var scrollInfo = editor.getScrollInfo();
|
||||
if (!scrollMap) {
|
||||
scrollMap = buildScrollMap();
|
||||
}
|
||||
lineNo = Math.floor(scrollInfo.top / editor.defaultTextHeight());
|
||||
posTo = scrollMap[lineNo];
|
||||
ui.area.view.stop(true).animate({scrollTop: posTo}, scrollAnimatePeriod);
|
||||
}
|
||||
|
||||
function syncScrollToEdit() {
|
||||
var lineNo, posTo;
|
||||
if (!scrollMap) {
|
||||
scrollMap = buildScrollMap();
|
||||
}
|
||||
var top = ui.area.view.scrollTop();
|
||||
lineNo = closestIndex(top, scrollMap);
|
||||
posTo = lineNo * editor.defaultTextHeight();
|
||||
editor.scrollTo(0, posTo);
|
||||
}
|
||||
|
||||
function closestIndex(num, arr) {
|
||||
var curr = arr[0];
|
||||
var index = 0;
|
||||
var diff = Math.abs(num - curr);
|
||||
for (var val = 0; val < arr.length; val++) {
|
||||
var newdiff = Math.abs(num - arr[val]);
|
||||
if (newdiff < diff) {
|
||||
diff = newdiff;
|
||||
curr = arr[val];
|
||||
index = val;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
45
public/js/unused.js
Normal file
45
public/js/unused.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
|
||||
//parse Youtube
|
||||
result.find(".youtube").each(function (key, value) {
|
||||
if (!$(value).attr('videoid')) return;
|
||||
setSizebyAttr(this, this);
|
||||
var icon = '<i class="icon fa fa-youtube-play fa-5x"></i>';
|
||||
$(this).append(icon);
|
||||
var videoid = $(value).attr('videoid');
|
||||
var thumbnail_src = '//img.youtube.com/vi/' + videoid + '/hqdefault.jpg';
|
||||
$(value).css('background-image', 'url(' + thumbnail_src + ')');
|
||||
$(this).click(function () {
|
||||
imgPlayiframe(this, '//www.youtube.com/embed/');
|
||||
});
|
||||
});
|
||||
//parse vimeo
|
||||
result.find(".vimeo").each(function (key, value) {
|
||||
if (!$(value).attr('videoid')) return;
|
||||
setSizebyAttr(this, this);
|
||||
var icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>';
|
||||
$(this).append(icon);
|
||||
var videoid = $(value).attr('videoid');
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'http://vimeo.com/api/v2/video/' + videoid + '.json',
|
||||
jsonp: 'callback',
|
||||
dataType: 'jsonp',
|
||||
success: function (data) {
|
||||
var thumbnail_src = data[0].thumbnail_large;
|
||||
$(value).css('background-image', 'url(' + thumbnail_src + ')');
|
||||
}
|
||||
});
|
||||
$(this).click(function () {
|
||||
imgPlayiframe(this, '//player.vimeo.com/video/');
|
||||
});
|
||||
});
|
||||
//todo list
|
||||
var lis = result[0].getElementsByTagName('li');
|
||||
for (var i = 0; i < lis.length; i++) {
|
||||
var html = lis[i].innerHTML;
|
||||
if (/^\s*\[[x ]\]\s*/.test(html)) {
|
||||
lis[i].innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled>')
|
||||
.replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked disabled>');
|
||||
lis[i].setAttribute('class', 'task-list-item');
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue