diff options
author | Wu Cheng-Han | 2017-03-13 18:56:32 +0800 |
---|---|---|
committer | Wu Cheng-Han | 2017-03-13 18:56:32 +0800 |
commit | edb1b4aa0a72ac8b0215211c9dbc54156c3ff91f (patch) | |
tree | fb5f8ddbfe9001e266b3b2487c3b2e62fbd2bbb5 /public | |
parent | c818cde78285490ec2931b68a72898f9754a6d81 (diff) | |
parent | 8246ac38506f8d62e2dd9699dcc4d62f14b65784 (diff) |
Merge branch 'master' of https://github.com/jackycute/HackMD
Diffstat (limited to 'public')
-rw-r--r-- | public/js/cover.js | 739 | ||||
-rw-r--r-- | public/js/extra.js | 1944 | ||||
-rw-r--r-- | public/js/google-drive-picker.js | 227 | ||||
-rw-r--r-- | public/js/google-drive-upload.js | 225 | ||||
-rw-r--r-- | public/js/history.js | 500 | ||||
-rw-r--r-- | public/js/htmlExport.js | 12 | ||||
-rw-r--r-- | public/js/index.js | 7123 | ||||
-rw-r--r-- | public/js/lib/common/login.js | 133 | ||||
-rw-r--r-- | public/js/lib/config/index.js | 28 | ||||
-rw-r--r-- | public/js/locale.js | 42 | ||||
-rw-r--r-- | public/js/pretty.js | 211 | ||||
-rw-r--r-- | public/js/render.js | 76 | ||||
-rwxr-xr-x | public/js/reveal-markdown.js | 741 | ||||
-rw-r--r-- | public/js/slide.js | 195 | ||||
-rw-r--r-- | public/js/syncscroll.js | 604 | ||||
-rwxr-xr-x | public/vendor/md-toc.js | 214 |
16 files changed, 6386 insertions, 6628 deletions
diff --git a/public/js/cover.js b/public/js/cover.js index bc6e73f9..a45a1c13 100644 --- a/public/js/cover.js +++ b/public/js/cover.js @@ -1,7 +1,10 @@ -require('./locale'); +/* eslint-env browser, jquery */ +/* global moment, serverurl */ -require('../css/cover.css'); -require('../css/site.css'); +require('./locale') + +require('../css/cover.css') +require('../css/site.css') import { checkIfAuth, @@ -9,7 +12,7 @@ import { getLoginState, resetCheckAuth, setloginStateChangeEvent -} from './lib/common/login'; +} from './lib/common/login' import { clearDuplicatedHistory, @@ -23,411 +26,403 @@ import { removeHistory, saveHistory, saveStorageHistoryToServer -} from './history'; +} from './history' -import { saveAs } from 'file-saver'; -import List from 'list.js'; -import S from 'string'; +import { saveAs } from 'file-saver' +import List from 'list.js' +import S from 'string' const options = { - valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'], - item: '<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">\ - <span class="id" style="display:none;"></span>\ - <a href="#">\ - <div class="item">\ - <div class="ui-history-pin fa fa-thumb-tack fa-fw"></div>\ - <div class="ui-history-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>\ - <div class="content">\ - <h4 class="text"></h4>\ - <p>\ - <i><i class="fa fa-clock-o"></i> visited </i><i class="fromNow"></i>\ - <br>\ - <i class="timestamp" style="display:none;"></i>\ - <i class="time"></i>\ - </p>\ - <p class="tags"></p>\ - </div>\ - </div>\ - </a>\ - </li>', - page: 18, - plugins: [ - ListPagination({ - outerWindow: 1 - }) - ] -}; -const historyList = new List('history', options); - -migrateHistoryFromTempCallback = pageInit; -setloginStateChangeEvent(pageInit); - -pageInit(); - -function pageInit() { - checkIfAuth( + valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'], + item: '<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">' + + '<span class="id" style="display:none;"></span>' + + '<a href="#">' + + '<div class="item">' + + '<div class="ui-history-pin fa fa-thumb-tack fa-fw"></div>' + + '<div class="ui-history-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>' + + '<div class="content">' + + '<h4 class="text"></h4>' + + '<p>' + + '<i><i class="fa fa-clock-o"></i> visited </i><i class="fromNow"></i>' + + '<br>' + + '<i class="timestamp" style="display:none;"></i>' + + '<i class="time"></i>' + + '</p>' + + '<p class="tags"></p>' + + '</div>' + + '</div>' + + '</a>' + + '</li>', + page: 18, + plugins: [ + window.ListPagination({ + outerWindow: 1 + }) + ] +} +const historyList = new List('history', options) + +window.migrateHistoryFromTempCallback = pageInit +setloginStateChangeEvent(pageInit) + +pageInit() + +function pageInit () { + checkIfAuth( data => { - $('.ui-signin').hide(); - $('.ui-or').hide(); - $('.ui-welcome').show(); - if (data.photo) $('.ui-avatar').prop('src', data.photo).show(); - else $('.ui-avatar').prop('src', '').hide(); - $('.ui-name').html(data.name); - $('.ui-signout').show(); - $(".ui-history").click(); - parseServerToHistory(historyList, parseHistoryCallback); + $('.ui-signin').hide() + $('.ui-or').hide() + $('.ui-welcome').show() + if (data.photo) $('.ui-avatar').prop('src', data.photo).show() + else $('.ui-avatar').prop('src', '').hide() + $('.ui-name').html(data.name) + $('.ui-signout').show() + $('.ui-history').click() + parseServerToHistory(historyList, parseHistoryCallback) }, () => { - $('.ui-signin').show(); - $('.ui-or').show(); - $('.ui-welcome').hide(); - $('.ui-avatar').prop('src', '').hide(); - $('.ui-name').html(''); - $('.ui-signout').hide(); - parseStorageToHistory(historyList, parseHistoryCallback); + $('.ui-signin').show() + $('.ui-or').show() + $('.ui-welcome').hide() + $('.ui-avatar').prop('src', '').hide() + $('.ui-name').html('') + $('.ui-signout').hide() + parseStorageToHistory(historyList, parseHistoryCallback) } - ); + ) } -$(".masthead-nav li").click(function () { - $(this).siblings().removeClass("active"); - $(this).addClass("active"); -}); +$('.masthead-nav li').click(function () { + $(this).siblings().removeClass('active') + $(this).addClass('active') +}) // prevent empty link change hash $('a[href="#"]').click(function (e) { - e.preventDefault(); -}); - -$(".ui-home").click(function (e) { - if (!$("#home").is(':visible')) { - $(".section:visible").hide(); - $("#home").fadeIn(); - } -}); - -$(".ui-history").click(() => { - if (!$("#history").is(':visible')) { - $(".section:visible").hide(); - $("#history").fadeIn(); - } -}); - -function checkHistoryList() { - if ($("#history-list").children().length > 0) { - $('.pagination').show(); - $(".ui-nohistory").hide(); - $(".ui-import-from-browser").hide(); - } else if ($("#history-list").children().length == 0) { - $('.pagination').hide(); - $(".ui-nohistory").slideDown(); - getStorageHistory(data => { - if (data && data.length > 0 && getLoginState() && historyList.items.length == 0) { - $(".ui-import-from-browser").slideDown(); - } - }); - } + e.preventDefault() +}) + +$('.ui-home').click(function (e) { + if (!$('#home').is(':visible')) { + $('.section:visible').hide() + $('#home').fadeIn() + } +}) + +$('.ui-history').click(() => { + if (!$('#history').is(':visible')) { + $('.section:visible').hide() + $('#history').fadeIn() + } +}) + +function checkHistoryList () { + if ($('#history-list').children().length > 0) { + $('.pagination').show() + $('.ui-nohistory').hide() + $('.ui-import-from-browser').hide() + } else if ($('#history-list').children().length === 0) { + $('.pagination').hide() + $('.ui-nohistory').slideDown() + getStorageHistory(data => { + if (data && data.length > 0 && getLoginState() && historyList.items.length === 0) { + $('.ui-import-from-browser').slideDown() + } + }) + } } -function parseHistoryCallback(list, notehistory) { - checkHistoryList(); - //sort by pinned then timestamp - list.sort('', { - sortFunction(a, b) { - const notea = a.values(); - const noteb = b.values(); - if (notea.pinned && !noteb.pinned) { - return -1; - } else if (!notea.pinned && noteb.pinned) { - return 1; - } else { - if (notea.timestamp > noteb.timestamp) { - return -1; - } else if (notea.timestamp < noteb.timestamp) { - return 1; - } else { - return 0; - } - } +function parseHistoryCallback (list, notehistory) { + checkHistoryList() + // sort by pinned then timestamp + list.sort('', { + sortFunction (a, b) { + const notea = a.values() + const noteb = b.values() + if (notea.pinned && !noteb.pinned) { + return -1 + } else if (!notea.pinned && noteb.pinned) { + return 1 + } else { + if (notea.timestamp > noteb.timestamp) { + return -1 + } else if (notea.timestamp < noteb.timestamp) { + return 1 + } else { + return 0 } - }); + } + } + }) // parse filter tags - const filtertags = []; - for (let i = 0, l = list.items.length; i < l; i++) { - const tags = list.items[i]._values.tags; - if (tags && tags.length > 0) { - for (let j = 0; j < tags.length; j++) { - //push info filtertags if not found - let found = false; - if (filtertags.includes(tags[j])) - found = true; - if (!found) - filtertags.push(tags[j]); - } - } + const filtertags = [] + for (let i = 0, l = list.items.length; i < l; i++) { + const tags = list.items[i]._values.tags + if (tags && tags.length > 0) { + for (let j = 0; j < tags.length; j++) { + // push info filtertags if not found + let found = false + if (filtertags.includes(tags[j])) { found = true } + if (!found) { filtertags.push(tags[j]) } + } } - buildTagsFilter(filtertags); + } + buildTagsFilter(filtertags) } // update items whenever list updated historyList.on('updated', e => { - for (let i = 0, l = e.items.length; i < l; i++) { - const item = e.items[i]; - if (item.visible()) { - const itemEl = $(item.elm); - const values = item._values; - const a = itemEl.find("a"); - const pin = itemEl.find(".ui-history-pin"); - const tagsEl = itemEl.find(".tags"); - //parse link to element a - a.attr('href', `${serverurl}/${values.id}`); - //parse pinned - if (values.pinned) { - pin.addClass('active'); - } else { - pin.removeClass('active'); - } - //parse tags - const tags = values.tags; - if (tags && tags.length > 0 && tagsEl.children().length <= 0) { - const labels = []; - for (let j = 0; j < tags.length; j++) { - //push into the item label - labels.push(`<span class='label label-default'>${tags[j]}</span>`); - } - tagsEl.html(labels.join(' ')); - } + for (let i = 0, l = e.items.length; i < l; i++) { + const item = e.items[i] + if (item.visible()) { + const itemEl = $(item.elm) + const values = item._values + const a = itemEl.find('a') + const pin = itemEl.find('.ui-history-pin') + const tagsEl = itemEl.find('.tags') + // parse link to element a + a.attr('href', `${serverurl}/${values.id}`) + // parse pinned + if (values.pinned) { + pin.addClass('active') + } else { + pin.removeClass('active') + } + // parse tags + const tags = values.tags + if (tags && tags.length > 0 && tagsEl.children().length <= 0) { + const labels = [] + for (let j = 0; j < tags.length; j++) { + // push into the item label + labels.push(`<span class='label label-default'>${tags[j]}</span>`) } + tagsEl.html(labels.join(' ')) + } } - $(".ui-history-close").off('click'); - $(".ui-history-close").on('click', historyCloseClick); - $(".ui-history-pin").off('click'); - $(".ui-history-pin").on('click', historyPinClick); -}); - -function historyCloseClick(e) { - e.preventDefault(); - const id = $(this).closest("a").siblings("span").html(); - const value = historyList.get('id', id)[0]._values; - $('.ui-delete-modal-msg').text('Do you really want to delete below history?'); - $('.ui-delete-modal-item').html(`<i class="fa fa-file-text"></i> ${value.text}<br><i class="fa fa-clock-o"></i> ${value.time}`); - clearHistory = false; - deleteId = id; + } + $('.ui-history-close').off('click') + $('.ui-history-close').on('click', historyCloseClick) + $('.ui-history-pin').off('click') + $('.ui-history-pin').on('click', historyPinClick) +}) + +function historyCloseClick (e) { + e.preventDefault() + const id = $(this).closest('a').siblings('span').html() + const value = historyList.get('id', id)[0]._values + $('.ui-delete-modal-msg').text('Do you really want to delete below history?') + $('.ui-delete-modal-item').html(`<i class="fa fa-file-text"></i> ${value.text}<br><i class="fa fa-clock-o"></i> ${value.time}`) + clearHistory = false + deleteId = id } -function historyPinClick(e) { - e.preventDefault(); - const $this = $(this); - const id = $this.closest("a").siblings("span").html(); - const item = historyList.get('id', id)[0]; - const values = item._values; - let pinned = values.pinned; - if (!values.pinned) { - pinned = true; - item._values.pinned = true; - } else { - pinned = false; - item._values.pinned = false; - } - checkIfAuth(() => { - postHistoryToServer(id, { - pinned - }, (err, result) => { - if (!err) { - if (pinned) - $this.addClass('active'); - else - $this.removeClass('active'); - } - }); - }, () => { - getHistory(notehistory => { - for(let i = 0; i < notehistory.length; i++) { - if (notehistory[i].id == id) { - notehistory[i].pinned = pinned; - break; - } - } - saveHistory(notehistory); - if (pinned) - $this.addClass('active'); - else - $this.removeClass('active'); - }); - }); +function historyPinClick (e) { + e.preventDefault() + const $this = $(this) + const id = $this.closest('a').siblings('span').html() + const item = historyList.get('id', id)[0] + const values = item._values + let pinned = values.pinned + if (!values.pinned) { + pinned = true + item._values.pinned = true + } else { + pinned = false + item._values.pinned = false + } + checkIfAuth(() => { + postHistoryToServer(id, { + pinned + }, (err, result) => { + if (!err) { + if (pinned) { $this.addClass('active') } else { $this.removeClass('active') } + } + }) + }, () => { + getHistory(notehistory => { + for (let i = 0; i < notehistory.length; i++) { + if (notehistory[i].id === id) { + notehistory[i].pinned = pinned + break + } + } + saveHistory(notehistory) + if (pinned) { $this.addClass('active') } else { $this.removeClass('active') } + }) + }) } -//auto update item fromNow every minutes -setInterval(updateItemFromNow, 60000); +// auto update item fromNow every minutes +setInterval(updateItemFromNow, 60000) -function updateItemFromNow() { - const items = $('.item').toArray(); - for (let i = 0; i < items.length; i++) { - const item = $(items[i]); - const timestamp = parseInt(item.find('.timestamp').text()); - item.find('.fromNow').text(moment(timestamp).fromNow()); - } +function updateItemFromNow () { + const items = $('.item').toArray() + for (let i = 0; i < items.length; i++) { + const item = $(items[i]) + const timestamp = parseInt(item.find('.timestamp').text()) + item.find('.fromNow').text(moment(timestamp).fromNow()) + } } -var clearHistory = false; -var deleteId = null; - -function deleteHistory() { - checkIfAuth(() => { - deleteServerHistory(deleteId, (err, result) => { - if (!err) { - if (clearHistory) { - historyList.clear(); - checkHistoryList(); - } else { - historyList.remove('id', deleteId); - checkHistoryList(); - } - } - $('.delete-modal').modal('hide'); - deleteId = null; - clearHistory = false; - }); - }, () => { +var clearHistory = false +var deleteId = null + +function deleteHistory () { + checkIfAuth(() => { + deleteServerHistory(deleteId, (err, result) => { + if (!err) { if (clearHistory) { - saveHistory([]); - historyList.clear(); - checkHistoryList(); - deleteId = null; + historyList.clear() + checkHistoryList() } else { - if (!deleteId) return; - getHistory(notehistory => { - const newnotehistory = removeHistory(deleteId, notehistory); - saveHistory(newnotehistory); - historyList.remove('id', deleteId); - checkHistoryList(); - deleteId = null; - }); + historyList.remove('id', deleteId) + checkHistoryList() } - $('.delete-modal').modal('hide'); - clearHistory = false; - }); + } + $('.delete-modal').modal('hide') + deleteId = null + clearHistory = false + }) + }, () => { + if (clearHistory) { + saveHistory([]) + historyList.clear() + checkHistoryList() + deleteId = null + } else { + if (!deleteId) return + getHistory(notehistory => { + const newnotehistory = removeHistory(deleteId, notehistory) + saveHistory(newnotehistory) + historyList.remove('id', deleteId) + checkHistoryList() + deleteId = null + }) + } + $('.delete-modal').modal('hide') + clearHistory = false + }) } -$(".ui-delete-modal-confirm").click(() => { - deleteHistory(); -}); - -$(".ui-import-from-browser").click(() => { - saveStorageHistoryToServer(() => { - parseStorageToHistory(historyList, parseHistoryCallback); - }); -}); - -$(".ui-save-history").click(() => { +$('.ui-delete-modal-confirm').click(() => { + deleteHistory() +}) + +$('.ui-import-from-browser').click(() => { + saveStorageHistoryToServer(() => { + parseStorageToHistory(historyList, parseHistoryCallback) + }) +}) + +$('.ui-save-history').click(() => { + getHistory(data => { + const history = JSON.stringify(data) + const blob = new Blob([history], { + type: 'application/json;charset=utf-8' + }) + saveAs(blob, `hackmd_history_${moment().format('YYYYMMDDHHmmss')}`, true) + }) +}) + +$('.ui-open-history').bind('change', e => { + const files = e.target.files || e.dataTransfer.files + const file = files[0] + const reader = new FileReader() + reader.onload = () => { + const notehistory = JSON.parse(reader.result) + // console.log(notehistory); + if (!reader.result) return getHistory(data => { - const history = JSON.stringify(data); - const blob = new Blob([history], { - type: "application/json;charset=utf-8" - }); - saveAs(blob, `hackmd_history_${moment().format('YYYYMMDDHHmmss')}`, true); - }); -}); - -$(".ui-open-history").bind("change", e => { - const files = e.target.files || e.dataTransfer.files; - const file = files[0]; - const reader = new FileReader(); - reader.onload = () => { - const notehistory = JSON.parse(reader.result); - //console.log(notehistory); - if (!reader.result) return; - getHistory(data => { - let mergedata = data.concat(notehistory); - mergedata = clearDuplicatedHistory(mergedata); - saveHistory(mergedata); - parseHistory(historyList, parseHistoryCallback); - }); - $(".ui-open-history").replaceWith($(".ui-open-history").val('').clone(true)); - }; - reader.readAsText(file); -}); - -$(".ui-clear-history").click(() => { - $('.ui-delete-modal-msg').text('Do you really want to clear all history?'); - $('.ui-delete-modal-item').html('There is no turning back.'); - clearHistory = true; - deleteId = null; -}); - -$(".ui-refresh-history").click(() => { - const lastTags = $(".ui-use-tags").select2('val'); - $(".ui-use-tags").select2('val', ''); - historyList.filter(); - const lastKeyword = $('.search').val(); - $('.search').val(''); - historyList.search(); - $('#history-list').slideUp('fast'); - $('.pagination').hide(); - - resetCheckAuth(); - historyList.clear(); - parseHistory(historyList, (list, notehistory) => { - parseHistoryCallback(list, notehistory); - $(".ui-use-tags").select2('val', lastTags); - $(".ui-use-tags").trigger('change'); - historyList.search(lastKeyword); - $('.search').val(lastKeyword); - checkHistoryList(); - $('#history-list').slideDown('fast'); - }); -}); - -$(".ui-logout").click(() => { - clearLoginState(); - location.href = `${serverurl}/logout`; -}); - -let filtertags = []; -$(".ui-use-tags").select2({ - placeholder: $(".ui-use-tags").attr('placeholder'), - multiple: true, - data() { - return { - results: filtertags - }; + let mergedata = data.concat(notehistory) + mergedata = clearDuplicatedHistory(mergedata) + saveHistory(mergedata) + parseHistory(historyList, parseHistoryCallback) + }) + $('.ui-open-history').replaceWith($('.ui-open-history').val('').clone(true)) + } + reader.readAsText(file) +}) + +$('.ui-clear-history').click(() => { + $('.ui-delete-modal-msg').text('Do you really want to clear all history?') + $('.ui-delete-modal-item').html('There is no turning back.') + clearHistory = true + deleteId = null +}) + +$('.ui-refresh-history').click(() => { + const lastTags = $('.ui-use-tags').select2('val') + $('.ui-use-tags').select2('val', '') + historyList.filter() + const lastKeyword = $('.search').val() + $('.search').val('') + historyList.search() + $('#history-list').slideUp('fast') + $('.pagination').hide() + + resetCheckAuth() + historyList.clear() + parseHistory(historyList, (list, notehistory) => { + parseHistoryCallback(list, notehistory) + $('.ui-use-tags').select2('val', lastTags) + $('.ui-use-tags').trigger('change') + historyList.search(lastKeyword) + $('.search').val(lastKeyword) + checkHistoryList() + $('#history-list').slideDown('fast') + }) +}) + +$('.ui-logout').click(() => { + clearLoginState() + location.href = `${serverurl}/logout` +}) + +let filtertags = [] +$('.ui-use-tags').select2({ + placeholder: $('.ui-use-tags').attr('placeholder'), + multiple: true, + data () { + return { + results: filtertags } -}); -$('.select2-input').css('width', 'inherit'); -buildTagsFilter([]); - -function buildTagsFilter(tags) { - for (let i = 0; i < tags.length; i++) - tags[i] = { - id: i, - text: S(tags[i]).unescapeHTML().s - }; - filtertags = tags; -} -$(".ui-use-tags").on('change', function () { - const tags = []; - const data = $(this).select2('data'); - for (let i = 0; i < data.length; i++) - tags.push(data[i].text); - if (tags.length > 0) { - historyList.filter(item => { - const values = item.values(); - if (!values.tags) return false; - let found = false; - for (let i = 0; i < tags.length; i++) { - if (values.tags.includes(tags[i])) { - found = true; - break; - } - } - return found; - }); - } else { - historyList.filter(); + } +}) +$('.select2-input').css('width', 'inherit') +buildTagsFilter([]) + +function buildTagsFilter (tags) { + for (let i = 0; i < tags.length; i++) { + tags[i] = { + id: i, + text: S(tags[i]).unescapeHTML().s } - checkHistoryList(); -}); + } + filtertags = tags +} +$('.ui-use-tags').on('change', function () { + const tags = [] + const data = $(this).select2('data') + for (let i = 0; i < data.length; i++) { tags.push(data[i].text) } + if (tags.length > 0) { + historyList.filter(item => { + const values = item.values() + if (!values.tags) return false + let found = false + for (let i = 0; i < tags.length; i++) { + if (values.tags.includes(tags[i])) { + found = true + break + } + } + return found + }) + } else { + historyList.filter() + } + checkHistoryList() +}) $('.search').keyup(() => { - checkHistoryList(); -}); + checkHistoryList() +}) diff --git a/public/js/extra.js b/public/js/extra.js index a3e840d2..844d52c6 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -1,1150 +1,1140 @@ -require('prismjs/themes/prism.css'); -require('prismjs/components/prism-wiki'); -require('prismjs/components/prism-haskell'); -require('prismjs/components/prism-go'); -require('prismjs/components/prism-typescript'); -require('prismjs/components/prism-jsx'); - -import Prism from 'prismjs'; -import hljs from 'highlight.js'; -import PDFObject from 'pdfobject'; -import S from 'string'; -import { saveAs } from 'file-saver'; - -require('./lib/common/login'); -require('../vendor/md-toc'); -var Viz = require("viz.js"); - -//auto update last change -window.createtime = null; -window.lastchangetime = null; +/* eslint-env browser, jquery */ +/* global moment, serverurl */ + +require('prismjs/themes/prism.css') +require('prismjs/components/prism-wiki') +require('prismjs/components/prism-haskell') +require('prismjs/components/prism-go') +require('prismjs/components/prism-typescript') +require('prismjs/components/prism-jsx') + +import Prism from 'prismjs' +import hljs from 'highlight.js' +import PDFObject from 'pdfobject' +import S from 'string' +import { saveAs } from 'file-saver' + +require('./lib/common/login') +require('../vendor/md-toc') +var Viz = require('viz.js') + +// auto update last change +window.createtime = null +window.lastchangetime = null window.lastchangeui = { - status: $(".ui-status-lastchange"), - time: $(".ui-lastchange"), - user: $(".ui-lastchangeuser"), - nouser: $(".ui-no-lastchangeuser") + status: $('.ui-status-lastchange'), + time: $('.ui-lastchange'), + user: $('.ui-lastchangeuser'), + nouser: $('.ui-no-lastchangeuser') } -const ownerui = $(".ui-owner"); +const ownerui = $('.ui-owner') -export function updateLastChange() { - if (!lastchangeui) return; - if (createtime) { - if (createtime && !lastchangetime) { - lastchangeui.status.text('created'); - } else { - lastchangeui.status.text('changed'); - } - const time = lastchangetime || createtime; - lastchangeui.time.html(moment(time).fromNow()); - lastchangeui.time.attr('title', moment(time).format('llll')); +export function updateLastChange () { + if (!window.lastchangeui) return + if (window.createtime) { + if (window.createtime && !window.lastchangetime) { + window.lastchangeui.status.text('created') + } else { + window.lastchangeui.status.text('changed') } + const time = window.lastchangetime || window.createtime + window.lastchangeui.time.html(moment(time).fromNow()) + window.lastchangeui.time.attr('title', moment(time).format('llll')) + } } -setInterval(updateLastChange, 60000); - -window.lastchangeuser = null; -window.lastchangeuserprofile = null; - -export function updateLastChangeUser() { - if (lastchangeui) { - if (lastchangeuser && lastchangeuserprofile) { - const icon = lastchangeui.user.children('i'); - icon.attr('title', lastchangeuserprofile.name).tooltip('fixTitle'); - if (lastchangeuserprofile.photo) - icon.attr('style', `background-image:url(${lastchangeuserprofile.photo})`); - lastchangeui.user.show(); - lastchangeui.nouser.hide(); - } else { - lastchangeui.user.hide(); - lastchangeui.nouser.show(); - } +setInterval(updateLastChange, 60000) + +window.lastchangeuser = null +window.lastchangeuserprofile = null + +export function updateLastChangeUser () { + if (window.lastchangeui) { + if (window.lastchangeuser && window.lastchangeuserprofile) { + const icon = window.lastchangeui.user.children('i') + icon.attr('title', window.lastchangeuserprofile.name).tooltip('fixTitle') + if (window.lastchangeuserprofile.photo) { icon.attr('style', `background-image:url(${window.lastchangeuserprofile.photo})`) } + window.lastchangeui.user.show() + window.lastchangeui.nouser.hide() + } else { + window.lastchangeui.user.hide() + window.lastchangeui.nouser.show() } + } } -window.owner = null; -window.ownerprofile = null; - -export function updateOwner() { - if (ownerui) { - if (owner && ownerprofile && owner !== lastchangeuser) { - const icon = ownerui.children('i'); - icon.attr('title', ownerprofile.name).tooltip('fixTitle'); - const styleString = `background-image:url(${ownerprofile.photo})`; - if (ownerprofile.photo && icon.attr('style') !== styleString) - icon.attr('style', styleString); - ownerui.show(); - } else { - ownerui.hide(); - } +window.owner = null +window.ownerprofile = null + +export function updateOwner () { + if (window.ownerui) { + if (window.owner && window.ownerprofile && window.owner !== window.lastchangeuser) { + const icon = ownerui.children('i') + icon.attr('title', window.ownerprofile.name).tooltip('fixTitle') + const styleString = `background-image:url(${window.ownerprofile.photo})` + if (window.ownerprofile.photo && icon.attr('style') !== styleString) { icon.attr('style', styleString) } + ownerui.show() + } else { + ownerui.hide() } + } } -//get title -function getTitle(view) { - let title = ""; - if (md && md.meta && md.meta.title && (typeof md.meta.title == "string" || typeof md.meta.title == "number")) { - title = md.meta.title; +// get title +function getTitle (view) { + let title = '' + if (md && md.meta && md.meta.title && (typeof md.meta.title === 'string' || typeof md.meta.title === 'number')) { + title = md.meta.title + } else { + const h1s = view.find('h1') + if (h1s.length > 0) { + title = h1s.first().text() } else { - const h1s = view.find("h1"); - if (h1s.length > 0) { - title = h1s.first().text(); - } else { - title = null; - } + title = null } - return title; + } + return title } -//render title -export function renderTitle(view) { - let title = getTitle(view); - if (title) { - title += ' - HackMD'; - } else { - title = 'HackMD - Collaborative markdown notes'; - } - return title; +// render title +export function renderTitle (view) { + let title = getTitle(view) + if (title) { + title += ' - HackMD' + } else { + title = 'HackMD - Collaborative markdown notes' + } + return title } -//render filename -export function renderFilename(view) { - let filename = getTitle(view); - if (!filename) { - filename = 'Untitled'; - } - return filename; +// render filename +export function renderFilename (view) { + let filename = getTitle(view) + if (!filename) { + filename = 'Untitled' + } + return filename } // render tags -export function renderTags(view) { - const tags = []; - const rawtags = []; - if (md && md.meta && md.meta.tags && (typeof md.meta.tags == "string" || typeof md.meta.tags == "number")) { - const metaTags = (`${md.meta.tags}`).split(','); - for (var i = 0; i < metaTags.length; i++) { - const text = metaTags[i].trim(); - if (text) rawtags.push(text); - } - } else { - view.find('h6').each((key, value) => { - if (/^tags/gmi.test($(value).text())) { - const codes = $(value).find("code"); - for (let i = 0; i < codes.length; i++) { - const text = codes[i].innerHTML.trim(); - if (text) rawtags.push(text); - } - } - }); +export function renderTags (view) { + const tags = [] + const rawtags = [] + if (md && md.meta && md.meta.tags && (typeof md.meta.tags === 'string' || typeof md.meta.tags === 'number')) { + const metaTags = (`${md.meta.tags}`).split(',') + for (let i = 0; i < metaTags.length; i++) { + const text = metaTags[i].trim() + if (text) rawtags.push(text) } - for (var i = 0; i < rawtags.length; i++) { - let found = false; - for (let j = 0; j < tags.length; j++) { - if (tags[j] == rawtags[i]) { - found = true; - break; - } + } else { + view.find('h6').each((key, value) => { + if (/^tags/gmi.test($(value).text())) { + const codes = $(value).find('code') + for (let i = 0; i < codes.length; i++) { + const text = codes[i].innerHTML.trim() + if (text) rawtags.push(text) } - if (!found) - tags.push(rawtags[i]); + } + }) + } + for (let i = 0; i < rawtags.length; i++) { + let found = false + for (let j = 0; j < tags.length; j++) { + if (tags[j] === rawtags[i]) { + found = true + break + } } - return tags; + if (!found) { tags.push(rawtags[i]) } + } + return tags } -function slugifyWithUTF8(text) { - let newText = S(text.toLowerCase()).trim().stripTags().dasherize().s; - newText = newText.replace(/([\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\[\\\]\^\`\{\|\}\~])/g, ''); - return newText; +function slugifyWithUTF8 (text) { + let newText = S(text.toLowerCase()).trim().stripTags().dasherize().s + newText = newText.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '') + return newText } -export function isValidURL(str) { - const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol +export function isValidURL (str) { + const 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; - } + '(\\#[-a-z\\d_]*)?$', 'i') // fragment locator + if (!pattern.test(str)) { + return false + } else { + return true + } } -//parse meta -export function parseMeta(md, edit, view, toc, tocAffix) { - let lang = null; - let dir = null; - let breaks = true; - if (md && md.meta) { - const meta = md.meta; - lang = meta.lang; - dir = meta.dir; - breaks = meta.breaks; - } - //text language - if (lang && typeof lang == "string") { - view.attr('lang', lang); - toc.attr('lang', lang); - tocAffix.attr('lang', lang); - if (edit) - edit.attr('lang', lang); - } else { - view.removeAttr('lang'); - toc.removeAttr('lang'); - tocAffix.removeAttr('lang'); - if (edit) - edit.removeAttr('lang', lang); - } - //text direction - if (dir && typeof dir == "string") { - view.attr('dir', dir); - toc.attr('dir', dir); - tocAffix.attr('dir', dir); - } else { - view.removeAttr('dir'); - toc.removeAttr('dir'); - tocAffix.removeAttr('dir'); - } - //breaks - if (typeof breaks === 'boolean' && !breaks) { - md.options.breaks = false; - } else { - md.options.breaks = true; - } +// parse meta +export function parseMeta (md, edit, view, toc, tocAffix) { + let lang = null + let dir = null + let breaks = true + if (md && md.meta) { + const meta = md.meta + lang = meta.lang + dir = meta.dir + breaks = meta.breaks + } + // text language + if (lang && typeof lang === 'string') { + view.attr('lang', lang) + toc.attr('lang', lang) + tocAffix.attr('lang', lang) + if (edit) { edit.attr('lang', lang) } + } else { + view.removeAttr('lang') + toc.removeAttr('lang') + tocAffix.removeAttr('lang') + if (edit) { edit.removeAttr('lang', lang) } + } + // text direction + if (dir && typeof dir === 'string') { + view.attr('dir', dir) + toc.attr('dir', dir) + tocAffix.attr('dir', dir) + } else { + view.removeAttr('dir') + toc.removeAttr('dir') + tocAffix.removeAttr('dir') + } + // breaks + if (typeof breaks === 'boolean' && !breaks) { + md.options.breaks = false + } else { + md.options.breaks = true + } } -window.viewAjaxCallback = null; - -//regex for extra tags -const spaceregex = /\s*/; -const notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/; -let coloregex = /\[color=([#|\(|\)|\s|\,|\w]*?)\]/; -coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, "g"); -let nameregex = /\[name=(.*?)\]/; -let timeregex = /\[time=([:|,|+|-|\(|\)|\s|\w]*?)\]/; -const nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, "g"); -nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, "g"); -timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, "g"); - -function replaceExtraTags(html) { - html = html.replace(coloregex, '<span class="color" data-color="$1"></span>'); - html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>'); - html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>'); - html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>'); - return html; +window.viewAjaxCallback = null + +// regex for extra tags +const spaceregex = /\s*/ +const notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/ +let coloregex = /\[color=([#|(|)|\s|,|\w]*?)\]/ +coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, 'g') +let nameregex = /\[name=(.*?)\]/ +let timeregex = /\[time=([:|,|+|-|(|)|\s|\w]*?)\]/ +const nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, 'g') +nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, 'g') +timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, 'g') + +function replaceExtraTags (html) { + html = html.replace(coloregex, '<span class="color" data-color="$1"></span>') + html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>') + html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>') + html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>') + return html } -if (typeof mermaid !== 'undefined' && mermaid) mermaid.startOnLoad = false; +if (typeof window.mermaid !== 'undefined' && window.mermaid) window.mermaid.startOnLoad = false -//dynamic event or object binding here -export function finishView(view) { - //todo list - const lis = view.find('li.raw').removeClass("raw").sortByDepth().toArray(); +// dynamic event or object binding here +export function finishView (view) { + // todo list + const lis = view.find('li.raw').removeClass('raw').sortByDepth().toArray() - for (let li of lis) { - let html = $(li).clone()[0].innerHTML; - const p = $(li).children('p'); - if (p.length == 1) { - html = p.html(); - li = p[0]; - } - html = replaceExtraTags(html); - li.innerHTML = html; - let disabled = 'disabled'; - if(typeof editor !== 'undefined' && havePermission()) - disabled = ''; - if (/^\s*\[[x ]\]\s*/.test(html)) { - li.innerHTML = html.replace(/^\s*\[ \]\s*/, `<input type="checkbox" class="task-list-item-checkbox "${disabled}><label></label>`) - .replace(/^\s*\[x\]\s*/, `<input type="checkbox" class="task-list-item-checkbox" checked ${disabled}><label></label>`); - li.setAttribute('class', 'task-list-item'); - } - if (typeof editor !== 'undefined' && havePermission()) - $(li).find('input').change(toggleTodoEvent); - //color tag in list will convert it to tag icon with color - const tag_color = $(li).closest('ul').find(".color"); - tag_color.each((key, value) => { - $(value).addClass('fa fa-tag').css('color', $(value).attr('data-color')); - }); + for (let li of lis) { + let html = $(li).clone()[0].innerHTML + const p = $(li).children('p') + if (p.length === 1) { + html = p.html() + li = p[0] } - - //youtube - view.find("div.youtube.raw").removeClass("raw") + html = replaceExtraTags(html) + li.innerHTML = html + let disabled = 'disabled' + if (typeof editor !== 'undefined' && window.havePermission()) { disabled = '' } + if (/^\s*\[[x ]\]\s*/.test(html)) { + li.innerHTML = html.replace(/^\s*\[ \]\s*/, `<input type="checkbox" class="task-list-item-checkbox "${disabled}><label></label>`) + .replace(/^\s*\[x\]\s*/, `<input type="checkbox" class="task-list-item-checkbox" checked ${disabled}><label></label>`) + li.setAttribute('class', 'task-list-item') + } + if (typeof editor !== 'undefined' && window.havePermission()) { $(li).find('input').change(toggleTodoEvent) } + // color tag in list will convert it to tag icon with color + const tagColor = $(li).closest('ul').find('.color') + tagColor.each((key, value) => { + $(value).addClass('fa fa-tag').css('color', $(value).attr('data-color')) + }) + } + + // youtube + view.find('div.youtube.raw').removeClass('raw') .click(function () { - imgPlayiframe(this, '//www.youtube.com/embed/'); - }); - //vimeo - view.find("div.vimeo.raw").removeClass("raw") + imgPlayiframe(this, '//www.youtube.com/embed/') + }) + // vimeo + view.find('div.vimeo.raw').removeClass('raw') .click(function () { - imgPlayiframe(this, '//player.vimeo.com/video/'); + imgPlayiframe(this, '//player.vimeo.com/video/') }) .each((key, value) => { - $.ajax({ - type: 'GET', - url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`, - jsonp: 'callback', - dataType: 'jsonp', - success(data) { - const thumbnail_src = data[0].thumbnail_large; - const image = `<img src="${thumbnail_src}" />`; - $(value).prepend(image); - if(viewAjaxCallback) viewAjaxCallback(); - } - }); - }); - //gist - view.find("code[data-gist-id]").each((key, value) => { - if ($(value).children().length == 0) - $(value).gist(viewAjaxCallback); - }); - //sequence diagram - const sequences = view.find("div.sequence-diagram.raw").removeClass("raw"); - sequences.each((key, value) => { - try { - var $value = $(value); - const $ele = $(value).parent().parent(); - - const sequence = $value; - sequence.sequenceDiagram({ - theme: 'simple' - }); - - $ele.addClass('sequence-diagram'); - $value.children().unwrap().unwrap(); - const svg = $ele.find('> svg'); - svg[0].setAttribute('viewBox', `0 0 ${svg.attr('width')} ${svg.attr('height')}`); - svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet'); - } catch (err) { - $value.unwrap(); - $value.parent().append('<div class="alert alert-warning">' + err + '</div>'); - console.warn(err); - } - }); - //flowchart - const flow = view.find("div.flow-chart.raw").removeClass("raw"); - flow.each((key, value) => { - try { - var $value = $(value); - const $ele = $(value).parent().parent(); - - const chart = flowchart.parse($value.text()); - $value.html(''); - chart.drawSVG(value, { - 'line-width': 2, - 'fill': 'none', - 'font-size': '16px', - 'font-family': "'Andale Mono', monospace" - }); - - $ele.addClass('flow-chart'); - $value.children().unwrap().unwrap(); - } catch (err) { - $value.unwrap(); - $value.parent().append('<div class="alert alert-warning">' + err + '</div>'); - console.warn(err); - } - }); - //graphviz - var graphvizs = view.find("div.graphviz.raw").removeClass("raw"); - graphvizs.each(function (key, value) { - try { - var $value = $(value); - var $ele = $(value).parent().parent(); - - var graphviz = Viz($value.text()); - if (!graphviz) throw Error('viz.js output empty graph'); - $value.html(graphviz); - - $ele.addClass('graphviz'); - $value.children().unwrap().unwrap(); - } catch (err) { - $value.unwrap(); - $value.parent().append('<div class="alert alert-warning">' + err + '</div>'); - console.warn(err); - } - }); - //mermaid - const mermaids = view.find("div.mermaid.raw").removeClass("raw"); - mermaids.each((key, value) => { - try { - var $value = $(value); - const $ele = $(value).closest('pre'); - - let mermaidError = null; - mermaid.parseError = (err, hash) => { - mermaidError = err; - }; - - if (mermaidAPI.parse($value.text())) { - $ele.addClass('mermaid'); - $ele.html($value.text()); - mermaid.init(undefined, $ele); - } else { - throw new Error(mermaidError); + $.ajax({ + type: 'GET', + url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`, + jsonp: 'callback', + dataType: 'jsonp', + success (data) { + const thumbnailSrc = data[0].thumbnail_large + const image = `<img src="${thumbnailSrc}" />` + $(value).prepend(image) + if (window.viewAjaxCallback) window.viewAjaxCallback() } - } catch (err) { - $value.unwrap(); - $value.parent().append('<div class="alert alert-warning">' + err + '</div>'); - console.warn(err); - } - }); - //image href new window(emoji not included) - const images = view.find("img.raw[src]").removeClass("raw"); - images.each((key, value) => { + }) + }) + // gist + view.find('code[data-gist-id]').each((key, value) => { + if ($(value).children().length === 0) { $(value).gist(window.viewAjaxCallback) } + }) + // sequence diagram + const sequences = view.find('div.sequence-diagram.raw').removeClass('raw') + sequences.each((key, value) => { + try { + var $value = $(value) + const $ele = $(value).parent().parent() + + const sequence = $value + sequence.sequenceDiagram({ + theme: 'simple' + }) + + $ele.addClass('sequence-diagram') + $value.children().unwrap().unwrap() + const svg = $ele.find('> svg') + svg[0].setAttribute('viewBox', `0 0 ${svg.attr('width')} ${svg.attr('height')}`) + svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet') + } catch (err) { + $value.unwrap() + $value.parent().append('<div class="alert alert-warning">' + err + '</div>') + console.warn(err) + } + }) + // flowchart + const flow = view.find('div.flow-chart.raw').removeClass('raw') + flow.each((key, value) => { + try { + var $value = $(value) + const $ele = $(value).parent().parent() + + const chart = window.flowchart.parse($value.text()) + $value.html('') + chart.drawSVG(value, { + 'line-width': 2, + 'fill': 'none', + 'font-size': '16px', + 'font-family': "'Andale Mono', monospace" + }) + + $ele.addClass('flow-chart') + $value.children().unwrap().unwrap() + } catch (err) { + $value.unwrap() + $value.parent().append('<div class="alert alert-warning">' + err + '</div>') + console.warn(err) + } + }) + // graphviz + var graphvizs = view.find('div.graphviz.raw').removeClass('raw') + graphvizs.each(function (key, value) { + try { + var $value = $(value) + var $ele = $(value).parent().parent() + + var graphviz = Viz($value.text()) + if (!graphviz) throw Error('viz.js output empty graph') + $value.html(graphviz) + + $ele.addClass('graphviz') + $value.children().unwrap().unwrap() + } catch (err) { + $value.unwrap() + $value.parent().append('<div class="alert alert-warning">' + err + '</div>') + console.warn(err) + } + }) + // mermaid + const mermaids = view.find('div.mermaid.raw').removeClass('raw') + mermaids.each((key, value) => { + try { + var $value = $(value) + const $ele = $(value).closest('pre') + + let mermaidError = null + window.mermaid.parseError = (err, hash) => { + mermaidError = err + } + + if (window.mermaidAPI.parse($value.text())) { + $ele.addClass('mermaid') + $ele.html($value.text()) + window.mermaid.init(undefined, $ele) + } else { + throw new Error(mermaidError) + } + } catch (err) { + $value.unwrap() + $value.parent().append('<div class="alert alert-warning">' + err + '</div>') + console.warn(err) + } + }) + // image href new window(emoji not included) + const images = view.find('img.raw[src]').removeClass('raw') + images.each((key, value) => { // if it's already wrapped by link, then ignore - const $value = $(value); - $value[0].onload = e => { - if(viewAjaxCallback) viewAjaxCallback(); - }; - }); - //blockquote - const blockquote = view.find("blockquote.raw").removeClass("raw"); - const blockquote_p = blockquote.find("p"); - blockquote_p.each((key, value) => { - let html = $(value).html(); - html = replaceExtraTags(html); - $(value).html(html); - }); - //color tag in blockquote will change its left border color - const blockquote_color = blockquote.find(".color"); - blockquote_color.each((key, value) => { - $(value).closest("blockquote").css('border-left-color', $(value).attr('data-color')); - }); - //slideshare - view.find("div.slideshare.raw").removeClass("raw") + const $value = $(value) + $value[0].onload = e => { + if (window.viewAjaxCallback) window.viewAjaxCallback() + } + }) + // blockquote + const blockquote = view.find('blockquote.raw').removeClass('raw') + const blockquoteP = blockquote.find('p') + blockquoteP.each((key, value) => { + let html = $(value).html() + html = replaceExtraTags(html) + $(value).html(html) + }) + // color tag in blockquote will change its left border color + const blockquoteColor = blockquote.find('.color') + blockquoteColor.each((key, value) => { + $(value).closest('blockquote').css('border-left-color', $(value).attr('data-color')) + }) + // slideshare + view.find('div.slideshare.raw').removeClass('raw') .each((key, value) => { - $.ajax({ - type: 'GET', - url: `//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`, - jsonp: 'callback', - dataType: 'jsonp', - success(data) { - const $html = $(data.html); - const iframe = $html.closest('iframe'); - const caption = $html.closest('div'); - const inner = $('<div class="inner"></div>').append(iframe); - const height = iframe.attr('height'); - const width = iframe.attr('width'); - const ratio = (height / width) * 100; - inner.css('padding-bottom', `${ratio}%`); - $(value).html(inner).append(caption); - if(viewAjaxCallback) viewAjaxCallback(); - } - }); - }); - //speakerdeck - view.find("div.speakerdeck.raw").removeClass("raw") + $.ajax({ + type: 'GET', + url: `//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`, + jsonp: 'callback', + dataType: 'jsonp', + success (data) { + const $html = $(data.html) + const iframe = $html.closest('iframe') + const caption = $html.closest('div') + const inner = $('<div class="inner"></div>').append(iframe) + const height = iframe.attr('height') + const width = iframe.attr('width') + const ratio = (height / width) * 100 + inner.css('padding-bottom', `${ratio}%`) + $(value).html(inner).append(caption) + if (window.viewAjaxCallback) window.viewAjaxCallback() + } + }) + }) + // speakerdeck + view.find('div.speakerdeck.raw').removeClass('raw') .each((key, value) => { - const url = `https://speakerdeck.com/oembed.json?url=https%3A%2F%2Fspeakerdeck.com%2F${encodeURIComponent($(value).attr('data-speakerdeckid'))}`; - //use yql because speakerdeck not support jsonp - $.ajax({ - url: 'https://query.yahooapis.com/v1/public/yql', - data: { - q: `select * from json where url ='${url}'`, - format: "json" - }, - dataType: "jsonp", - success(data) { - if (!data.query || !data.query.results) return; - const json = data.query.results.json; - const html = json.html; - var ratio = json.height / json.width; - $(value).html(html); - const iframe = $(value).children('iframe'); - const src = iframe.attr('src'); - if (src.indexOf('//') == 0) - iframe.attr('src', `https:${src}`); - const inner = $('<div class="inner"></div>').append(iframe); - const height = iframe.attr('height'); - const width = iframe.attr('width'); - var ratio = (height / width) * 100; - inner.css('padding-bottom', `${ratio}%`); - $(value).html(inner); - if(viewAjaxCallback) viewAjaxCallback(); - } - }); - }); - //pdf - view.find("div.pdf.raw").removeClass("raw") + const url = `https://speakerdeck.com/oembed.json?url=https%3A%2F%2Fspeakerdeck.com%2F${encodeURIComponent($(value).attr('data-speakerdeckid'))}` + // use yql because speakerdeck not support jsonp + $.ajax({ + url: 'https://query.yahooapis.com/v1/public/yql', + data: { + q: `select * from json where url ='${url}'`, + format: 'json' + }, + dataType: 'jsonp', + success (data) { + if (!data.query || !data.query.results) return + const json = data.query.results.json + const html = json.html + var ratio = json.height / json.width + $(value).html(html) + const iframe = $(value).children('iframe') + const src = iframe.attr('src') + if (src.indexOf('//') === 0) { iframe.attr('src', `https:${src}`) } + const inner = $('<div class="inner"></div>').append(iframe) + const height = iframe.attr('height') + const width = iframe.attr('width') + ratio = (height / width) * 100 + inner.css('padding-bottom', `${ratio}%`) + $(value).html(inner) + if (window.viewAjaxCallback) window.viewAjaxCallback() + } + }) + }) + // pdf + view.find('div.pdf.raw').removeClass('raw') .each(function (key, value) { - const url = $(value).attr('data-pdfurl'); - const inner = $('<div></div>'); - $(this).append(inner); - PDFObject.embed(url, inner, { - height: '400px' - }); - }); - //syntax highlighting - view.find("code.raw").removeClass("raw") + const url = $(value).attr('data-pdfurl') + const inner = $('<div></div>') + $(this).append(inner) + PDFObject.embed(url, inner, { + height: '400px' + }) + }) + // syntax highlighting + view.find('code.raw').removeClass('raw') .each((key, value) => { - const langDiv = $(value); - if (langDiv.length > 0) { - const reallang = langDiv[0].className.replace(/hljs|wrap/g, '').trim(); - const codeDiv = langDiv.find('.code'); - let code = ""; - if (codeDiv.length > 0) code = codeDiv.html(); - else code = langDiv.html(); - if (!reallang) { - var result = { - value: code - }; - } else if (reallang == "haskell" || reallang == "go" || reallang == "typescript" || reallang == "jsx") { - code = S(code).unescapeHTML().s; - var result = { - value: Prism.highlight(code, Prism.languages[reallang]) - }; - } else if (reallang == "tiddlywiki" || reallang == "mediawiki") { - code = S(code).unescapeHTML().s; - var result = { - value: Prism.highlight(code, Prism.languages.wiki) - }; - } else { - code = S(code).unescapeHTML().s; - const languages = hljs.listLanguages(); - if (!languages.includes(reallang)) { - var result = hljs.highlightAuto(code); - } else { - var result = hljs.highlight(reallang, code); - } - } - if (codeDiv.length > 0) codeDiv.html(result.value); - else langDiv.html(result.value); + const langDiv = $(value) + if (langDiv.length > 0) { + const reallang = langDiv[0].className.replace(/hljs|wrap/g, '').trim() + const codeDiv = langDiv.find('.code') + let code = '' + if (codeDiv.length > 0) code = codeDiv.html() + else code = langDiv.html() + var result + if (!reallang) { + result = { + value: code + } + } else if (reallang === 'haskell' || reallang === 'go' || reallang === 'typescript' || reallang === 'jsx') { + code = S(code).unescapeHTML().s + result = { + value: Prism.highlight(code, Prism.languages[reallang]) + } + } else if (reallang === 'tiddlywiki' || reallang === 'mediawiki') { + code = S(code).unescapeHTML().s + result = { + value: Prism.highlight(code, Prism.languages.wiki) + } + } else { + code = S(code).unescapeHTML().s + const languages = hljs.listLanguages() + if (!languages.includes(reallang)) { + result = hljs.highlightAuto(code) + } else { + result = hljs.highlight(reallang, code) + } } - }); - //mathjax - const mathjaxdivs = view.find('span.mathjax.raw').removeClass("raw").toArray(); - try { - if (mathjaxdivs.length > 1) { - MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs]); - MathJax.Hub.Queue(viewAjaxCallback); - } else if (mathjaxdivs.length > 0) { - MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs[0]]); - MathJax.Hub.Queue(viewAjaxCallback); - } - } catch (err) { - console.warn(err); + if (codeDiv.length > 0) codeDiv.html(result.value) + else langDiv.html(result.value) + } + }) + // mathjax + const mathjaxdivs = view.find('span.mathjax.raw').removeClass('raw').toArray() + try { + if (mathjaxdivs.length > 1) { + window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, mathjaxdivs]) + window.MathJax.Hub.Queue(window.viewAjaxCallback) + } else if (mathjaxdivs.length > 0) { + window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, mathjaxdivs[0]]) + window.MathJax.Hub.Queue(window.viewAjaxCallback) } - //render title - document.title = renderTitle(view); + } catch (err) { + console.warn(err) + } + // render title + document.title = renderTitle(view) } -//only static transform should be here -export function postProcess(code) { - const result = $(`<div>${code}</div>`); - //link should open in new window or tab - result.find('a:not([href^="#"]):not([target])').attr('target', '_blank'); - //update continue line numbers - const linenumberdivs = result.find('.gutter.linenumber').toArray(); - for (let i = 0; i < linenumberdivs.length; i++) { - if ($(linenumberdivs[i]).hasClass('continue')) { - const startnumber = linenumberdivs[i - 1] ? parseInt($(linenumberdivs[i - 1]).find('> span').last().attr('data-linenumber')) : 0; - $(linenumberdivs[i]).find('> span').each((key, value) => { - $(value).attr('data-linenumber', startnumber + key + 1); - }); - } - } - // show yaml meta paring error - if (md.metaError) { - var warning = result.find('div#meta-error'); - if (warning && warning.length > 0) { - warning.text(md.metaError) - } else { - warning = $('<div id="meta-error" class="alert alert-warning">' + md.metaError + '</div>') - result.prepend(warning); - } +// only static transform should be here +export function postProcess (code) { + const result = $(`<div>${code}</div>`) + // link should open in new window or tab + result.find('a:not([href^="#"]):not([target])').attr('target', '_blank') + // update continue line numbers + const linenumberdivs = result.find('.gutter.linenumber').toArray() + for (let i = 0; i < linenumberdivs.length; i++) { + if ($(linenumberdivs[i]).hasClass('continue')) { + const startnumber = linenumberdivs[i - 1] ? parseInt($(linenumberdivs[i - 1]).find('> span').last().attr('data-linenumber')) : 0 + $(linenumberdivs[i]).find('> span').each((key, value) => { + $(value).attr('data-linenumber', startnumber + key + 1) + }) + } + } + // show yaml meta paring error + if (md.metaError) { + var warning = result.find('div#meta-error') + if (warning && warning.length > 0) { + warning.text(md.metaError) + } else { + warning = $('<div id="meta-error" class="alert alert-warning">' + md.metaError + '</div>') + result.prepend(warning) } - return result; + } + return result } -window.postProcess = postProcess; - -function generateCleanHTML(view) { - const src = view.clone(); - const eles = src.find('*'); - //remove syncscroll parts - eles.removeClass('part'); - src.find('*[class=""]').removeAttr('class'); - eles.removeAttr('data-startline data-endline'); - src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll'); - //remove gist content - src.find("code[data-gist-id]").children().remove(); - //disable todo list - src.find("input.task-list-item-checkbox").attr('disabled', ''); - //replace emoji image path - src.find("img.emoji").each((key, value) => { - let name = $(value).attr('alt'); - name = name.substr(1); - name = name.slice(0, name.length - 1); - $(value).attr('src', `https://www.tortue.me/emoji/${name}.png`); - }); - //replace video to iframe - src.find("div[data-videoid]").each((key, value) => { - const id = $(value).attr('data-videoid'); - const style = $(value).attr('style'); - let url = null; - if ($(value).hasClass('youtube')) { - url = 'https://www.youtube.com/embed/'; - } else if ($(value).hasClass('vimeo')) { - url = 'https://player.vimeo.com/video/'; - } - if (url) { - const iframe = $('<iframe frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'); - iframe.attr('src', url + id); - iframe.attr('style', style); - $(value).html(iframe); - } - }); - return src; +window.postProcess = postProcess + +function generateCleanHTML (view) { + const src = view.clone() + const eles = src.find('*') + // remove syncscroll parts + eles.removeClass('part') + src.find('*[class=""]').removeAttr('class') + eles.removeAttr('data-startline data-endline') + src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll') + // remove gist content + src.find('code[data-gist-id]').children().remove() + // disable todo list + src.find('input.task-list-item-checkbox').attr('disabled', '') + // replace emoji image path + src.find('img.emoji').each((key, value) => { + let name = $(value).attr('alt') + name = name.substr(1) + name = name.slice(0, name.length - 1) + $(value).attr('src', `https://www.tortue.me/emoji/${name}.png`) + }) + // replace video to iframe + src.find('div[data-videoid]').each((key, value) => { + const id = $(value).attr('data-videoid') + const style = $(value).attr('style') + let url = null + if ($(value).hasClass('youtube')) { + url = 'https://www.youtube.com/embed/' + } else if ($(value).hasClass('vimeo')) { + url = 'https://player.vimeo.com/video/' + } + if (url) { + const iframe = $('<iframe frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>') + iframe.attr('src', url + id) + iframe.attr('style', style) + $(value).html(iframe) + } + }) + return src } -export function exportToRawHTML(view) { - const filename = `${renderFilename(ui.area.markdown)}.html`; - const src = generateCleanHTML(view); - $(src).find('a.anchor').remove(); - const html = src[0].outerHTML; - const blob = new Blob([html], { - type: "text/html;charset=utf-8" - }); - saveAs(blob, filename, true); +export function exportToRawHTML (view) { + const filename = `${renderFilename(window.ui.area.markdown)}.html` + const src = generateCleanHTML(view) + $(src).find('a.anchor').remove() + const html = src[0].outerHTML + const blob = new Blob([html], { + type: 'text/html;charset=utf-8' + }) + saveAs(blob, filename, true) } -//extract markdown body to html and compile to template -export function exportToHTML(view) { - const title = renderTitle(ui.area.markdown); - const filename = `${renderFilename(ui.area.markdown)}.html`; - const src = generateCleanHTML(view); - //generate toc - const toc = $('#ui-toc').clone(); - toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll'); - const tocAffix = $('#ui-toc-affix').clone(); - tocAffix.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll'); - //generate html via template - $.get(`${serverurl}/build/html.min.css`, css => { - $.get(`${serverurl}/views/html.hbs`, data => { - const template = Handlebars.compile(data); - const context = { - url: serverurl, - title, - css, - html: src[0].outerHTML, - 'ui-toc': toc.html(), - 'ui-toc-affix': tocAffix.html(), - lang: (md && md.meta && md.meta.lang) ? `lang="${md.meta.lang}"` : null, - dir: (md && md.meta && md.meta.dir) ? `dir="${md.meta.dir}"` : null - }; - const html = template(context); +// extract markdown body to html and compile to template +export function exportToHTML (view) { + const title = renderTitle(window.ui.area.markdown) + const filename = `${renderFilename(window.ui.area.markdown)}.html` + const src = generateCleanHTML(view) + // generate toc + const toc = $('#ui-toc').clone() + toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll') + const tocAffix = $('#ui-toc-affix').clone() + tocAffix.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll') + // generate html via template + $.get(`${serverurl}/build/html.min.css`, css => { + $.get(`${serverurl}/views/html.hbs`, data => { + const template = window.Handlebars.compile(data) + const context = { + url: serverurl, + title, + css, + html: src[0].outerHTML, + 'ui-toc': toc.html(), + 'ui-toc-affix': tocAffix.html(), + lang: (md && md.meta && md.meta.lang) ? `lang="${md.meta.lang}"` : null, + dir: (md && md.meta && md.meta.dir) ? `dir="${md.meta.dir}"` : null + } + const html = template(context) // console.log(html); - const blob = new Blob([html], { - type: "text/html;charset=utf-8" - }); - saveAs(blob, filename, true); - }); - }); + const blob = new Blob([html], { + type: 'text/html;charset=utf-8' + }) + saveAs(blob, filename, true) + }) + }) } -//jQuery sortByDepth +// jQuery sortByDepth $.fn.sortByDepth = function () { - const ar = this.map(function () { - return { - length: $(this).parents().length, - elt: this - } - }).get(); - - const result = []; - let i = ar.length; - ar.sort((a, b) => a.length - b.length); - while (i--) { - result.push(ar[i].elt); - } - return $(result); -}; - -function toggleTodoEvent(e) { - const startline = $(this).closest('li').attr('data-startline') - 1; - const line = editor.getLine(startline); - const matches = line.match(/^[>\s]*[\-\+\*]\s\[([x ])\]/); - if (matches && matches.length >= 2) { - let checked = null; - if (matches[1] == 'x') - checked = true; - else if (matches[1] == ' ') - checked = false; - const replacements = matches[0].match(/(^[>\s]*[\-\+\*]\s\[)([x ])(\])/); - editor.replaceRange(checked ? ' ' : 'x', { - line: startline, - ch: replacements[1].length - }, { - line: startline, - ch: replacements[1].length + 1 - }, '+input'); + const ar = this.map(function () { + return { + length: $(this).parents().length, + elt: this } + }).get() + + const result = [] + let i = ar.length + ar.sort((a, b) => a.length - b.length) + while (i--) { + result.push(ar[i].elt) + } + return $(result) } -//remove hash -function removeHash() { - history.pushState("", document.title, window.location.pathname + window.location.search); +function toggleTodoEvent (e) { + const startline = $(this).closest('li').attr('data-startline') - 1 + const line = window.editor.getLine(startline) + const matches = line.match(/^[>\s]*[-+*]\s\[([x ])\]/) + if (matches && matches.length >= 2) { + let checked = null + if (matches[1] === 'x') { checked = true } else if (matches[1] === ' ') { checked = false } + const replacements = matches[0].match(/(^[>\s]*[-+*]\s\[)([x ])(\])/) + window.editor.replaceRange(checked ? ' ' : 'x', { + line: startline, + ch: replacements[1].length + }, { + line: startline, + ch: replacements[1].length + 1 + }, '+input') + } } -let tocExpand = false; +// remove hash +function removeHash () { + history.pushState('', document.title, window.location.pathname + window.location.search) +} -function checkExpandToggle() { - const toc = $('.ui-toc-dropdown .toc'); - const toggle = $('.expand-toggle'); - if (!tocExpand) { - toc.removeClass('expand'); - toggle.text('Expand all'); - } else { - toc.addClass('expand'); - toggle.text('Collapse all'); - } +let tocExpand = false + +function checkExpandToggle () { + const toc = $('.ui-toc-dropdown .toc') + const toggle = $('.expand-toggle') + if (!tocExpand) { + toc.removeClass('expand') + toggle.text('Expand all') + } else { + toc.addClass('expand') + toggle.text('Collapse all') + } } -//toc -export function generateToc(id) { - const target = $(`#${id}`); - target.html(''); - new Toc('doc', { - 'level': 3, - 'top': -1, - 'class': 'toc', - 'ulClass': 'nav', - 'targetId': id, - 'process': getHeaderContent - }); - if (target.text() == 'undefined') - target.html(''); - const tocMenu = $('<div class="toc-menu"></div'); - const toggle = $('<a class="expand-toggle" href="#">Expand all</a>'); - const backtotop = $('<a class="back-to-top" href="#">Back to top</a>'); - const gotobottom = $('<a class="go-to-bottom" href="#">Go to bottom</a>'); - checkExpandToggle(); - toggle.click(e => { - e.preventDefault(); - e.stopPropagation(); - tocExpand = !tocExpand; - checkExpandToggle(); - }); - backtotop.click(e => { - e.preventDefault(); - e.stopPropagation(); - if (scrollToTop) - scrollToTop(); - removeHash(); - }); - gotobottom.click(e => { - e.preventDefault(); - e.stopPropagation(); - if (scrollToBottom) - scrollToBottom(); - removeHash(); - }); - tocMenu.append(toggle).append(backtotop).append(gotobottom); - target.append(tocMenu); +// toc +export function generateToc (id) { + const target = $(`#${id}`) + target.html('') + /* eslint-disable no-unused-vars */ + var toc = new window.Toc('doc', { + 'level': 3, + 'top': -1, + 'class': 'toc', + 'ulClass': 'nav', + 'targetId': id, + 'process': getHeaderContent + }) + /* eslint-enable no-unsed-vars */ + if (target.text() === 'undefined') { target.html('') } + const tocMenu = $('<div class="toc-menu"></div') + const toggle = $('<a class="expand-toggle" href="#">Expand all</a>') + const backtotop = $('<a class="back-to-top" href="#">Back to top</a>') + const gotobottom = $('<a class="go-to-bottom" href="#">Go to bottom</a>') + checkExpandToggle() + toggle.click(e => { + e.preventDefault() + e.stopPropagation() + tocExpand = !tocExpand + checkExpandToggle() + }) + backtotop.click(e => { + e.preventDefault() + e.stopPropagation() + if (window.scrollToTop) { window.scrollToTop() } + removeHash() + }) + gotobottom.click(e => { + e.preventDefault() + e.stopPropagation() + if (window.scrollToBottom) { window.scrollToBottom() } + removeHash() + }) + tocMenu.append(toggle).append(backtotop).append(gotobottom) + target.append(tocMenu) } -//smooth all hash trigger scrolling -export function smoothHashScroll() { - const hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray(); +// smooth all hash trigger scrolling +export function smoothHashScroll () { + const hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray() - for (const element of hashElements) { - const $element = $(element); - const hash = element.hash; - if (hash) { - $element.on('click', function (e) { + for (const element of hashElements) { + const $element = $(element) + const hash = element.hash + if (hash) { + $element.on('click', function (e) { // store hash - const hash = decodeURIComponent(this.hash); + const hash = decodeURIComponent(this.hash) // escape special characters in jquery selector - const $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, "\\$1")); + const $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, '\\$1')) // return if no element been selected - if ($hash.length <= 0) return; + if ($hash.length <= 0) return // prevent default anchor click behavior - e.preventDefault(); + e.preventDefault() // animate - $('body, html').stop(true, true).animate({ - scrollTop: $hash.offset().top - }, 100, "linear", () => { + $('body, html').stop(true, true).animate({ + scrollTop: $hash.offset().top + }, 100, 'linear', () => { // when done, add hash to url // (default click behaviour) - window.location.hash = hash; - }); - }); - $element.attr('smoothhashscroll', ''); - } + window.location.hash = hash + }) + }) + $element.attr('smoothhashscroll', '') } + } } -function imgPlayiframe(element, src) { - if (!$(element).attr("data-videoid")) return; - const iframe = $("<iframe frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>"); - $(iframe).attr("src", `${src + $(element).attr("data-videoid")}?autoplay=1`); - $(element).find('img').css('visibility', 'hidden'); - $(element).append(iframe); +function imgPlayiframe (element, src) { + if (!$(element).attr('data-videoid')) return + const iframe = $("<iframe frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>") + $(iframe).attr('src', `${src + $(element).attr('data-videoid')}?autoplay=1`) + $(element).find('img').css('visibility', 'hidden') + $(element).append(iframe) } const anchorForId = id => { - const anchor = document.createElement("a"); - anchor.className = "anchor hidden-xs"; - anchor.href = `#${id}`; - anchor.innerHTML = "<span class=\"octicon octicon-link\"></span>"; - anchor.title = id; - return anchor; -}; + const anchor = document.createElement('a') + anchor.className = 'anchor hidden-xs' + anchor.href = `#${id}` + anchor.innerHTML = '<span class="octicon octicon-link"></span>' + anchor.title = id + return anchor +} const linkifyAnchors = (level, containingElement) => { - const headers = containingElement.getElementsByTagName(`h${level}`); - - for (let i = 0, l = headers.length; i < l; i++) { - let header = headers[i]; - if (header.getElementsByClassName("anchor").length == 0) { - if (typeof header.id == "undefined" || header.id == "") { - //to escape characters not allow in css and humanize - const id = slugifyWithUTF8(getHeaderContent(header)); - header.id = id; - } - header.insertBefore(anchorForId(header.id), header.firstChild); - } + const headers = containingElement.getElementsByTagName(`h${level}`) + + for (let i = 0, l = headers.length; i < l; i++) { + let header = headers[i] + if (header.getElementsByClassName('anchor').length === 0) { + if (typeof header.id === 'undefined' || header.id === '') { + // to escape characters not allow in css and humanize + const id = slugifyWithUTF8(getHeaderContent(header)) + header.id = id + } + header.insertBefore(anchorForId(header.id), header.firstChild) } -}; + } +} -export function autoLinkify(view) { - const contentBlock = view[0]; - if (!contentBlock) { - return; - } - for (let level = 1; level <= 6; level++) { - linkifyAnchors(level, contentBlock); - } +export function autoLinkify (view) { + const contentBlock = view[0] + if (!contentBlock) { + return + } + for (let level = 1; level <= 6; level++) { + linkifyAnchors(level, contentBlock) + } } -function getHeaderContent(header) { - const headerHTML = $(header).clone(); - headerHTML.find('.MathJax_Preview').remove(); - headerHTML.find('.MathJax').remove(); - return headerHTML[0].innerHTML; +function getHeaderContent (header) { + const headerHTML = $(header).clone() + headerHTML.find('.MathJax_Preview').remove() + headerHTML.find('.MathJax').remove() + return headerHTML[0].innerHTML } -export function deduplicatedHeaderId(view) { - const headers = view.find(':header.raw').removeClass('raw').toArray(); - for (let i = 0; i < headers.length; i++) { - const id = $(headers[i]).attr('id'); - if (!id) continue; - const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray(); - for (let j = 0; j < duplicatedHeaders.length; j++) { - if (duplicatedHeaders[j] != headers[i]) { - const newId = id + j; - const $duplicatedHeader = $(duplicatedHeaders[j]); - $duplicatedHeader.attr('id', newId); - const $headerLink = $duplicatedHeader.find(`> a.anchor[href="#${id}"]`); - $headerLink.attr('href', `#${newId}`); - $headerLink.attr('title', newId); - } - } +export function deduplicatedHeaderId (view) { + const headers = view.find(':header.raw').removeClass('raw').toArray() + for (let i = 0; i < headers.length; i++) { + const id = $(headers[i]).attr('id') + if (!id) continue + const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray() + for (let j = 0; j < duplicatedHeaders.length; j++) { + if (duplicatedHeaders[j] !== headers[i]) { + const newId = id + j + const $duplicatedHeader = $(duplicatedHeaders[j]) + $duplicatedHeader.attr('id', newId) + const $headerLink = $duplicatedHeader.find(`> a.anchor[href="#${id}"]`) + $headerLink.attr('href', `#${newId}`) + $headerLink.attr('title', newId) + } } + } } -export function renderTOC(view) { - const tocs = view.find('.toc').toArray(); - for (let i = 0; i < tocs.length; i++) { - const toc = $(tocs[i]); - const id = `toc${i}`; - toc.attr('id', id); - const target = $(`#${id}`); - target.html(''); - new Toc('doc', { - 'level': 3, - 'top': -1, - 'class': 'toc', - 'targetId': id, - 'process': getHeaderContent - }); - if (target.text() == 'undefined') - target.html(''); - target.replaceWith(target.html()); - } +export function renderTOC (view) { + const tocs = view.find('.toc').toArray() + for (let i = 0; i < tocs.length; i++) { + const toc = $(tocs[i]) + const id = `toc${i}` + toc.attr('id', id) + const target = $(`#${id}`) + target.html('') + /* eslint-disable no-unused-vars */ + var toc = new window.Toc('doc', { + 'level': 3, + 'top': -1, + 'class': 'toc', + 'targetId': id, + 'process': getHeaderContent + }) + /* eslint-enable no-unused-vars */ + if (target.text() === 'undefined') { target.html('') } + target.replaceWith(target.html()) + } } -export function scrollToHash() { - const hash = location.hash; - location.hash = ""; - location.hash = hash; +export function scrollToHash () { + const hash = location.hash + location.hash = '' + location.hash = hash } -function highlightRender(code, lang) { - if (!lang || /no(-?)highlight|plain|text/.test(lang)) - return; - code = S(code).escapeHTML().s - if (lang == 'sequence') { - return `<div class="sequence-diagram raw">${code}</div>`; - } else if (lang == 'flow') { - return `<div class="flow-chart raw">${code}</div>`; - } else if (lang == 'graphviz') { - return `<div class="graphviz raw">${code}</div>`; - } else if (lang == 'mermaid') { - return `<div class="mermaid raw">${code}</div>`; +function highlightRender (code, lang) { + if (!lang || /no(-?)highlight|plain|text/.test(lang)) { return } + code = S(code).escapeHTML().s + if (lang === 'sequence') { + return `<div class="sequence-diagram raw">${code}</div>` + } else if (lang === 'flow') { + return `<div class="flow-chart raw">${code}</div>` + } else if (lang === 'graphviz') { + return `<div class="graphviz raw">${code}</div>` + } else if (lang === 'mermaid') { + return `<div class="mermaid raw">${code}</div>` + } + const result = { + value: code + } + const showlinenumbers = /=$|=\d+$|=\+$/.test(lang) + if (showlinenumbers) { + let startnumber = 1 + const matches = lang.match(/=(\d+)$/) + if (matches) { startnumber = parseInt(matches[1]) } + const lines = result.value.split('\n') + const linenumbers = [] + for (let i = 0; i < lines.length - 1; i++) { + linenumbers[i] = `<span data-linenumber='${startnumber + i}'></span>` } - const result = { - value: code - }; - const showlinenumbers = /\=$|\=\d+$|\=\+$/.test(lang); - if (showlinenumbers) { - let startnumber = 1; - const matches = lang.match(/\=(\d+)$/); - if (matches) - startnumber = parseInt(matches[1]); - const lines = result.value.split('\n'); - const linenumbers = []; - for (let i = 0; i < lines.length - 1; i++) { - linenumbers[i] = `<span data-linenumber='${startnumber + i}'></span>`; - } - const continuelinenumber = /\=\+$/.test(lang); - const linegutter = `<div class='gutter linenumber${continuelinenumber ? " continue" : ""}'>${linenumbers.join('\n')}</div>`; - result.value = `<div class='wrapper'>${linegutter}<div class='code'>${result.value}</div></div>`; - } - return result.value; + const continuelinenumber = /=\+$/.test(lang) + const linegutter = `<div class='gutter linenumber${continuelinenumber ? ' continue' : ''}'>${linenumbers.join('\n')}</div>` + result.value = `<div class='wrapper'>${linegutter}<div class='code'>${result.value}</div></div>` + } + return result.value } -import markdownit from 'markdown-it'; -import markdownitContainer from 'markdown-it-container'; +import markdownit from 'markdown-it' +import markdownitContainer from 'markdown-it-container' export let md = markdownit('default', { - html: true, - breaks: true, - langPrefix: "", - linkify: true, - typographer: true, - highlight: highlightRender -}); -window.md = md; - -md.use(require('markdown-it-abbr')); -md.use(require('markdown-it-footnote')); -md.use(require('markdown-it-deflist')); -md.use(require('markdown-it-mark')); -md.use(require('markdown-it-ins')); -md.use(require('markdown-it-sub')); -md.use(require('markdown-it-sup')); + html: true, + breaks: true, + langPrefix: '', + linkify: true, + typographer: true, + highlight: highlightRender +}) +window.md = md + +md.use(require('markdown-it-abbr')) +md.use(require('markdown-it-footnote')) +md.use(require('markdown-it-deflist')) +md.use(require('markdown-it-mark')) +md.use(require('markdown-it-ins')) +md.use(require('markdown-it-sub')) +md.use(require('markdown-it-sup')) md.use(require('markdown-it-mathjax')({ - beforeMath: '<span class="mathjax raw">', - afterMath: '</span>', - beforeInlineMath: '<span class="mathjax raw">\\(', - afterInlineMath: '\\)</span>', - beforeDisplayMath: '<span class="mathjax raw">\\[', - afterDisplayMath: '\\]</span>' -})); -md.use(require('markdown-it-imsize')); + beforeMath: '<span class="mathjax raw">', + afterMath: '</span>', + beforeInlineMath: '<span class="mathjax raw">\\(', + afterInlineMath: '\\)</span>', + beforeDisplayMath: '<span class="mathjax raw">\\[', + afterDisplayMath: '\\]</span>' +})) +md.use(require('markdown-it-imsize')) md.use(require('markdown-it-emoji'), { - shortcuts: {} -}); - -emojify.setConfig({ - blacklist: { - elements: ['script', 'textarea', 'a', 'pre', 'code', 'svg'], - classes: ['no-emojify'] - }, - img_dir: `${serverurl}/build/emojify.js/dist/images/basic`, - ignore_emoticons: true -}); - -md.renderer.rules.emoji = (token, idx) => emojify.replace(`:${token[idx].markup}:`); - -function renderContainer(tokens, idx, options, env, self) { - tokens[idx].attrJoin('role', 'alert'); - tokens[idx].attrJoin('class', 'alert'); - tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`); - return self.renderToken(...arguments); + shortcuts: {} +}) + +window.emojify.setConfig({ + blacklist: { + elements: ['script', 'textarea', 'a', 'pre', 'code', 'svg'], + classes: ['no-emojify'] + }, + img_dir: `${serverurl}/build/emojify.js/dist/images/basic`, + ignore_emoticons: true +}) + +md.renderer.rules.emoji = (token, idx) => window.emojify.replace(`:${token[idx].markup}:`) + +function renderContainer (tokens, idx, options, env, self) { + tokens[idx].attrJoin('role', 'alert') + tokens[idx].attrJoin('class', 'alert') + tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`) + return self.renderToken(...arguments) } -md.use(markdownitContainer, 'success', { render: renderContainer }); -md.use(markdownitContainer, 'info', { render: renderContainer }); -md.use(markdownitContainer, 'warning', { render: renderContainer }); -md.use(markdownitContainer, 'danger', { render: renderContainer }); +md.use(markdownitContainer, 'success', { render: renderContainer }) +md.use(markdownitContainer, 'info', { render: renderContainer }) +md.use(markdownitContainer, 'warning', { render: renderContainer }) +md.use(markdownitContainer, 'danger', { render: renderContainer }) md.renderer.rules.image = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw'); - return self.renderToken(...arguments); -}; + tokens[idx].attrJoin('class', 'raw') + return self.renderToken(...arguments) +} md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw'); - return self.renderToken(...arguments); -}; + tokens[idx].attrJoin('class', 'raw') + return self.renderToken(...arguments) +} md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw'); - return self.renderToken(...arguments); -}; + tokens[idx].attrJoin('class', 'raw') + return self.renderToken(...arguments) +} md.renderer.rules.heading_open = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw'); - return self.renderToken(...arguments); -}; + tokens[idx].attrJoin('class', 'raw') + return self.renderToken(...arguments) +} md.renderer.rules.fence = (tokens, idx, options, env, self) => { - const token = tokens[idx]; - const info = token.info ? md.utils.unescapeAll(token.info).trim() : ''; - let langName = ''; - let highlighted; - - if (info) { - langName = info.split(/\s+/g)[0]; - if (/\!$/.test(info)) token.attrJoin('class', 'wrap'); - token.attrJoin('class', options.langPrefix + langName.replace(/\=$|\=\d+$|\=\+$|\!$|\=\!$/, '')); - token.attrJoin('class', 'hljs'); - token.attrJoin('class', 'raw'); - } - - if (options.highlight) { - highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content); - } else { - highlighted = md.utils.escapeHtml(token.content); - } - - if (highlighted.indexOf('<pre') === 0) { - return `${highlighted}\n`; - } - - return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`; -}; + const token = tokens[idx] + const info = token.info ? md.utils.unescapeAll(token.info).trim() : '' + let langName = '' + let highlighted + + if (info) { + langName = info.split(/\s+/g)[0] + if (/!$/.test(info)) token.attrJoin('class', 'wrap') + token.attrJoin('class', options.langPrefix + langName.replace(/=$|=\d+$|=\+$|!$|=!$/, '')) + token.attrJoin('class', 'hljs') + token.attrJoin('class', 'raw') + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content) + } else { + highlighted = md.utils.escapeHtml(token.content) + } + + if (highlighted.indexOf('<pre') === 0) { + return `${highlighted}\n` + } + + return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n` +} /* Defined regex markdown it plugins */ -import Plugin from 'markdown-it-regexp'; +import Plugin from 'markdown-it-regexp' -//youtube +// youtube const youtubePlugin = new Plugin( // regexp to match /{%youtube\s*([\d\D]*?)\s*%}/, (match, utils) => { - const videoid = match[1]; - if (!videoid) return; - const div = $('<div class="youtube raw"></div>'); - div.attr('data-videoid', videoid); - const thumbnail_src = `//img.youtube.com/vi/${videoid}/hqdefault.jpg`; - const image = `<img src="${thumbnail_src}" />`; - div.append(image); - const icon = '<i class="icon fa fa-youtube-play fa-5x"></i>'; - div.append(icon); - return div[0].outerHTML; + const videoid = match[1] + if (!videoid) return + const div = $('<div class="youtube raw"></div>') + div.attr('data-videoid', videoid) + const thumbnailSrc = `//img.youtube.com/vi/${videoid}/hqdefault.jpg` + const image = `<img src="${thumbnailSrc}" />` + div.append(image) + const icon = '<i class="icon fa fa-youtube-play fa-5x"></i>' + div.append(icon) + return div[0].outerHTML } -); -//vimeo +) +// vimeo const vimeoPlugin = new Plugin( // regexp to match /{%vimeo\s*([\d\D]*?)\s*%}/, (match, utils) => { - const videoid = match[1]; - if (!videoid) return; - const div = $('<div class="vimeo raw"></div>'); - div.attr('data-videoid', videoid); - const icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>'; - div.append(icon); - return div[0].outerHTML; + const videoid = match[1] + if (!videoid) return + const div = $('<div class="vimeo raw"></div>') + div.attr('data-videoid', videoid) + const icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>' + div.append(icon) + return div[0].outerHTML } -); -//gist +) +// gist const gistPlugin = new Plugin( // regexp to match /{%gist\s*([\d\D]*?)\s*%}/, (match, utils) => { - const gistid = match[1]; - const code = `<code data-gist-id="${gistid}"/>`; - return code; + const gistid = match[1] + const code = `<code data-gist-id="${gistid}"/>` + return code } -); -//TOC +) +// TOC const tocPlugin = new Plugin( // regexp to match /^\[TOC\]$/i, (match, utils) => '<div class="toc"></div>' -); -//slideshare +) +// slideshare const slidesharePlugin = new Plugin( // regexp to match /{%slideshare\s*([\d\D]*?)\s*%}/, (match, utils) => { - const slideshareid = match[1]; - const div = $('<div class="slideshare raw"></div>'); - div.attr('data-slideshareid', slideshareid); - return div[0].outerHTML; + const slideshareid = match[1] + const div = $('<div class="slideshare raw"></div>') + div.attr('data-slideshareid', slideshareid) + return div[0].outerHTML } -); -//speakerdeck +) +// speakerdeck const speakerdeckPlugin = new Plugin( // regexp to match /{%speakerdeck\s*([\d\D]*?)\s*%}/, (match, utils) => { - const speakerdeckid = match[1]; - const div = $('<div class="speakerdeck raw"></div>'); - div.attr('data-speakerdeckid', speakerdeckid); - return div[0].outerHTML; + const speakerdeckid = match[1] + const div = $('<div class="speakerdeck raw"></div>') + div.attr('data-speakerdeckid', speakerdeckid) + return div[0].outerHTML } -); -//pdf +) +// pdf const pdfPlugin = new Plugin( // regexp to match /{%pdf\s*([\d\D]*?)\s*%}/, (match, utils) => { - const pdfurl = match[1]; - if (!isValidURL(pdfurl)) return match[0]; - const div = $('<div class="pdf raw"></div>'); - div.attr('data-pdfurl', pdfurl); - return div[0].outerHTML; + const pdfurl = match[1] + if (!isValidURL(pdfurl)) return match[0] + const div = $('<div class="pdf raw"></div>') + div.attr('data-pdfurl', pdfurl) + return div[0].outerHTML } -); +) -//yaml meta, from https://github.com/eugeneware/remarkable-meta -function get(state, line) { - const pos = state.bMarks[line]; - const max = state.eMarks[line]; - return state.src.substr(pos, max - pos); +// yaml meta, from https://github.com/eugeneware/remarkable-meta +function get (state, line) { + const pos = state.bMarks[line] + const max = state.eMarks[line] + return state.src.substr(pos, max - pos) } -function meta(state, start, end, silent) { - if (start !== 0 || state.blkIndent !== 0) return false; - if (state.tShift[start] < 0) return false; - if (!get(state, start).match(/^---$/)) return false; - - const data = []; - for (var line = start + 1; line < end; line++) { - const str = get(state, line); - if (str.match(/^(\.{3}|-{3})$/)) break; - if (state.tShift[line] < 0) break; - data.push(str); - } - - if (line >= end) return false; - - try { - md.meta = jsyaml.safeLoad(data.join('\n')) || {}; - delete md.metaError; - } catch(err) { - md.metaError = err; - console.warn(err); - return false; - } - - state.line = line + 1; - - return true; +function meta (state, start, end, silent) { + if (start !== 0 || state.blkIndent !== 0) return false + if (state.tShift[start] < 0) return false + if (!get(state, start).match(/^---$/)) return false + + const data = [] + for (var line = start + 1; line < end; line++) { + const str = get(state, line) + if (str.match(/^(\.{3}|-{3})$/)) break + if (state.tShift[line] < 0) break + data.push(str) + } + + if (line >= end) return false + + try { + md.meta = window.jsyaml.safeLoad(data.join('\n')) || {} + delete md.metaError + } catch (err) { + md.metaError = err + console.warn(err) + return false + } + + state.line = line + 1 + + return true } -function metaPlugin(md) { - md.meta = md.meta || {}; - md.block.ruler.before('code', 'meta', meta, { - alt: [] - }); +function metaPlugin (md) { + md.meta = md.meta || {} + md.block.ruler.before('code', 'meta', meta, { + alt: [] + }) } -md.use(metaPlugin); -md.use(youtubePlugin); -md.use(vimeoPlugin); -md.use(gistPlugin); -md.use(tocPlugin); -md.use(slidesharePlugin); -md.use(speakerdeckPlugin); -md.use(pdfPlugin); +md.use(metaPlugin) +md.use(youtubePlugin) +md.use(vimeoPlugin) +md.use(gistPlugin) +md.use(tocPlugin) +md.use(slidesharePlugin) +md.use(speakerdeckPlugin) +md.use(pdfPlugin) export default { md -}; +} diff --git a/public/js/google-drive-picker.js b/public/js/google-drive-picker.js index 94aa77ff..5006cd25 100644 --- a/public/js/google-drive-picker.js +++ b/public/js/google-drive-picker.js @@ -1,119 +1,118 @@ -/**! +/** ! * Google Drive File Picker Example * By Daniel Lo Nigro (http://dan.cx/) */ -(function() { - /** - * Initialise a Google Driver file picker - */ - var FilePicker = window.FilePicker = function(options) { - // Config - this.apiKey = options.apiKey; - this.clientId = options.clientId; - - // Elements - this.buttonEl = options.buttonEl; - - // Events - this.onSelect = options.onSelect; - this.buttonEl.on('click', this.open.bind(this)); - - // Disable the button until the API loads, as it won't work properly until then. - this.buttonEl.prop('disabled', true); +(function () { + /** + * Initialise a Google Driver file picker + */ + var FilePicker = window.FilePicker = function (options) { + // Config + this.apiKey = options.apiKey + this.clientId = options.clientId - // Load the drive API - gapi.client.setApiKey(this.apiKey); - gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this)); - google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) }); - } + // Elements + this.buttonEl = options.buttonEl - FilePicker.prototype = { - /** - * Open the file picker. - */ - open: function() { - // Check if the user has already authenticated - var token = gapi.auth.getToken(); - if (token) { - this._showPicker(); - } else { - // The user has not yet authenticated with Google - // We need to do the authentication before displaying the Drive picker. - this._doAuth(false, function() { this._showPicker(); }.bind(this)); - } - }, - - /** - * Show the file picker once authentication has been done. - * @private - */ - _showPicker: function() { - var accessToken = gapi.auth.getToken().access_token; - var view = new google.picker.DocsView(); - view.setMimeTypes("text/markdown,text/html"); - view.setIncludeFolders(true); - view.setOwnedByMe(true); - this.picker = new google.picker.PickerBuilder(). - enableFeature(google.picker.Feature.NAV_HIDDEN). - addView(view). - setAppId(this.clientId). - setOAuthToken(accessToken). - setCallback(this._pickerCallback.bind(this)). - build(). - setVisible(true); - }, - - /** - * Called when a file has been selected in the Google Drive file picker. - * @private - */ - _pickerCallback: function(data) { - if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) { - var file = data[google.picker.Response.DOCUMENTS][0], - id = file[google.picker.Document.ID], - request = gapi.client.drive.files.get({ - fileId: id - }); - - request.execute(this._fileGetCallback.bind(this)); - } - }, - /** - * Called when file details have been retrieved from Google Drive. - * @private - */ - _fileGetCallback: function(file) { - if (this.onSelect) { - this.onSelect(file); - } - }, - - /** - * Called when the Google Drive file picker API has finished loading. - * @private - */ - _pickerApiLoaded: function() { - this.buttonEl.prop('disabled', false); - }, - - /** - * Called when the Google Drive API has finished loading. - * @private - */ - _driveApiLoaded: function() { - this._doAuth(true); - }, - - /** - * Authenticate with Google Drive via the Google JavaScript API. - * @private - */ - _doAuth: function(immediate, callback) { - gapi.auth.authorize({ - client_id: this.clientId, - scope: 'https://www.googleapis.com/auth/drive.readonly', - immediate: immediate - }, callback ? callback : function() {}); - } - }; -}()); + // Events + this.onSelect = options.onSelect + this.buttonEl.on('click', this.open.bind(this)) + + // Disable the button until the API loads, as it won't work properly until then. + this.buttonEl.prop('disabled', true) + + // Load the drive API + window.gapi.client.setApiKey(this.apiKey) + window.gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this)) + window.google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) }) + } + + FilePicker.prototype = { + /** + * Open the file picker. + */ + open: function () { + // Check if the user has already authenticated + var token = window.gapi.auth.getToken() + if (token) { + this._showPicker() + } else { + // The user has not yet authenticated with Google + // We need to do the authentication before displaying the Drive picker. + this._doAuth(false, function () { this._showPicker() }.bind(this)) + } + }, + + /** + * Show the file picker once authentication has been done. + * @private + */ + _showPicker: function () { + var accessToken = window.gapi.auth.getToken().access_token + var view = new window.google.picker.DocsView() + view.setMimeTypes('text/markdown,text/html') + view.setIncludeFolders(true) + view.setOwnedByMe(true) + this.picker = new window.google.picker.PickerBuilder() + .enableFeature(window.google.picker.Feature.NAV_HIDDEN) + .addView(view) + .setAppId(this.clientId) + .setOAuthToken(accessToken) + .setCallback(this._pickerCallback.bind(this)) + .build() + .setVisible(true) + }, + + /** + * Called when a file has been selected in the Google Drive file picker. + * @private + */ + _pickerCallback: function (data) { + if (data[window.google.picker.Response.ACTION] === window.google.picker.Action.PICKED) { + var file = data[window.google.picker.Response.DOCUMENTS][0] + var id = file[window.google.picker.Document.ID] + var request = window.gapi.client.drive.files.get({ + fileId: id + }) + request.execute(this._fileGetCallback.bind(this)) + } + }, + /** + * Called when file details have been retrieved from Google Drive. + * @private + */ + _fileGetCallback: function (file) { + if (this.onSelect) { + this.onSelect(file) + } + }, + + /** + * Called when the Google Drive file picker API has finished loading. + * @private + */ + _pickerApiLoaded: function () { + this.buttonEl.prop('disabled', false) + }, + + /** + * Called when the Google Drive API has finished loading. + * @private + */ + _driveApiLoaded: function () { + this._doAuth(true) + }, + + /** + * Authenticate with Google Drive via the Google JavaScript API. + * @private + */ + _doAuth: function (immediate, callback) { + window.gapi.auth.authorize({ + client_id: this.clientId, + scope: 'https://www.googleapis.com/auth/drive.readonly', + immediate: immediate + }, callback || function () {}) + } + } +}()) diff --git a/public/js/google-drive-upload.js b/public/js/google-drive-upload.js index eabc5b7f..6c0e8a62 100644 --- a/public/js/google-drive-upload.js +++ b/public/js/google-drive-upload.js @@ -1,30 +1,31 @@ +/* eslint-env browser, jquery */ /** * Helper for implementing retries with backoff. Initial retry * delay is 1 second, increasing by 2x (+jitter) for subsequent retries * * @constructor */ -var RetryHandler = function() { - this.interval = 1000; // Start at one second - this.maxInterval = 60 * 1000; // Don't wait longer than a minute -}; +var RetryHandler = function () { + this.interval = 1000 // Start at one second + this.maxInterval = 60 * 1000 // Don't wait longer than a minute +} /** * Invoke the function after waiting * * @param {function} fn Function to invoke */ -RetryHandler.prototype.retry = function(fn) { - setTimeout(fn, this.interval); - this.interval = this.nextInterval_(); -}; +RetryHandler.prototype.retry = function (fn) { + setTimeout(fn, this.interval) + this.interval = this.nextInterval_() +} /** * Reset the counter (e.g. after successful request.) */ -RetryHandler.prototype.reset = function() { - this.interval = 1000; -}; +RetryHandler.prototype.reset = function () { + this.interval = 1000 +} /** * Calculate the next wait time. @@ -32,10 +33,10 @@ RetryHandler.prototype.reset = function() { * * @private */ -RetryHandler.prototype.nextInterval_ = function() { - var interval = this.interval * 2 + this.getRandomInt_(0, 1000); - return Math.min(interval, this.maxInterval); -}; +RetryHandler.prototype.nextInterval_ = function () { + var interval = this.interval * 2 + this.getRandomInt_(0, 1000) + return Math.min(interval, this.maxInterval) +} /** * Get a random int in the range of min to max. Used to add jitter to wait times. @@ -44,10 +45,9 @@ RetryHandler.prototype.nextInterval_ = function() { * @param {number} max Upper bounds * @private */ -RetryHandler.prototype.getRandomInt_ = function(min, max) { - return Math.floor(Math.random() * (max - min + 1) + min); -}; - +RetryHandler.prototype.getRandomInt_ = function (min, max) { + return Math.floor(Math.random() * (max - min + 1) + min) +} /** * Helper class for resumable uploads using XHR/CORS. Can upload any Blob-like item, whether @@ -75,116 +75,115 @@ RetryHandler.prototype.getRandomInt_ = function(min, max) { * @param {function} [options.onProgress] Callback for status for the in-progress upload * @param {function} [options.onError] Callback if upload fails */ -var MediaUploader = function(options) { - var noop = function() {}; - this.file = options.file; - this.contentType = options.contentType || this.file.type || 'application/octet-stream'; +var MediaUploader = function (options) { + var noop = function () {} + this.file = options.file + this.contentType = options.contentType || this.file.type || 'application/octet-stream' this.metadata = options.metadata || { 'title': this.file.name, 'mimeType': this.contentType - }; - this.token = options.token; - this.onComplete = options.onComplete || noop; - this.onProgress = options.onProgress || noop; - this.onError = options.onError || noop; - this.offset = options.offset || 0; - this.chunkSize = options.chunkSize || 0; - this.retryHandler = new RetryHandler(); + } + this.token = options.token + this.onComplete = options.onComplete || noop + this.onProgress = options.onProgress || noop + this.onError = options.onError || noop + this.offset = options.offset || 0 + this.chunkSize = options.chunkSize || 0 + this.retryHandler = new RetryHandler() - this.url = options.url; + this.url = options.url if (!this.url) { - var params = options.params || {}; - params.uploadType = 'resumable'; - this.url = this.buildUrl_(options.fileId, params, options.baseUrl); + var params = options.params || {} + params.uploadType = 'resumable' + this.url = this.buildUrl_(options.fileId, params, options.baseUrl) } - this.httpMethod = options.fileId ? 'PUT' : 'POST'; -}; + this.httpMethod = options.fileId ? 'PUT' : 'POST' +} /** * Initiate the upload. */ -MediaUploader.prototype.upload = function() { - var self = this; - var xhr = new XMLHttpRequest(); +MediaUploader.prototype.upload = function () { + var xhr = new XMLHttpRequest() - xhr.open(this.httpMethod, this.url, true); - xhr.setRequestHeader('Authorization', 'Bearer ' + this.token); - xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.setRequestHeader('X-Upload-Content-Length', this.file.size); - xhr.setRequestHeader('X-Upload-Content-Type', this.contentType); + xhr.open(this.httpMethod, this.url, true) + xhr.setRequestHeader('Authorization', 'Bearer ' + this.token) + xhr.setRequestHeader('Content-Type', 'application/json') + xhr.setRequestHeader('X-Upload-Content-Length', this.file.size) + xhr.setRequestHeader('X-Upload-Content-Type', this.contentType) - xhr.onload = function(e) { + xhr.onload = function (e) { if (e.target.status < 400) { - var location = e.target.getResponseHeader('Location'); - this.url = location; - this.sendFile_(); + var location = e.target.getResponseHeader('Location') + this.url = location + this.sendFile_() } else { - this.onUploadError_(e); + this.onUploadError_(e) } - }.bind(this); - xhr.onerror = this.onUploadError_.bind(this); - xhr.send(JSON.stringify(this.metadata)); -}; + }.bind(this) + xhr.onerror = this.onUploadError_.bind(this) + xhr.send(JSON.stringify(this.metadata)) +} /** * Send the actual file content. * * @private */ -MediaUploader.prototype.sendFile_ = function() { - var content = this.file; - var end = this.file.size; +MediaUploader.prototype.sendFile_ = function () { + var content = this.file + var end = this.file.size if (this.offset || this.chunkSize) { // Only bother to slice the file if we're either resuming or uploading in chunks if (this.chunkSize) { - end = Math.min(this.offset + this.chunkSize, this.file.size); + end = Math.min(this.offset + this.chunkSize, this.file.size) } - content = content.slice(this.offset, end); + content = content.slice(this.offset, end) } - var xhr = new XMLHttpRequest(); - xhr.open('PUT', this.url, true); - xhr.setRequestHeader('Content-Type', this.contentType); - xhr.setRequestHeader('Content-Range', "bytes " + this.offset + "-" + (end - 1) + "/" + this.file.size); - xhr.setRequestHeader('X-Upload-Content-Type', this.file.type); + var xhr = new XMLHttpRequest() + xhr.open('PUT', this.url, true) + xhr.setRequestHeader('Content-Type', this.contentType) + xhr.setRequestHeader('Content-Range', 'bytes ' + this.offset + '-' + (end - 1) + '/' + this.file.size) + xhr.setRequestHeader('X-Upload-Content-Type', this.file.type) if (xhr.upload) { - xhr.upload.addEventListener('progress', this.onProgress); + xhr.upload.addEventListener('progress', this.onProgress) } - xhr.onload = this.onContentUploadSuccess_.bind(this); - xhr.onerror = this.onContentUploadError_.bind(this); - xhr.send(content); -}; + xhr.onload = this.onContentUploadSuccess_.bind(this) + xhr.onerror = this.onContentUploadError_.bind(this) + xhr.send(content) +} /** * Query for the state of the file for resumption. * * @private */ -MediaUploader.prototype.resume_ = function() { - var xhr = new XMLHttpRequest(); - xhr.open('PUT', this.url, true); - xhr.setRequestHeader('Content-Range', "bytes */" + this.file.size); - xhr.setRequestHeader('X-Upload-Content-Type', this.file.type); +MediaUploader.prototype.resume_ = function () { + var xhr = new XMLHttpRequest() + xhr.open('PUT', this.url, true) + xhr.setRequestHeader('Content-Range', 'bytes */' + this.file.size) + xhr.setRequestHeader('X-Upload-Content-Type', this.file.type) if (xhr.upload) { - xhr.upload.addEventListener('progress', this.onProgress); + xhr.upload.addEventListener('progress', this.onProgress) } - xhr.onload = this.onContentUploadSuccess_.bind(this); - xhr.onerror = this.onContentUploadError_.bind(this); - xhr.send(); -}; + xhr.onload = this.onContentUploadSuccess_.bind(this) + xhr.onerror = this.onContentUploadError_.bind(this) + xhr.send() +} /** * Extract the last saved range if available in the request. * * @param {XMLHttpRequest} xhr Request object */ -MediaUploader.prototype.extractRange_ = function(xhr) { - var range = xhr.getResponseHeader('Range'); +MediaUploader.prototype.extractRange_ = function (xhr) { + var range = xhr.getResponseHeader('Range') if (range) { - this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1; + this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1 } -}; +} /** * Handle successful responses for uploads. Depending on the context, @@ -194,17 +193,17 @@ MediaUploader.prototype.extractRange_ = function(xhr) { * @private * @param {object} e XHR event */ -MediaUploader.prototype.onContentUploadSuccess_ = function(e) { - if (e.target.status == 200 || e.target.status == 201) { - this.onComplete(e.target.response); - } else if (e.target.status == 308) { - this.extractRange_(e.target); - this.retryHandler.reset(); - this.sendFile_(); +MediaUploader.prototype.onContentUploadSuccess_ = function (e) { + if (e.target.status === 200 || e.target.status === 201) { + this.onComplete(e.target.response) + } else if (e.target.status === 308) { + this.extractRange_(e.target) + this.retryHandler.reset() + this.sendFile_() } else { - this.onContentUploadError_(e); + this.onContentUploadError_(e) } -}; +} /** * Handles errors for uploads. Either retries or aborts depending @@ -213,13 +212,13 @@ MediaUploader.prototype.onContentUploadSuccess_ = function(e) { * @private * @param {object} e XHR event */ -MediaUploader.prototype.onContentUploadError_ = function(e) { +MediaUploader.prototype.onContentUploadError_ = function (e) { if (e.target.status && e.target.status < 500) { - this.onError(e.target.response); + this.onError(e.target.response) } else { - this.retryHandler.retry(this.resume_.bind(this)); + this.retryHandler.retry(this.resume_.bind(this)) } -}; +} /** * Handles errors for the initial request. @@ -227,9 +226,9 @@ MediaUploader.prototype.onContentUploadError_ = function(e) { * @private * @param {object} e XHR event */ -MediaUploader.prototype.onUploadError_ = function(e) { - this.onError(e.target.response); // TODO - Retries for initial upload -}; +MediaUploader.prototype.onUploadError_ = function (e) { + this.onError(e.target.response) // TODO - Retries for initial upload +} /** * Construct a query string from a hash/object @@ -238,12 +237,12 @@ MediaUploader.prototype.onUploadError_ = function(e) { * @param {object} [params] Key/value pairs for query string * @return {string} query string */ -MediaUploader.prototype.buildQuery_ = function(params) { - params = params || {}; - return Object.keys(params).map(function(key) { - return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); - }).join('&'); -}; +MediaUploader.prototype.buildQuery_ = function (params) { + params = params || {} + return Object.keys(params).map(function (key) { + return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + }).join('&') +} /** * Build the drive upload URL @@ -253,16 +252,16 @@ MediaUploader.prototype.buildQuery_ = function(params) { * @param {object} [params] Query parameters * @return {string} URL */ -MediaUploader.prototype.buildUrl_ = function(id, params, baseUrl) { - var url = baseUrl || 'https://www.googleapis.com/upload/drive/v2/files/'; +MediaUploader.prototype.buildUrl_ = function (id, params, baseUrl) { + var url = baseUrl || 'https://www.googleapis.com/upload/drive/v2/files/' if (id) { - url += id; + url += id } - var query = this.buildQuery_(params); + var query = this.buildQuery_(params) if (query) { - url += '?' + query; + url += '?' + query } - return url; -}; + return url +} -window.MediaUploader = MediaUploader; +window.MediaUploader = MediaUploader diff --git a/public/js/history.js b/public/js/history.js index 34b2cba7..e14b80d8 100644 --- a/public/js/history.js +++ b/public/js/history.js @@ -1,372 +1,328 @@ -import store from 'store'; -import S from 'string'; +/* eslint-env browser, jquery */ +/* global serverurl, Cookies, moment */ + +import store from 'store' +import S from 'string' import { checkIfAuth -} from './lib/common/login'; +} from './lib/common/login' import { urlpath -} from './lib/config'; +} from './lib/config' -window.migrateHistoryFromTempCallback = null; +window.migrateHistoryFromTempCallback = null -migrateHistoryFromTemp(); +migrateHistoryFromTemp() -function migrateHistoryFromTemp() { - if (url('#tempid')) { - $.get(`${serverurl}/temp`, { - tempid: url('#tempid') - }) - .done(data => { - if (data && data.temp) { - getStorageHistory(olddata => { - if (!olddata || olddata.length == 0) { - saveHistoryToStorage(JSON.parse(data.temp)); - } - }); - } - }) - .always(() => { - let hash = location.hash.split('#')[1]; - hash = hash.split('&'); - for (let i = 0; i < hash.length; i++) - if (hash[i].indexOf('tempid') == 0) { - hash.splice(i, 1); - i--; - } - hash = hash.join('&'); - location.hash = hash; - if (migrateHistoryFromTempCallback) - migrateHistoryFromTempCallback(); - }); - } +function migrateHistoryFromTemp () { + if (window.url('#tempid')) { + $.get(`${serverurl}/temp`, { + tempid: window.url('#tempid') + }) + .done(data => { + if (data && data.temp) { + getStorageHistory(olddata => { + if (!olddata || olddata.length === 0) { + saveHistoryToStorage(JSON.parse(data.temp)) + } + }) + } + }) + .always(() => { + let hash = location.hash.split('#')[1] + hash = hash.split('&') + for (let i = 0; i < hash.length; i++) { + if (hash[i].indexOf('tempid') === 0) { + hash.splice(i, 1) + i-- + } + } + hash = hash.join('&') + location.hash = hash + if (window.migrateHistoryFromTempCallback) { window.migrateHistoryFromTempCallback() } + }) + } } -export function saveHistory(notehistory) { - checkIfAuth( +export function saveHistory (notehistory) { + checkIfAuth( () => { - saveHistoryToServer(notehistory); + saveHistoryToServer(notehistory) }, () => { - saveHistoryToStorage(notehistory); + saveHistoryToStorage(notehistory) } - ); + ) } -function saveHistoryToStorage(notehistory) { - if (store.enabled) - store.set('notehistory', JSON.stringify(notehistory)); - else - saveHistoryToCookie(notehistory); +function saveHistoryToStorage (notehistory) { + if (store.enabled) { store.set('notehistory', JSON.stringify(notehistory)) } else { saveHistoryToCookie(notehistory) } } -function saveHistoryToCookie(notehistory) { - Cookies.set('notehistory', notehistory, { - expires: 365 - }); +function saveHistoryToCookie (notehistory) { + Cookies.set('notehistory', notehistory, { + expires: 365 + }) } -function saveHistoryToServer(notehistory) { - $.post(`${serverurl}/history`, { - history: JSON.stringify(notehistory) - }); +function saveHistoryToServer (notehistory) { + $.post(`${serverurl}/history`, { + history: JSON.stringify(notehistory) + }) } -function saveCookieHistoryToStorage(callback) { - store.set('notehistory', Cookies.get('notehistory')); - callback(); -} - -export function saveStorageHistoryToServer(callback) { - const data = store.get('notehistory'); - if (data) { - $.post(`${serverurl}/history`, { - history: data - }) - .done(data => { - callback(data); - }); - } -} - -function saveCookieHistoryToServer(callback) { +export function saveStorageHistoryToServer (callback) { + const data = store.get('notehistory') + if (data) { $.post(`${serverurl}/history`, { - history: Cookies.get('notehistory') - }) - .done(data => { - callback(data); - }); + history: data + }) + .done(data => { + callback(data) + }) + } } -export function clearDuplicatedHistory(notehistory) { - const newnotehistory = []; - for (let i = 0; i < notehistory.length; i++) { - let found = false; - for (let j = 0; j < newnotehistory.length; j++) { - const id = notehistory[i].id.replace(/\=+$/, ''); - const newId = newnotehistory[j].id.replace(/\=+$/, ''); - if (id == newId || notehistory[i].id == newnotehistory[j].id || !notehistory[i].id || !newnotehistory[j].id) { - const time = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a')); - const newTime = (typeof newnotehistory[i].time === 'number' ? moment(newnotehistory[i].time) : moment(newnotehistory[i].time, 'MMMM Do YYYY, h:mm:ss a')); - if(time >= newTime) { - newnotehistory[j] = notehistory[i]; - } - found = true; - break; - } +export function clearDuplicatedHistory (notehistory) { + const newnotehistory = [] + for (let i = 0; i < notehistory.length; i++) { + let found = false + for (let j = 0; j < newnotehistory.length; j++) { + const id = notehistory[i].id.replace(/=+$/, '') + const newId = newnotehistory[j].id.replace(/=+$/, '') + if (id === newId || notehistory[i].id === newnotehistory[j].id || !notehistory[i].id || !newnotehistory[j].id) { + const time = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a')) + const newTime = (typeof newnotehistory[i].time === 'number' ? moment(newnotehistory[i].time) : moment(newnotehistory[i].time, 'MMMM Do YYYY, h:mm:ss a')) + if (time >= newTime) { + newnotehistory[j] = notehistory[i] } - if (!found) - newnotehistory.push(notehistory[i]); + found = true + break + } } - return newnotehistory; + if (!found) { newnotehistory.push(notehistory[i]) } + } + return newnotehistory } -function addHistory(id, text, time, tags, pinned, notehistory) { +function addHistory (id, text, time, tags, pinned, notehistory) { // only add when note id exists - if (id) { - notehistory.push({ - id, - text, - time, - tags, - pinned - }); - } - return notehistory; + if (id) { + notehistory.push({ + id, + text, + time, + tags, + pinned + }) + } + return notehistory } -export function removeHistory(id, notehistory) { - for (let i = 0; i < notehistory.length; i++) { - if (notehistory[i].id == id) { - notehistory.splice(i, 1); - i -= 1; - } +export function removeHistory (id, notehistory) { + for (let i = 0; i < notehistory.length; i++) { + if (notehistory[i].id === id) { + notehistory.splice(i, 1) + i -= 1 } - return notehistory; + } + return notehistory } -//used for inner -export function writeHistory(title, tags) { - checkIfAuth( +// used for inner +export function writeHistory (title, tags) { + checkIfAuth( () => { // no need to do this anymore, this will count from server-side // writeHistoryToServer(title, tags); }, () => { - writeHistoryToStorage(title, tags); + writeHistoryToStorage(title, tags) } - ); + ) } -function writeHistoryToServer(title, tags) { - $.get(`${serverurl}/history`) - .done(data => { - try { - if (data.history) { - var notehistory = data.history; - } else { - var notehistory = []; - } - } catch (err) { - var notehistory = []; - } - if (!notehistory) - notehistory = []; - - const newnotehistory = generateHistory(title, tags, notehistory); - saveHistoryToServer(newnotehistory); - }) - .fail((xhr, status, error) => { - console.error(xhr.responseText); - }); +function writeHistoryToCookie (title, tags) { + var notehistory + try { + notehistory = Cookies.getJSON('notehistory') + } catch (err) { + notehistory = [] + } + if (!notehistory) { notehistory = [] } + const newnotehistory = generateHistory(title, tags, notehistory) + saveHistoryToCookie(newnotehistory) } -function writeHistoryToCookie(title, tags) { - try { - var notehistory = Cookies.getJSON('notehistory'); - } catch (err) { - var notehistory = []; - } - if (!notehistory) - notehistory = []; - - const newnotehistory = generateHistory(title, tags, notehistory); - saveHistoryToCookie(newnotehistory); -} - -function writeHistoryToStorage(title, tags) { - if (store.enabled) { - let data = store.get('notehistory'); - if (data) { - if (typeof data == "string") - data = JSON.parse(data); - var notehistory = data; - } else - var notehistory = []; - if (!notehistory) - notehistory = []; - - const newnotehistory = generateHistory(title, tags, notehistory); - saveHistoryToStorage(newnotehistory); +function writeHistoryToStorage (title, tags) { + if (store.enabled) { + let data = store.get('notehistory') + var notehistory + if (data) { + if (typeof data === 'string') { data = JSON.parse(data) } + notehistory = data } else { - writeHistoryToCookie(title, tags); + notehistory = [] } + if (!notehistory) { notehistory = [] } + + const newnotehistory = generateHistory(title, tags, notehistory) + saveHistoryToStorage(newnotehistory) + } else { + writeHistoryToCookie(title, tags) + } } if (!Array.isArray) { - Array.isArray = arg => Object.prototype.toString.call(arg) === '[object Array]'; + Array.isArray = arg => Object.prototype.toString.call(arg) === '[object Array]' } -function renderHistory(title, tags) { - //console.debug(tags); - const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1]; - return { - id, - text: title, - time: moment().valueOf(), - tags - }; +function renderHistory (title, tags) { + // console.debug(tags); + const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1] + return { + id, + text: title, + time: moment().valueOf(), + tags + } } -function generateHistory(title, tags, notehistory) { - const info = renderHistory(title, tags); - //keep any pinned data - let pinned = false; - for (let i = 0; i < notehistory.length; i++) { - if (notehistory[i].id == info.id && notehistory[i].pinned) { - pinned = true; - break; - } +function generateHistory (title, tags, notehistory) { + const info = renderHistory(title, tags) + // keep any pinned data + let pinned = false + for (let i = 0; i < notehistory.length; i++) { + if (notehistory[i].id === info.id && notehistory[i].pinned) { + pinned = true + break } - notehistory = removeHistory(info.id, notehistory); - notehistory = addHistory(info.id, info.text, info.time, info.tags, pinned, notehistory); - notehistory = clearDuplicatedHistory(notehistory); - return notehistory; + } + notehistory = removeHistory(info.id, notehistory) + notehistory = addHistory(info.id, info.text, info.time, info.tags, pinned, notehistory) + notehistory = clearDuplicatedHistory(notehistory) + return notehistory } -//used for outer -export function getHistory(callback) { - checkIfAuth( +// used for outer +export function getHistory (callback) { + checkIfAuth( () => { - getServerHistory(callback); + getServerHistory(callback) }, () => { - getStorageHistory(callback); + getStorageHistory(callback) } - ); + ) } -function getServerHistory(callback) { - $.get(`${serverurl}/history`) +function getServerHistory (callback) { + $.get(`${serverurl}/history`) .done(data => { - if (data.history) { - callback(data.history); - } + if (data.history) { + callback(data.history) + } }) .fail((xhr, status, error) => { - console.error(xhr.responseText); - }); + console.error(xhr.responseText) + }) } -function getCookieHistory(callback) { - callback(Cookies.getJSON('notehistory')); +function getCookieHistory (callback) { + callback(Cookies.getJSON('notehistory')) } -export function getStorageHistory(callback) { - if (store.enabled) { - let data = store.get('notehistory'); - if (data) { - if (typeof data == "string") - data = JSON.parse(data); - callback(data); - } else - getCookieHistory(callback); - } else { - getCookieHistory(callback); - } +export function getStorageHistory (callback) { + if (store.enabled) { + let data = store.get('notehistory') + if (data) { + if (typeof data === 'string') { data = JSON.parse(data) } + callback(data) + } else { getCookieHistory(callback) } + } else { + getCookieHistory(callback) + } } -export function parseHistory(list, callback) { - checkIfAuth( +export function parseHistory (list, callback) { + checkIfAuth( () => { - parseServerToHistory(list, callback); + parseServerToHistory(list, callback) }, () => { - parseStorageToHistory(list, callback); + parseStorageToHistory(list, callback) } - ); + ) } -export function parseServerToHistory(list, callback) { - $.get(`${serverurl}/history`) +export function parseServerToHistory (list, callback) { + $.get(`${serverurl}/history`) .done(data => { - if (data.history) { - parseToHistory(list, data.history, callback); - } + if (data.history) { + parseToHistory(list, data.history, callback) + } }) .fail((xhr, status, error) => { - console.error(xhr.responseText); - }); + console.error(xhr.responseText) + }) } -function parseCookieToHistory(list, callback) { - const notehistory = Cookies.getJSON('notehistory'); - parseToHistory(list, notehistory, callback); +function parseCookieToHistory (list, callback) { + const notehistory = Cookies.getJSON('notehistory') + parseToHistory(list, notehistory, callback) } -export function parseStorageToHistory(list, callback) { - if (store.enabled) { - let data = store.get('notehistory'); - if (data) { - if (typeof data == "string") - data = JSON.parse(data); - parseToHistory(list, data, callback); - } else - parseCookieToHistory(list, callback); - } else { - parseCookieToHistory(list, callback); - } +export function parseStorageToHistory (list, callback) { + if (store.enabled) { + let data = store.get('notehistory') + if (data) { + if (typeof data === 'string') { data = JSON.parse(data) } + parseToHistory(list, data, callback) + } else { parseCookieToHistory(list, callback) } + } else { + parseCookieToHistory(list, callback) + } } -function parseToHistory(list, notehistory, callback) { - if (!callback) return; - else if (!list || !notehistory) callback(list, notehistory); - else if (notehistory && notehistory.length > 0) { - for (let i = 0; i < notehistory.length; i++) { - //parse time to timestamp and fromNow - const timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a')); - notehistory[i].timestamp = timestamp.valueOf(); - notehistory[i].fromNow = timestamp.fromNow(); - notehistory[i].time = timestamp.format('llll'); +function parseToHistory (list, notehistory, callback) { + if (!callback) return + else if (!list || !notehistory) callback(list, notehistory) + else if (notehistory && notehistory.length > 0) { + for (let i = 0; i < notehistory.length; i++) { + // parse time to timestamp and fromNow + const timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a')) + notehistory[i].timestamp = timestamp.valueOf() + notehistory[i].fromNow = timestamp.fromNow() + notehistory[i].time = timestamp.format('llll') // prevent XSS - notehistory[i].text = S(notehistory[i].text).escapeHTML().s; - notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : []; + notehistory[i].text = S(notehistory[i].text).escapeHTML().s + notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : [] // add to list - if (notehistory[i].id && list.get('id', notehistory[i].id).length == 0) - list.add(notehistory[i]); - } + if (notehistory[i].id && list.get('id', notehistory[i].id).length === 0) { list.add(notehistory[i]) } } - callback(list, notehistory); + } + callback(list, notehistory) } -export function postHistoryToServer(noteId, data, callback) { - $.post(`${serverurl}/history/${noteId}`, data) +export function postHistoryToServer (noteId, data, callback) { + $.post(`${serverurl}/history/${noteId}`, data) .done(result => callback(null, result)) .fail((xhr, status, error) => { - console.error(xhr.responseText); - return callback(error, null); - }); + console.error(xhr.responseText) + return callback(error, null) + }) } -export function deleteServerHistory(noteId, callback) { - $.ajax({ - url: `${serverurl}/history${noteId ? '/' + noteId : ""}`, - type: 'DELETE' - }) +export function deleteServerHistory (noteId, callback) { + $.ajax({ + url: `${serverurl}/history${noteId ? '/' + noteId : ''}`, + type: 'DELETE' + }) .done(result => callback(null, result)) .fail((xhr, status, error) => { - console.error(xhr.responseText); - return callback(error, null); - }); + console.error(xhr.responseText) + return callback(error, null) + }) } diff --git a/public/js/htmlExport.js b/public/js/htmlExport.js index 1c2c5eb9..1a873aca 100644 --- a/public/js/htmlExport.js +++ b/public/js/htmlExport.js @@ -1,6 +1,6 @@ -require('../css/github-extract.css'); -require('../css/markdown.css'); -require('../css/extra.css'); -require('../css/slide-preview.css'); -require('../css/google-font.css'); -require('../css/site.css'); +require('../css/github-extract.css') +require('../css/markdown.css') +require('../css/extra.css') +require('../css/slide-preview.css') +require('../css/google-font.css') +require('../css/site.css') diff --git a/public/js/index.js b/public/js/index.js index f0c476ef..e672a68d 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1,26 +1,30 @@ -/* jquery and jquery plugins */ -require('../vendor/showup/showup'); +/* eslint-env browser, jquery */ +/* global CodeMirror, Cookies, moment, editor, ui, Spinner, + modeType, Idle, serverurl, key, gapi, Dropbox, FilePicker + ot, MediaUploader, hex2rgb, num_loaded, Visibility */ -require('../css/index.css'); -require('../css/extra.css'); -require('../css/slide-preview.css'); -require('../css/site.css'); +require('../vendor/showup/showup') -require('highlight.js/styles/github-gist.css'); +require('../css/index.css') +require('../css/extra.css') +require('../css/slide-preview.css') +require('../css/site.css') -var toMarkdown = require('to-markdown'); +require('highlight.js/styles/github-gist.css') -var saveAs = require('file-saver').saveAs; -var randomColor = require('randomcolor'); +var toMarkdown = require('to-markdown') -var _ = require("lodash"); +var saveAs = require('file-saver').saveAs +var randomColor = require('randomcolor') -var List = require('list.js'); +var _ = require('lodash') + +var List = require('list.js') import { checkLoginStateChanged, setloginStateChangeEvent -} from './lib/common/login'; +} from './lib/common/login' import { debug, @@ -31,7 +35,7 @@ import { noteurl, urlpath, version -} from './lib/config'; +} from './lib/config' import { autoLinkify, @@ -53,14 +57,14 @@ import { updateLastChange, updateLastChangeUser, updateOwner -} from './extra'; +} from './extra' import { clearMap, setupSyncAreas, syncScrollToEdit, syncScrollToView -} from './syncscroll'; +} from './syncscroll' import { writeHistory, @@ -68,4007 +72,3856 @@ import { getHistory, saveHistory, removeHistory -} from './history'; +} from './history' -var renderer = require('./render'); -var preventXSS = renderer.preventXSS; +var renderer = require('./render') +var preventXSS = renderer.preventXSS -var defaultTextHeight = 20; -var viewportMargin = 20; -var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault; -var defaultEditorMode = 'gfm'; +var defaultTextHeight = 20 +var viewportMargin = 20 +var mac = CodeMirror.keyMap['default'] === CodeMirror.keyMap.macDefault +var defaultEditorMode = 'gfm' var defaultExtraKeys = { - "F10": function (cm) { - cm.setOption("fullScreen", !cm.getOption("fullScreen")); - }, - "Esc": function (cm) { - if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass; - else if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false); - }, - "Cmd-S": function () { - return false; - }, - "Ctrl-S": function () { - return false; - }, - "Enter": "newlineAndIndentContinueMarkdownList", - "Tab": function (cm) { - var tab = '\t'; - var spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" "); - //auto indent whole line when in list or blockquote - var cursor = cm.getCursor(); - var line = cm.getLine(cursor.line); - var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/; - var match; - var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1; - if (multiple) { - cm.execCommand('defaultTab'); - } else if ((match = regex.exec(line)) !== null) { - var ch = match[1].length; - var pos = { - line: cursor.line, - ch: ch - }; - if (cm.getOption('indentWithTabs')) - cm.replaceRange(tab, pos, pos, '+input'); - else - cm.replaceRange(spaces, pos, pos, '+input'); - } else { - if (cm.getOption('indentWithTabs')) - cm.execCommand('defaultTab'); - else { - cm.replaceSelection(spaces); - } - } - }, - "Cmd-Left": "goLineLeftSmart", - "Cmd-Right": "goLineRight", - "Ctrl-C": function (cm) { - if (!mac && cm.getOption('keyMap').substr(0, 3) === 'vim') document.execCommand("copy"); - else return CodeMirror.Pass; - }, - "Ctrl-*": function (cm) { - wrapTextWith(cm, '*'); - }, - "Shift-Ctrl-8": function (cm) { - wrapTextWith(cm, '*'); - }, - "Ctrl-_": function (cm) { - wrapTextWith(cm, '_'); - }, - "Shift-Ctrl--": function (cm) { - wrapTextWith(cm, '_'); - }, - "Ctrl-~": function (cm) { - wrapTextWith(cm, '~'); - }, - "Shift-Ctrl-`": function (cm) { - wrapTextWith(cm, '~'); - }, - "Ctrl-^": function (cm) { - wrapTextWith(cm, '^'); - }, - "Shift-Ctrl-6": function (cm) { - wrapTextWith(cm, '^'); - }, - "Ctrl-+": function (cm) { - wrapTextWith(cm, '+'); - }, - "Shift-Ctrl-=": function (cm) { - wrapTextWith(cm, '+'); - }, - "Ctrl-=": function (cm) { - wrapTextWith(cm, '='); - }, - "Shift-Ctrl-Backspace": function (cm) { - wrapTextWith(cm, 'Backspace'); - } -}; - -var wrapSymbols = ['*', '_', '~', '^', '+', '=']; - -function wrapTextWith(cm, symbol) { - if (!cm.getSelection()) { - return CodeMirror.Pass; + 'F10': function (cm) { + cm.setOption('fullScreen', !cm.getOption('fullScreen')) + }, + 'Esc': function (cm) { + if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass + else if (cm.getOption('fullScreen')) cm.setOption('fullScreen', false) + }, + 'Cmd-S': function () { + return false + }, + 'Ctrl-S': function () { + return false + }, + 'Enter': 'newlineAndIndentContinueMarkdownList', + 'Tab': function (cm) { + var tab = '\t' + var spaces = Array(parseInt(cm.getOption('indentUnit')) + 1).join(' ') + // auto indent whole line when in list or blockquote + var cursor = cm.getCursor() + var line = cm.getLine(cursor.line) + var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/ + var match + var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1 + if (multiple) { + cm.execCommand('defaultTab') + } else if ((match = regex.exec(line)) !== null) { + var ch = match[1].length + var pos = { + line: cursor.line, + ch: ch + } + if (cm.getOption('indentWithTabs')) { cm.replaceRange(tab, pos, pos, '+input') } else { cm.replaceRange(spaces, pos, pos, '+input') } } else { - var ranges = cm.listSelections(); - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - if (!range.empty()) { - var from = range.from(), to = range.to(); - if (symbol !== 'Backspace') { - cm.replaceRange(symbol, to, to, '+input'); - cm.replaceRange(symbol, from, from, '+input'); + if (cm.getOption('indentWithTabs')) { cm.execCommand('defaultTab') } else { + cm.replaceSelection(spaces) + } + } + }, + 'Cmd-Left': 'goLineLeftSmart', + 'Cmd-Right': 'goLineRight', + 'Ctrl-C': function (cm) { + if (!mac && cm.getOption('keyMap').substr(0, 3) === 'vim') document.execCommand('copy') + else return CodeMirror.Pass + }, + 'Ctrl-*': function (cm) { + wrapTextWith(cm, '*') + }, + 'Shift-Ctrl-8': function (cm) { + wrapTextWith(cm, '*') + }, + 'Ctrl-_': function (cm) { + wrapTextWith(cm, '_') + }, + 'Shift-Ctrl--': function (cm) { + wrapTextWith(cm, '_') + }, + 'Ctrl-~': function (cm) { + wrapTextWith(cm, '~') + }, + 'Shift-Ctrl-`': function (cm) { + wrapTextWith(cm, '~') + }, + 'Ctrl-^': function (cm) { + wrapTextWith(cm, '^') + }, + 'Shift-Ctrl-6': function (cm) { + wrapTextWith(cm, '^') + }, + 'Ctrl-+': function (cm) { + wrapTextWith(cm, '+') + }, + 'Shift-Ctrl-=': function (cm) { + wrapTextWith(cm, '+') + }, + 'Ctrl-=': function (cm) { + wrapTextWith(cm, '=') + }, + 'Shift-Ctrl-Backspace': function (cm) { + wrapTextWith(cm, 'Backspace') + } +} + +var wrapSymbols = ['*', '_', '~', '^', '+', '='] + +function wrapTextWith (cm, symbol) { + if (!cm.getSelection()) { + return CodeMirror.Pass + } else { + var ranges = cm.listSelections() + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i] + if (!range.empty()) { + var from = range.from() + var to = range.to() + if (symbol !== 'Backspace') { + cm.replaceRange(symbol, to, to, '+input') + cm.replaceRange(symbol, from, from, '+input') // workaround selection range not correct after add symbol - var _ranges = cm.listSelections(); - var anchorIndex = editor.indexFromPos(_ranges[i].anchor); - var headIndex = editor.indexFromPos(_ranges[i].head); - if (anchorIndex > headIndex) { - _ranges[i].anchor.ch--; - } else { - _ranges[i].head.ch--; - } - cm.setSelections(_ranges); - } else { - var preEndPos = { - line: to.line, - ch: to.ch + 1 - }; - var preText = cm.getRange(to, preEndPos); - var preIndex = wrapSymbols.indexOf(preText); - var postEndPos = { - line: from.line, - ch: from.ch - 1 - }; - var postText = cm.getRange(postEndPos, from); - var postIndex = wrapSymbols.indexOf(postText); + var _ranges = cm.listSelections() + var anchorIndex = window.editor.indexFromPos(_ranges[i].anchor) + var headIndex = window.editor.indexFromPos(_ranges[i].head) + if (anchorIndex > headIndex) { + _ranges[i].anchor.ch-- + } else { + _ranges[i].head.ch-- + } + cm.setSelections(_ranges) + } else { + var preEndPos = { + line: to.line, + ch: to.ch + 1 + } + var preText = cm.getRange(to, preEndPos) + var preIndex = wrapSymbols.indexOf(preText) + var postEndPos = { + line: from.line, + ch: from.ch - 1 + } + var postText = cm.getRange(postEndPos, from) + var postIndex = wrapSymbols.indexOf(postText) // check if surround symbol are list in array and matched - if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) { - cm.replaceRange("", to, preEndPos, '+delete'); - cm.replaceRange("", postEndPos, from, '+delete'); - } - } - } + if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) { + cm.replaceRange('', to, preEndPos, '+delete') + cm.replaceRange('', postEndPos, from, '+delete') + } } + } } + } } -var idleTime = 300000; //5 mins -var updateViewDebounce = 100; -var cursorMenuThrottle = 50; -var cursorActivityDebounce = 50; -var cursorAnimatePeriod = 100; -var supportContainers = ['success', 'info', 'warning', 'danger']; -var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go']; -var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid']; +var idleTime = 300000 // 5 mins +var updateViewDebounce = 100 +var cursorMenuThrottle = 50 +var cursorActivityDebounce = 50 +var cursorAnimatePeriod = 100 +var supportContainers = ['success', 'info', 'warning', 'danger'] +var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go'] +var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid'] var supportHeaders = [ - { - text: '# h1', - search: '#' - }, - { - text: '## h2', - search: '##' - }, - { - text: '### h3', - search: '###' - }, - { - text: '#### h4', - search: '####' - }, - { - text: '##### h5', - search: '#####' - }, - { - text: '###### h6', - search: '######' - }, - { - text: '###### tags: `example`', - search: '###### tags:' - } -]; + { + text: '# h1', + search: '#' + }, + { + text: '## h2', + search: '##' + }, + { + text: '### h3', + search: '###' + }, + { + text: '#### h4', + search: '####' + }, + { + text: '##### h5', + search: '#####' + }, + { + text: '###### h6', + search: '######' + }, + { + text: '###### tags: `example`', + search: '###### tags:' + } +] var supportReferrals = [ - { - text: '[reference link]', - search: '[]' - }, - { - text: '[reference]: https:// "title"', - search: '[]:' - }, - { - text: '[^footnote link]', - search: '[^]' - }, - { - text: '[^footnote reference]: https:// "title"', - search: '[^]:' - }, - { - text: '^[inline footnote]', - search: '^[]' - }, - { - text: '[link text][reference]', - search: '[][]' - }, - { - text: '[link text](https:// "title")', - search: '[]()' - }, - { - text: '![image alt][reference]', - search: '![][]' - }, - { - text: '![image alt](https:// "title")', - search: '![]()' - }, - { - text: '![image alt](https:// "title" =WidthxHeight)', - search: '![]()' - }, - { - text: '[TOC]', - search: '[]' - } -]; + { + text: '[reference link]', + search: '[]' + }, + { + text: '[reference]: https:// "title"', + search: '[]:' + }, + { + text: '[^footnote link]', + search: '[^]' + }, + { + text: '[^footnote reference]: https:// "title"', + search: '[^]:' + }, + { + text: '^[inline footnote]', + search: '^[]' + }, + { + text: '[link text][reference]', + search: '[][]' + }, + { + text: '[link text](https:// "title")', + search: '[]()' + }, + { + text: '![image alt][reference]', + search: '![][]' + }, + { + text: '![image alt](https:// "title")', + search: '![]()' + }, + { + text: '![image alt](https:// "title" =WidthxHeight)', + search: '![]()' + }, + { + text: '[TOC]', + search: '[]' + } +] var supportExternals = [ - { - text: '{%youtube youtubeid %}', - search: 'youtube' - }, - { - text: '{%vimeo vimeoid %}', - search: 'vimeo' - }, - { - text: '{%gist gistid %}', - search: 'gist' - }, - { - text: '{%slideshare slideshareid %}', - search: 'slideshare' - }, - { - text: '{%speakerdeck speakerdeckid %}', - search: 'speakerdeck' - }, - { - text: '{%pdf pdfurl %}', - search: 'pdf' - } -]; + { + text: '{%youtube youtubeid %}', + search: 'youtube' + }, + { + text: '{%vimeo vimeoid %}', + search: 'vimeo' + }, + { + text: '{%gist gistid %}', + search: 'gist' + }, + { + text: '{%slideshare slideshareid %}', + search: 'slideshare' + }, + { + text: '{%speakerdeck speakerdeckid %}', + search: 'speakerdeck' + }, + { + text: '{%pdf pdfurl %}', + search: 'pdf' + } +] var supportExtraTags = [ - { - text: '[name tag]', - search: '[]', - command: function () { - return '[name=' + personalInfo.name + ']'; - }, - }, - { - text: '[time tag]', - search: '[]', - command: function () { - return '[time=' + moment().format('llll') + ']'; - }, - }, - { - text: '[my color tag]', - search: '[]', - command: function () { - return '[color=' + personalInfo.color + ']'; - } - }, - { - text: '[random color tag]', - search: '[]', - command: function () { - var color = randomColor(); - return '[color=' + color + ']'; - } - } -]; + { + text: '[name tag]', + search: '[]', + command: function () { + return '[name=' + window.personalInfo.name + ']' + } + }, + { + text: '[time tag]', + search: '[]', + command: function () { + return '[time=' + moment().format('llll') + ']' + } + }, + { + text: '[my color tag]', + search: '[]', + command: function () { + return '[color=' + window.personalInfo.color + ']' + } + }, + { + text: '[random color tag]', + search: '[]', + command: function () { + var color = randomColor() + return '[color=' + color + ']' + } + } +] window.modeType = { - edit: { - name: "edit" - }, - view: { - name: "view" - }, - both: { - name: "both" - } -}; + edit: { + name: 'edit' + }, + view: { + name: 'view' + }, + both: { + name: '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.view; - -//global vars -window.loaded = false; -window.needRefresh = false; -window.isDirty = false; -window.editShown = false; -window.visibleXS = false; -window.visibleSM = false; -window.visibleMD = false; -window.visibleLG = false; -window.isTouchDevice = 'ontouchstart' in document.documentElement; -window.currentMode = defaultMode; -window.currentStatus = statusType.offline; + 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.view + +// global vars +window.loaded = false +window.needRefresh = false +window.isDirty = false +window.editShown = false +window.visibleXS = false +window.visibleSM = false +window.visibleMD = false +window.visibleLG = false +window.isTouchDevice = 'ontouchstart' in document.documentElement +window.currentMode = defaultMode +window.currentStatus = statusType.offline window.lastInfo = { - needRestore: false, - cursor: null, - scroll: null, - edit: { - scroll: { - left: null, - top: null - }, - cursor: { - line: null, - ch: null - }, - selections: null + needRestore: false, + cursor: null, + scroll: null, + edit: { + scroll: { + left: null, + top: null }, - view: { - scroll: { - left: null, - top: null - } + cursor: { + line: null, + ch: null }, - history: null -}; -window.personalInfo = {}; -window.onlineUsers = []; + selections: null + }, + view: { + scroll: { + left: null, + top: null + } + }, + history: null +} +window.personalInfo = {} +window.onlineUsers = [] window.fileTypes = { - "pl": "perl", - "cgi": "perl", - "js": "javascript", - "php": "php", - "sh": "bash", - "rb": "ruby", - "html": "html", - "py": "python" -}; - -//editor settings -var textit = document.getElementById("textit"); -if (!textit) throw new Error("There was no textit area!"); + 'pl': 'perl', + 'cgi': 'perl', + 'js': 'javascript', + 'php': 'php', + 'sh': 'bash', + 'rb': 'ruby', + 'html': 'html', + 'py': 'python' +} + +// editor settings +var textit = document.getElementById('textit') +if (!textit) throw new Error('There was no textit area!') window.editor = CodeMirror.fromTextArea(textit, { - mode: defaultEditorMode, - backdrop: defaultEditorMode, - keyMap: "sublime", - viewportMargin: viewportMargin, - styleActiveLine: true, - lineNumbers: true, - lineWrapping: true, - showCursorWhenSelecting: true, - highlightSelectionMatches: true, - indentUnit: 4, - continueComments: "Enter", - theme: "one-dark", - inputStyle: "textarea", - matchBrackets: true, - autoCloseBrackets: true, - matchTags: { - bothTags: true - }, - autoCloseTags: true, - foldGutter: true, - gutters: ["CodeMirror-linenumbers", "authorship-gutters", "CodeMirror-foldgutter"], - extraKeys: defaultExtraKeys, - flattenSpans: true, - addModeClass: true, - readOnly: true, - autoRefresh: true, - otherCursors: true, - placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)" -}); -var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor); -defaultTextHeight = parseInt($(".CodeMirror").css('line-height')); - -var statusBarTemplate = null; -var statusBar = null; -var statusPanel = null; -var statusCursor = null; -var statusFile = null; -var statusIndicators = null; -var statusLength = null; -var statusKeymap = null; -var statusIndent = null; -var statusTheme = null; -var statusSpellcheck = null; -var statusPreferences = null; - -function getStatusBarTemplate(callback) { - $.get(serverurl + '/views/statusbar.html', function (template) { - statusBarTemplate = template; - if (callback) callback(); - }); -} -getStatusBarTemplate(); - -function addStatusBar() { - if (!statusBarTemplate) { - getStatusBarTemplate(addStatusBar); - return; - } - statusBar = $(statusBarTemplate); - statusCursor = statusBar.find('.status-cursor'); - statusFile = statusBar.find('.status-file'); - statusIndicators = statusBar.find('.status-indicators'); - statusIndent = statusBar.find('.status-indent'); - statusKeymap = statusBar.find('.status-keymap'); - statusLength = statusBar.find('.status-length'); - statusTheme = statusBar.find('.status-theme'); - statusSpellcheck = statusBar.find('.status-spellcheck'); - statusPreferences = statusBar.find('.status-preferences'); - statusPanel = editor.addPanel(statusBar[0], { - position: "bottom" - }); - - setIndent(); - setKeymap(); - setTheme(); - setSpellcheck(); - setPreferences(); -} - -function setIndent() { - var cookieIndentType = Cookies.get('indent_type'); - var cookieTabSize = parseInt(Cookies.get('tab_size')); - var cookieSpaceUnits = parseInt(Cookies.get('space_units')); - if (cookieIndentType) { - if (cookieIndentType == 'tab') { - editor.setOption('indentWithTabs', true); - if (cookieTabSize) - editor.setOption('indentUnit', cookieTabSize); - } else if (cookieIndentType == 'space') { - editor.setOption('indentWithTabs', false); - if (cookieSpaceUnits) - editor.setOption('indentUnit', cookieSpaceUnits); - } - } - if (cookieTabSize) - editor.setOption('tabSize', cookieTabSize); - - var type = statusIndicators.find('.indent-type'); - var widthLabel = statusIndicators.find('.indent-width-label'); - var widthInput = statusIndicators.find('.indent-width-input'); - - function setType() { - if (editor.getOption('indentWithTabs')) { - Cookies.set('indent_type', 'tab', { - expires: 365 - }); - type.text('Tab Size:'); - } else { - Cookies.set('indent_type', 'space', { - expires: 365 - }); - type.text('Spaces:'); - } - } - setType(); - - function setUnit() { - var unit = editor.getOption('indentUnit'); - if (editor.getOption('indentWithTabs')) { - Cookies.set('tab_size', unit, { - expires: 365 - }); - } else { - Cookies.set('space_units', unit, { - expires: 365 - }); - } - widthLabel.text(unit); + mode: defaultEditorMode, + backdrop: defaultEditorMode, + keyMap: 'sublime', + viewportMargin: viewportMargin, + styleActiveLine: true, + lineNumbers: true, + lineWrapping: true, + showCursorWhenSelecting: true, + highlightSelectionMatches: true, + indentUnit: 4, + continueComments: 'Enter', + theme: 'one-dark', + inputStyle: 'textarea', + matchBrackets: true, + autoCloseBrackets: true, + matchTags: { + bothTags: true + }, + autoCloseTags: true, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'authorship-gutters', 'CodeMirror-foldgutter'], + extraKeys: defaultExtraKeys, + flattenSpans: true, + addModeClass: true, + readOnly: true, + autoRefresh: true, + otherCursors: true, + placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)" +}) +var inlineAttach = window.inlineAttachment.editors.codemirror4.attach(editor) +defaultTextHeight = parseInt($('.CodeMirror').css('line-height')) + +var statusBarTemplate = null +var statusBar = null +var statusCursor = null +var statusFile = null +var statusIndicators = null +var statusLength = null +var statusTheme = null +var statusSpellcheck = null + +function getStatusBarTemplate (callback) { + $.get(serverurl + '/views/statusbar.html', function (template) { + statusBarTemplate = template + if (callback) callback() + }) +} +getStatusBarTemplate() + +function addStatusBar () { + if (!statusBarTemplate) { + getStatusBarTemplate(addStatusBar) + return + } + statusBar = $(statusBarTemplate) + statusCursor = statusBar.find('.status-cursor') + statusFile = statusBar.find('.status-file') + statusIndicators = statusBar.find('.status-indicators') + statusBar.find('.status-indent') + statusBar.find('.status-keymap') + statusLength = statusBar.find('.status-length') + statusTheme = statusBar.find('.status-theme') + statusSpellcheck = statusBar.find('.status-spellcheck') + statusBar.find('.status-preferences') + editor.addPanel(statusBar[0], { + position: 'bottom' + }) + + setIndent() + setKeymap() + setTheme() + setSpellcheck() + setPreferences() +} + +function setIndent () { + var cookieIndentType = Cookies.get('indent_type') + var cookieTabSize = parseInt(Cookies.get('tab_size')) + var cookieSpaceUnits = parseInt(Cookies.get('space_units')) + if (cookieIndentType) { + if (cookieIndentType === 'tab') { + editor.setOption('indentWithTabs', true) + if (cookieTabSize) { editor.setOption('indentUnit', cookieTabSize) } + } else if (cookieIndentType === 'space') { + editor.setOption('indentWithTabs', false) + if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) } + } + } + if (cookieTabSize) { editor.setOption('tabSize', cookieTabSize) } + + var type = statusIndicators.find('.indent-type') + var widthLabel = statusIndicators.find('.indent-width-label') + var widthInput = statusIndicators.find('.indent-width-input') + + function setType () { + if (editor.getOption('indentWithTabs')) { + Cookies.set('indent_type', 'tab', { + expires: 365 + }) + type.text('Tab Size:') + } else { + Cookies.set('indent_type', 'space', { + expires: 365 + }) + type.text('Spaces:') + } + } + setType() + + function setUnit () { + var unit = editor.getOption('indentUnit') + if (editor.getOption('indentWithTabs')) { + Cookies.set('tab_size', unit, { + expires: 365 + }) + } else { + Cookies.set('space_units', unit, { + expires: 365 + }) + } + widthLabel.text(unit) + } + setUnit() + + type.click(function () { + if (editor.getOption('indentWithTabs')) { + editor.setOption('indentWithTabs', false) + cookieSpaceUnits = parseInt(Cookies.get('space_units')) + if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) } + } else { + editor.setOption('indentWithTabs', true) + cookieTabSize = parseInt(Cookies.get('tab_size')) + if (cookieTabSize) { + editor.setOption('indentUnit', cookieTabSize) + editor.setOption('tabSize', cookieTabSize) + } + } + setType() + setUnit() + }) + widthLabel.click(function () { + if (widthLabel.is(':visible')) { + widthLabel.addClass('hidden') + widthInput.removeClass('hidden') + widthInput.val(editor.getOption('indentUnit')) + widthInput.select() + } else { + widthLabel.removeClass('hidden') + widthInput.addClass('hidden') + } + }) + widthInput.on('change', function () { + var val = parseInt(widthInput.val()) + if (!val) val = editor.getOption('indentUnit') + if (val < 1) val = 1 + else if (val > 10) val = 10 + + if (editor.getOption('indentWithTabs')) { + editor.setOption('tabSize', val) + } + editor.setOption('indentUnit', val) + setUnit() + }) + widthInput.on('blur', function () { + widthLabel.removeClass('hidden') + widthInput.addClass('hidden') + }) +} + +function setKeymap () { + var cookieKeymap = Cookies.get('keymap') + if (cookieKeymap) { editor.setOption('keyMap', cookieKeymap) } + + var label = statusIndicators.find('.ui-keymap-label') + var sublime = statusIndicators.find('.ui-keymap-sublime') + var emacs = statusIndicators.find('.ui-keymap-emacs') + var vim = statusIndicators.find('.ui-keymap-vim') + + function setKeymapLabel () { + var keymap = editor.getOption('keyMap') + Cookies.set('keymap', keymap, { + expires: 365 + }) + label.text(keymap) + restoreOverrideEditorKeymap() + setOverrideBrowserKeymap() + } + setKeymapLabel() + + sublime.click(function () { + editor.setOption('keyMap', 'sublime') + setKeymapLabel() + }) + emacs.click(function () { + editor.setOption('keyMap', 'emacs') + setKeymapLabel() + }) + vim.click(function () { + editor.setOption('keyMap', 'vim') + setKeymapLabel() + }) +} + +function setTheme () { + var cookieTheme = Cookies.get('theme') + if (cookieTheme) { + editor.setOption('theme', cookieTheme) + } + + var themeToggle = statusTheme.find('.ui-theme-toggle') + themeToggle.click(function () { + var theme = editor.getOption('theme') + if (theme === 'one-dark') { + theme = 'default' + } else { + theme = 'one-dark' } - setUnit(); - - type.click(function () { - if (editor.getOption('indentWithTabs')) { - editor.setOption('indentWithTabs', false); - cookieSpaceUnits = parseInt(Cookies.get('space_units')); - if (cookieSpaceUnits) - editor.setOption('indentUnit', cookieSpaceUnits) - } else { - editor.setOption('indentWithTabs', true); - cookieTabSize = parseInt(Cookies.get('tab_size')); - if (cookieTabSize) { - editor.setOption('indentUnit', cookieTabSize); - editor.setOption('tabSize', cookieTabSize); - } - } - setType(); - setUnit(); - }); - widthLabel.click(function () { - if (widthLabel.is(':visible')) { - widthLabel.addClass('hidden'); - widthInput.removeClass('hidden'); - widthInput.val(editor.getOption('indentUnit')); - widthInput.select(); - } else { - widthLabel.removeClass('hidden'); - widthInput.addClass('hidden'); - } - }); - widthInput.on('change', function () { - var val = parseInt(widthInput.val()); - if (!val) val = editor.getOption('indentUnit'); - if (val < 1) val = 1; - else if (val > 10) val = 10; - - if (editor.getOption('indentWithTabs')) { - editor.setOption('tabSize', val); - } - editor.setOption('indentUnit', val); - setUnit(); - }); - widthInput.on('blur', function () { - widthLabel.removeClass('hidden'); - widthInput.addClass('hidden'); - }); -} - -function setKeymap() { - var cookieKeymap = Cookies.get('keymap'); - if (cookieKeymap) - editor.setOption('keyMap', cookieKeymap); - - var label = statusIndicators.find('.ui-keymap-label'); - var sublime = statusIndicators.find('.ui-keymap-sublime'); - var emacs = statusIndicators.find('.ui-keymap-emacs'); - var vim = statusIndicators.find('.ui-keymap-vim'); - - function setKeymapLabel() { - var keymap = editor.getOption('keyMap'); - Cookies.set('keymap', keymap, { - expires: 365 - }); - label.text(keymap); - restoreOverrideEditorKeymap(); - setOverrideBrowserKeymap(); - } - setKeymapLabel(); - - sublime.click(function () { - editor.setOption('keyMap', 'sublime'); - setKeymapLabel(); - }); - emacs.click(function () { - editor.setOption('keyMap', 'emacs'); - setKeymapLabel(); - }); - vim.click(function () { - editor.setOption('keyMap', 'vim'); - setKeymapLabel(); - }); -} - -function setTheme() { - var cookieTheme = Cookies.get('theme'); - if (cookieTheme) { - editor.setOption('theme', cookieTheme); - } - - var themeToggle = statusTheme.find('.ui-theme-toggle'); - themeToggle.click(function () { - var theme = editor.getOption('theme'); - if (theme == "one-dark") { - theme = "default"; - } else { - theme = "one-dark"; - } - editor.setOption('theme', theme); - Cookies.set('theme', theme, { - expires: 365 - }); - checkTheme(); - }); - function checkTheme() { - var theme = editor.getOption('theme'); - if (theme == "one-dark") { - themeToggle.removeClass('active'); - } else { - themeToggle.addClass('active'); - } + editor.setOption('theme', theme) + Cookies.set('theme', theme, { + expires: 365 + }) + checkTheme() + }) + function checkTheme () { + var theme = editor.getOption('theme') + if (theme === 'one-dark') { + themeToggle.removeClass('active') + } else { + themeToggle.addClass('active') } - checkTheme(); + } + checkTheme() } -function setSpellcheck() { - var cookieSpellcheck = Cookies.get('spellcheck'); - if (cookieSpellcheck) { - var mode = null; - if (cookieSpellcheck === 'true' || cookieSpellcheck === true) { - mode = 'spell-checker'; - } else { - mode = defaultEditorMode; - } - if (mode && mode !== editor.getOption('mode')) { - editor.setOption('mode', mode); - } - } - - var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle'); - spellcheckToggle.click(function () { - var mode = editor.getOption('mode'); - if (mode == defaultEditorMode) { - mode = "spell-checker"; - } else { - mode = defaultEditorMode; - } - if (mode && mode !== editor.getOption('mode')) { - editor.setOption('mode', mode); - } - Cookies.set('spellcheck', (mode == "spell-checker"), { - expires: 365 - }); - checkSpellcheck(); - }); - function checkSpellcheck() { - var mode = editor.getOption('mode'); - if (mode == defaultEditorMode) { - spellcheckToggle.removeClass('active'); - } else { - spellcheckToggle.addClass('active'); - } +function setSpellcheck () { + var cookieSpellcheck = Cookies.get('spellcheck') + if (cookieSpellcheck) { + var mode = null + if (cookieSpellcheck === 'true' || cookieSpellcheck === true) { + mode = 'spell-checker' + } else { + mode = defaultEditorMode } - checkSpellcheck(); - - //workaround spellcheck might not activate beacuse the ajax loading - if (num_loaded < 2) { - var spellcheckTimer = setInterval(function () { - if (num_loaded >= 2) { - if (editor.getOption('mode') == "spell-checker") - editor.setOption('mode', "spell-checker"); - clearInterval(spellcheckTimer); - } - }, 100); + if (mode && mode !== editor.getOption('mode')) { + editor.setOption('mode', mode) } -} + } -var jumpToAddressBarKeymapName = mac ? "Cmd-L" : "Ctrl-L"; -var jumpToAddressBarKeymapValue = null; -function resetEditorKeymapToBrowserKeymap() { - var keymap = editor.getOption('keyMap'); - if (!jumpToAddressBarKeymapValue) { - jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]; - delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]; - } -} -function restoreOverrideEditorKeymap() { - var keymap = editor.getOption('keyMap'); - if (jumpToAddressBarKeymapValue) { - CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = jumpToAddressBarKeymapValue; - jumpToAddressBarKeymapValue = null; - } -} -function setOverrideBrowserKeymap() { - var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]'); - if(overrideBrowserKeymap.is(":checked")) { - Cookies.set('preferences-override-browser-keymap', true, { - expires: 365 - }); - restoreOverrideEditorKeymap(); + var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle') + spellcheckToggle.click(function () { + var mode = editor.getOption('mode') + if (mode === defaultEditorMode) { + mode = 'spell-checker' } else { - Cookies.remove('preferences-override-browser-keymap'); - resetEditorKeymapToBrowserKeymap(); + mode = defaultEditorMode } -} - -function setPreferences() { - var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]'); - var cookieOverrideBrowserKeymap = Cookies.get('preferences-override-browser-keymap'); - if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === "true") { - overrideBrowserKeymap.prop('checked', true); - } else { - overrideBrowserKeymap.prop('checked', false); + if (mode && mode !== editor.getOption('mode')) { + editor.setOption('mode', mode) } - setOverrideBrowserKeymap(); - - overrideBrowserKeymap.change(function() { - setOverrideBrowserKeymap(); - }); -} - -var selection = null; - -function updateStatusBar() { - if (!statusBar) return; - var cursor = editor.getCursor(); - var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1); - if (selection) { - var anchor = selection.anchor; - var head = selection.head; - var start = head.line <= anchor.line ? head : anchor; - var end = head.line >= anchor.line ? head : anchor; - var selectionText = ' — Selected '; - var selectionCharCount = Math.abs(head.ch - anchor.ch); - // borrow from brackets EditorStatusBar.js - if (start.line !== end.line) { - var lines = end.line - start.line + 1; - if (end.ch === 0) { - lines--; - } - selectionText += lines + ' lines'; - } else if (selectionCharCount > 0) - selectionText += selectionCharCount + ' columns'; - if (start.line !== end.line || selectionCharCount > 0) - cursorText += selectionText; - } - statusCursor.text(cursorText); - var fileText = ' — ' + editor.lineCount() + ' Lines'; - statusFile.text(fileText); - var docLength = editor.getValue().length; - statusLength.text('Length ' + docLength); - if (docLength > (docmaxlength * 0.95)) { - statusLength.css('color', 'red'); - statusLength.attr('title', 'Your almost reach note max length limit.'); - } else if (docLength > (docmaxlength * 0.8)) { - statusLength.css('color', 'orange'); - statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.'); + Cookies.set('spellcheck', (mode === 'spell-checker'), { + expires: 365 + }) + checkSpellcheck() + }) + function checkSpellcheck () { + var mode = editor.getOption('mode') + if (mode === defaultEditorMode) { + spellcheckToggle.removeClass('active') } else { - statusLength.css('color', 'white'); - statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.'); - } -} - -//ui vars + spellcheckToggle.addClass('active') + } + } + checkSpellcheck() + + // workaround spellcheck might not activate beacuse the ajax loading + /* eslint-disable camelcase */ + if (num_loaded < 2) { + var spellcheckTimer = setInterval(function () { + if (num_loaded >= 2) { + if (editor.getOption('mode') === 'spell-checker') { editor.setOption('mode', 'spell-checker') } + clearInterval(spellcheckTimer) + } + }, 100) + } + /* eslint-endable camelcase */ +} + +var jumpToAddressBarKeymapName = mac ? 'Cmd-L' : 'Ctrl-L' +var jumpToAddressBarKeymapValue = null +function resetEditorKeymapToBrowserKeymap () { + var keymap = editor.getOption('keyMap') + if (!jumpToAddressBarKeymapValue) { + jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] + delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] + } +} +function restoreOverrideEditorKeymap () { + var keymap = editor.getOption('keyMap') + if (jumpToAddressBarKeymapValue) { + CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = jumpToAddressBarKeymapValue + jumpToAddressBarKeymapValue = null + } +} +function setOverrideBrowserKeymap () { + var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]') + if (overrideBrowserKeymap.is(':checked')) { + Cookies.set('preferences-override-browser-keymap', true, { + expires: 365 + }) + restoreOverrideEditorKeymap() + } else { + Cookies.remove('preferences-override-browser-keymap') + resetEditorKeymapToBrowserKeymap() + } +} + +function setPreferences () { + var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]') + var cookieOverrideBrowserKeymap = Cookies.get('preferences-override-browser-keymap') + if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') { + overrideBrowserKeymap.prop('checked', true) + } else { + overrideBrowserKeymap.prop('checked', false) + } + setOverrideBrowserKeymap() + + overrideBrowserKeymap.change(function () { + setOverrideBrowserKeymap() + }) +} + +var selection = null + +function updateStatusBar () { + if (!statusBar) return + var cursor = editor.getCursor() + var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1) + if (selection) { + var anchor = selection.anchor + var head = selection.head + var start = head.line <= anchor.line ? head : anchor + var end = head.line >= anchor.line ? head : anchor + var selectionText = ' — Selected ' + var selectionCharCount = Math.abs(head.ch - anchor.ch) + // borrow from brackets EditorStatusBar.js + if (start.line !== end.line) { + var lines = end.line - start.line + 1 + if (end.ch === 0) { + lines-- + } + selectionText += lines + ' lines' + } else if (selectionCharCount > 0) { selectionText += selectionCharCount + ' columns' } + if (start.line !== end.line || selectionCharCount > 0) { cursorText += selectionText } + } + statusCursor.text(cursorText) + var fileText = ' — ' + editor.lineCount() + ' Lines' + statusFile.text(fileText) + var docLength = editor.getValue().length + statusLength.text('Length ' + docLength) + if (docLength > (docmaxlength * 0.95)) { + statusLength.css('color', 'red') + statusLength.attr('title', 'Your almost reach note max length limit.') + } else if (docLength > (docmaxlength * 0.8)) { + statusLength.css('color', 'orange') + statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.') + } else { + statusLength.css('color', 'white') + statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.') + } +} + +// ui vars window.ui = { - spinner: $(".ui-spinner"), - content: $(".ui-content"), - toolbar: { - shortStatus: $(".ui-short-status"), - status: $(".ui-status"), - new: $(".ui-new"), - publish: $(".ui-publish"), - extra: { - revision: $(".ui-extra-revision"), - slide: $(".ui-extra-slide") - }, - download: { - markdown: $(".ui-download-markdown"), - html: $(".ui-download-html"), - rawhtml: $(".ui-download-raw-html"), - pdf: $(".ui-download-pdf-beta"), - }, - export: { - dropbox: $(".ui-save-dropbox"), - googleDrive: $(".ui-save-google-drive"), - gist: $(".ui-save-gist"), - snippet: $(".ui-save-snippet") - }, - import: { - dropbox: $(".ui-import-dropbox"), - googleDrive: $(".ui-import-google-drive"), - gist: $(".ui-import-gist"), - snippet: $(".ui-import-snippet"), - clipboard: $(".ui-import-clipboard") - }, - mode: $(".ui-mode"), - edit: $(".ui-edit"), - view: $(".ui-view"), - both: $(".ui-both"), - uploadImage: $(".ui-upload-image") + spinner: $('.ui-spinner'), + content: $('.ui-content'), + toolbar: { + shortStatus: $('.ui-short-status'), + status: $('.ui-status'), + new: $('.ui-new'), + publish: $('.ui-publish'), + extra: { + revision: $('.ui-extra-revision'), + slide: $('.ui-extra-slide') }, - infobar: { - lastchange: $(".ui-lastchange"), - lastchangeuser: $(".ui-lastchangeuser"), - nolastchangeuser: $(".ui-no-lastchangeuser"), - permission: { - permission: $(".ui-permission"), - label: $(".ui-permission-label"), - freely: $(".ui-permission-freely"), - editable: $(".ui-permission-editable"), - locked: $(".ui-permission-locked"), - private: $(".ui-permission-private"), - limited: $(".ui-permission-limited"), - protected: $(".ui-permission-protected") - }, - delete: $(".ui-delete-note") + download: { + markdown: $('.ui-download-markdown'), + html: $('.ui-download-html'), + rawhtml: $('.ui-download-raw-html'), + pdf: $('.ui-download-pdf-beta') }, - toc: { - toc: $('.ui-toc'), - affix: $('.ui-affix-toc'), - label: $('.ui-toc-label'), - dropdown: $('.ui-toc-dropdown') + export: { + dropbox: $('.ui-save-dropbox'), + googleDrive: $('.ui-save-google-drive'), + gist: $('.ui-save-gist'), + snippet: $('.ui-save-snippet') }, - area: { - edit: $(".ui-edit-area"), - view: $(".ui-view-area"), - codemirror: $(".ui-edit-area .CodeMirror"), - codemirrorScroll: $(".ui-edit-area .CodeMirror .CodeMirror-scroll"), - codemirrorSizer: $(".ui-edit-area .CodeMirror .CodeMirror-sizer"), - codemirrorSizerInner: $(".ui-edit-area .CodeMirror .CodeMirror-sizer > div"), - markdown: $(".ui-view-area .markdown-body"), - resize: { - handle: $('.ui-resizable-handle'), - syncToggle: $('.ui-sync-toggle') - } + import: { + dropbox: $('.ui-import-dropbox'), + googleDrive: $('.ui-import-google-drive'), + gist: $('.ui-import-gist'), + snippet: $('.ui-import-snippet'), + clipboard: $('.ui-import-clipboard') }, - modal: { - snippetImportProjects: $("#snippetImportModalProjects"), - snippetImportSnippets: $("#snippetImportModalSnippets"), - revision: $("#revisionModal") - } -}; - -//page actions + mode: $('.ui-mode'), + edit: $('.ui-edit'), + view: $('.ui-view'), + both: $('.ui-both'), + uploadImage: $('.ui-upload-image') + }, + infobar: { + lastchange: $('.ui-lastchange'), + lastchangeuser: $('.ui-lastchangeuser'), + nolastchangeuser: $('.ui-no-lastchangeuser'), + permission: { + permission: $('.ui-permission'), + label: $('.ui-permission-label'), + freely: $('.ui-permission-freely'), + editable: $('.ui-permission-editable'), + locked: $('.ui-permission-locked'), + private: $('.ui-permission-private'), + limited: $('.ui-permission-limited'), + protected: $('.ui-permission-protected') + }, + delete: $('.ui-delete-note') + }, + toc: { + toc: $('.ui-toc'), + affix: $('.ui-affix-toc'), + label: $('.ui-toc-label'), + dropdown: $('.ui-toc-dropdown') + }, + area: { + edit: $('.ui-edit-area'), + view: $('.ui-view-area'), + codemirror: $('.ui-edit-area .CodeMirror'), + codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'), + codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'), + codemirrorSizerInner: $('.ui-edit-area .CodeMirror .CodeMirror-sizer > div'), + markdown: $('.ui-view-area .markdown-body'), + resize: { + handle: $('.ui-resizable-handle'), + syncToggle: $('.ui-sync-toggle') + } + }, + modal: { + snippetImportProjects: $('#snippetImportModalProjects'), + snippetImportSnippets: $('#snippetImportModalSnippets'), + revision: $('#revisionModal') + } +} + +// 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]); - -//idle + 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 +} + +/* eslint-disable no-unused-vars */ +var spinner = new Spinner(opts).spin(ui.spinner[0]) +/* eslint-enable no-unused-vars */ + +// idle var idle = new Idle({ - onAway: function () { - idle.isAway = true; - emitUserStatus(); - updateOnlineStatus(); - }, - onAwayBack: function () { - idle.isAway = false; - emitUserStatus(); - updateOnlineStatus(); - setHaveUnreadChanges(false); - updateTitleReminder(); - }, - awayTimeout: idleTime -}); + onAway: function () { + idle.isAway = true + emitUserStatus() + updateOnlineStatus() + }, + onAwayBack: function () { + idle.isAway = false + emitUserStatus() + updateOnlineStatus() + setHaveUnreadChanges(false) + updateTitleReminder() + }, + awayTimeout: idleTime +}) ui.area.codemirror.on('touchstart', function () { - idle.onActive(); -}); + idle.onActive() +}) -var haveUnreadChanges = false; +var haveUnreadChanges = false -function setHaveUnreadChanges(bool) { - if (!loaded) return; - if (bool && (idle.isAway || Visibility.hidden())) { - haveUnreadChanges = true; - } else if (!bool && !idle.isAway && !Visibility.hidden()) { - haveUnreadChanges = false; - } +function setHaveUnreadChanges (bool) { + if (!window.loaded) return + if (bool && (idle.isAway || Visibility.hidden())) { + haveUnreadChanges = true + } else if (!bool && !idle.isAway && !Visibility.hidden()) { + haveUnreadChanges = false + } } -function updateTitleReminder() { - if (!loaded) return; - if (haveUnreadChanges) { - document.title = '• ' + renderTitle(ui.area.markdown); - } else { - document.title = renderTitle(ui.area.markdown); - } +function updateTitleReminder () { + if (!window.loaded) return + if (haveUnreadChanges) { + document.title = '• ' + renderTitle(ui.area.markdown) + } else { + document.title = renderTitle(ui.area.markdown) + } } -function setRefreshModal(status) { - $('#refreshModal').modal('show'); - $('#refreshModal').find('.modal-body > div').hide(); - $('#refreshModal').find('.' + status).show(); +function setRefreshModal (status) { + $('#refreshModal').modal('show') + $('#refreshModal').find('.modal-body > div').hide() + $('#refreshModal').find('.' + status).show() } -function setNeedRefresh() { - needRefresh = true; - editor.setOption('readOnly', true); - socket.disconnect(); - showStatus(statusType.offline); +function setNeedRefresh () { + window.needRefresh = true + editor.setOption('readOnly', true) + socket.disconnect() + showStatus(statusType.offline) } setloginStateChangeEvent(function () { - setRefreshModal('user-state-changed'); - setNeedRefresh(); -}); + setRefreshModal('user-state-changed') + setNeedRefresh() +}) -//visibility -var wasFocus = false; +// visibility +var wasFocus = false Visibility.change(function (e, state) { - var hidden = Visibility.hidden(); - if (hidden) { - if (editorHasFocus()) { - wasFocus = true; - editor.getInputField().blur(); - } - } else { - if (wasFocus) { - if (!visibleXS) { - editor.focus(); - editor.refresh(); - } - wasFocus = false; - } - setHaveUnreadChanges(false); - } - updateTitleReminder(); -}); + var hidden = Visibility.hidden() + if (hidden) { + if (editorHasFocus()) { + wasFocus = true + editor.getInputField().blur() + } + } else { + if (wasFocus) { + if (!window.visibleXS) { + editor.focus() + editor.refresh() + } + wasFocus = false + } + setHaveUnreadChanges(false) + } + updateTitleReminder() +}) -//when page ready +// when page ready $(document).ready(function () { - idle.checkAway(); - checkResponsive(); - //if in smaller screen, we don't need advanced scrollbar - var scrollbarStyle; - if (visibleXS) { - scrollbarStyle = 'native'; - } else { - scrollbarStyle = 'overlay'; - } - if (scrollbarStyle != editor.getOption('scrollbarStyle')) { - editor.setOption('scrollbarStyle', scrollbarStyle); - clearMap(); - } - checkEditorStyle(); + idle.checkAway() + checkResponsive() + // if in smaller screen, we don't need advanced scrollbar + var scrollbarStyle + if (window.visibleXS) { + scrollbarStyle = 'native' + } else { + scrollbarStyle = 'overlay' + } + if (scrollbarStyle !== editor.getOption('scrollbarStyle')) { + editor.setOption('scrollbarStyle', scrollbarStyle) + clearMap() + } + checkEditorStyle() /* we need this only on touch devices */ - if (isTouchDevice) { + if (window.isTouchDevice) { /* cache dom references */ - var $body = jQuery('body'); + var $body = jQuery('body') /* bind events */ - $(document) + $(document) .on('focus', 'textarea, input', function () { - $body.addClass('fixfixed'); + $body.addClass('fixfixed') }) .on('blur', 'textarea, input', function () { - $body.removeClass('fixfixed'); - }); - } - //showup - $().showUp('.navbar', { - upClass: 'navbar-hide', - downClass: 'navbar-show' - }); - //tooltip - $('[data-toggle="tooltip"]').tooltip(); + $body.removeClass('fixfixed') + }) + } + // showup + $().showUp('.navbar', { + upClass: 'navbar-hide', + downClass: 'navbar-show' + }) + // tooltip + $('[data-toggle="tooltip"]').tooltip() // shortcuts // allow on all tags - key.filter = function (e) { return true; }; - key('ctrl+alt+e', function (e) { - changeMode(modeType.edit); - }); - key('ctrl+alt+v', function (e) { - changeMode(modeType.view); - }); - key('ctrl+alt+b', function (e) { - changeMode(modeType.both); - }); + key.filter = function (e) { return true } + key('ctrl+alt+e', function (e) { + changeMode(modeType.edit) + }) + key('ctrl+alt+v', function (e) { + changeMode(modeType.view) + }) + key('ctrl+alt+b', function (e) { + changeMode(modeType.both) + }) // toggle-dropdown - $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) { - e.stopPropagation(); - }); -}); -//when page resize + $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) { + e.stopPropagation() + }) +}) +// when page resize $(window).resize(function () { - checkLayout(); - checkEditorStyle(); - checkTocStyle(); - checkCursorMenu(); - windowResize(); -}); -//when page unload + checkLayout() + checkEditorStyle() + checkTocStyle() + checkCursorMenu() + windowResize() +}) +// when page unload $(window).on('unload', function () { - //updateHistoryInner(); -}); + // updateHistoryInner(); +}) $(window).on('error', function () { - //setNeedRefresh(); -}); - -setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown); + // setNeedRefresh(); +}) -function autoSyncscroll() { - if (editorHasFocus()) { - syncScrollToView(); +setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown) + +function autoSyncscroll () { + if (editorHasFocus()) { + syncScrollToView() + } else { + syncScrollToEdit() + } +} + +var windowResizeDebounce = 200 +var windowResize = _.debounce(windowResizeInner, windowResizeDebounce) + +function windowResizeInner (callback) { + checkLayout() + checkResponsive() + checkEditorStyle() + checkTocStyle() + checkCursorMenu() + // refresh editor + if (window.loaded) { + if (editor.getOption('scrollbarStyle') === 'native') { + setTimeout(function () { + clearMap() + autoSyncscroll() + updateScrollspy() + if (callback && typeof callback === 'function') { callback() } + }, 1) } else { - syncScrollToEdit(); - } -} - -var windowResizeDebounce = 200; -var windowResize = _.debounce(windowResizeInner, windowResizeDebounce); - -function windowResizeInner(callback) { - checkLayout(); - checkResponsive(); - checkEditorStyle(); - checkTocStyle(); - checkCursorMenu(); - //refresh editor - if (loaded) { - if (editor.getOption('scrollbarStyle') === 'native') { - setTimeout(function () { - clearMap(); - autoSyncscroll(); - updateScrollspy(); - if (callback && typeof callback === 'function') - callback(); - }, 1); - } else { // force it load all docs at once to prevent scroll knob blink - editor.setOption('viewportMargin', Infinity); - setTimeout(function () { - clearMap(); - autoSyncscroll(); - editor.setOption('viewportMargin', viewportMargin); - //add or update user cursors - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id != personalInfo.id) - buildCursor(onlineUsers[i]); - } - updateScrollspy(); - if (callback && typeof callback === 'function') - callback(); - }, 1); + editor.setOption('viewportMargin', Infinity) + setTimeout(function () { + clearMap() + autoSyncscroll() + editor.setOption('viewportMargin', viewportMargin) + // add or update user cursors + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id !== window.personalInfo.id) { buildCursor(window.onlineUsers[i]) } } + updateScrollspy() + if (callback && typeof callback === 'function') { callback() } + }, 1) } + } } -function checkLayout() { - var navbarHieght = $('.navbar').outerHeight(); - $('body').css('padding-top', navbarHieght + 'px'); +function checkLayout () { + var navbarHieght = $('.navbar').outerHeight() + $('body').css('padding-top', navbarHieght + 'px') } -function editorHasFocus() { - return $(editor.getInputField()).is(":focus"); +function editorHasFocus () { + return $(editor.getInputField()).is(':focus') } -//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"); +// 768-792px have a gap +function checkResponsive () { + window.visibleXS = $('.visible-xs').is(':visible') + window.visibleSM = $('.visible-sm').is(':visible') + window.visibleMD = $('.visible-md').is(':visible') + window.visibleLG = $('.visible-lg').is(':visible') - if (visibleXS && currentMode == modeType.both) - if (editorHasFocus()) - changeMode(modeType.edit); - else - changeMode(modeType.view); + if (window.visibleXS && window.currentMode === modeType.both) { + if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) } + } - emitUserStatus(); + emitUserStatus() } -var lastEditorWidth = 0; -var previousFocusOnEditor = null; +var lastEditorWidth = 0 +var previousFocusOnEditor = null -function checkEditorStyle() { - var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height(); +function checkEditorStyle () { + var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height() // set editor height and min height based on scrollbar style and mode - var scrollbarStyle = editor.getOption('scrollbarStyle'); - if (scrollbarStyle == 'overlay' || currentMode == modeType.both) { - ui.area.codemirrorScroll.css('height', desireHeight + 'px'); - ui.area.codemirrorScroll.css('min-height', ''); - checkEditorScrollbar(); - } else if (scrollbarStyle == 'native') { - ui.area.codemirrorScroll.css('height', ''); - ui.area.codemirrorScroll.css('min-height', desireHeight + 'px'); - } + var scrollbarStyle = editor.getOption('scrollbarStyle') + if (scrollbarStyle === 'overlay' || window.currentMode === modeType.both) { + ui.area.codemirrorScroll.css('height', desireHeight + 'px') + ui.area.codemirrorScroll.css('min-height', '') + checkEditorScrollbar() + } else if (scrollbarStyle === 'native') { + ui.area.codemirrorScroll.css('height', '') + ui.area.codemirrorScroll.css('min-height', desireHeight + 'px') + } // workaround editor will have wrong doc height when editor height changed - editor.setSize(null, ui.area.edit.height()); - //make editor resizable - if (!ui.area.resize.handle.length) { - ui.area.edit.resizable({ - handles: 'e', - maxWidth: $(window).width() * 0.7, - minWidth: $(window).width() * 0.2, - create: function (e, ui) { - $(this).parent().on('resize', function (e) { - e.stopPropagation(); - }); - }, - start: function (e) { - editor.setOption('viewportMargin', Infinity); - }, - resize: function (e) { - ui.area.resize.syncToggle.stop(true, true).show(); - checkTocStyle(); - }, - stop: function (e) { - lastEditorWidth = ui.area.edit.width(); + editor.setSize(null, ui.area.edit.height()) + // make editor resizable + if (!ui.area.resize.handle.length) { + ui.area.edit.resizable({ + handles: 'e', + maxWidth: $(window).width() * 0.7, + minWidth: $(window).width() * 0.2, + create: function (e, ui) { + $(this).parent().on('resize', function (e) { + e.stopPropagation() + }) + }, + start: function (e) { + editor.setOption('viewportMargin', Infinity) + }, + resize: function (e) { + ui.area.resize.syncToggle.stop(true, true).show() + checkTocStyle() + }, + stop: function (e) { + lastEditorWidth = ui.area.edit.width() // workaround that scroll event bindings - preventSyncScrollToView = 2; - preventSyncScrollToEdit = true; - editor.setOption('viewportMargin', viewportMargin); - if (editorHasFocus()) { - windowResizeInner(function () { - ui.area.codemirrorScroll.scroll(); - }); - } else { - windowResizeInner(function () { - ui.area.view.scroll(); - }); - } - checkEditorScrollbar(); - } - }); - ui.area.resize.handle = $('.ui-resizable-handle'); - } - if (!ui.area.resize.syncToggle.length) { - ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>'); - ui.area.resize.syncToggle.hover(function () { - previousFocusOnEditor = editorHasFocus(); - }, function () { - previousFocusOnEditor = null; - }); - ui.area.resize.syncToggle.click(function () { - syncscroll = !syncscroll; - checkSyncToggle(); - }); - ui.area.resize.handle.append(ui.area.resize.syncToggle); - ui.area.resize.syncToggle.hide(); - ui.area.resize.handle.hover(function () { - ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100); - }, function () { - ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300); - }); - } -} - -function checkSyncToggle() { - if (syncscroll) { - if (previousFocusOnEditor) { - preventSyncScrollToView = false; - syncScrollToView(); + window.preventSyncScrollToView = 2 + window.preventSyncScrollToEdit = true + editor.setOption('viewportMargin', viewportMargin) + if (editorHasFocus()) { + windowResizeInner(function () { + ui.area.codemirrorScroll.scroll() + }) } else { - preventSyncScrollToEdit = false; - syncScrollToEdit(); + windowResizeInner(function () { + ui.area.view.scroll() + }) } - ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link'); + checkEditorScrollbar() + } + }) + ui.area.resize.handle = $('.ui-resizable-handle') + } + if (!ui.area.resize.syncToggle.length) { + ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>') + ui.area.resize.syncToggle.hover(function () { + previousFocusOnEditor = editorHasFocus() + }, function () { + previousFocusOnEditor = null + }) + ui.area.resize.syncToggle.click(function () { + window.syncscroll = !window.syncscroll + checkSyncToggle() + }) + ui.area.resize.handle.append(ui.area.resize.syncToggle) + ui.area.resize.syncToggle.hide() + ui.area.resize.handle.hover(function () { + ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100) + }, function () { + ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300) + }) + } +} + +function checkSyncToggle () { + if (window.syncscroll) { + if (previousFocusOnEditor) { + window.preventSyncScrollToView = false + syncScrollToView() } else { - ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink'); + window.preventSyncScrollToEdit = false + syncScrollToEdit() } + ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link') + } else { + ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink') + } } var checkEditorScrollbar = _.debounce(function () { - editor.operation(checkEditorScrollbarInner); -}, 50); + editor.operation(checkEditorScrollbarInner) +}, 50) -function checkEditorScrollbarInner() { +function checkEditorScrollbarInner () { // workaround simple scroll bar knob // will get wrong position when editor height changed - var scrollInfo = editor.getScrollInfo(); - editor.scrollTo(null, scrollInfo.top - 1); - editor.scrollTo(null, scrollInfo.top); -} - -function checkTocStyle() { - //toc right - var paddingRight = parseFloat(ui.area.markdown.css('padding-right')); - var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight)); - ui.toc.toc.css('right', right + 'px'); - //affix toc left - var newbool; - var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2; - //for ipad or wider device - if (rightMargin >= 133) { - newbool = true; - var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2; - var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin; - ui.toc.affix.css('left', left + 'px'); - ui.toc.affix.css('width', rightMargin + 'px'); - } else { - newbool = false; - } - //toc scrollspy - ui.toc.toc.removeClass('scrollspy-body, scrollspy-view'); - ui.toc.affix.removeClass('scrollspy-body, scrollspy-view'); - if (currentMode == modeType.both) { - ui.toc.toc.addClass('scrollspy-view'); - ui.toc.affix.addClass('scrollspy-view'); - } else if (currentMode != modeType.both && !newbool) { - ui.toc.toc.addClass('scrollspy-body'); - ui.toc.affix.addClass('scrollspy-body'); - } else { - ui.toc.toc.addClass('scrollspy-view'); - ui.toc.affix.addClass('scrollspy-body'); - } - if (newbool != enoughForAffixToc) { - enoughForAffixToc = newbool; - generateScrollspy(); - } -} - -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 = num + " " + statusType.online.msg; - 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; - } -} - -var lastMode = null; - -function changeMode(type) { + var scrollInfo = editor.getScrollInfo() + editor.scrollTo(null, scrollInfo.top - 1) + editor.scrollTo(null, scrollInfo.top) +} + +function checkTocStyle () { + // toc right + var paddingRight = parseFloat(ui.area.markdown.css('padding-right')) + var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight)) + ui.toc.toc.css('right', right + 'px') + // affix toc left + var newbool + var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2 + // for ipad or wider device + if (rightMargin >= 133) { + newbool = true + var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2 + var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin + ui.toc.affix.css('left', left + 'px') + ui.toc.affix.css('width', rightMargin + 'px') + } else { + newbool = false + } + // toc scrollspy + ui.toc.toc.removeClass('scrollspy-body, scrollspy-view') + ui.toc.affix.removeClass('scrollspy-body, scrollspy-view') + if (window.currentMode === modeType.both) { + ui.toc.toc.addClass('scrollspy-view') + ui.toc.affix.addClass('scrollspy-view') + } else if (window.currentMode !== modeType.both && !newbool) { + ui.toc.toc.addClass('scrollspy-body') + ui.toc.affix.addClass('scrollspy-body') + } else { + ui.toc.toc.addClass('scrollspy-view') + ui.toc.affix.addClass('scrollspy-body') + } + if (newbool !== enoughForAffixToc) { + enoughForAffixToc = newbool + generateScrollspy() + } +} + +function showStatus (type, num) { + window.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 (window.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 = num + ' ' + statusType.online.msg + 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 (window.currentMode) { + case modeType.edit: + changeMode(modeType.view) + break + case modeType.view: + changeMode(modeType.edit) + break + case modeType.both: + changeMode(modeType.view) + break + } +} + +var lastMode = null + +function changeMode (type) { // lock navbar to prevent it hide after changeMode - lockNavbar(); - saveInfo(); - if (type) { - lastMode = currentMode; - 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.show(); - break; - } + lockNavbar() + saveInfo() + if (type) { + lastMode = window.currentMode + window.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 (window.currentMode) { + case modeType.edit: + ui.area.edit.show() + ui.area.view.hide() + if (!window.editShown) { + editor.refresh() + window.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.show() + break + } // save mode to url - if (history.replaceState && loaded) history.replaceState(null, "", serverurl + '/' + noteid + '?' + currentMode.name); - if (currentMode == modeType.view) { - editor.getInputField().blur(); - } - if (currentMode == modeType.edit || currentMode == modeType.both) { - ui.toolbar.uploadImage.fadeIn(); - //add and update status bar - if (!statusBar) { - addStatusBar(); - updateStatusBar(); - } - //work around foldGutter might not init properly - editor.setOption('foldGutter', false); - editor.setOption('foldGutter', true); - } else { - ui.toolbar.uploadImage.fadeOut(); - } - if (currentMode != modeType.edit) { - $(document.body).css('background-color', 'white'); - updateView(); - } else { - $(document.body).css('background-color', ui.area.codemirror.css('background-color')); - } - //check resizable editor style - if (currentMode == modeType.both) { - if (lastEditorWidth > 0) - ui.area.edit.css('width', lastEditorWidth + 'px'); - else - ui.area.edit.css('width', ''); - ui.area.resize.handle.show(); - } else { - ui.area.edit.css('width', ''); - ui.area.resize.handle.hide(); - } - - windowResizeInner(); - - restoreInfo(); - - if (lastMode == modeType.view && currentMode == modeType.both) { - preventSyncScrollToView = 2; - syncScrollToEdit(null, true); - } - - if (lastMode == modeType.edit && currentMode == modeType.both) { - preventSyncScrollToEdit = 2; - syncScrollToView(null, true); - } - - if (lastMode == modeType.both && currentMode != modeType.both) { - preventSyncScrollToView = false; - preventSyncScrollToEdit = false; - } - - if (lastMode != modeType.edit && currentMode == modeType.edit) { - editor.refresh(); - } - - $(document.body).scrollspy('refresh'); - ui.area.view.scrollspy('refresh'); - - 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-pencil').removeClass('fa-eye'); - 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-eye'); - } else if (ui.area.view.is(":visible")) { //view - ui.toolbar.view.addClass("active"); - modeIcon.addClass('fa-pencil'); - } - unlockNavbar(); -} - -function lockNavbar() { - $('.navbar').addClass('locked'); + if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + window.currentMode.name) + if (window.currentMode === modeType.view) { + editor.getInputField().blur() + } + if (window.currentMode === modeType.edit || window.currentMode === modeType.both) { + ui.toolbar.uploadImage.fadeIn() + // add and update status bar + if (!statusBar) { + addStatusBar() + updateStatusBar() + } + // work around foldGutter might not init properly + editor.setOption('foldGutter', false) + editor.setOption('foldGutter', true) + } else { + ui.toolbar.uploadImage.fadeOut() + } + if (window.currentMode !== modeType.edit) { + $(document.body).css('background-color', 'white') + updateView() + } else { + $(document.body).css('background-color', ui.area.codemirror.css('background-color')) + } + // check resizable editor style + if (window.currentMode === modeType.both) { + if (lastEditorWidth > 0) { ui.area.edit.css('width', lastEditorWidth + 'px') } else { ui.area.edit.css('width', '') } + ui.area.resize.handle.show() + } else { + ui.area.edit.css('width', '') + ui.area.resize.handle.hide() + } + + windowResizeInner() + + restoreInfo() + + if (lastMode === modeType.view && window.currentMode === modeType.both) { + window.preventSyncScrollToView = 2 + syncScrollToEdit(null, true) + } + + if (lastMode === modeType.edit && window.currentMode === modeType.both) { + window.preventSyncScrollToEdit = 2 + syncScrollToView(null, true) + } + + if (lastMode === modeType.both && window.currentMode !== modeType.both) { + window.preventSyncScrollToView = false + window.preventSyncScrollToEdit = false + } + + if (lastMode !== modeType.edit && window.currentMode === modeType.edit) { + editor.refresh() + } + + $(document.body).scrollspy('refresh') + ui.area.view.scrollspy('refresh') + + 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-pencil').removeClass('fa-eye') + 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-eye') + } else if (ui.area.view.is(':visible')) { // view + ui.toolbar.view.addClass('active') + modeIcon.addClass('fa-pencil') + } + unlockNavbar() +} + +function lockNavbar () { + $('.navbar').addClass('locked') } var unlockNavbar = _.debounce(function () { - $('.navbar').removeClass('locked'); -}, 200); - -function closestIndex(arr, closestTo) { - var closest = Math.max.apply(null, arr); //Get the highest number in arr in case it match nothing. - var index = 0; - for (var i = 0; i < arr.length; i++) { //Loop the array - if (arr[i] >= closestTo && arr[i] < closest) { - closest = arr[i]; //Check if it's higher than your number, but lower than your closest value - index = i; - } - } - return index; // return the value -} + $('.navbar').removeClass('locked') +}, 200) -function showMessageModal(title, header, href, text, success) { - var modal = $('.message-modal'); - modal.find('.modal-title').html(title); - modal.find('.modal-body h5').html(header); - if (href) - modal.find('.modal-body a').attr('href', href).text(text); - else - modal.find('.modal-body a').removeAttr('href').text(text); - modal.find('.modal-footer button').removeClass('btn-default btn-success btn-danger') - if (success) - modal.find('.modal-footer button').addClass('btn-success'); - else - modal.find('.modal-footer button').addClass('btn-danger'); - modal.modal('show'); +function showMessageModal (title, header, href, text, success) { + var modal = $('.message-modal') + modal.find('.modal-title').html(title) + modal.find('.modal-body h5').html(header) + if (href) { modal.find('.modal-body a').attr('href', href).text(text) } else { modal.find('.modal-body a').removeAttr('href').text(text) } + modal.find('.modal-footer button').removeClass('btn-default btn-success btn-danger') + if (success) { modal.find('.modal-footer button').addClass('btn-success') } else { modal.find('.modal-footer button').addClass('btn-danger') } + modal.modal('show') } // check if dropbox app key is set and load scripts if (DROPBOX_APP_KEY) { - $('<script>') + $('<script>') .attr('type', 'text/javascript') .attr('src', 'https://www.dropbox.com/static/api/2/dropins.js') .attr('id', 'dropboxjs') .attr('data-app-key', DROPBOX_APP_KEY) .prop('async', true) .prop('defer', true) - .appendTo('body'); + .appendTo('body') } else { - ui.toolbar.import.dropbox.hide(); - ui.toolbar.export.dropbox.hide(); + ui.toolbar.import.dropbox.hide() + ui.toolbar.export.dropbox.hide() } // check if google api key and client id are set and load scripts if (GOOGLE_API_KEY && GOOGLE_CLIENT_ID) { - $('<script>') + $('<script>') .attr('type', 'text/javascript') .attr('src', 'https://www.google.com/jsapi?callback=onGoogleAPILoaded') .prop('async', true) .prop('defer', true) - .appendTo('body'); + .appendTo('body') } else { - ui.toolbar.import.googleDrive.hide(); - ui.toolbar.export.googleDrive.hide(); + ui.toolbar.import.googleDrive.hide() + ui.toolbar.export.googleDrive.hide() } -function onGoogleAPILoaded() { - $('<script>') +function onGoogleAPILoaded () { + $('<script>') .attr('type', 'text/javascript') .attr('src', 'https://apis.google.com/js/client:plusone.js?onload=onGoogleClientLoaded') .prop('async', true) .prop('defer', true) - .appendTo('body'); + .appendTo('body') } -window.onGoogleAPILoaded = onGoogleAPILoaded; +window.onGoogleAPILoaded = onGoogleAPILoaded -//button actions -//share -ui.toolbar.publish.attr("href", noteurl + "/publish"); +// button actions +// share +ui.toolbar.publish.attr('href', noteurl + '/publish') // extra -//slide -ui.toolbar.extra.slide.attr("href", noteurl + "/slide"); -//download -//markdown +// slide +ui.toolbar.extra.slide.attr('href', noteurl + '/slide') +// download +// markdown ui.toolbar.download.markdown.click(function (e) { - e.preventDefault(); - e.stopPropagation(); - 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, true); -}); -//html + e.preventDefault() + e.stopPropagation() + 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, true) +}) +// html ui.toolbar.download.html.click(function (e) { - e.preventDefault(); - e.stopPropagation(); - exportToHTML(ui.area.markdown); -}); + e.preventDefault() + e.stopPropagation() + exportToHTML(ui.area.markdown) +}) // raw html ui.toolbar.download.rawhtml.click(function (e) { - e.preventDefault(); - e.stopPropagation(); - exportToRawHTML(ui.area.markdown); -}); -//pdf -ui.toolbar.download.pdf.attr("download", "").attr("href", noteurl + "/pdf"); -//export to dropbox + e.preventDefault() + e.stopPropagation() + exportToRawHTML(ui.area.markdown) +}) +// pdf +ui.toolbar.download.pdf.attr('download', '').attr('href', noteurl + '/pdf') +// export to dropbox ui.toolbar.export.dropbox.click(function () { - var filename = renderFilename(ui.area.markdown) + '.md'; - var options = { - files: [ - { - 'url': noteurl + "/download", - 'filename': filename - } - ], - error: function (errorMessage) { - console.error(errorMessage); - } - }; - Dropbox.save(options); -}); -function uploadToGoogleDrive(accessToken) { - ui.spinner.show(); - var filename = renderFilename(ui.area.markdown) + '.md'; - var markdown = editor.getValue(); - var blob = new Blob([markdown], { - type: "text/markdown;charset=utf-8" - }); - blob.name = filename; - var uploader = new MediaUploader({ - file: blob, - token: accessToken, - onComplete: function (data) { - data = JSON.parse(data); - showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true); - ui.spinner.hide(); - }, - onError: function (data) { - var modal = $('.export-modal'); - showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false); - ui.spinner.hide(); - } - }); - uploader.upload(); -} -function googleApiAuth(immediate, callback) { - gapi.auth.authorize( - { - 'client_id': GOOGLE_CLIENT_ID, - 'scope': 'https://www.googleapis.com/auth/drive.file', - 'immediate': immediate - }, callback ? callback : function () { }); -} -function onGoogleClientLoaded() { - googleApiAuth(true); - buildImportFromGoogleDrive(); -} -window.onGoogleClientLoaded = onGoogleClientLoaded; + var filename = renderFilename(ui.area.markdown) + '.md' + var options = { + files: [ + { + 'url': noteurl + '/download', + 'filename': filename + } + ], + error: function (errorMessage) { + console.error(errorMessage) + } + } + Dropbox.save(options) +}) +function uploadToGoogleDrive (accessToken) { + ui.spinner.show() + var filename = renderFilename(ui.area.markdown) + '.md' + var markdown = editor.getValue() + var blob = new Blob([markdown], { + type: 'text/markdown;charset=utf-8' + }) + blob.name = filename + var uploader = new MediaUploader({ + file: blob, + token: accessToken, + onComplete: function (data) { + data = JSON.parse(data) + showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true) + ui.spinner.hide() + }, + onError: function (data) { + showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false) + ui.spinner.hide() + } + }) + uploader.upload() +} +function googleApiAuth (immediate, callback) { + gapi.auth.authorize( + { + 'client_id': GOOGLE_CLIENT_ID, + 'scope': 'https://www.googleapis.com/auth/drive.file', + 'immediate': immediate + }, callback || function () { }) +} +function onGoogleClientLoaded () { + googleApiAuth(true) + buildImportFromGoogleDrive() +} +window.onGoogleClientLoaded = onGoogleClientLoaded // export to google drive ui.toolbar.export.googleDrive.click(function (e) { - var token = gapi.auth.getToken(); - if (token) { - uploadToGoogleDrive(token.access_token); - } else { - googleApiAuth(false, function (result) { - uploadToGoogleDrive(result.access_token); - }); - } -}); -//export to gist -ui.toolbar.export.gist.attr("href", noteurl + "/gist"); -//export to snippet -ui.toolbar.export.snippet.click(function() { - ui.spinner.show(); - $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects') + var token = gapi.auth.getToken() + if (token) { + uploadToGoogleDrive(token.access_token) + } else { + googleApiAuth(false, function (result) { + uploadToGoogleDrive(result.access_token) + }) + } +}) +// export to gist +ui.toolbar.export.gist.attr('href', noteurl + '/gist') +// export to snippet +ui.toolbar.export.snippet.click(function () { + ui.spinner.show() + $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects') .done(function (data) { - $("#snippetExportModalAccessToken").val(data.accesstoken); - $("#snippetExportModalBaseURL").val(data.baseURL); - $("#snippetExportModalLoading").hide(); - $("#snippetExportModal").modal('toggle'); - $("#snippetExportModalProjects").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>'); - if (data.projects) { - data.projects.sort(function(a,b) { - return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0); - }); - data.projects.forEach(function(project) { - if (!project.snippets_enabled - || (project.permissions.project_access === null && project.permissions.group_access === null) - || (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) - { - return; - } - $('<option>').val(project.id).text(project.path_with_namespace).appendTo("#snippetExportModalProjects"); - }); - $("#snippetExportModalProjects").prop('disabled', false); - } - $("#snippetExportModalLoading").hide(); + $('#snippetExportModalAccessToken').val(data.accesstoken) + $('#snippetExportModalBaseURL').val(data.baseURL) + $('#snippetExportModalLoading').hide() + $('#snippetExportModal').modal('toggle') + $('#snippetExportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>') + if (data.projects) { + data.projects.sort(function (a, b) { + return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0) + }) + data.projects.forEach(function (project) { + if (!project.snippets_enabled || + (project.permissions.project_access === null && project.permissions.group_access === null) || + (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) { + return + } + $('<option>').val(project.id).text(project.path_with_namespace).appendTo('#snippetExportModalProjects') + }) + $('#snippetExportModalProjects').prop('disabled', false) + } + $('#snippetExportModalLoading').hide() }) .fail(function (data) { - showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false); + showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false) }) .always(function () { - ui.spinner.hide(); - }); -}); -//import from dropbox + ui.spinner.hide() + }) +}) +// 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); -}); + 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 google drive -var picker = null; -function buildImportFromGoogleDrive() { - picker = new FilePicker({ - apiKey: GOOGLE_API_KEY, - clientId: GOOGLE_CLIENT_ID, - buttonEl: ui.toolbar.import.googleDrive, - onSelect: function (file) { - if (file.downloadUrl) { - ui.spinner.show(); - var accessToken = gapi.auth.getToken().access_token; - $.ajax({ - type: 'GET', - beforeSend: function (request) { - request.setRequestHeader('Authorization', 'Bearer ' + accessToken); - }, - url: file.downloadUrl, - success: function (data) { - if (file.fileExtension == 'html') - parseToEditor(data); - else - replaceAll(data); - }, - error: function (data) { - showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false); - }, - complete: function () { - ui.spinner.hide(); - } - }); - } - } - }); +function buildImportFromGoogleDrive () { + FilePicker({ + apiKey: GOOGLE_API_KEY, + clientId: GOOGLE_CLIENT_ID, + buttonEl: ui.toolbar.import.googleDrive, + onSelect: function (file) { + if (file.downloadUrl) { + ui.spinner.show() + var accessToken = gapi.auth.getToken().access_token + $.ajax({ + type: 'GET', + beforeSend: function (request) { + request.setRequestHeader('Authorization', 'Bearer ' + accessToken) + }, + url: file.downloadUrl, + success: function (data) { + if (file.fileExtension === 'html') { parseToEditor(data) } else { replaceAll(data) } + }, + error: function (data) { + showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false) + }, + complete: function () { + ui.spinner.hide() + } + }) + } + } + }) } -//import from gist +// import from gist ui.toolbar.import.gist.click(function () { - //na -}); -//import from snippet + // na +}) +// import from snippet ui.toolbar.import.snippet.click(function () { - ui.spinner.show(); - $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects') + ui.spinner.show() + $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects') .done(function (data) { - $("#snippetImportModalAccessToken").val(data.accesstoken); - $("#snippetImportModalBaseURL").val(data.baseURL); - $("#snippetImportModalContent").prop('disabled', false); - $("#snippetImportModalConfirm").prop('disabled', false); - $("#snippetImportModalLoading").hide(); - $("#snippetImportModal").modal('toggle'); - $("#snippetImportModalProjects").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>'); - if (data.projects) { - data.projects.sort(function(a,b) { - return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0); - }); - data.projects.forEach(function(project) { - if (!project.snippets_enabled - || (project.permissions.project_access === null && project.permissions.group_access === null) - || (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) - { - return; - } - $('<option>').val(project.id).text(project.path_with_namespace).appendTo("#snippetImportModalProjects"); - }); - $("#snippetImportModalProjects").prop('disabled', false); - } - $("#snippetImportModalLoading").hide(); + $('#snippetImportModalAccessToken').val(data.accesstoken) + $('#snippetImportModalBaseURL').val(data.baseURL) + $('#snippetImportModalContent').prop('disabled', false) + $('#snippetImportModalConfirm').prop('disabled', false) + $('#snippetImportModalLoading').hide() + $('#snippetImportModal').modal('toggle') + $('#snippetImportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>') + if (data.projects) { + data.projects.sort(function (a, b) { + return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0) + }) + data.projects.forEach(function (project) { + if (!project.snippets_enabled || + (project.permissions.project_access === null && project.permissions.group_access === null) || + (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) { + return + } + $('<option>').val(project.id).text(project.path_with_namespace).appendTo('#snippetImportModalProjects') + }) + $('#snippetImportModalProjects').prop('disabled', false) + } + $('#snippetImportModalLoading').hide() }) .fail(function (data) { - showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false); + showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false) }) .always(function () { - ui.spinner.hide(); - }); -}); -//import from clipboard + ui.spinner.hide() + }) +}) +// import from clipboard ui.toolbar.import.clipboard.click(function () { - //na -}); -//upload image + // na +}) +// upload image ui.toolbar.uploadImage.bind('change', function (e) { - var files = e.target.files || e.dataTransfer.files; - e.dataTransfer = {}; - e.dataTransfer.files = files; - inlineAttach.onDrop(e); -}); -//toc + var files = e.target.files || e.dataTransfer.files + e.dataTransfer = {} + e.dataTransfer.files = files + inlineAttach.onDrop(e) +}) +// toc ui.toc.dropdown.click(function (e) { - e.stopPropagation(); -}); + e.stopPropagation() +}) // prevent empty link change hash $('a[href="#"]').click(function (e) { - e.preventDefault(); -}); - -//modal actions -var revisions = []; -var revisionViewer = null; -var revisionInsert = []; -var revisionDelete = []; -var revisionInsertAnnotation = null; -var revisionDeleteAnnotation = null; -var revisionList = ui.modal.revision.find('.ui-revision-list'); -var revision = null; -var revisionTime = null; + e.preventDefault() +}) + +// modal actions +var revisions = [] +var revisionViewer = null +var revisionInsert = [] +var revisionDelete = [] +var revisionInsertAnnotation = null +var revisionDeleteAnnotation = null +var revisionList = ui.modal.revision.find('.ui-revision-list') +var revision = null +var revisionTime = null ui.modal.revision.on('show.bs.modal', function (e) { - $.get(noteurl + '/revision') - .done(function(data) { - parseRevisions(data.revision); - initRevisionViewer(); + $.get(noteurl + '/revision') + .done(function (data) { + parseRevisions(data.revision) + initRevisionViewer() }) - .fail(function(err) { - + .fail(function (err) { + if (debug) { + console.log(err) + } }) - .always(function() { - //na - }); -}); -function checkRevisionViewer() { - if (revisionViewer) { - var container = $(revisionViewer.display.wrapper).parent(); - $(revisionViewer.display.scroller).css('height', container.height() + 'px'); - revisionViewer.refresh(); - } -} -ui.modal.revision.on('shown.bs.modal', checkRevisionViewer); -$(window).resize(checkRevisionViewer); -function parseRevisions(_revisions) { - if (_revisions.length != revisions) { - revisions = _revisions; - var lastRevision = null; - if (revisionList.children().length > 0) { - lastRevision = revisionList.find('.active').attr('data-revision-time'); - } - revisionList.html(''); - for (var i = 0; i < revisions.length; i++) { - var revision = revisions[i]; - var item = $('<a href="#" class="list-group-item"></a>'); - item.attr('data-revision-time', revision.time); - if (lastRevision == revision.time) item.addClass('active'); - var itemHeading = $('<h5 class="list-group-item-heading"></h5>'); - itemHeading.html('<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll')); - var itemText = $('<p class="list-group-item-text"></p>'); - itemText.html('<i class="fa fa-file-text"></i> Length: ' + revision.length); - item.append(itemHeading).append(itemText); - item.click(function (e) { - var time = $(this).attr('data-revision-time'); - selectRevision(time); - }); - revisionList.append(item); - } - if (!lastRevision) { - selectRevision(revisions[0].time); - } - } -} -function selectRevision(time) { - if (time == revisionTime) return; - $.get(noteurl + '/revision/' + time) - .done(function(data) { - revision = data; - revisionTime = time; - var lastScrollInfo = revisionViewer.getScrollInfo(); - revisionList.children().removeClass('active'); - revisionList.find('[data-revision-time="' + time + '"]').addClass('active'); - var content = revision.content; - revisionViewer.setValue(content); - revisionViewer.scrollTo(null, lastScrollInfo.top); - revisionInsert = []; - revisionDelete = []; + .always(function () { + // na + }) +}) +function checkRevisionViewer () { + if (revisionViewer) { + var container = $(revisionViewer.display.wrapper).parent() + $(revisionViewer.display.scroller).css('height', container.height() + 'px') + revisionViewer.refresh() + } +} +ui.modal.revision.on('shown.bs.modal', checkRevisionViewer) +$(window).resize(checkRevisionViewer) +function parseRevisions (_revisions) { + if (_revisions.length !== revisions) { + revisions = _revisions + var lastRevision = null + if (revisionList.children().length > 0) { + lastRevision = revisionList.find('.active').attr('data-revision-time') + } + revisionList.html('') + for (var i = 0; i < revisions.length; i++) { + var revision = revisions[i] + var item = $('<a href="#" class="list-group-item"></a>') + item.attr('data-revision-time', revision.time) + if (lastRevision === revision.time) item.addClass('active') + var itemHeading = $('<h5 class="list-group-item-heading"></h5>') + itemHeading.html('<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll')) + var itemText = $('<p class="list-group-item-text"></p>') + itemText.html('<i class="fa fa-file-text"></i> Length: ' + revision.length) + item.append(itemHeading).append(itemText) + item.click(function (e) { + var time = $(this).attr('data-revision-time') + selectRevision(time) + }) + revisionList.append(item) + } + if (!lastRevision) { + selectRevision(revisions[0].time) + } + } +} +function selectRevision (time) { + if (time === revisionTime) return + $.get(noteurl + '/revision/' + time) + .done(function (data) { + revision = data + revisionTime = time + var lastScrollInfo = revisionViewer.getScrollInfo() + revisionList.children().removeClass('active') + revisionList.find('[data-revision-time="' + time + '"]').addClass('active') + var content = revision.content + revisionViewer.setValue(content) + revisionViewer.scrollTo(null, lastScrollInfo.top) + revisionInsert = [] + revisionDelete = [] // mark the text which have been insert or delete - if (revision.patch.length > 0) { - var bias = 0; - for (var j = 0; j < revision.patch.length; j++) { - var patch = revision.patch[j]; - var currIndex = patch.start1 + bias; - for (var i = 0; i < patch.diffs.length; i++) { - var diff = patch.diffs[i]; + if (revision.patch.length > 0) { + var bias = 0 + for (var j = 0; j < revision.patch.length; j++) { + var patch = revision.patch[j] + var currIndex = patch.start1 + bias + for (var i = 0; i < patch.diffs.length; i++) { + var diff = patch.diffs[i] // ignore if diff only contains line breaks - if ((diff[1].match(new RegExp("\n", "g")) || []).length == diff[1].length) continue; - switch(diff[0]) { - case 0: // retain - currIndex += diff[1].length; - break; - case 1: // insert - var prePos = revisionViewer.posFromIndex(currIndex); - var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length); - revisionInsert.push({ - from: prePos, - to: postPos - }); - revisionViewer.markText(prePos, postPos, { - css: 'background-color: rgba(230,255,230,0.7); text-decoration: underline;' - }); - currIndex += diff[1].length; - break; - case -1: // delete - var prePos = revisionViewer.posFromIndex(currIndex); - revisionViewer.replaceRange(diff[1], prePos); - var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length); - revisionDelete.push({ - from: prePos, - to: postPos - }); - revisionViewer.markText(prePos, postPos, { - css: 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;' - }); - bias += diff[1].length; - currIndex += diff[1].length; - break; - } - } + if ((diff[1].match(/\n/g) || []).length === diff[1].length) continue + var prePos + var postPos + switch (diff[0]) { + case 0: // retain + currIndex += diff[1].length + break + case 1: // insert + prePos = revisionViewer.posFromIndex(currIndex) + postPos = revisionViewer.posFromIndex(currIndex + diff[1].length) + revisionInsert.push({ + from: prePos, + to: postPos + }) + revisionViewer.markText(prePos, postPos, { + css: 'background-color: rgba(230,255,230,0.7); text-decoration: underline;' + }) + currIndex += diff[1].length + break + case -1: // delete + prePos = revisionViewer.posFromIndex(currIndex) + revisionViewer.replaceRange(diff[1], prePos) + postPos = revisionViewer.posFromIndex(currIndex + diff[1].length) + revisionDelete.push({ + from: prePos, + to: postPos + }) + revisionViewer.markText(prePos, postPos, { + css: 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;' + }) + bias += diff[1].length + currIndex += diff[1].length + break } + } } - revisionInsertAnnotation.update(revisionInsert); - revisionDeleteAnnotation.update(revisionDelete); + } + revisionInsertAnnotation.update(revisionInsert) + revisionDeleteAnnotation.update(revisionDelete) }) - .fail(function(err) { - + .fail(function (err) { + if (debug) { + console.log(err) + } + }) + .always(function () { + // na }) - .always(function() { - //na - }); -} -function initRevisionViewer() { - if (revisionViewer) return; - var revisionViewerTextArea = document.getElementById("revisionViewer"); - revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, { - mode: defaultEditorMode, - viewportMargin: viewportMargin, - lineNumbers: true, - lineWrapping: true, - showCursorWhenSelecting: true, - inputStyle: "textarea", - gutters: ["CodeMirror-linenumbers"], - flattenSpans: true, - addModeClass: true, - readOnly: true, - autoRefresh: true, - scrollbarStyle: 'overlay' - }); - revisionInsertAnnotation = revisionViewer.annotateScrollbar({ className:"CodeMirror-insert-match" }); - revisionDeleteAnnotation = revisionViewer.annotateScrollbar({ className:"CodeMirror-delete-match" }); - checkRevisionViewer(); +} +function initRevisionViewer () { + if (revisionViewer) return + var revisionViewerTextArea = document.getElementById('revisionViewer') + revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, { + mode: defaultEditorMode, + viewportMargin: viewportMargin, + lineNumbers: true, + lineWrapping: true, + showCursorWhenSelecting: true, + inputStyle: 'textarea', + gutters: ['CodeMirror-linenumbers'], + flattenSpans: true, + addModeClass: true, + readOnly: true, + autoRefresh: true, + scrollbarStyle: 'overlay' + }) + revisionInsertAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-insert-match' }) + revisionDeleteAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-delete-match' }) + checkRevisionViewer() } $('#revisionModalDownload').click(function () { - if (!revision) return; - var filename = renderFilename(ui.area.markdown) + '_' + revisionTime + '.md'; - var blob = new Blob([revision.content], { - type: "text/markdown;charset=utf-8" - }); - saveAs(blob, filename, true); -}); + if (!revision) return + var filename = renderFilename(ui.area.markdown) + '_' + revisionTime + '.md' + var blob = new Blob([revision.content], { + type: 'text/markdown;charset=utf-8' + }) + saveAs(blob, filename, true) +}) $('#revisionModalRevert').click(function () { - if (!revision) return; - editor.setValue(revision.content); - ui.modal.revision.modal('hide'); -}); -//snippet projects -ui.modal.snippetImportProjects.change(function() { - var accesstoken = $("#snippetImportModalAccessToken").val(), - baseURL = $("#snippetImportModalBaseURL").val(), - project = $("#snippetImportModalProjects").val(); - - $("#snippetImportModalLoading").show(); - $("#snippetImportModalContent").val('/projects/' + project); - $.get(baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken) - .done(function(data) { - $("#snippetImportModalSnippets").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>'); - data.forEach(function(snippet) { - $('<option>').val(snippet.id).text(snippet.title).appendTo($("#snippetImportModalSnippets")); - }); - $("#snippetImportModalLoading").hide(); - $("#snippetImportModalSnippets").prop('disabled', false); + if (!revision) return + editor.setValue(revision.content) + ui.modal.revision.modal('hide') +}) +// snippet projects +ui.modal.snippetImportProjects.change(function () { + var accesstoken = $('#snippetImportModalAccessToken').val() + var baseURL = $('#snippetImportModalBaseURL').val() + var project = $('#snippetImportModalProjects').val() + + $('#snippetImportModalLoading').show() + $('#snippetImportModalContent').val('/projects/' + project) + $.get(baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken) + .done(function (data) { + $('#snippetImportModalSnippets').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>') + data.forEach(function (snippet) { + $('<option>').val(snippet.id).text(snippet.title).appendTo($('#snippetImportModalSnippets')) + }) + $('#snippetImportModalLoading').hide() + $('#snippetImportModalSnippets').prop('disabled', false) }) - .fail(function(err) { - + .fail(function (err) { + if (debug) { + console.log(err) + } }) - .always(function() { - //na - }); -}); -//snippet snippets -ui.modal.snippetImportSnippets.change(function() { - var project = $("#snippetImportModalProjects").val(), - snippet = $("#snippetImportModalSnippets").val(); - - $("#snippetImportModalContent").val($("#snippetImportModalContent").val() + '/snippets/' + snippet); -}) - -function scrollToTop() { - if (currentMode == modeType.both) { - if (editor.getScrollInfo().top != 0) - editor.scrollTo(0, 0); - else - ui.area.view.animate({ - scrollTop: 0 - }, 100, "linear"); - } else { - $('body, html').stop(true, true).animate({ - scrollTop: 0 - }, 100, "linear"); - } -} - -function scrollToBottom() { - if (currentMode == modeType.both) { - var scrollInfo = editor.getScrollInfo(); - var scrollHeight = scrollInfo.height; - if (scrollInfo.top != scrollHeight) - editor.scrollTo(0, scrollHeight * 2); - else - ui.area.view.animate({ - scrollTop: ui.area.view[0].scrollHeight - }, 100, "linear"); - } else { - $('body, html').stop(true, true).animate({ - scrollTop: $(document.body)[0].scrollHeight - }, 100, "linear"); - } -} - -window.scrollToTop = scrollToTop; -window.scrollToBottom = scrollToBottom; - -var enoughForAffixToc = true; - -//scrollspy -function generateScrollspy() { - $(document.body).scrollspy({ - target: '.scrollspy-body' - }); - ui.area.view.scrollspy({ - target: '.scrollspy-view' - }); - $(document.body).scrollspy('refresh'); - ui.area.view.scrollspy('refresh'); - if (enoughForAffixToc) { - ui.toc.toc.hide(); - ui.toc.affix.show(); - } else { - ui.toc.affix.hide(); - ui.toc.toc.show(); - } - //$(document.body).scroll(); - //ui.area.view.scroll(); -} + .always(function () { + // na + }) +}) +// snippet snippets +ui.modal.snippetImportSnippets.change(function () { + var snippet = $('#snippetImportModalSnippets').val() + $('#snippetImportModalContent').val($('#snippetImportModalContent').val() + '/snippets/' + snippet) +}) -function updateScrollspy() { - var headers = ui.area.markdown.find('h1, h2, h3').toArray(); - var headerMap = []; - for (var i = 0; i < headers.length; i++) { - headerMap.push($(headers[i]).offset().top - parseInt($(headers[i]).css('margin-top'))); - } - applyScrollspyActive($(window).scrollTop(), headerMap, headers, - $('.scrollspy-body'), 0); - var offset = ui.area.view.scrollTop() - ui.area.view.offset().top; - applyScrollspyActive(ui.area.view.scrollTop(), headerMap, headers, - $('.scrollspy-view'), offset - 10); -} - -function applyScrollspyActive(top, headerMap, headers, target, offset) { - var index = 0; - for (var i = headerMap.length - 1; i >= 0; i--) { - if (top >= (headerMap[i] + offset) && headerMap[i + 1] && top < (headerMap[i + 1] + offset)) { - index = i; - break; - } - } - var header = $(headers[index]); - var active = target.find('a[href="#' + header.attr('id') + '"]'); - active.closest('li').addClass('active').parent().closest('li').addClass('active').parent().closest('li').addClass('active'); +function scrollToTop () { + if (window.currentMode === modeType.both) { + if (editor.getScrollInfo().top !== 0) { editor.scrollTo(0, 0) } else { + ui.area.view.animate({ + scrollTop: 0 + }, 100, 'linear') + } + } else { + $('body, html').stop(true, true).animate({ + scrollTop: 0 + }, 100, 'linear') + } +} + +function scrollToBottom () { + if (window.currentMode === modeType.both) { + var scrollInfo = editor.getScrollInfo() + var scrollHeight = scrollInfo.height + if (scrollInfo.top !== scrollHeight) { editor.scrollTo(0, scrollHeight * 2) } else { + ui.area.view.animate({ + scrollTop: ui.area.view[0].scrollHeight + }, 100, 'linear') + } + } else { + $('body, html').stop(true, true).animate({ + scrollTop: $(document.body)[0].scrollHeight + }, 100, 'linear') + } +} + +window.scrollToTop = scrollToTop +window.scrollToBottom = scrollToBottom + +var enoughForAffixToc = true + +// scrollspy +function generateScrollspy () { + $(document.body).scrollspy({ + target: '.scrollspy-body' + }) + ui.area.view.scrollspy({ + target: '.scrollspy-view' + }) + $(document.body).scrollspy('refresh') + ui.area.view.scrollspy('refresh') + if (enoughForAffixToc) { + ui.toc.toc.hide() + ui.toc.affix.show() + } else { + ui.toc.affix.hide() + ui.toc.toc.show() + } + // $(document.body).scroll(); + // ui.area.view.scroll(); +} + +function updateScrollspy () { + var headers = ui.area.markdown.find('h1, h2, h3').toArray() + var headerMap = [] + for (var i = 0; i < headers.length; i++) { + headerMap.push($(headers[i]).offset().top - parseInt($(headers[i]).css('margin-top'))) + } + applyScrollspyActive($(window).scrollTop(), headerMap, headers, + $('.scrollspy-body'), 0) + var offset = ui.area.view.scrollTop() - ui.area.view.offset().top + applyScrollspyActive(ui.area.view.scrollTop(), headerMap, headers, + $('.scrollspy-view'), offset - 10) +} + +function applyScrollspyActive (top, headerMap, headers, target, offset) { + var index = 0 + for (var i = headerMap.length - 1; i >= 0; i--) { + if (top >= (headerMap[i] + offset) && headerMap[i + 1] && top < (headerMap[i + 1] + offset)) { + index = i + break + } + } + var header = $(headers[index]) + var active = target.find('a[href="#' + header.attr('id') + '"]') + active.closest('li').addClass('active').parent().closest('li').addClass('active').parent().closest('li').addClass('active') } // clipboard modal -//fix for wrong autofocus +// 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(''); - } -}); + $('#clipboardModal').blur() +}) +$('#clipboardModalClear').click(function () { + $('#clipboardModalContent').html('') +}) +$('#clipboardModalConfirm').click(function () { + var data = $('#clipboardModalContent').html() + if (data) { + parseToEditor(data) + $('#clipboardModal').modal('hide') + $('#clipboardModalContent').html('') + } +}) // refresh modal $('#refreshModalRefresh').click(function () { - location.reload(true); -}); + location.reload(true) +}) // gist import modal -$("#gistImportModalClear").click(function () { - $("#gistImportModalContent").val(''); -}); -$("#gistImportModalConfirm").click(function () { - var gisturl = $("#gistImportModalContent").val(); - if (!gisturl) return; - $('#gistImportModal').modal('hide'); - $("#gistImportModalContent").val(''); - if (!isValidURL(gisturl)) { - showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid URL :(', '', '', false); - return; +$('#gistImportModalClear').click(function () { + $('#gistImportModalContent').val('') +}) +$('#gistImportModalConfirm').click(function () { + var gisturl = $('#gistImportModalContent').val() + if (!gisturl) return + $('#gistImportModal').modal('hide') + $('#gistImportModalContent').val('') + if (!isValidURL(gisturl)) { + showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid URL :(', '', '', false) + } else { + var hostname = window.url('hostname', gisturl) + if (hostname !== 'gist.github.com') { + showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', '', false) } else { - var hostname = url('hostname', gisturl) - if (hostname !== 'gist.github.com') { - showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', '', false); - } else { - ui.spinner.show(); - $.get('https://api.github.com/gists/' + url('-1', gisturl)) + ui.spinner.show() + $.get('https://api.github.com/gists/' + window.url('-1', gisturl)) .done(function (data) { - if (data.files) { - var contents = ""; - Object.keys(data.files).forEach(function (key) { - contents += key; - contents += '\n---\n'; - contents += data.files[key].content; - contents += '\n\n'; - }); - replaceAll(contents); - } else { - showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Unable to fetch gist files :(', '', '', false); - } + if (data.files) { + var contents = '' + Object.keys(data.files).forEach(function (key) { + contents += key + contents += '\n---\n' + contents += data.files[key].content + contents += '\n\n' + }) + replaceAll(contents) + } else { + showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Unable to fetch gist files :(', '', '', false) + } }) .fail(function (data) { - showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', JSON.stringify(data), false); + showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', JSON.stringify(data), false) }) .always(function () { - ui.spinner.hide(); - }); - } + ui.spinner.hide() + }) } -}); + } +}) // snippet import modal -$("#snippetImportModalClear").click(function () { - $("#snippetImportModalContent").val(''); - $("#snippetImportModalProjects").val('init'); - $("#snippetImportModalSnippets").val('init'); - $("#snippetImportModalSnippets").prop('disabled', true); -}); -$("#snippetImportModalConfirm").click(function () { - var snippeturl = $("#snippetImportModalContent").val(); - if (!snippeturl) return; - $('#snippetImportModal').modal('hide'); - $("#snippetImportModalContent").val(''); - if (!/^.+\/snippets\/.+$/.test(snippeturl)) { - showMessageModal('<i class="fa fa-github"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', '', false); - } else { - ui.spinner.show(); - var accessToken = '?access_token=' + $("#snippetImportModalAccessToken").val(); - var fullURL = $("#snippetImportModalBaseURL").val() + '/api/v3' + snippeturl; - $.get(fullURL + accessToken) - .done(function(data) { - var content = '# ' + (data.title || "Snippet Import"); - var fileInfo = data.file_name.split('.'); - fileInfo[1] = (fileInfo[1]) ? fileInfo[1] : "md"; - $.get(fullURL + '/raw' + accessToken) +$('#snippetImportModalClear').click(function () { + $('#snippetImportModalContent').val('') + $('#snippetImportModalProjects').val('init') + $('#snippetImportModalSnippets').val('init') + $('#snippetImportModalSnippets').prop('disabled', true) +}) +$('#snippetImportModalConfirm').click(function () { + var snippeturl = $('#snippetImportModalContent').val() + if (!snippeturl) return + $('#snippetImportModal').modal('hide') + $('#snippetImportModalContent').val('') + if (!/^.+\/snippets\/.+$/.test(snippeturl)) { + showMessageModal('<i class="fa fa-github"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', '', false) + } else { + ui.spinner.show() + var accessToken = '?access_token=' + $('#snippetImportModalAccessToken').val() + var fullURL = $('#snippetImportModalBaseURL').val() + '/api/v3' + snippeturl + $.get(fullURL + accessToken) + .done(function (data) { + var content = '# ' + (data.title || 'Snippet Import') + var fileInfo = data.file_name.split('.') + fileInfo[1] = (fileInfo[1]) ? fileInfo[1] : 'md' + $.get(fullURL + '/raw' + accessToken) .done(function (raw) { - if (raw) { - content += "\n\n"; - if (fileInfo[1] != "md") { - content += "```" + fileTypes[fileInfo[1]] + "\n"; - } - content += raw; - if (fileInfo[1] != "md") { - content += "\n```"; - } - replaceAll(content); + if (raw) { + content += '\n\n' + if (fileInfo[1] !== 'md') { + content += '```' + window.fileTypes[fileInfo[1]] + '\n' + } + content += raw + if (fileInfo[1] !== 'md') { + content += '\n```' } + replaceAll(content) + } }) .fail(function (data) { - showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false); + showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false) }) .always(function () { - ui.spinner.hide(); - }); + ui.spinner.hide() + }) }) .fail(function (data) { - showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false); - }); - } -}); - -//snippet export modal -$("#snippetExportModalConfirm").click(function() { - var accesstoken = $("#snippetExportModalAccessToken").val(), - baseURL = $("#snippetExportModalBaseURL").val(), - data = { - title: $("#snippetExportModalTitle").val(), - file_name: $("#snippetExportModalFileName").val(), - code: editor.getValue(), - visibility_level: $("#snippetExportModalVisibility").val() - }; - if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$("#snippetExportModalProjects").val()) return; - $("#snippetExportModalLoading").show(); - var fullURL = baseURL + '/api/v3/projects/' + $("#snippetExportModalProjects").val() + '/snippets?access_token=' + accesstoken; - $.post(fullURL + showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false) + }) + } +}) + +// snippet export modal +$('#snippetExportModalConfirm').click(function () { + var accesstoken = $('#snippetExportModalAccessToken').val() + var baseURL = $('#snippetExportModalBaseURL').val() + var data = { + title: $('#snippetExportModalTitle').val(), + file_name: $('#snippetExportModalFileName').val(), + code: editor.getValue(), + visibility_level: $('#snippetExportModalVisibility').val() + } + if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$('#snippetExportModalProjects').val()) return + $('#snippetExportModalLoading').show() + var fullURL = baseURL + '/api/v3/projects/' + $('#snippetExportModalProjects').val() + '/snippets?access_token=' + accesstoken + $.post(fullURL , data - , function(ret) { - $("#snippetExportModalLoading").hide(); - $('#snippetExportModal').modal('hide'); - var redirect = baseURL + '/' + $("#snippetExportModalProjects option[value='" + $("#snippetExportModalProjects").val() + "']").text() + '/snippets/' + ret.id; - showMessageModal('<i class="fa fa-gitlab"></i> Export to Snippet', 'Export Successful!', redirect, 'View Snippet Here', true); + , function (ret) { + $('#snippetExportModalLoading').hide() + $('#snippetExportModal').modal('hide') + var redirect = baseURL + '/' + $("#snippetExportModalProjects option[value='" + $('#snippetExportModalProjects').val() + "']").text() + '/snippets/' + ret.id + showMessageModal('<i class="fa fa-gitlab"></i> Export to Snippet', 'Export Successful!', redirect, 'View Snippet Here', true) } , 'json' - ); -}); - -function parseToEditor(data) { - var parsed = toMarkdown(data); - if (parsed) - replaceAll(parsed); -} - -function replaceAll(data) { - editor.replaceRange(data, { - line: 0, - ch: 0 - }, { - line: editor.lastLine(), - ch: editor.lastLine().length - }, '+input'); -} - -function importFromUrl(url) { - //console.log(url); - if (!url) return; - if (!isValidURL(url)) { - showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Not a valid URL :(', '', '', false); - return; - } - $.ajax({ - method: "GET", - url: url, - success: function (data) { - var extension = url.split('.').pop(); - if (extension == 'html') - parseToEditor(data); - else - replaceAll(data); - }, - error: function (data) { - showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Import failed :(', '', JSON.stringify(data), false); - }, - complete: function () { - ui.spinner.hide(); - } - }); + ) +}) + +function parseToEditor (data) { + var parsed = toMarkdown(data) + if (parsed) { replaceAll(parsed) } +} + +function replaceAll (data) { + editor.replaceRange(data, { + line: 0, + ch: 0 + }, { + line: editor.lastLine(), + ch: editor.lastLine().length + }, '+input') +} + +function importFromUrl (url) { + // console.log(url); + if (!url) return + if (!isValidURL(url)) { + showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Not a valid URL :(', '', '', false) + return + } + $.ajax({ + method: 'GET', + url: url, + success: function (data) { + var extension = url.split('.').pop() + if (extension === 'html') { parseToEditor(data) } else { replaceAll(data) } + }, + error: function (data) { + showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Import failed :(', '', JSON.stringify(data), false) + }, + complete: function () { + ui.spinner.hide() + } + }) } -//mode +// mode ui.toolbar.mode.click(function () { - toggleMode(); -}); -//edit + toggleMode() +}) +// edit ui.toolbar.edit.click(function () { - changeMode(modeType.edit); -}); -//view + changeMode(modeType.edit) +}) +// view ui.toolbar.view.click(function () { - changeMode(modeType.view); -}); -//both + changeMode(modeType.view) +}) +// both ui.toolbar.both.click(function () { - changeMode(modeType.both); -}); -//permission -//freely + changeMode(modeType.both) +}) +// permission +// freely ui.infobar.permission.freely.click(function () { - emitPermission("freely"); -}); -//editable + emitPermission('freely') +}) +// editable ui.infobar.permission.editable.click(function () { - emitPermission("editable"); -}); -//locked + emitPermission('editable') +}) +// locked ui.infobar.permission.locked.click(function () { - emitPermission("locked"); -}); -//private + emitPermission('locked') +}) +// private ui.infobar.permission.private.click(function () { - emitPermission("private"); -}); -//limited -ui.infobar.permission.limited.click(function() { - emitPermission("limited"); -}); -//protected -ui.infobar.permission.protected.click(function() { - emitPermission("protected"); -}); + emitPermission('private') +}) +// limited +ui.infobar.permission.limited.click(function () { + emitPermission('limited') +}) +// protected +ui.infobar.permission.protected.click(function () { + emitPermission('protected') +}) // delete note ui.infobar.delete.click(function () { - $('.delete-modal').modal('show'); -}); + $('.delete-modal').modal('show') +}) $('.ui-delete-modal-confirm').click(function () { - socket.emit('delete'); -}); - -function emitPermission(_permission) { - if (_permission != permission) { - socket.emit('permission', _permission); - } -} - -function updatePermission(newPermission) { - if (permission != newPermission) { - permission = newPermission; - if (loaded) refreshView(); - } - var label = null; - var title = null; - switch (permission) { - case "freely": - label = '<i class="fa fa-leaf"></i> Freely'; - title = "Anyone can edit"; - break; - case "editable": - label = '<i class="fa fa-shield"></i> Editable'; - title = "Signed people can edit"; - break; - case "limited": - label = '<i class="fa fa-id-card"></i> Limited'; - title = "Signed people can edit (forbid guest)" - break; - case "locked": - label = '<i class="fa fa-lock"></i> Locked'; - title = "Only owner can edit"; - break; - case "protected": - label = '<i class="fa fa-umbrella"></i> Protected'; - title = "Only owner can edit (forbid guest)"; - break; - case "private": - label = '<i class="fa fa-hand-stop-o"></i> Private'; - title = "Only owner can view & edit"; - break; - } - if (personalInfo.userid && owner && personalInfo.userid == owner) { - label += ' <i class="fa fa-caret-down"></i>'; - ui.infobar.permission.label.removeClass('disabled'); - } else { - ui.infobar.permission.label.addClass('disabled'); - } - ui.infobar.permission.label.html(label).attr('title', title); -} - -function havePermission() { - var bool = false; - switch (permission) { - case "freely": - bool = true; - break; - case "editable": - case "limited": - if (!personalInfo.login) { - bool = false; - } else { - bool = true; - } - break; - case "locked": - case "private": - case "protected": - if (!owner || personalInfo.userid != owner) { - bool = false; - } else { - bool = true; - } - break; - } - return bool; + socket.emit('delete') +}) + +function emitPermission (_permission) { + if (_permission !== permission) { + socket.emit('permission', _permission) + } +} + +function updatePermission (newPermission) { + if (permission !== newPermission) { + permission = newPermission + if (window.loaded) refreshView() + } + var label = null + var title = null + switch (permission) { + case 'freely': + label = '<i class="fa fa-leaf"></i> Freely' + title = 'Anyone can edit' + break + case 'editable': + label = '<i class="fa fa-shield"></i> Editable' + title = 'Signed people can edit' + break + case 'limited': + label = '<i class="fa fa-id-card"></i> Limited' + title = 'Signed people can edit (forbid guest)' + break + case 'locked': + label = '<i class="fa fa-lock"></i> Locked' + title = 'Only owner can edit' + break + case 'protected': + label = '<i class="fa fa-umbrella"></i> Protected' + title = 'Only owner can edit (forbid guest)' + break + case 'private': + label = '<i class="fa fa-hand-stop-o"></i> Private' + title = 'Only owner can view & edit' + break + } + if (window.personalInfo.userid && window.owner && window.personalInfo.userid === window.owner) { + label += ' <i class="fa fa-caret-down"></i>' + ui.infobar.permission.label.removeClass('disabled') + } else { + ui.infobar.permission.label.addClass('disabled') + } + ui.infobar.permission.label.html(label).attr('title', title) +} + +function havePermission () { + var bool = false + switch (permission) { + case 'freely': + bool = true + break + case 'editable': + case 'limited': + if (!window.personalInfo.login) { + bool = false + } else { + bool = true + } + break + case 'locked': + case 'private': + case 'protected': + if (!window.owner || window.personalInfo.userid !== window.owner) { + bool = false + } else { + bool = true + } + break + } + return bool } // global module workaround -window.havePermission = havePermission; +window.havePermission = havePermission -//socket.io actions -var io = require("socket.io-client"); +// socket.io actions +var io = require('socket.io-client') var socket = io.connect({ - path: urlpath ? '/' + urlpath + '/socket.io/' : '', - timeout: 5000, //5 secs to timeout, - reconnectionAttempts: 20 // retry 20 times on connect failed -}); -//overwrite original event for checking login state -var on = socket.on; + path: urlpath ? '/' + urlpath + '/socket.io/' : '', + timeout: 5000, // 5 secs to timeout, + reconnectionAttempts: 20 // retry 20 times on connect failed +}) +// overwrite original event for checking login state +var on = socket.on socket.on = function () { - if (!checkLoginStateChanged() && !needRefresh) - return on.apply(socket, arguments); -}; -var emit = socket.emit; + if (!checkLoginStateChanged() && !window.needRefresh) { return on.apply(socket, arguments) } +} +var emit = socket.emit socket.emit = function () { - if (!checkLoginStateChanged() && !needRefresh) - emit.apply(socket, arguments); -}; + if (!checkLoginStateChanged() && !window.needRefresh) { emit.apply(socket, arguments) } +} socket.on('info', function (data) { - console.error(data); - switch (data.code) { - case 403: - location.href = serverurl + "/403"; - break; - case 404: - location.href = serverurl + "/404"; - break; - case 500: - location.href = serverurl + "/500"; - break; - } -}); + console.error(data) + switch (data.code) { + case 403: + location.href = serverurl + '/403' + break + case 404: + location.href = serverurl + '/404' + break + case 500: + location.href = serverurl + '/500' + break + } +}) socket.on('error', function (data) { - console.error(data); - if (data.message && data.message.indexOf('AUTH failed') === 0) - location.href = serverurl + "/403"; -}); + console.error(data) + if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' } +}) socket.on('delete', function () { - if (personalInfo.login) { - deleteServerHistory(noteid, function (err, data) { - if (!err) location.href = serverurl; - }); - } else { - getHistory(function (notehistory) { - var newnotehistory = removeHistory(noteid, notehistory); - saveHistory(newnotehistory); - location.href = serverurl; - }); - } -}); -var retryTimer = null; + if (window.personalInfo.login) { + deleteServerHistory(noteid, function (err, data) { + if (!err) location.href = serverurl + }) + } else { + getHistory(function (notehistory) { + var newnotehistory = removeHistory(noteid, notehistory) + saveHistory(newnotehistory) + location.href = serverurl + }) + } +}) +var retryTimer = null socket.on('maintenance', function () { - cmClient.revision = -1; -}); + cmClient.revision = -1 +}) socket.on('disconnect', function (data) { - showStatus(statusType.offline); - if (loaded) { - saveInfo(); - lastInfo.history = editor.getHistory(); - } - if (!editor.getOption('readOnly')) - editor.setOption('readOnly', true); - if (!retryTimer) { - retryTimer = setInterval(function () { - if (!needRefresh) socket.connect(); - }, 1000); - } -}); + showStatus(statusType.offline) + if (window.loaded) { + saveInfo() + window.lastInfo.history = editor.getHistory() + } + if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) } + if (!retryTimer) { + retryTimer = setInterval(function () { + if (!window.needRefresh) socket.connect() + }, 1000) + } +}) socket.on('reconnect', function (data) { - //sync back any change in offline - emitUserStatus(true); - cursorActivity(); - socket.emit('online users'); -}); + // sync back any change in offline + emitUserStatus(true) + cursorActivity() + socket.emit('online users') +}) socket.on('connect', function (data) { - clearInterval(retryTimer); - retryTimer = null; - personalInfo['id'] = socket.id; - showStatus(statusType.connected); - socket.emit('version'); -}); + clearInterval(retryTimer) + retryTimer = null + window.personalInfo['id'] = socket.id + showStatus(statusType.connected) + socket.emit('version') +}) socket.on('version', function (data) { - if (version != data.version) { - if (version < data.minimumCompatibleVersion) { - setRefreshModal('incompatible-version'); - setNeedRefresh(); - } else { - setRefreshModal('new-version'); - } - } -}); -var authors = []; -var authorship = []; -var authorshipMarks = {}; -var authorMarks = {}; // temp variable -var addTextMarkers = []; // temp variable -function updateInfo(data) { - //console.log(data); - if (data.hasOwnProperty('createtime') && createtime !== data.createtime) { - createtime = data.createtime; - updateLastChange(); - } - if (data.hasOwnProperty('updatetime') && lastchangetime !== data.updatetime) { - lastchangetime = data.updatetime; - updateLastChange(); - } - if (data.hasOwnProperty('owner') && owner !== data.owner) { - owner = data.owner; - ownerprofile = data.ownerprofile; - updateOwner(); - } - if (data.hasOwnProperty('lastchangeuser') && lastchangeuser !== data.lastchangeuser) { - lastchangeuser = data.lastchangeuser; - lastchangeuserprofile = data.lastchangeuserprofile; - updateLastChangeUser(); - updateOwner(); - } - if (data.hasOwnProperty('authors') && authors !== data.authors) { - authors = data.authors; - } - if (data.hasOwnProperty('authorship') && authorship !== data.authorship) { - authorship = data.authorship; - updateAuthorship(); + if (version !== data.version) { + if (version < data.minimumCompatibleVersion) { + setRefreshModal('incompatible-version') + setNeedRefresh() + } else { + setRefreshModal('new-version') } + } +}) +var authors = [] +var authorship = [] +var authorMarks = {} // temp variable +var addTextMarkers = [] // temp variable +function updateInfo (data) { + // console.log(data); + if (data.hasOwnProperty('createtime') && window.createtime !== data.createtime) { + window.createtime = data.createtime + updateLastChange() + } + if (data.hasOwnProperty('updatetime') && window.lastchangetime !== data.updatetime) { + window.lastchangetime = data.updatetime + updateLastChange() + } + if (data.hasOwnProperty('owner') && window.owner !== data.owner) { + window.owner = data.owner + window.ownerprofile = data.ownerprofile + updateOwner() + } + if (data.hasOwnProperty('lastchangeuser') && window.lastchangeuser !== data.lastchangeuser) { + window.lastchangeuser = data.lastchangeuser + window.lastchangeuserprofile = data.lastchangeuserprofile + updateLastChangeUser() + updateOwner() + } + if (data.hasOwnProperty('authors') && authors !== data.authors) { + authors = data.authors + } + if (data.hasOwnProperty('authorship') && authorship !== data.authorship) { + authorship = data.authorship + updateAuthorship() + } } var updateAuthorship = _.debounce(function () { - editor.operation(updateAuthorshipInner); -}, 50); -function initMark() { - return { - gutter: { - userid: null, - timestamp: null - }, - textmarkers: [] - }; -} -function initMarkAndCheckGutter(mark, author, timestamp) { - if (!mark) mark = initMark(); - if (!mark.gutter.userid || mark.gutter.timestamp > timestamp) { - mark.gutter.userid = author.userid; - mark.gutter.timestamp = timestamp; - } - return mark; -} -var gutterStylePrefix = "border-left: 3px solid "; -var gutterStylePostfix = "; height: " + defaultTextHeight + "px; margin-left: 3px;"; -var textMarkderStylePrefix = "background-image: linear-gradient(to top, "; -var textMarkderStylePostfix = " 1px, transparent 1px);"; + editor.operation(updateAuthorshipInner) +}, 50) +function initMark () { + return { + gutter: { + userid: null, + timestamp: null + }, + textmarkers: [] + } +} +function initMarkAndCheckGutter (mark, author, timestamp) { + if (!mark) mark = initMark() + if (!mark.gutter.userid || mark.gutter.timestamp > timestamp) { + mark.gutter.userid = author.userid + mark.gutter.timestamp = timestamp + } + return mark +} +var gutterStylePrefix = 'border-left: 3px solid ' +var gutterStylePostfix = '; height: ' + defaultTextHeight + 'px; margin-left: 3px;' +var textMarkderStylePrefix = 'background-image: linear-gradient(to top, ' +var textMarkderStylePostfix = ' 1px, transparent 1px);' var addStyleRule = (function () { - var added = {}; - var styleElement = document.createElement('style'); - document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement); - var styleSheet = styleElement.sheet; - - return function (css) { - if (added[css]) { - return; - } - added[css] = true; - styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length); - }; -}()); -function updateAuthorshipInner() { + var added = {} + var styleElement = document.createElement('style') + document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement) + var styleSheet = styleElement.sheet + + return function (css) { + if (added[css]) { + return + } + added[css] = true + styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length) + } +}()) +function updateAuthorshipInner () { // ignore when ot not synced yet - if (havePendingOperation()) return; - authorMarks = {}; - for (var i = 0; i < authorship.length; i++) { - var atom = authorship[i]; - var author = authors[atom[0]]; - if (author) { - var prePos = editor.posFromIndex(atom[1]); - var preLine = editor.getLine(prePos.line); - var postPos = editor.posFromIndex(atom[2]); - var postLine = editor.getLine(postPos.line); - if (prePos.ch == 0 && postPos.ch == postLine.length) { - for (var j = prePos.line; j <= postPos.line; j++) { - if (editor.getLine(j)) { - authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]); - } - } - } else if (postPos.line - prePos.line >= 1) { - var startLine = prePos.line; - var endLine = postPos.line; - if (prePos.ch == preLine.length) { - startLine++; - } else if (prePos.ch != 0) { - var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]); - var _postPos = { - line: prePos.line, - ch: preLine.length - }; - if (JSON.stringify(prePos) != JSON.stringify(_postPos)) { - mark.textmarkers.push({ - userid: author.userid, - pos: [prePos, _postPos] - }); - startLine++; - } - authorMarks[prePos.line] = mark; - } - if (postPos.ch == 0) { - endLine--; - } else if (postPos.ch != postLine.length) { - var mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3]); - var _prePos = { - line: postPos.line, - ch: 0 - }; - if (JSON.stringify(_prePos) != JSON.stringify(postPos)) { - mark.textmarkers.push({ - userid: author.userid, - pos: [_prePos, postPos] - }); - endLine--; - } - authorMarks[postPos.line] = mark; - } - for (var j = startLine; j <= endLine; j++) { - if (editor.getLine(j)) { - authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]); - } - } - } else { - var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]); - if (JSON.stringify(prePos) != JSON.stringify(postPos)) { - mark.textmarkers.push({ - userid: author.userid, - pos: [prePos, postPos] - }); - } - authorMarks[prePos.line] = mark; - } - } - } - addTextMarkers = []; - editor.eachLine(iterateLine); - var allTextMarks = editor.getAllMarks(); - for (var i = 0; i < allTextMarks.length; i++) { - var _textMarker = allTextMarks[i]; - var pos = _textMarker.find(); - var found = false; - for (var j = 0; j < addTextMarkers.length; j++) { - var textMarker = addTextMarkers[j]; - var author = authors[textMarker.userid]; - var className = 'authorship-inline-' + author.color.substr(1); - var obj = { - from: textMarker.pos[0], - to: textMarker.pos[1] - }; - if (JSON.stringify(pos) == JSON.stringify(obj) && _textMarker.className && + if (havePendingOperation()) return + authorMarks = {} + for (let i = 0; i < authorship.length; i++) { + var atom = authorship[i] + let author = authors[atom[0]] + if (author) { + var prePos = editor.posFromIndex(atom[1]) + var preLine = editor.getLine(prePos.line) + var postPos = editor.posFromIndex(atom[2]) + var postLine = editor.getLine(postPos.line) + if (prePos.ch === 0 && postPos.ch === postLine.length) { + for (let j = prePos.line; j <= postPos.line; j++) { + if (editor.getLine(j)) { + authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]) + } + } + } else if (postPos.line - prePos.line >= 1) { + var startLine = prePos.line + var endLine = postPos.line + if (prePos.ch === preLine.length) { + startLine++ + } else if (prePos.ch !== 0) { + let mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]) + var _postPos = { + line: prePos.line, + ch: preLine.length + } + if (JSON.stringify(prePos) !== JSON.stringify(_postPos)) { + mark.textmarkers.push({ userid: author.userid, pos: [prePos, _postPos] }) + startLine++ + } + authorMarks[prePos.line] = mark + } + if (postPos.ch === 0) { + endLine-- + } else if (postPos.ch !== postLine.length) { + let mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3]) + var _prePos = { + line: postPos.line, + ch: 0 + } + if (JSON.stringify(_prePos) !== JSON.stringify(postPos)) { + mark.textmarkers.push({ userid: author.userid, pos: [_prePos, postPos] }) + endLine-- + } + authorMarks[postPos.line] = mark + } + for (let j = startLine; j <= endLine; j++) { + if (editor.getLine(j)) { + authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]) + } + } + } else { + let mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]) + if (JSON.stringify(prePos) !== JSON.stringify(postPos)) { + mark.textmarkers.push({ + userid: author.userid, + pos: [prePos, postPos] + }) + } + authorMarks[prePos.line] = mark + } + } + } + addTextMarkers = [] + editor.eachLine(iterateLine) + var allTextMarks = editor.getAllMarks() + for (let i = 0; i < allTextMarks.length; i++) { + let _textMarker = allTextMarks[i] + var pos = _textMarker.find() + var found = false + for (let j = 0; j < addTextMarkers.length; j++) { + let textMarker = addTextMarkers[j] + let author = authors[textMarker.userid] + let className = 'authorship-inline-' + author.color.substr(1) + var obj = { + from: textMarker.pos[0], + to: textMarker.pos[1] + } + if (JSON.stringify(pos) === JSON.stringify(obj) && _textMarker.className && _textMarker.className.indexOf(className) > -1) { - addTextMarkers.splice(j, 1); - j--; - found = true; - break; - } - } - if (!found && _textMarker.className && _textMarker.className.indexOf('authorship-inline') > -1) { - _textMarker.clear(); - } - } - for (var i = 0; i < addTextMarkers.length; i++) { - var textMarker = addTextMarkers[i]; - var author = authors[textMarker.userid]; - var rgbcolor = hex2rgb(author.color); - var colorString = "rgba(" + rgbcolor.red + "," + rgbcolor.green + "," + rgbcolor.blue + ",0.7)"; - var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix; - var className = 'authorship-inline-' + author.color.substr(1); - var rule = "." + className + "{" + styleString + "}"; - addStyleRule(rule); - var _textMarker = editor.markText(textMarker.pos[0], textMarker.pos[1], { - className: 'authorship-inline ' + className, - title: author.name - }); - } - authorshipMarks = authorMarks; -} -function iterateLine(line) { - var lineNumber = line.lineNo(); - var currMark = authorMarks[lineNumber]; - var author = currMark ? authors[currMark.gutter.userid] : null; - if (currMark && author) { - var className = 'authorship-gutter-' + author.color.substr(1); - var gutters = line.gutterMarkers; - if (!gutters || !gutters['authorship-gutters'] || + addTextMarkers.splice(j, 1) + j-- + found = true + break + } + } + if (!found && _textMarker.className && _textMarker.className.indexOf('authorship-inline') > -1) { + _textMarker.clear() + } + } + for (let i = 0; i < addTextMarkers.length; i++) { + let textMarker = addTextMarkers[i] + let author = authors[textMarker.userid] + var rgbcolor = hex2rgb(author.color) + var colorString = 'rgba(' + rgbcolor.red + ',' + rgbcolor.green + ',' + rgbcolor.blue + ',0.7)' + var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix + let className = 'authorship-inline-' + author.color.substr(1) + var rule = '.' + className + '{' + styleString + '}' + addStyleRule(rule) + editor.markText(textMarker.pos[0], textMarker.pos[1], { + className: 'authorship-inline ' + className, + title: author.name + }) + } +} +function iterateLine (line) { + var lineNumber = line.lineNo() + var currMark = authorMarks[lineNumber] + var author = currMark ? authors[currMark.gutter.userid] : null + if (currMark && author) { + let className = 'authorship-gutter-' + author.color.substr(1) + var gutters = line.gutterMarkers + if (!gutters || !gutters['authorship-gutters'] || !gutters['authorship-gutters'].className || !gutters['authorship-gutters'].className.indexOf(className) < 0) { - var styleString = gutterStylePrefix + author.color + gutterStylePostfix; - var rule = "." + className + "{" + styleString + "}"; - addStyleRule(rule); - var gutter = $('<div>', { - class: 'authorship-gutter ' + className, - title: author.name - }); - editor.setGutterMarker(line, "authorship-gutters", gutter[0]); - } - } else { - editor.setGutterMarker(line, "authorship-gutters", null); - } - if (currMark && currMark.textmarkers.length > 0) { - for (var i = 0; i < currMark.textmarkers.length; i++) { - var textMarker = currMark.textmarkers[i]; - if (textMarker.userid != currMark.gutter.userid) { - addTextMarkers.push(textMarker); - } - } - } + var styleString = gutterStylePrefix + author.color + gutterStylePostfix + var rule = '.' + className + '{' + styleString + '}' + addStyleRule(rule) + var gutter = $('<div>', { + class: 'authorship-gutter ' + className, + title: author.name + }) + editor.setGutterMarker(line, 'authorship-gutters', gutter[0]) + } + } else { + editor.setGutterMarker(line, 'authorship-gutters', null) + } + if (currMark && currMark.textmarkers.length > 0) { + for (var i = 0; i < currMark.textmarkers.length; i++) { + let textMarker = currMark.textmarkers[i] + if (textMarker.userid !== currMark.gutter.userid) { + addTextMarkers.push(textMarker) + } + } + } } editor.on('update', function () { - $('.authorship-gutter:not([data-original-title])').tooltip({ - container: '.CodeMirror-lines', - placement: 'right', - delay: { "show": 500, "hide": 100 } - }); - $('.authorship-inline:not([data-original-title])').tooltip({ - container: '.CodeMirror-lines', - placement: 'bottom', - delay: { "show": 500, "hide": 100 } - }); + $('.authorship-gutter:not([data-original-title])').tooltip({ + container: '.CodeMirror-lines', + placement: 'right', + delay: { 'show': 500, 'hide': 100 } + }) + $('.authorship-inline:not([data-original-title])').tooltip({ + container: '.CodeMirror-lines', + placement: 'bottom', + delay: { 'show': 500, 'hide': 100 } + }) // clear tooltip which described element has been removed - $('[id^="tooltip"]').each(function (index, element) { - var $ele = $(element); - if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove(); - }); -}); + $('[id^="tooltip"]').each(function (index, element) { + var $ele = $(element) + if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove() + }) +}) socket.on('check', function (data) { - //console.log(data); - updateInfo(data); -}); + // console.log(data); + updateInfo(data) +}) socket.on('permission', function (data) { - updatePermission(data.permission); -}); -var docmaxlength = null; -var permission = null; + updatePermission(data.permission) +}) +var docmaxlength = null +var permission = null socket.on('refresh', function (data) { - //console.log(data); - docmaxlength = data.docmaxlength; - editor.setOption("maxLength", docmaxlength); - updateInfo(data); - updatePermission(data.permission); - if (!loaded) { + // console.log(data); + docmaxlength = data.docmaxlength + editor.setOption('maxLength', docmaxlength) + updateInfo(data) + updatePermission(data.permission) + if (!window.loaded) { // auto change mode if no content detected - var nocontent = editor.getValue().length <= 0; - if (nocontent) { - if (visibleXS) - currentMode = modeType.edit; - else - currentMode = modeType.both; - } + var nocontent = editor.getValue().length <= 0 + if (nocontent) { + if (window.visibleXS) { window.currentMode = modeType.edit } else { window.currentMode = modeType.both } + } // parse mode from url - if (window.location.search.length > 0) { - var urlMode = modeType[window.location.search.substr(1)]; - if (urlMode) currentMode = urlMode; - } - changeMode(currentMode); - if (nocontent && !visibleXS) { - editor.focus(); - editor.refresh(); - } - updateViewInner(); // bring up view rendering earlier - updateHistory(); //update history whether have content or not - loaded = true; - emitUserStatus(); //send first user status - updateOnlineStatus(); //update first online status - setTimeout(function () { - //work around editor not refresh or doc not fully loaded - windowResizeInner(); - //work around might not scroll to hash - scrollToHash(); - }, 1); - } - if (editor.getOption('readOnly')) - editor.setOption('readOnly', false); -}); - -var EditorClient = ot.EditorClient; -var SocketIOAdapter = ot.SocketIOAdapter; -var CodeMirrorAdapter = ot.CodeMirrorAdapter; -var cmClient = null; -var synchronized_ = null; - -function havePendingOperation() { - return (cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding')) ? true : false; -} - -socket.on('doc', function (obj) { - var body = obj.str; - var bodyMismatch = editor.getValue() !== body; - var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force; + if (window.location.search.length > 0) { + var urlMode = modeType[window.location.search.substr(1)] + if (urlMode) window.currentMode = urlMode + } + changeMode(window.currentMode) + if (nocontent && !window.visibleXS) { + editor.focus() + editor.refresh() + } + updateViewInner() // bring up view rendering earlier + updateHistory() // update history whether have content or not + window.loaded = true + emitUserStatus() // send first user status + updateOnlineStatus() // update first online status + setTimeout(function () { + // work around editor not refresh or doc not fully loaded + windowResizeInner() + // work around might not scroll to hash + scrollToHash() + }, 1) + } + if (editor.getOption('readOnly')) { editor.setOption('readOnly', false) } +}) - saveInfo(); - if (setDoc && bodyMismatch) { - if (cmClient) cmClient.editorAdapter.ignoreNextChange = true; - if (body) editor.setValue(body); - else editor.setValue(""); - } +var EditorClient = ot.EditorClient +var SocketIOAdapter = ot.SocketIOAdapter +var CodeMirrorAdapter = ot.CodeMirrorAdapter +var cmClient = null +var synchronized_ = null - if (!loaded) { - editor.clearHistory(); - ui.spinner.hide(); - ui.content.fadeIn(); - } else { - //if current doc is equal to the doc before disconnect - if (setDoc && bodyMismatch) editor.clearHistory(); - else if (lastInfo.history) editor.setHistory(lastInfo.history); - lastInfo.history = null; - } +function havePendingOperation () { + return !!((cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding'))) +} - if (!cmClient) { - cmClient = window.cmClient = new EditorClient( +socket.on('doc', function (obj) { + var body = obj.str + var bodyMismatch = editor.getValue() !== body + var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force + + saveInfo() + if (setDoc && bodyMismatch) { + if (cmClient) cmClient.editorAdapter.ignoreNextChange = true + if (body) editor.setValue(body) + else editor.setValue('') + } + + if (!window.loaded) { + editor.clearHistory() + ui.spinner.hide() + ui.content.fadeIn() + } else { + // if current doc is equal to the doc before disconnect + if (setDoc && bodyMismatch) editor.clearHistory() + else if (window.lastInfo.history) editor.setHistory(window.lastInfo.history) + window.lastInfo.history = null + } + + if (!cmClient) { + cmClient = window.cmClient = new EditorClient( obj.revision, obj.clients, new SocketIOAdapter(socket), new CodeMirrorAdapter(editor) - ); - synchronized_ = cmClient.state; - } else if (setDoc) { - if (bodyMismatch) { - cmClient.undoManager.undoStack.length = 0; - cmClient.undoManager.redoStack.length = 0; - } - cmClient.revision = obj.revision; - cmClient.setState(synchronized_); - cmClient.initializeClientList(); - cmClient.initializeClients(obj.clients); - } else if (havePendingOperation()) { - cmClient.serverReconnect(); - } - - if (setDoc && bodyMismatch) { - isDirty = true; - updateView(); - } - - restoreInfo(); -}); + ) + synchronized_ = cmClient.state + } else if (setDoc) { + if (bodyMismatch) { + cmClient.undoManager.undoStack.length = 0 + cmClient.undoManager.redoStack.length = 0 + } + cmClient.revision = obj.revision + cmClient.setState(synchronized_) + cmClient.initializeClientList() + cmClient.initializeClients(obj.clients) + } else if (havePendingOperation()) { + cmClient.serverReconnect() + } + + if (setDoc && bodyMismatch) { + window.isDirty = true + updateView() + } + + restoreInfo() +}) socket.on('ack', function () { - isDirty = true; - updateView(); -}); + window.isDirty = true + updateView() +}) socket.on('operation', function () { - isDirty = true; - updateView(); -}); + window.isDirty = true + updateView() +}) socket.on('online users', function (data) { - if (debug) - console.debug(data); - onlineUsers = data.users; - updateOnlineStatus(); - $('.CodeMirror-other-cursors').children().each(function (key, value) { - var found = false; - for (var i = 0; i < data.users.length; i++) { - var user = data.users[i]; - if ($(this).attr('id') == user.id) - found = true; - } - if (!found) - $(this).stop(true).fadeOut("normal", function () { - $(this).remove(); - }); - }); + if (debug) { console.debug(data) } + window.onlineUsers = data.users + updateOnlineStatus() + $('.CodeMirror-other-cursors').children().each(function (key, value) { + var found = false for (var i = 0; i < data.users.length; i++) { - var user = data.users[i]; - if (user.id != socket.id) - buildCursor(user); - else - personalInfo = user; - } -}); + var user = data.users[i] + if ($(this).attr('id') === user.id) { found = true } + } + if (!found) { + $(this).stop(true).fadeOut('normal', function () { + $(this).remove() + }) + } + }) + for (var i = 0; i < data.users.length; i++) { + var user = data.users[i] + if (user.id !== socket.id) { buildCursor(user) } else { window.personalInfo = user } + } +}) socket.on('user status', function (data) { - if (debug) - console.debug(data); - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == data.id) { - onlineUsers[i] = data; - } - } - updateOnlineStatus(); - if (data.id != socket.id) - buildCursor(data); -}); + if (debug) { console.debug(data) } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === data.id) { + window.onlineUsers[i] = data + } + } + updateOnlineStatus() + if (data.id !== socket.id) { buildCursor(data) } +}) socket.on('cursor focus', function (data) { - if (debug) - console.debug(data); - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == data.id) { - onlineUsers[i].cursor = data.cursor; - } - } - if (data.id != socket.id) - buildCursor(data); - //force show - var cursor = $('div[data-clientid="' + data.id + '"]'); - if (cursor.length > 0) { - cursor.stop(true).fadeIn(); - } -}); + if (debug) { console.debug(data) } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === data.id) { + window.onlineUsers[i].cursor = data.cursor + } + } + if (data.id !== socket.id) { buildCursor(data) } + // force show + var cursor = $('div[data-clientid="' + data.id + '"]') + if (cursor.length > 0) { + cursor.stop(true).fadeIn() + } +}) socket.on('cursor activity', function (data) { - if (debug) - console.debug(data); - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == data.id) { - onlineUsers[i].cursor = data.cursor; - } + if (debug) { console.debug(data) } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === data.id) { + window.onlineUsers[i].cursor = data.cursor } - if (data.id != socket.id) - buildCursor(data); -}); + } + if (data.id !== socket.id) { buildCursor(data) } +}) socket.on('cursor blur', function (data) { - if (debug) - console.debug(data); - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == data.id) { - onlineUsers[i].cursor = null; - } - } - if (data.id != socket.id) - buildCursor(data); - //force hide - var cursor = $('div[data-clientid="' + data.id + '"]'); - if (cursor.length > 0) { - cursor.stop(true).fadeOut(); - } -}); + if (debug) { console.debug(data) } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === data.id) { + window.onlineUsers[i].cursor = null + } + } + if (data.id !== socket.id) { buildCursor(data) } + // force hide + var cursor = $('div[data-clientid="' + data.id + '"]') + if (cursor.length > 0) { + cursor.stop(true).fadeOut() + } +}) var options = { - valueNames: ['id', 'name'], - item: '<li class="ui-user-item">\ - <span class="id" style="display:none;"></span>\ - <a href="#">\ - <span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>\ - </a>\ - </li>' -}; -var onlineUserList = new List('online-user-list', options); -var shortOnlineUserList = new List('short-online-user-list', options); - -function updateOnlineStatus() { - if (!loaded || !socket.connected) return; - var _onlineUsers = deduplicateOnlineUsers(onlineUsers); - showStatus(statusType.online, _onlineUsers.length); - var items = onlineUserList.items; - //update or remove current list items - for (var i = 0; i < items.length; i++) { - var found = false; - var foundindex = null; - for (var j = 0; j < _onlineUsers.length; j++) { - if (items[i].values().id == _onlineUsers[j].id) { - foundindex = j; - found = true; - break; - } - } - var id = items[i].values().id; - if (found) { - onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]); - shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]); - } else { - onlineUserList.remove('id', id); - shortOnlineUserList.remove('id', id); - } - } - //add not in list items - for (var i = 0; i < _onlineUsers.length; i++) { - var found = false; - for (var j = 0; j < items.length; j++) { - if (items[j].values().id == _onlineUsers[i].id) { - found = true; - break; - } - } - if (!found) { - onlineUserList.add(_onlineUsers[i]); - shortOnlineUserList.add(_onlineUsers[i]); - } + valueNames: ['id', 'name'], + item: '<li class="ui-user-item">' + + '<span class="id" style="display:none;"></span>' + + '<a href="#">' + + '<span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>' + + '</a>' + + '</li>' +} +var onlineUserList = new List('online-user-list', options) +var shortOnlineUserList = new List('short-online-user-list', options) + +function updateOnlineStatus () { + if (!window.loaded || !socket.connected) return + var _onlineUsers = deduplicateOnlineUsers(window.onlineUsers) + showStatus(statusType.online, _onlineUsers.length) + var items = onlineUserList.items + // update or remove current list items + for (let i = 0; i < items.length; i++) { + let found = false + let foundindex = null + for (let j = 0; j < _onlineUsers.length; j++) { + if (items[i].values().id === _onlineUsers[j].id) { + foundindex = j + found = true + break + } + } + let id = items[i].values().id + if (found) { + onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]) + shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]) + } else { + onlineUserList.remove('id', id) + shortOnlineUserList.remove('id', id) + } + } + // add not in list items + for (let i = 0; i < _onlineUsers.length; i++) { + let found = false + for (let j = 0; j < items.length; j++) { + if (items[j].values().id === _onlineUsers[i].id) { + found = true + break + } + } + if (!found) { + onlineUserList.add(_onlineUsers[i]) + shortOnlineUserList.add(_onlineUsers[i]) + } + } + // sorting + sortOnlineUserList(onlineUserList) + sortOnlineUserList(shortOnlineUserList) + // render list items + renderUserStatusList(onlineUserList) + renderUserStatusList(shortOnlineUserList) +} + +function sortOnlineUserList (list) { + // sort order by isSelf, login state, idle state, alphabet name, color brightness + list.sort('', { + sortFunction: function (a, b) { + var usera = a.values() + var userb = b.values() + var useraIsSelf = (usera.id === window.personalInfo.id || (usera.login && usera.userid === window.personalInfo.userid)) + var userbIsSelf = (userb.id === window.personalInfo.id || (userb.login && userb.userid === window.personalInfo.userid)) + if (useraIsSelf && !userbIsSelf) { + return -1 + } else if (!useraIsSelf && userbIsSelf) { + return 1 + } else { + if (usera.login && !userb.login) { return -1 } else if (!usera.login && userb.login) { return 1 } else { + if (!usera.idle && userb.idle) { return -1 } else if (usera.idle && !userb.idle) { return 1 } else { + if (usera.name && userb.name && usera.name.toLowerCase() < userb.name.toLowerCase()) { + return -1 + } else if (usera.name && userb.name && usera.name.toLowerCase() > userb.name.toLowerCase()) { return 1 } else { if (usera.color && userb.color && usera.color.toLowerCase() < userb.color.toLowerCase()) { return -1 } else if (usera.color && userb.color && usera.color.toLowerCase() > userb.color.toLowerCase()) { return 1 } else { return 0 } } + } + } + } + } + }) +} + +function renderUserStatusList (list) { + var items = list.items + for (var j = 0; j < items.length; j++) { + var item = items[j] + var userstatus = $(item.elm).find('.ui-user-status') + var usericon = $(item.elm).find('.ui-user-icon') + if (item.values().login && item.values().photo) { + usericon.css('background-image', 'url(' + item.values().photo + ')') + // add 1px more to right, make it feel aligned + usericon.css('margin-right', '6px') + $(item.elm).css('border-left', '4px solid ' + item.values().color) + usericon.css('margin-left', '-4px') + } else { + usericon.css('background-color', item.values().color) } - //sorting - sortOnlineUserList(onlineUserList); - sortOnlineUserList(shortOnlineUserList); - //render list items - renderUserStatusList(onlineUserList); - renderUserStatusList(shortOnlineUserList); -} - -function sortOnlineUserList(list) { - //sort order by isSelf, login state, idle state, alphabet name, color brightness - list.sort('', { - sortFunction: function (a, b) { - var usera = a.values(); - var userb = b.values(); - var useraIsSelf = (usera.id == personalInfo.id || (usera.login && usera.userid == personalInfo.userid)); - var userbIsSelf = (userb.id == personalInfo.id || (userb.login && userb.userid == personalInfo.userid)); - if (useraIsSelf && !userbIsSelf) { - return -1; - } else if (!useraIsSelf && userbIsSelf) { - return 1; - } else { - if (usera.login && !userb.login) - return -1; - else if (!usera.login && userb.login) - return 1; - else { - if (!usera.idle && userb.idle) - return -1; - else if (usera.idle && !userb.idle) - return 1; - else { - if (usera.name && userb.name && usera.name.toLowerCase() < userb.name.toLowerCase()) { - return -1; - } else if (usera.name && userb.name && usera.name.toLowerCase() > userb.name.toLowerCase()) { - return 1; - } else { - if (usera.color && userb.color && usera.color.toLowerCase() < userb.color.toLowerCase()) - return -1; - else if (usera.color && userb.color && usera.color.toLowerCase() > userb.color.toLowerCase()) - return 1; - else - return 0; - } - } - } - } - } - }); -} - -function renderUserStatusList(list) { - var items = list.items; - for (var j = 0; j < items.length; j++) { - var item = items[j]; - var userstatus = $(item.elm).find('.ui-user-status'); - var usericon = $(item.elm).find('.ui-user-icon'); - if (item.values().login && item.values().photo) { - usericon.css('background-image', 'url(' + item.values().photo + ')'); - //add 1px more to right, make it feel aligned - usericon.css('margin-right', '6px'); - $(item.elm).css('border-left', '4px solid ' + item.values().color); - usericon.css('margin-left', '-4px'); - } else { - usericon.css('background-color', item.values().color); - } - userstatus.removeClass('ui-user-status-offline ui-user-status-online ui-user-status-idle'); - if (item.values().idle) - userstatus.addClass('ui-user-status-idle'); - else - userstatus.addClass('ui-user-status-online'); - } -} - -function deduplicateOnlineUsers(list) { - var _onlineUsers = []; - for (var i = 0; i < list.length; i++) { - var user = $.extend({}, list[i]); - if (!user.userid) - _onlineUsers.push(user); - else { - var found = false; - for (var j = 0; j < _onlineUsers.length; j++) { - if (_onlineUsers[j].userid == user.userid) { - //keep self color when login - if (user.id == personalInfo.id) { - _onlineUsers[j].color = user.color; - } - //keep idle state if any of self client not idle - if (!user.idle) { - _onlineUsers[j].idle = user.idle; - _onlineUsers[j].color = user.color; - } - found = true; - break; - } - } - if (!found) - _onlineUsers.push(user); + userstatus.removeClass('ui-user-status-offline ui-user-status-online ui-user-status-idle') + if (item.values().idle) { userstatus.addClass('ui-user-status-idle') } else { userstatus.addClass('ui-user-status-online') } + } +} + +function deduplicateOnlineUsers (list) { + var _onlineUsers = [] + for (var i = 0; i < list.length; i++) { + var user = $.extend({}, list[i]) + if (!user.userid) { _onlineUsers.push(user) } else { + var found = false + for (var j = 0; j < _onlineUsers.length; j++) { + if (_onlineUsers[j].userid === user.userid) { + // keep self color when login + if (user.id === window.personalInfo.id) { + _onlineUsers[j].color = user.color + } + // keep idle state if any of self client not idle + if (!user.idle) { + _onlineUsers[j].idle = user.idle + _onlineUsers[j].color = user.color + } + found = true + break } + } + if (!found) { _onlineUsers.push(user) } } - return _onlineUsers; + } + return _onlineUsers } -var userStatusCache = null; +var userStatusCache = null -function emitUserStatus(force) { - if (!loaded) return; - var type = null; - if (visibleXS) - type = 'xs'; - else if (visibleSM) - type = 'sm'; - else if (visibleMD) - type = 'md'; - else if (visibleLG) - type = 'lg'; +function emitUserStatus (force) { + if (!window.loaded) return + var type = null + if (window.visibleXS) { type = 'xs' } else if (window.visibleSM) { type = 'sm' } else if (window.visibleMD) { type = 'md' } else if (window.visibleLG) { type = 'lg' } - personalInfo['idle'] = idle.isAway; - personalInfo['type'] = type; + window.personalInfo['idle'] = idle.isAway + window.personalInfo['type'] = type - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == personalInfo.id) { - onlineUsers[i] = personalInfo; - } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === window.personalInfo.id) { + window.onlineUsers[i] = window.personalInfo } + } - var userStatus = { - idle: idle.isAway, - type: type - }; + var userStatus = { + idle: idle.isAway, + type: type + } - if (force || JSON.stringify(userStatus) != JSON.stringify(userStatusCache)) { - socket.emit('user status', userStatus); - userStatusCache = userStatus; - } + if (force || JSON.stringify(userStatus) !== JSON.stringify(userStatusCache)) { + socket.emit('user status', userStatus) + userStatusCache = userStatus + } } -function checkCursorTag(coord, ele) { - if (!ele) return; // return if element not exists +function checkCursorTag (coord, ele) { + if (!ele) return // return if element not exists // set margin - var tagRightMargin = 0; - var tagBottomMargin = 2; + var tagRightMargin = 0 + var tagBottomMargin = 2 // use sizer to get the real doc size (won't count status bar and gutters) - var docWidth = ui.area.codemirrorSizer.width(); - var docHeight = ui.area.codemirrorSizer.height(); + var docWidth = ui.area.codemirrorSizer.width() // get editor size (status bar not count in) - var editorWidth = ui.area.codemirror.width(); - var editorHeight = ui.area.codemirror.height(); + var editorHeight = ui.area.codemirror.height() // get element size - var width = ele.outerWidth(); - var height = ele.outerHeight(); - var padding = (ele.outerWidth() - ele.width()) / 2; + var width = ele.outerWidth() + var height = ele.outerHeight() + var padding = (ele.outerWidth() - ele.width()) / 2 // get coord position - var left = coord.left; - var top = coord.top; + var left = coord.left + var top = coord.top // get doc top offset (to workaround with viewport) - var docTopOffset = ui.area.codemirrorSizerInner.position().top; + var docTopOffset = ui.area.codemirrorSizerInner.position().top // set offset - var offsetLeft = -3; - var offsetTop = defaultTextHeight; + var offsetLeft = -3 + var offsetTop = defaultTextHeight // only do when have width and height - if (width > 0 && height > 0) { + if (width > 0 && height > 0) { // flip x when element right bound larger than doc width - if (left + width + offsetLeft + tagRightMargin > docWidth) { - offsetLeft = -(width + tagRightMargin) + padding + offsetLeft; - } + if (left + width + offsetLeft + tagRightMargin > docWidth) { + offsetLeft = -(width + tagRightMargin) + padding + offsetLeft + } // flip y when element bottom bound larger than doc height // and element top position is larger than element height - if (top + docTopOffset + height + offsetTop + tagBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + tagBottomMargin) { - offsetTop = -(height); - } + if (top + docTopOffset + height + offsetTop + tagBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + tagBottomMargin) { + offsetTop = -(height) } + } // set position - ele[0].style.left = offsetLeft + 'px'; - ele[0].style.top = offsetTop + 'px'; -} - -function buildCursor(user) { - if (currentMode == modeType.view) return; - if (!user.cursor) return; - var coord = editor.charCoords(user.cursor, 'windows'); - coord.left = coord.left < 4 ? 4 : coord.left; - coord.top = coord.top < 0 ? 0 : coord.top; - var iconClass = 'fa-user'; - switch (user.type) { - case 'xs': - iconClass = 'fa-mobile'; - break; - case 'sm': - iconClass = 'fa-tablet'; - break; - case 'md': - iconClass = 'fa-desktop'; - break; - case 'lg': - iconClass = 'fa-desktop'; - break; - } - if ($('.CodeMirror-other-cursors').length <= 0) { - $("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors'); - } - if ($('div[data-clientid="' + user.id + '"]').length <= 0) { - var cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>'); - cursor.attr('data-line', user.cursor.line); - cursor.attr('data-ch', user.cursor.ch); - cursor.attr('data-offset-left', 0); - cursor.attr('data-offset-top', 0); - - var cursorbar = $('<div class="cursorbar"> </div>'); - cursorbar[0].style.height = defaultTextHeight + 'px'; - cursorbar[0].style.borderLeft = '2px solid ' + user.color; - - var icon = '<i class="fa ' + iconClass + '"></i>'; - - var cursortag = $('<div class="cursortag">' + icon + ' <span class="name">' + user.name + '</span></div>'); - //cursortag[0].style.background = color; - cursortag[0].style.color = user.color; - - cursor.attr('data-mode', 'hover'); - cursortag.delay(2000).fadeOut("fast"); - cursor.hover( + ele[0].style.left = offsetLeft + 'px' + ele[0].style.top = offsetTop + 'px' +} + +function buildCursor (user) { + if (window.currentMode === modeType.view) return + if (!user.cursor) return + var coord = editor.charCoords(user.cursor, 'windows') + coord.left = coord.left < 4 ? 4 : coord.left + coord.top = coord.top < 0 ? 0 : coord.top + var iconClass = 'fa-user' + switch (user.type) { + case 'xs': + iconClass = 'fa-mobile' + break + case 'sm': + iconClass = 'fa-tablet' + break + case 'md': + iconClass = 'fa-desktop' + break + case 'lg': + iconClass = 'fa-desktop' + break + } + if ($('.CodeMirror-other-cursors').length <= 0) { + $("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors') + } + if ($('div[data-clientid="' + user.id + '"]').length <= 0) { + let cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>') + cursor.attr('data-line', user.cursor.line) + cursor.attr('data-ch', user.cursor.ch) + cursor.attr('data-offset-left', 0) + cursor.attr('data-offset-top', 0) + + let cursorbar = $('<div class="cursorbar"> </div>') + cursorbar[0].style.height = defaultTextHeight + 'px' + cursorbar[0].style.borderLeft = '2px solid ' + user.color + + var icon = '<i class="fa ' + iconClass + '"></i>' + + let cursortag = $('<div class="cursortag">' + icon + ' <span class="name">' + user.name + '</span></div>') + // cursortag[0].style.background = color; + cursortag[0].style.color = user.color + + cursor.attr('data-mode', 'hover') + cursortag.delay(2000).fadeOut('fast') + cursor.hover( function () { - if (cursor.attr('data-mode') == 'hover') - cursortag.stop(true).fadeIn("fast"); + if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeIn('fast') } }, function () { - if (cursor.attr('data-mode') == 'hover') - cursortag.stop(true).fadeOut("fast"); - }); - - function switchMode(ele) { - if (ele.attr('data-mode') == 'state') - ele.attr('data-mode', 'hover'); - else if (ele.attr('data-mode') == 'hover') - ele.attr('data-mode', 'state'); - } - - function switchTag(ele) { - if (ele.css('display') === 'none') - ele.stop(true).fadeIn("fast"); - else - ele.stop(true).fadeOut("fast"); - } - var hideCursorTagDelay = 2000; - var hideCursorTagTimer = null; - - function hideCursorTag() { - if (cursor.attr('data-mode') == 'hover') - cursortag.fadeOut("fast"); - } - cursor.on('touchstart', function (e) { - var display = cursortag.css('display'); - cursortag.stop(true).fadeIn("fast"); - clearTimeout(hideCursorTagTimer); - hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay); - if (display === 'none') { - e.preventDefault(); - e.stopPropagation(); - } - }); - cursortag.on('mousedown touchstart', function (e) { - if (cursor.attr('data-mode') == 'state') - switchTag(cursortag); - switchMode(cursor); - e.preventDefault(); - e.stopPropagation(); - }); - - cursor.append(cursorbar); - cursor.append(cursortag); - - cursor[0].style.left = coord.left + 'px'; - cursor[0].style.top = coord.top + 'px'; - $('.CodeMirror-other-cursors').append(cursor); - - if (!user.idle) - cursor.stop(true).fadeIn(); - - checkCursorTag(coord, cursortag); - } else { - var cursor = $('div[data-clientid="' + user.id + '"]'); - var lineDiff = Math.abs(cursor.attr('data-line') - user.cursor.line); - cursor.attr('data-line', user.cursor.line); - cursor.attr('data-ch', user.cursor.ch); - - var cursorbar = cursor.find('.cursorbar'); - cursorbar[0].style.height = defaultTextHeight + 'px'; - cursorbar[0].style.borderLeft = '2px solid ' + user.color; - - var cursortag = cursor.find('.cursortag'); - cursortag.find('i').removeClass().addClass('fa').addClass(iconClass); - cursortag.find(".name").text(user.name); - - if (cursor.css('display') === 'none') { - cursor[0].style.left = coord.left + 'px'; - cursor[0].style.top = coord.top + 'px'; - } else { - cursor.animate({ - "left": coord.left, - "top": coord.top - }, { - duration: cursorAnimatePeriod, - queue: false - }); - } + if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeOut('fast') } + }) - if (user.idle && cursor.css('display') !== 'none') - cursor.stop(true).fadeOut(); - else if (!user.idle && cursor.css('display') === 'none') - cursor.stop(true).fadeIn(); + var hideCursorTagDelay = 2000 + var hideCursorTagTimer = null - checkCursorTag(coord, cursortag); + var switchMode = function (ele) { + if (ele.attr('data-mode') === 'state') { ele.attr('data-mode', 'hover') } else if (ele.attr('data-mode') === 'hover') { ele.attr('data-mode', 'state') } } -} -//editor actions -function removeNullByte(cm, change) { - var str = change.text.join("\n"); - if (/\u0000/g.test(str) && change.update) { - change.update(change.from, change.to, str.replace(/\u0000/g, "").split("\n")); + var switchTag = function (ele) { + if (ele.css('display') === 'none') { ele.stop(true).fadeIn('fast') } else { ele.stop(true).fadeOut('fast') } } -} -function enforceMaxLength(cm, change) { - var maxLength = cm.getOption("maxLength"); - if (maxLength && change.update) { - var str = change.text.join("\n"); - var delta = str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from)); - if (delta <= 0) { - return false; - } - delta = cm.getValue().length + delta - maxLength; - if (delta > 0) { - str = str.substr(0, str.length - delta); - change.update(change.from, change.to, str.split("\n")); - return true; - } + + var hideCursorTag = function () { + if (cursor.attr('data-mode') === 'hover') { cursortag.fadeOut('fast') } } - return false; -} -var ignoreEmitEvents = ['setValue', 'ignoreHistory']; -editor.on('beforeChange', function (cm, change) { - if (debug) - console.debug(change); - removeNullByte(cm, change); - if (enforceMaxLength(cm, change)) { - $('.limit-modal').modal('show'); - } - var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) != -1); - if (!isIgnoreEmitEvent) { - if (!havePermission()) { - change.canceled = true; - switch (permission) { - case "editable": - $('.signin-modal').modal('show'); - break; - case "locked": - case "private": - $('.locked-modal').modal('show'); - break; - } - } + cursor.on('touchstart', function (e) { + var display = cursortag.css('display') + cursortag.stop(true).fadeIn('fast') + clearTimeout(hideCursorTagTimer) + hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay) + if (display === 'none') { + e.preventDefault() + e.stopPropagation() + } + }) + cursortag.on('mousedown touchstart', function (e) { + if (cursor.attr('data-mode') === 'state') { switchTag(cursortag) } + switchMode(cursor) + e.preventDefault() + e.stopPropagation() + }) + + cursor.append(cursorbar) + cursor.append(cursortag) + + cursor[0].style.left = coord.left + 'px' + cursor[0].style.top = coord.top + 'px' + $('.CodeMirror-other-cursors').append(cursor) + + if (!user.idle) { cursor.stop(true).fadeIn() } + + checkCursorTag(coord, cursortag) + } else { + let cursor = $('div[data-clientid="' + user.id + '"]') + cursor.attr('data-line', user.cursor.line) + cursor.attr('data-ch', user.cursor.ch) + + let cursorbar = cursor.find('.cursorbar') + cursorbar[0].style.height = defaultTextHeight + 'px' + cursorbar[0].style.borderLeft = '2px solid ' + user.color + + let cursortag = cursor.find('.cursortag') + cursortag.find('i').removeClass().addClass('fa').addClass(iconClass) + cursortag.find('.name').text(user.name) + + if (cursor.css('display') === 'none') { + cursor[0].style.left = coord.left + 'px' + cursor[0].style.top = coord.top + 'px' } else { - if (change.origin == 'ignoreHistory') { - setHaveUnreadChanges(true); - updateTitleReminder(); - } - } - if (cmClient && !socket.connected) - cmClient.editorAdapter.ignoreNextChange = true; -}); + cursor.animate({ + 'left': coord.left, + 'top': coord.top + }, { + duration: cursorAnimatePeriod, + queue: false + }) + } + + if (user.idle && cursor.css('display') !== 'none') { cursor.stop(true).fadeOut() } else if (!user.idle && cursor.css('display') === 'none') { cursor.stop(true).fadeIn() } + + checkCursorTag(coord, cursortag) + } +} + +// editor actions +function removeNullByte (cm, change) { + var str = change.text.join('\n') + if (/\u0000/g.test(str) && change.update) { + change.update(change.from, change.to, str.replace(/\u0000/g, '').split('\n')) + } +} +function enforceMaxLength (cm, change) { + var maxLength = cm.getOption('maxLength') + if (maxLength && change.update) { + var str = change.text.join('\n') + var delta = str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from)) + if (delta <= 0) { + return false + } + delta = cm.getValue().length + delta - maxLength + if (delta > 0) { + str = str.substr(0, str.length - delta) + change.update(change.from, change.to, str.split('\n')) + return true + } + } + return false +} +var ignoreEmitEvents = ['setValue', 'ignoreHistory'] +editor.on('beforeChange', function (cm, change) { + if (debug) { console.debug(change) } + removeNullByte(cm, change) + if (enforceMaxLength(cm, change)) { + $('.limit-modal').modal('show') + } + var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) !== -1) + if (!isIgnoreEmitEvent) { + if (!havePermission()) { + change.canceled = true + switch (permission) { + case 'editable': + $('.signin-modal').modal('show') + break + case 'locked': + case 'private': + $('.locked-modal').modal('show') + break + } + } + } else { + if (change.origin === 'ignoreHistory') { + setHaveUnreadChanges(true) + updateTitleReminder() + } + } + if (cmClient && !socket.connected) { cmClient.editorAdapter.ignoreNextChange = true } +}) editor.on('cut', function () { - //na -}); + // na +}) editor.on('paste', function () { - //na -}); + // na +}) editor.on('changes', function (cm, changes) { - updateHistory(); - var docLength = editor.getValue().length; - //workaround for big documents - var newViewportMargin = 20; - if (docLength > 20000) { - newViewportMargin = 1; - } else if (docLength > 10000) { - newViewportMargin = 10; - } else if (docLength > 5000) { - newViewportMargin = 15; - } - if (newViewportMargin != viewportMargin) { - viewportMargin = newViewportMargin; - windowResize(); - } - checkEditorScrollbar(); - if (ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && editorHasFocus()) { - postUpdateEvent = function () { - syncScrollToView(); - postUpdateEvent = null; - }; - } -}); + updateHistory() + var docLength = editor.getValue().length + // workaround for big documents + var newViewportMargin = 20 + if (docLength > 20000) { + newViewportMargin = 1 + } else if (docLength > 10000) { + newViewportMargin = 10 + } else if (docLength > 5000) { + newViewportMargin = 15 + } + if (newViewportMargin !== viewportMargin) { + viewportMargin = newViewportMargin + windowResize() + } + checkEditorScrollbar() + if (ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && editorHasFocus()) { + postUpdateEvent = function () { + syncScrollToView() + postUpdateEvent = null + } + } +}) editor.on('focus', function (cm) { - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == personalInfo.id) { - onlineUsers[i].cursor = editor.getCursor(); - } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === window.personalInfo.id) { + window.onlineUsers[i].cursor = editor.getCursor() } - personalInfo['cursor'] = editor.getCursor(); - socket.emit('cursor focus', editor.getCursor()); -}); + } + window.personalInfo['cursor'] = editor.getCursor() + socket.emit('cursor focus', editor.getCursor()) +}) editor.on('cursorActivity', function (cm) { - updateStatusBar(); - cursorActivity(); -}); + updateStatusBar() + cursorActivity() +}) editor.on('beforeSelectionChange', function (doc, selections) { - if (selections) - selection = selections.ranges[0]; - else - selection = null; - updateStatusBar(); -}); - -var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce); - -function cursorActivityInner() { - if (editorHasFocus() && !Visibility.hidden()) { - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == personalInfo.id) { - onlineUsers[i].cursor = editor.getCursor(); - } - } - personalInfo['cursor'] = editor.getCursor(); - socket.emit('cursor activity', editor.getCursor()); + if (selections) { selection = selections.ranges[0] } else { selection = null } + updateStatusBar() +}) + +var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce) + +function cursorActivityInner () { + if (editorHasFocus() && !Visibility.hidden()) { + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === window.personalInfo.id) { + window.onlineUsers[i].cursor = editor.getCursor() + } } + window.personalInfo['cursor'] = editor.getCursor() + socket.emit('cursor activity', editor.getCursor()) + } } editor.on('blur', function (cm) { - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id == personalInfo.id) { - onlineUsers[i].cursor = null; - } + for (var i = 0; i < window.onlineUsers.length; i++) { + if (window.onlineUsers[i].id === window.personalInfo.id) { + window.onlineUsers[i].cursor = null } - personalInfo['cursor'] = null; - socket.emit('cursor blur'); -}); - -function saveInfo() { - var scrollbarStyle = editor.getOption('scrollbarStyle'); - var left = $(window).scrollLeft(); - var top = $(window).scrollTop(); - switch (currentMode) { - case modeType.edit: - if (scrollbarStyle == 'native') { - lastInfo.edit.scroll.left = left; - lastInfo.edit.scroll.top = top; - } else { - lastInfo.edit.scroll = editor.getScrollInfo(); - } - 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.edit.selections = editor.listSelections(); - lastInfo.needRestore = true; -} - -function restoreInfo() { - var scrollbarStyle = editor.getOption('scrollbarStyle'); - if (lastInfo.needRestore) { - var line = lastInfo.edit.cursor.line; - var ch = lastInfo.edit.cursor.ch; - editor.setCursor(line, ch); - editor.setSelections(lastInfo.edit.selections); - switch (currentMode) { - case modeType.edit: - if (scrollbarStyle == 'native') { - $(window).scrollLeft(lastInfo.edit.scroll.left); - $(window).scrollTop(lastInfo.edit.scroll.top); - } else { - var left = lastInfo.edit.scroll.left; - var top = lastInfo.edit.scroll.top; - editor.scrollIntoView(); - editor.scrollTo(left, top); - } - break; - case modeType.view: - $(window).scrollLeft(lastInfo.view.scroll.left); - $(window).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; - } + } + window.personalInfo['cursor'] = null + socket.emit('cursor blur') +}) - lastInfo.needRestore = false; +function saveInfo () { + var scrollbarStyle = editor.getOption('scrollbarStyle') + var left = $(window).scrollLeft() + var top = $(window).scrollTop() + switch (window.currentMode) { + case modeType.edit: + if (scrollbarStyle === 'native') { + window.lastInfo.edit.scroll.left = left + window.lastInfo.edit.scroll.top = top + } else { + window.lastInfo.edit.scroll = editor.getScrollInfo() + } + break + case modeType.view: + window.lastInfo.view.scroll.left = left + window.lastInfo.view.scroll.top = top + break + case modeType.both: + window.lastInfo.edit.scroll = editor.getScrollInfo() + window.lastInfo.view.scroll.left = ui.area.view.scrollLeft() + window.lastInfo.view.scroll.top = ui.area.view.scrollTop() + break + } + window.lastInfo.edit.cursor = editor.getCursor() + window.lastInfo.edit.selections = editor.listSelections() + window.lastInfo.needRestore = true +} + +function restoreInfo () { + var scrollbarStyle = editor.getOption('scrollbarStyle') + if (window.lastInfo.needRestore) { + var line = window.lastInfo.edit.cursor.line + var ch = window.lastInfo.edit.cursor.ch + editor.setCursor(line, ch) + editor.setSelections(window.lastInfo.edit.selections) + switch (window.currentMode) { + case modeType.edit: + if (scrollbarStyle === 'native') { + $(window).scrollLeft(window.lastInfo.edit.scroll.left) + $(window).scrollTop(window.lastInfo.edit.scroll.top) + } else { + let left = window.lastInfo.edit.scroll.left + let top = window.lastInfo.edit.scroll.top + editor.scrollIntoView() + editor.scrollTo(left, top) + } + break + case modeType.view: + $(window).scrollLeft(window.lastInfo.view.scroll.left) + $(window).scrollTop(window.lastInfo.view.scroll.top) + break + case modeType.both: + let left = window.lastInfo.edit.scroll.left + let top = window.lastInfo.edit.scroll.top + editor.scrollIntoView() + editor.scrollTo(left, top) + ui.area.view.scrollLeft(window.lastInfo.view.scroll.left) + ui.area.view.scrollTop(window.lastInfo.view.scroll.top) + break } + + window.lastInfo.needRestore = false + } } -//view actions -function refreshView() { - ui.area.markdown.html(''); - isDirty = true; - updateViewInner(); +// view actions +function refreshView () { + ui.area.markdown.html('') + window.isDirty = true + updateViewInner() } var updateView = _.debounce(function () { - editor.operation(updateViewInner); -}, updateViewDebounce); - -var lastResult = null; -var postUpdateEvent = null; - -function updateViewInner() { - if (currentMode == modeType.edit || !isDirty) return; - var value = editor.getValue(); - var lastMeta = md.meta; - md.meta = {}; - delete md.metaError; - var rendered = md.render(value); - if (md.meta.type && md.meta.type === 'slide') { - var slideOptions = { - separator: '^(\r\n?|\n)---(\r\n?|\n)$', - verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' - }; - var slides = RevealMarkdown.slidify(editor.getValue(), slideOptions); - ui.area.markdown.html(slides); - RevealMarkdown.initialize(); + editor.operation(updateViewInner) +}, updateViewDebounce) + +var lastResult = null +var postUpdateEvent = null + +function updateViewInner () { + if (window.currentMode === modeType.edit || !window.isDirty) return + var value = editor.getValue() + var lastMeta = md.meta + md.meta = {} + delete md.metaError + var rendered = md.render(value) + if (md.meta.type && md.meta.type === 'slide') { + var slideOptions = { + separator: '^(\r\n?|\n)---(\r\n?|\n)$', + verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' + } + var slides = window.RevealMarkdown.slidify(editor.getValue(), slideOptions) + ui.area.markdown.html(slides) + window.RevealMarkdown.initialize() // prevent XSS - ui.area.markdown.html(preventXSS(ui.area.markdown.html())); - ui.area.markdown.addClass('slides'); - syncscroll = false; - checkSyncToggle(); - } else { - if (lastMeta.type && lastMeta.type === 'slide') { - refreshView(); - ui.area.markdown.removeClass('slides'); - syncscroll = true; - checkSyncToggle(); - } + ui.area.markdown.html(preventXSS(ui.area.markdown.html())) + ui.area.markdown.addClass('slides') + window.syncscroll = false + checkSyncToggle() + } else { + if (lastMeta.type && lastMeta.type === 'slide') { + refreshView() + ui.area.markdown.removeClass('slides') + window.syncscroll = true + checkSyncToggle() + } // only render again when meta changed - if (JSON.stringify(md.meta) != JSON.stringify(lastMeta)) { - parseMeta(md, ui.area.codemirror, ui.area.markdown, $('#ui-toc'), $('#ui-toc-affix')); - rendered = md.render(value); - } + if (JSON.stringify(md.meta) !== JSON.stringify(lastMeta)) { + parseMeta(md, ui.area.codemirror, ui.area.markdown, $('#ui-toc'), $('#ui-toc-affix')) + rendered = md.render(value) + } // prevent XSS - rendered = preventXSS(rendered); - var result = postProcess(rendered).children().toArray(); - partialUpdate(result, lastResult, ui.area.markdown.children().toArray()); - if (result && lastResult && result.length != lastResult.length) - updateDataAttrs(result, ui.area.markdown.children().toArray()); - lastResult = $(result).clone(); - } - finishView(ui.area.markdown); - autoLinkify(ui.area.markdown); - deduplicatedHeaderId(ui.area.markdown); - renderTOC(ui.area.markdown); - generateToc('ui-toc'); - generateToc('ui-toc-affix'); - generateScrollspy(); - updateScrollspy(); - smoothHashScroll(); - isDirty = false; - clearMap(); - //buildMap(); - updateTitleReminder(); - if (postUpdateEvent && typeof postUpdateEvent === 'function') - postUpdateEvent(); -} - -var updateHistoryDebounce = 600; + rendered = preventXSS(rendered) + var result = postProcess(rendered).children().toArray() + partialUpdate(result, lastResult, ui.area.markdown.children().toArray()) + if (result && lastResult && result.length !== lastResult.length) { updateDataAttrs(result, ui.area.markdown.children().toArray()) } + lastResult = $(result).clone() + } + finishView(ui.area.markdown) + autoLinkify(ui.area.markdown) + deduplicatedHeaderId(ui.area.markdown) + renderTOC(ui.area.markdown) + generateToc('ui-toc') + generateToc('ui-toc-affix') + generateScrollspy() + updateScrollspy() + smoothHashScroll() + window.isDirty = false + clearMap() + // buildMap(); + updateTitleReminder() + if (postUpdateEvent && typeof postUpdateEvent === 'function') { postUpdateEvent() } +} + +var updateHistoryDebounce = 600 var updateHistory = _.debounce(updateHistoryInner, updateHistoryDebounce) -function updateHistoryInner() { - writeHistory(renderFilename(ui.area.markdown), renderTags(ui.area.markdown)); -} - -function updateDataAttrs(src, des) { - //sync data attr startline and endline - for (var i = 0; i < src.length; i++) { - copyAttribute(src[i], des[i], 'data-startline'); - copyAttribute(src[i], des[i], 'data-endline'); - } -} - -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 + 1; 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 + 1; 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); - } - tarEnd += overlap; - srcEnd += overlap; - var repeatAdd = (start - srcEnd) < (start - tarEnd); - var repeatDiff = Math.abs(srcEnd - tarEnd) - 1; - //push new elements - var newElements = []; - if (srcEnd >= start) { - for (var j = start; j <= srcEnd; j++) { - if (!src[j]) continue; - newElements.push(src[j].outerHTML); - } - } else if (repeatAdd) { - for (var j = srcEnd - repeatDiff; j <= srcEnd; j++) { - if (!des[j]) continue; - newElements.push(des[j].outerHTML); - } - } - //push remove elements - var removeElements = []; - if (tarEnd >= start) { - for (var j = start; j <= tarEnd; j++) { - if (!des[j]) continue; - removeElements.push(des[j]); - } - } else if (!repeatAdd) { - for (var j = start; j <= start + repeatDiff; j++) { - if (!des[j]) continue; - removeElements.push(des[j]); - } - } - //add elements - if (debug) { - console.log('ADD ELEMENTS'); - console.log(newElements.join('\n')); - } - if (des[start]) - $(newElements.join('')).insertBefore(des[start]); - else - $(newElements.join('')).insertAfter(des[start - 1]); - //remove elements - if (debug) - console.log('REMOVE ELEMENTS'); - for (var j = 0; j < removeElements.length; j++) { - if (debug) { - console.log(removeElements[j].outerHTML); - } - if (removeElements[j]) - $(removeElements[j]).remove(); - } - } -} - -function cloneAndRemoveDataAttr(el) { - if (!el) return; - var rawEl = $(el).clone(); - rawEl.removeAttr('data-startline data-endline'); - rawEl.find('[data-startline]').removeAttr('data-startline data-endline'); - return rawEl[0]; -} - -function copyAttribute(src, des, attr) { - if (src && src.getAttribute(attr) && des) - des.setAttribute(attr, src.getAttribute(attr)); +function updateHistoryInner () { + writeHistory(renderFilename(ui.area.markdown), renderTags(ui.area.markdown)) +} + +function updateDataAttrs (src, des) { + // sync data attr startline and endline + for (var i = 0; i < src.length; i++) { + copyAttribute(src[i], des[i], 'data-startline') + copyAttribute(src[i], des[i], 'data-endline') + } +} + +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 (let 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 + // find diff start position + for (let i = 0; i < tar.length; i++) { + // copyAttribute(src[i], des[i], 'data-startline'); + // copyAttribute(src[i], des[i], 'data-endline'); + let rawSrc = cloneAndRemoveDataAttr(src[i]) + let 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 (let i = 0; i < src.length; i++) { + // copyAttribute(src[i], des[i], 'data-startline'); + // copyAttribute(src[i], des[i], 'data-endline'); + let rawSrc = cloneAndRemoveDataAttr(src[i]) + let rawTar = cloneAndRemoveDataAttr(tar[i]) + if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) { + start = i + break + } + } + // tar end + for (let i = 1; i <= tar.length + 1; i++) { + let srcLength = src.length + let tarLength = tar.length + // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline'); + // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); + let rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]) + let rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]) + if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) { + tarEnd = tar.length - i + break + } + } + // src end + for (let i = 1; i <= src.length + 1; i++) { + let srcLength = src.length + let tarLength = tar.length + // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline'); + // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline'); + let rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]) + let 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) + } + tarEnd += overlap + srcEnd += overlap + var repeatAdd = (start - srcEnd) < (start - tarEnd) + var repeatDiff = Math.abs(srcEnd - tarEnd) - 1 + // push new elements + var newElements = [] + if (srcEnd >= start) { + for (let j = start; j <= srcEnd; j++) { + if (!src[j]) continue + newElements.push(src[j].outerHTML) + } + } else if (repeatAdd) { + for (let j = srcEnd - repeatDiff; j <= srcEnd; j++) { + if (!des[j]) continue + newElements.push(des[j].outerHTML) + } + } + // push remove elements + var removeElements = [] + if (tarEnd >= start) { + for (let j = start; j <= tarEnd; j++) { + if (!des[j]) continue + removeElements.push(des[j]) + } + } else if (!repeatAdd) { + for (let j = start; j <= start + repeatDiff; j++) { + if (!des[j]) continue + removeElements.push(des[j]) + } + } + // add elements + if (debug) { + console.log('ADD ELEMENTS') + console.log(newElements.join('\n')) + } + if (des[start]) { $(newElements.join('')).insertBefore(des[start]) } else { $(newElements.join('')).insertAfter(des[start - 1]) } + // remove elements + if (debug) { console.log('REMOVE ELEMENTS') } + for (let j = 0; j < removeElements.length; j++) { + if (debug) { + console.log(removeElements[j].outerHTML) + } + if (removeElements[j]) { $(removeElements[j]).remove() } + } + } +} + +function cloneAndRemoveDataAttr (el) { + if (!el) return + var rawEl = $(el).clone() + rawEl.removeAttr('data-startline data-endline') + rawEl.find('[data-startline]').removeAttr('data-startline data-endline') + return rawEl[0] +} + +function copyAttribute (src, des, attr) { + if (src && src.getAttribute(attr) && des) { des.setAttribute(attr, src.getAttribute(attr)) } } if ($('.cursor-menu').length <= 0) { - $("<div class='cursor-menu'>").insertAfter('.CodeMirror-cursors'); + $("<div class='cursor-menu'>").insertAfter('.CodeMirror-cursors') } -function reverseSortCursorMenu(dropdown) { - var items = dropdown.find('.textcomplete-item'); - items.sort(function (a, b) { - return $(b).attr('data-index') - $(a).attr('data-index'); - }); - return items; +function reverseSortCursorMenu (dropdown) { + var items = dropdown.find('.textcomplete-item') + items.sort(function (a, b) { + return $(b).attr('data-index') - $(a).attr('data-index') + }) + return items } -var checkCursorMenu = _.throttle(checkCursorMenuInner, cursorMenuThrottle); +var checkCursorMenu = _.throttle(checkCursorMenuInner, cursorMenuThrottle) -function checkCursorMenuInner() { +function checkCursorMenuInner () { // get element - var dropdown = $('.cursor-menu > .dropdown-menu'); + var dropdown = $('.cursor-menu > .dropdown-menu') // return if not exists - if (dropdown.length <= 0) return; + if (dropdown.length <= 0) return // set margin - var menuRightMargin = 10; - var menuBottomMargin = 4; + var menuRightMargin = 10 + var menuBottomMargin = 4 // use sizer to get the real doc size (won't count status bar and gutters) - var docWidth = ui.area.codemirrorSizer.width(); - var docHeight = ui.area.codemirrorSizer.height(); + var docWidth = ui.area.codemirrorSizer.width() // get editor size (status bar not count in) - var editorWidth = ui.area.codemirror.width(); - var editorHeight = ui.area.codemirror.height(); + var editorHeight = ui.area.codemirror.height() // get element size - var width = dropdown.outerWidth(); - var height = dropdown.outerHeight(); + var width = dropdown.outerWidth() + var height = dropdown.outerHeight() // get cursor - var cursor = editor.getCursor(); + var cursor = editor.getCursor() // set element cursor data - if (!dropdown.hasClass('CodeMirror-other-cursor')) - dropdown.addClass('CodeMirror-other-cursor'); - dropdown.attr('data-line', cursor.line); - dropdown.attr('data-ch', cursor.ch); + if (!dropdown.hasClass('CodeMirror-other-cursor')) { dropdown.addClass('CodeMirror-other-cursor') } + dropdown.attr('data-line', cursor.line) + dropdown.attr('data-ch', cursor.ch) // get coord position - var coord = editor.charCoords({ - line: cursor.line, - ch: cursor.ch - }, 'windows'); - var left = coord.left; - var top = coord.top; + var coord = editor.charCoords({ + line: cursor.line, + ch: cursor.ch + }, 'windows') + var left = coord.left + var top = coord.top // get doc top offset (to workaround with viewport) - var docTopOffset = ui.area.codemirrorSizerInner.position().top; + var docTopOffset = ui.area.codemirrorSizerInner.position().top // set offset - var offsetLeft = 0; - var offsetTop = defaultTextHeight; + var offsetLeft = 0 + var offsetTop = defaultTextHeight // set up side down - window.upSideDown = false; - var lastUpSideDown = upSideDown = false; + window.upSideDown = false + var lastUpSideDown = window.upSideDown = false // only do when have width and height - if (width > 0 && height > 0) { + if (width > 0 && height > 0) { // make element right bound not larger than doc width - if (left + width + offsetLeft + menuRightMargin > docWidth) - offsetLeft = -(left + width - docWidth + menuRightMargin); + if (left + width + offsetLeft + menuRightMargin > docWidth) { offsetLeft = -(left + width - docWidth + menuRightMargin) } // flip y when element bottom bound larger than doc height // and element top position is larger than element height - if (top + docTopOffset + height + offsetTop + menuBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + menuBottomMargin) { - offsetTop = -(height + menuBottomMargin); + if (top + docTopOffset + height + offsetTop + menuBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + menuBottomMargin) { + offsetTop = -(height + menuBottomMargin) // reverse sort menu because upSideDown - dropdown.html(reverseSortCursorMenu(dropdown)); - upSideDown = true; - } - var textCompleteDropdown = $(editor.getInputField()).data('textComplete').dropdown; - lastUpSideDown = textCompleteDropdown.upSideDown; - textCompleteDropdown.upSideDown = upSideDown; + dropdown.html(reverseSortCursorMenu(dropdown)) + window.upSideDown = true } + var textCompleteDropdown = $(editor.getInputField()).data('textComplete').dropdown + lastUpSideDown = textCompleteDropdown.upSideDown + textCompleteDropdown.upSideDown = window.upSideDown + } // make menu scroll top only if upSideDown changed - if (upSideDown !== lastUpSideDown) - dropdown.scrollTop(dropdown[0].scrollHeight); + if (window.upSideDown !== lastUpSideDown) { dropdown.scrollTop(dropdown[0].scrollHeight) } // set element offset data - dropdown.attr('data-offset-left', offsetLeft); - dropdown.attr('data-offset-top', offsetTop); + dropdown.attr('data-offset-left', offsetLeft) + dropdown.attr('data-offset-top', offsetTop) // set position - dropdown[0].style.left = left + offsetLeft + 'px'; - dropdown[0].style.top = top + offsetTop + 'px'; + dropdown[0].style.left = left + offsetLeft + 'px' + dropdown[0].style.top = top + offsetTop + 'px' } -function checkInIndentCode() { +function checkInIndentCode () { // if line starts with tab or four spaces is a code block - var line = editor.getLine(editor.getCursor().line); - var isIndentCode = ((line.substr(0, 4) === ' ') || (line.substr(0, 1) === '\t')); - return isIndentCode; -} - -var isInCode = false; - -function checkInCode() { - isInCode = checkAbove(matchInCode) || checkInIndentCode(); -} - -function checkAbove(method) { - var cursor = editor.getCursor(); - var text = []; - for (var i = 0; i < cursor.line; i++) //contain current line - text.push(editor.getLine(i)); - text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch); - //console.log(text); - return method(text); -} - -function checkBelow(method) { - var cursor = editor.getCursor(); - var count = editor.lineCount(); - var text = []; - for (var i = cursor.line + 1; i < count; i++) //contain current line - text.push(editor.getLine(i)); - text = editor.getLine(cursor.line).slice(cursor.ch) + '\n' + text.join('\n'); - //console.log(text); - return method(text); -} - -function matchInCode(text) { - var match; - match = text.match(/`{3,}/g); + var line = editor.getLine(editor.getCursor().line) + var isIndentCode = ((line.substr(0, 4) === ' ') || (line.substr(0, 1) === '\t')) + return isIndentCode +} + +var isInCode = false + +function checkInCode () { + isInCode = checkAbove(matchInCode) || checkInIndentCode() +} + +function checkAbove (method) { + var cursor = editor.getCursor() + var text = [] + for (var i = 0; i < cursor.line; i++) { // contain current line + text.push(editor.getLine(i)) + } + text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch) + // console.log(text); + return method(text) +} + +function checkBelow (method) { + var cursor = editor.getCursor() + var count = editor.lineCount() + var text = [] + for (var i = cursor.line + 1; i < count; i++) { // contain current line + text.push(editor.getLine(i)) + } + text = editor.getLine(cursor.line).slice(cursor.ch) + '\n' + text.join('\n') + // console.log(text); + return method(text) +} + +function matchInCode (text) { + var match + match = text.match(/`{3,}/g) + if (match && match.length % 2) { + return true + } else { + match = text.match(/`/g) if (match && match.length % 2) { - return true; + return true } else { - match = text.match(/`/g); - if (match && match.length % 2) { - return true; - } else { - return false; - } + return false } + } } -var isInContainer = false; -var isInContainerSyntax = false; +var isInContainer = false +var isInContainerSyntax = false -function checkInContainer() { - isInContainer = checkAbove(matchInContainer) && !checkInIndentCode(); +function checkInContainer () { + isInContainer = checkAbove(matchInContainer) && !checkInIndentCode() } -function checkInContainerSyntax() { +function checkInContainerSyntax () { // if line starts with :::, it's in container syntax - var line = editor.getLine(editor.getCursor().line); - isInContainerSyntax = (line.substr(0, 3) === ':::'); + var line = editor.getLine(editor.getCursor().line) + isInContainerSyntax = (line.substr(0, 3) === ':::') } -function matchInContainer(text) { - var match; - match = text.match(/:{3,}/g); - if (match && match.length % 2) { - return true; - } else { - return false; - } +function matchInContainer (text) { + var match + match = text.match(/:{3,}/g) + if (match && match.length % 2) { + return true + } else { + return false + } } $(editor.getInputField()) .textcomplete([ - { // emoji strategy - match: /(^|\n|\s)\B:([\-+\w]*)$/, - search: function (term, callback) { - var line = editor.getLine(editor.getCursor().line); - term = line.match(this.match)[2]; - var list = []; - $.map(emojify.emojiNames, function (emoji) { - if (emoji.indexOf(term) === 0) //match at first character - list.push(emoji); - }); - $.map(emojify.emojiNames, function (emoji) { - if (emoji.indexOf(term) !== -1) //match inside the word - list.push(emoji); - }); - callback(list); - }, - template: function (value) { - return '<img class="emoji" src="' + serverurl + '/build/emojify.js/dist/images/basic/' + value + '.png"></img> ' + value; - }, - replace: function (value) { - return '$1:' + value + ': '; - }, - index: 1, - context: function (text) { - checkInCode(); - checkInContainer(); - checkInContainerSyntax(); - return !isInCode && !isInContainerSyntax; + { // emoji strategy + match: /(^|\n|\s)\B:([-+\w]*)$/, + search: function (term, callback) { + var line = editor.getLine(editor.getCursor().line) + term = line.match(this.match)[2] + var list = [] + $.map(window.emojify.emojiNames, function (emoji) { + if (emoji.indexOf(term) === 0) { // match at first character + list.push(emoji) } - }, - { // Code block language strategy - langs: supportCodeModes, - charts: supportCharts, - match: /(^|\n)```(\w+)$/, - search: function (term, callback) { - var line = editor.getLine(editor.getCursor().line); - term = line.match(this.match)[2]; - var list = []; - $.map(this.langs, function (lang) { - if (lang.indexOf(term) === 0 && lang !== term) - list.push(lang); - }); - $.map(this.charts, function (chart) { - if (chart.indexOf(term) === 0 && chart !== term) - list.push(chart); - }); - callback(list); - }, - replace: function (lang) { - var ending = ''; - if (!checkBelow(matchInCode)) { - ending = '\n\n```'; - } - if (this.langs.indexOf(lang) !== -1) - return '$1```' + lang + '=' + ending; - else if (this.charts.indexOf(lang) !== -1) - return '$1```' + lang + ending; - }, - done: function () { - var cursor = editor.getCursor(); - var text = []; - text.push(editor.getLine(cursor.line - 1)); - text.push(editor.getLine(cursor.line)); - text = text.join('\n'); - //console.log(text); - if (text == '\n```') - editor.doc.cm.execCommand("goLineUp"); - }, - context: function (text) { - return isInCode; + }) + $.map(window.emojify.emojiNames, function (emoji) { + if (emoji.indexOf(term) !== -1) { // match inside the word + list.push(emoji) } + }) + callback(list) }, - { // Container strategy - containers: supportContainers, - match: /(^|\n):::(\s*)(\w*)$/, - search: function (term, callback) { - var line = editor.getLine(editor.getCursor().line); - term = line.match(this.match)[3].trim(); - var list = []; - $.map(this.containers, function (container) { - if (container.indexOf(term) === 0 && container !== term) - list.push(container); - }); - callback(list); - }, - replace: function (lang) { - var ending = ''; - if (!checkBelow(matchInContainer)) { - ending = '\n\n:::'; - } - if (this.containers.indexOf(lang) !== -1) - return '$1:::$2' + lang + ending; - }, - done: function () { - var cursor = editor.getCursor(); - var text = []; - text.push(editor.getLine(cursor.line - 1)); - text.push(editor.getLine(cursor.line)); - text = text.join('\n'); - //console.log(text); - if (text == '\n:::') - editor.doc.cm.execCommand("goLineUp"); - }, - context: function (text) { - return !isInCode && isInContainer; - } + template: function (value) { + return '<img class="emoji" src="' + serverurl + '/build/emojify.js/dist/images/basic/' + value + '.png"></img> ' + value }, - { //header - match: /(?:^|\n)(\s{0,3})(#{1,6}\w*)$/, - search: function (term, callback) { - callback($.map(supportHeaders, function (header) { - return header.search.indexOf(term) === 0 ? header.text : null; - })); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } + replace: function (value) { + return '$1:' + value + ': ' }, - { //extra tags for blockquote - match: /(?:^|\n|\s)(\>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|\:|)\s*\w*)$/, - search: function (term, callback) { - var line = editor.getLine(editor.getCursor().line); - var quote = line.match(this.match)[1].trim(); - var list = []; - if (quote.indexOf('>') == 0) { - $.map(supportExtraTags, function (extratag) { - if (extratag.search.indexOf(term) === 0) - list.push(extratag.command()); - }); - } - $.map(supportReferrals, function (referral) { - if (referral.search.indexOf(term) === 0) - list.push(referral.text); - }) - callback(list); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } + index: 1, + context: function (text) { + checkInCode() + checkInContainer() + checkInContainerSyntax() + return !isInCode && !isInContainerSyntax + } + }, + { // Code block language strategy + langs: supportCodeModes, + charts: supportCharts, + match: /(^|\n)```(\w+)$/, + search: function (term, callback) { + var line = editor.getLine(editor.getCursor().line) + term = line.match(this.match)[2] + var list = [] + $.map(this.langs, function (lang) { + if (lang.indexOf(term) === 0 && lang !== term) { list.push(lang) } + }) + $.map(this.charts, function (chart) { + if (chart.indexOf(term) === 0 && chart !== term) { list.push(chart) } + }) + callback(list) }, - { //extra tags for list - match: /(^[>\s]*[\-\+\*]\s(?:\[[x ]\]|.*))(\[\])(\w*)$/, - search: function (term, callback) { - var list = []; - $.map(supportExtraTags, function (extratag) { - if (extratag.search.indexOf(term) === 0) - list.push(extratag.command()); - }); - $.map(supportReferrals, function (referral) { - if (referral.search.indexOf(term) === 0) - list.push(referral.text); - }) - callback(list); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } + replace: function (lang) { + var ending = '' + if (!checkBelow(matchInCode)) { + ending = '\n\n```' + } + if (this.langs.indexOf(lang) !== -1) { return '$1```' + lang + '=' + ending } else if (this.charts.indexOf(lang) !== -1) { return '$1```' + lang + ending } }, - { //referral - match: /(^\s*|\n|\s{2})((\[\]|\[\]\[\]|\[\]\(\)|\!|\!\[\]|\!\[\]\[\]|\!\[\]\(\))\s*\w*)$/, - search: function (term, callback) { - callback($.map(supportReferrals, function (referral) { - return referral.search.indexOf(term) === 0 ? referral.text : null; - })); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } + done: function () { + var cursor = editor.getCursor() + var text = [] + text.push(editor.getLine(cursor.line - 1)) + text.push(editor.getLine(cursor.line)) + text = text.join('\n') + // console.log(text); + if (text === '\n```') { editor.doc.cm.execCommand('goLineUp') } }, - { //externals - match: /(^|\n|\s)\{\}(\w*)$/, - search: function (term, callback) { - callback($.map(supportExternals, function (external) { - return external.search.indexOf(term) === 0 ? external.text : null; - })); - }, - replace: function (value) { - return '$1' + value; - }, - context: function (text) { - return !isInCode; - } - } - ], { - appendTo: $('.cursor-menu') - }) - .on({ - 'textComplete:beforeSearch': function (e) { - //NA + context: function (text) { + return isInCode + } + }, + { // Container strategy + containers: supportContainers, + match: /(^|\n):::(\s*)(\w*)$/, + search: function (term, callback) { + var line = editor.getLine(editor.getCursor().line) + term = line.match(this.match)[3].trim() + var list = [] + $.map(this.containers, function (container) { + if (container.indexOf(term) === 0 && container !== term) { list.push(container) } + }) + callback(list) }, - 'textComplete:afterSearch': function (e) { - checkCursorMenu(); + replace: function (lang) { + var ending = '' + if (!checkBelow(matchInContainer)) { + ending = '\n\n:::' + } + if (this.containers.indexOf(lang) !== -1) { return '$1:::$2' + lang + ending } }, - 'textComplete:select': function (e, value, strategy) { - //NA + done: function () { + var cursor = editor.getCursor() + var text = [] + text.push(editor.getLine(cursor.line - 1)) + text.push(editor.getLine(cursor.line)) + text = text.join('\n') + // console.log(text); + if (text === '\n:::') { editor.doc.cm.execCommand('goLineUp') } }, - 'textComplete:show': function (e) { - $(this).data('autocompleting', true); - editor.setOption("extraKeys", { - "Up": function () { - return false; - }, - "Right": function () { - editor.doc.cm.execCommand("goCharRight"); - }, - "Down": function () { - return false; - }, - "Left": function () { - editor.doc.cm.execCommand("goCharLeft"); - }, - "Enter": function () { - return false; - }, - "Backspace": function () { - editor.doc.cm.execCommand("delCharBefore"); - } - }); + context: function (text) { + return !isInCode && isInContainer + } + }, + { // header + match: /(?:^|\n)(\s{0,3})(#{1,6}\w*)$/, + search: function (term, callback) { + callback($.map(supportHeaders, function (header) { + return header.search.indexOf(term) === 0 ? header.text : null + })) + }, + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode + } + }, + { // extra tags for blockquote + match: /(?:^|\n|\s)(>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|:|)\s*\w*)$/, + search: function (term, callback) { + var line = editor.getLine(editor.getCursor().line) + var quote = line.match(this.match)[1].trim() + var list = [] + if (quote.indexOf('>') === 0) { + $.map(supportExtraTags, function (extratag) { + if (extratag.search.indexOf(term) === 0) { list.push(extratag.command()) } + }) + } + $.map(supportReferrals, function (referral) { + if (referral.search.indexOf(term) === 0) { list.push(referral.text) } + }) + callback(list) + }, + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode + } + }, + { // extra tags for list + match: /(^[>\s]*[-+*]\s(?:\[[x ]\]|.*))(\[\])(\w*)$/, + search: function (term, callback) { + var list = [] + $.map(supportExtraTags, function (extratag) { + if (extratag.search.indexOf(term) === 0) { list.push(extratag.command()) } + }) + $.map(supportReferrals, function (referral) { + if (referral.search.indexOf(term) === 0) { list.push(referral.text) } + }) + callback(list) }, - 'textComplete:hide': function (e) { - $(this).data('autocompleting', false); - editor.setOption("extraKeys", defaultExtraKeys); + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode + } + }, + { // referral + match: /(^\s*|\n|\s{2})((\[\]|\[\]\[\]|\[\]\(\)|!|!\[\]|!\[\]\[\]|!\[\]\(\))\s*\w*)$/, + search: function (term, callback) { + callback($.map(supportReferrals, function (referral) { + return referral.search.indexOf(term) === 0 ? referral.text : null + })) + }, + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode + } + }, + { // externals + match: /(^|\n|\s)\{\}(\w*)$/, + search: function (term, callback) { + callback($.map(supportExternals, function (external) { + return external.search.indexOf(term) === 0 ? external.text : null + })) + }, + replace: function (value) { + return '$1' + value + }, + context: function (text) { + return !isInCode } - }); + } + ], { + appendTo: $('.cursor-menu') + }) + .on({ + 'textComplete:beforeSearch': function (e) { + // NA + }, + 'textComplete:afterSearch': function (e) { + checkCursorMenu() + }, + 'textComplete:select': function (e, value, strategy) { + // NA + }, + 'textComplete:show': function (e) { + $(this).data('autocompleting', true) + editor.setOption('extraKeys', { + 'Up': function () { + return false + }, + 'Right': function () { + editor.doc.cm.execCommand('goCharRight') + }, + 'Down': function () { + return false + }, + 'Left': function () { + editor.doc.cm.execCommand('goCharLeft') + }, + 'Enter': function () { + return false + }, + 'Backspace': function () { + editor.doc.cm.execCommand('delCharBefore') + } + }) + }, + 'textComplete:hide': function (e) { + $(this).data('autocompleting', false) + editor.setOption('extraKeys', defaultExtraKeys) + } + }) diff --git a/public/js/lib/common/login.js b/public/js/lib/common/login.js index 58fa55c6..18cd377d 100644 --- a/public/js/lib/common/login.js +++ b/public/js/lib/common/login.js @@ -1,89 +1,92 @@ -import { serverurl } from '../config'; +/* eslint-env browser, jquery */ +/* global Cookies */ -let checkAuth = false; -let profile = null; -let lastLoginState = getLoginState(); -let lastUserId = getUserId(); -var loginStateChangeEvent = null; +import { serverurl } from '../config' -export function setloginStateChangeEvent(func) { - loginStateChangeEvent = func; +let checkAuth = false +let profile = null +let lastLoginState = getLoginState() +let lastUserId = getUserId() +var loginStateChangeEvent = null + +export function setloginStateChangeEvent (func) { + loginStateChangeEvent = func } -export function resetCheckAuth() { - checkAuth = false; +export function resetCheckAuth () { + checkAuth = false } -export function setLoginState(bool, id) { - Cookies.set('loginstate', bool, { - expires: 365 - }); - if (id) { - Cookies.set('userid', id, { - expires: 365 - }); - } else { - Cookies.remove('userid'); - } - lastLoginState = bool; - lastUserId = id; - checkLoginStateChanged(); +export function setLoginState (bool, id) { + Cookies.set('loginstate', bool, { + expires: 365 + }) + if (id) { + Cookies.set('userid', id, { + expires: 365 + }) + } else { + Cookies.remove('userid') + } + lastLoginState = bool + lastUserId = id + checkLoginStateChanged() } -export function checkLoginStateChanged() { - if (getLoginState() != lastLoginState || getUserId() != lastUserId) { - if (loginStateChangeEvent) setTimeout(loginStateChangeEvent, 100); - return true; - } else { - return false; - } +export function checkLoginStateChanged () { + if (getLoginState() !== lastLoginState || getUserId() !== lastUserId) { + if (loginStateChangeEvent) setTimeout(loginStateChangeEvent, 100) + return true + } else { + return false + } } -export function getLoginState() { - const state = Cookies.get('loginstate'); - return state === "true" || state === true; +export function getLoginState () { + const state = Cookies.get('loginstate') + return state === 'true' || state === true } -export function getUserId() { - return Cookies.get('userid'); +export function getUserId () { + return Cookies.get('userid') } -export function clearLoginState() { - Cookies.remove('loginstate'); +export function clearLoginState () { + Cookies.remove('loginstate') } -export function checkIfAuth(yesCallback, noCallback) { - const cookieLoginState = getLoginState(); - if (checkLoginStateChanged()) checkAuth = false; - if (!checkAuth || typeof cookieLoginState == 'undefined') { - $.get(`${serverurl}/me`) +export function checkIfAuth (yesCallback, noCallback) { + const cookieLoginState = getLoginState() + if (checkLoginStateChanged()) checkAuth = false + if (!checkAuth || typeof cookieLoginState === 'undefined') { + $.get(`${serverurl}/me`) .done(data => { - if (data && data.status == 'ok') { - profile = data; - yesCallback(profile); - setLoginState(true, data.id); - } else { - noCallback(); - setLoginState(false); - } + if (data && data.status === 'ok') { + profile = data + yesCallback(profile) + setLoginState(true, data.id) + } else { + noCallback() + setLoginState(false) + } }) .fail(() => { - noCallback(); + noCallback() }) .always(() => { - checkAuth = true; - }); - } else if (cookieLoginState) { - yesCallback(profile); - } else { - noCallback(); - } + checkAuth = true + }) + } else if (cookieLoginState) { + yesCallback(profile) + } else { + noCallback() + } } export default { - checkAuth, - profile, - lastLoginState, - lastUserId, - loginStateChangeEvent -}; + checkAuth, + profile, + lastLoginState, + lastUserId, + loginStateChangeEvent +} diff --git a/public/js/lib/config/index.js b/public/js/lib/config/index.js index 2b73679f..1ea7a7ab 100644 --- a/public/js/lib/config/index.js +++ b/public/js/lib/config/index.js @@ -1,19 +1,19 @@ -import configJson from '../../../../config.json'; // root path json config +import configJson from '../../../../config.json' // root path json config -const config = 'production' === process.env.NODE_ENV ? configJson.production : configJson.development; +const config = process.env.NODE_ENV === 'production' ? configJson.production : configJson.development -export const GOOGLE_API_KEY = (config.google && config.google.apiKey) || ''; -export const GOOGLE_CLIENT_ID = (config.google && config.google.clientID) || ''; -export const DROPBOX_APP_KEY = (config.dropbox && config.dropbox.appKey) || ''; +export const GOOGLE_API_KEY = (config.google && config.google.apiKey) || '' +export const GOOGLE_CLIENT_ID = (config.google && config.google.clientID) || '' +export const DROPBOX_APP_KEY = (config.dropbox && config.dropbox.appKey) || '' -export const domain = config.domain || ''; // domain name -export const urlpath = config.urlpath || ''; // sub url path, like: www.example.com/<urlpath> -export const debug = config.debug || false; +export const domain = config.domain || '' // domain name +export const urlpath = config.urlpath || '' // sub url path, like: www.example.com/<urlpath> +export const debug = config.debug || false -export const port = window.location.port; -export const serverurl = `${window.location.protocol}//${domain ? domain : window.location.hostname}${port ? ':' + port : ''}${urlpath ? '/' + urlpath : ''}`; -window.serverurl = serverurl; -export const noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1]; -export const noteurl = `${serverurl}/${noteid}`; +export const port = window.location.port +export const serverurl = `${window.location.protocol}//${domain || window.location.hostname}${port ? ':' + port : ''}${urlpath ? '/' + urlpath : ''}` +window.serverurl = serverurl +export const noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1] +export const noteurl = `${serverurl}/${noteid}` -export const version = '0.5.0'; +export const version = '0.5.0' diff --git a/public/js/locale.js b/public/js/locale.js index e6d11cd2..2a2c1814 100644 --- a/public/js/locale.js +++ b/public/js/locale.js @@ -1,26 +1,28 @@ -var lang = "en"; -var userLang = navigator.language || navigator.userLanguage; -var userLangCode = userLang.split('-')[0]; -var userCountryCode = userLang.split('-')[1]; -var locale = $('.ui-locale'); -var supportLangs = []; -$(".ui-locale option").each(function() { - supportLangs.push($(this).val()); -}); +/* eslint-env browser, jquery */ +/* global Cookies */ + +var lang = 'en' +var userLang = navigator.language || navigator.userLanguage +var userLangCode = userLang.split('-')[0] +var locale = $('.ui-locale') +var supportLangs = [] +$('.ui-locale option').each(function () { + supportLangs.push($(this).val()) +}) if (Cookies.get('locale')) { - lang = Cookies.get('locale'); + lang = Cookies.get('locale') } else if (supportLangs.indexOf(userLang) !== -1) { - lang = supportLangs[supportLangs.indexOf(userLang)]; + lang = supportLangs[supportLangs.indexOf(userLang)] } else if (supportLangs.indexOf(userLangCode) !== -1) { - lang = supportLangs[supportLangs.indexOf(userLangCode)]; + lang = supportLangs[supportLangs.indexOf(userLangCode)] } -locale.val(lang); -$('select.ui-locale option[value="' + lang + '"]').attr('selected','selected'); +locale.val(lang) +$('select.ui-locale option[value="' + lang + '"]').attr('selected', 'selected') -locale.change(function() { - Cookies.set('locale', $(this).val(), { - expires: 365 - }); - window.location.reload(); -}); +locale.change(function () { + Cookies.set('locale', $(this).val(), { + expires: 365 + }) + window.location.reload() +}) diff --git a/public/js/pretty.js b/public/js/pretty.js index 18d0dc0d..718941a8 100644 --- a/public/js/pretty.js +++ b/public/js/pretty.js @@ -1,8 +1,11 @@ -require('../css/extra.css'); -require('../css/slide-preview.css'); -require('../css/site.css'); +/* eslint-env browser, jquery */ +/* global refreshView */ -require('highlight.js/styles/github-gist.css'); +require('../css/extra.css') +require('../css/slide-preview.css') +require('../css/site.css') + +require('highlight.js/styles/github-gist.css') import { autoLinkify, @@ -16,126 +19,126 @@ import { scrollToHash, smoothHashScroll, updateLastChange -} from './extra'; +} from './extra' -import { preventXSS } from './render'; +import { preventXSS } from './render' -const markdown = $("#doc.markdown-body"); -const text = markdown.text(); -const lastMeta = md.meta; -md.meta = {}; -delete md.metaError; -let rendered = md.render(text); +const markdown = $('#doc.markdown-body') +const text = markdown.text() +const lastMeta = md.meta +md.meta = {} +delete md.metaError +let rendered = md.render(text) if (md.meta.type && md.meta.type === 'slide') { - const slideOptions = { - separator: '^(\r\n?|\n)---(\r\n?|\n)$', - verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' - }; - const slides = RevealMarkdown.slidify(text, slideOptions); - markdown.html(slides); - RevealMarkdown.initialize(); + const slideOptions = { + separator: '^(\r\n?|\n)---(\r\n?|\n)$', + verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' + } + const slides = window.RevealMarkdown.slidify(text, slideOptions) + markdown.html(slides) + window.RevealMarkdown.initialize() // prevent XSS - markdown.html(preventXSS(markdown.html())); - markdown.addClass('slides'); + markdown.html(preventXSS(markdown.html())) + markdown.addClass('slides') } else { - if (lastMeta.type && lastMeta.type === 'slide') { - refreshView(); - markdown.removeClass('slides'); - } + if (lastMeta.type && lastMeta.type === 'slide') { + refreshView() + markdown.removeClass('slides') + } // only render again when meta changed - if (JSON.stringify(md.meta) != JSON.stringify(lastMeta)) { - parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix')); - rendered = md.render(text); - } + if (JSON.stringify(md.meta) !== JSON.stringify(lastMeta)) { + parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix')) + rendered = md.render(text) + } // prevent XSS - rendered = preventXSS(rendered); - const result = postProcess(rendered); - markdown.html(result.html()); + rendered = preventXSS(rendered) + const result = postProcess(rendered) + markdown.html(result.html()) } -$(document.body).show(); +$(document.body).show() -finishView(markdown); -autoLinkify(markdown); -deduplicatedHeaderId(markdown); -renderTOC(markdown); -generateToc('ui-toc'); -generateToc('ui-toc-affix'); -smoothHashScroll(); -createtime = lastchangeui.time.attr('data-createtime'); -lastchangetime = lastchangeui.time.attr('data-updatetime'); -updateLastChange(); +finishView(markdown) +autoLinkify(markdown) +deduplicatedHeaderId(markdown) +renderTOC(markdown) +generateToc('ui-toc') +generateToc('ui-toc-affix') +smoothHashScroll() +window.createtime = window.lastchangeui.time.attr('data-createtime') +window.lastchangetime = window.lastchangeui.time.attr('data-updatetime') +updateLastChange() -const url = window.location.pathname; -$('.ui-edit').attr('href', `${url}/edit`); -const toc = $('.ui-toc'); -const tocAffix = $('.ui-affix-toc'); -const tocDropdown = $('.ui-toc-dropdown'); -//toc +const url = window.location.pathname +$('.ui-edit').attr('href', `${url}/edit`) +const toc = $('.ui-toc') +const tocAffix = $('.ui-affix-toc') +const tocDropdown = $('.ui-toc-dropdown') +// toc tocDropdown.click(e => { - e.stopPropagation(); -}); + e.stopPropagation() +}) -let enoughForAffixToc = true; +let enoughForAffixToc = true -function generateScrollspy() { - $(document.body).scrollspy({ - target: '' - }); - $(document.body).scrollspy('refresh'); - if (enoughForAffixToc) { - toc.hide(); - tocAffix.show(); - } else { - tocAffix.hide(); - toc.show(); - } - $(document.body).scroll(); +function generateScrollspy () { + $(document.body).scrollspy({ + target: '' + }) + $(document.body).scrollspy('refresh') + if (enoughForAffixToc) { + toc.hide() + tocAffix.show() + } else { + tocAffix.hide() + toc.show() + } + $(document.body).scroll() } -function windowResize() { - //toc right - const paddingRight = parseFloat(markdown.css('padding-right')); - const right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight)); - toc.css('right', `${right}px`); - //affix toc left - let newbool; - const rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2; - //for ipad or wider device - if (rightMargin >= 133) { - newbool = true; - const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2; - const left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin; - tocAffix.css('left', `${left}px`); - } else { - newbool = false; - } - if (newbool != enoughForAffixToc) { - enoughForAffixToc = newbool; - generateScrollspy(); - } +function windowResize () { + // toc right + const paddingRight = parseFloat(markdown.css('padding-right')) + const right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight)) + toc.css('right', `${right}px`) + // affix toc left + let newbool + const rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2 + // for ipad or wider device + if (rightMargin >= 133) { + newbool = true + const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2 + const left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin + tocAffix.css('left', `${left}px`) + } else { + newbool = false + } + if (newbool !== enoughForAffixToc) { + enoughForAffixToc = newbool + generateScrollspy() + } } $(window).resize(() => { - windowResize(); -}); + windowResize() +}) $(document).ready(() => { - windowResize(); - generateScrollspy(); - setTimeout(scrollToHash, 0); - //tooltip - $('[data-toggle="tooltip"]').tooltip(); -}); + windowResize() + generateScrollspy() + setTimeout(scrollToHash, 0) + // tooltip + $('[data-toggle="tooltip"]').tooltip() +}) -export function scrollToTop() { - $('body, html').stop(true, true).animate({ - scrollTop: 0 - }, 100, "linear"); +export function scrollToTop () { + $('body, html').stop(true, true).animate({ + scrollTop: 0 + }, 100, 'linear') } -export function scrollToBottom() { - $('body, html').stop(true, true).animate({ - scrollTop: $(document.body)[0].scrollHeight - }, 100, "linear"); +export function scrollToBottom () { + $('body, html').stop(true, true).animate({ + scrollTop: $(document.body)[0].scrollHeight + }, 100, 'linear') } -window.scrollToTop = scrollToTop; -window.scrollToBottom = scrollToBottom; +window.scrollToTop = scrollToTop +window.scrollToBottom = scrollToBottom diff --git a/public/js/render.js b/public/js/render.js index 5d6d0aa2..61663a4b 100644 --- a/public/js/render.js +++ b/public/js/render.js @@ -1,62 +1,64 @@ +/* eslint-env browser, jquery */ +/* global filterXSS */ // allow some attributes -var whiteListAttr = ['id', 'class', 'style']; -window.whiteListAttr = whiteListAttr; +var whiteListAttr = ['id', 'class', 'style'] +window.whiteListAttr = whiteListAttr // allow link starts with '.', '/' and custom protocol with '://' -var linkRegex = /^([\w|-]+:\/\/)|^([\.|\/])+/; +var linkRegex = /^([\w|-]+:\/\/)|^([.|/])+/ // allow data uri, from https://gist.github.com/bgrins/6194623 -var dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*)\s*$/i; +var dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*)\s*$/i // custom white list -var whiteList = filterXSS.whiteList; +var whiteList = filterXSS.whiteList // allow ol specify start number -whiteList['ol'] = ['start']; +whiteList['ol'] = ['start'] // allow li specify value number -whiteList['li'] = ['value']; +whiteList['li'] = ['value'] // allow style tag -whiteList['style'] = []; +whiteList['style'] = [] // allow kbd tag -whiteList['kbd'] = []; +whiteList['kbd'] = [] // allow ifram tag with some safe attributes -whiteList['iframe'] = ['allowfullscreen', 'name', 'referrerpolicy', 'sandbox', 'src', 'srcdoc', 'width', 'height']; +whiteList['iframe'] = ['allowfullscreen', 'name', 'referrerpolicy', 'sandbox', 'src', 'srcdoc', 'width', 'height'] // allow summary tag -whiteList['summary'] = []; +whiteList['summary'] = [] var filterXSSOptions = { - allowCommentTag: true, - whiteList: whiteList, - escapeHtml: function (html) { + allowCommentTag: true, + whiteList: whiteList, + escapeHtml: function (html) { // allow html comment in multiple lines - return html.replace(/<(.*?)>/g, '<$1>'); - }, - onIgnoreTag: function (tag, html, options) { + return html.replace(/<(.*?)>/g, '<$1>') + }, + onIgnoreTag: function (tag, html, options) { // allow comment tag - if (tag == "!--") { + if (tag === '!--') { // do not filter its attributes - return html; - } - }, - onTagAttr: function (tag, name, value, isWhiteAttr) { + return html + } + }, + onTagAttr: function (tag, name, value, isWhiteAttr) { // allow href and src that match linkRegex - if (isWhiteAttr && (name === 'href' || name === 'src') && linkRegex.test(value)) { - return name + '="' + filterXSS.escapeAttrValue(value) + '"'; - } + if (isWhiteAttr && (name === 'href' || name === 'src') && linkRegex.test(value)) { + return name + '="' + filterXSS.escapeAttrValue(value) + '"' + } // allow data uri in img src - if (isWhiteAttr && (tag == "img" && name === 'src') && dataUriRegex.test(value)) { - return name + '="' + filterXSS.escapeAttrValue(value) + '"'; - } - }, - onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) { + if (isWhiteAttr && (tag === 'img' && name === 'src') && dataUriRegex.test(value)) { + return name + '="' + filterXSS.escapeAttrValue(value) + '"' + } + }, + onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) { // allow attr start with 'data-' or in the whiteListAttr - if (name.substr(0, 5) === 'data-' || whiteListAttr.indexOf(name) !== -1) { + if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) { // escape its value using built-in escapeAttrValue function - return name + '="' + filterXSS.escapeAttrValue(value) + '"'; - } + return name + '="' + filterXSS.escapeAttrValue(value) + '"' } -}; + } +} -function preventXSS(html) { - return filterXSS(html, filterXSSOptions); +function preventXSS (html) { + return filterXSS(html, filterXSSOptions) } -window.preventXSS = preventXSS; +window.preventXSS = preventXSS module.exports = { preventXSS: preventXSS diff --git a/public/js/reveal-markdown.js b/public/js/reveal-markdown.js index 3c3e1f5b..eca148d8 100755 --- a/public/js/reveal-markdown.js +++ b/public/js/reveal-markdown.js @@ -1,396 +1,355 @@ +/* eslint-env browser, jquery */ + +import { preventXSS } from './render' +import { md } from './extra' + /** * The reveal.js markdown plugin. Handles parsing of * markdown inside of presentations as well as loading * of external markdown documents. */ -(function( root, factory ) { - if( typeof exports === 'object' ) { - module.exports = factory(); - } - else { - // Browser globals (root is window) - root.RevealMarkdown = factory(); - root.RevealMarkdown.initialize(); - } -}( this, function() { - - var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$', - DEFAULT_NOTES_SEPARATOR = 'note:', - DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$', - DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$'; - - var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__'; - - - /** - * Retrieves the markdown contents of a slide section - * element. Normalizes leading tabs/whitespace. - */ - function getMarkdownFromSlide( section ) { - - var template = section.querySelector( 'script' ); - - // strip leading whitespace so it isn't evaluated as code - var text = ( template || section ).textContent; - - // restore script end tags - text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' ); - - var leadingWs = text.match( /^\n?(\s*)/ )[1].length, - leadingTabs = text.match( /^\n?(\t*)/ )[1].length; - - if( leadingTabs > 0 ) { - text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' ); - } - else if( leadingWs > 1 ) { - text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' ); - } - - return text; - - } - - /** - * Given a markdown slide section element, this will - * return all arguments that aren't related to markdown - * parsing. Used to forward any other user-defined arguments - * to the output markdown slide. - */ - function getForwardedAttributes( section ) { - - var attributes = section.attributes; - var result = []; - - for( var i = 0, len = attributes.length; i < len; i++ ) { - var name = attributes[i].name, - value = attributes[i].value; - - // disregard attributes that are used for markdown loading/parsing - if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue; - - if( value ) { - result.push( name + '="' + value + '"' ); - } - else { - result.push( name ); - } - } - - return result.join( ' ' ); - - } - - /** - * Inspects the given options and fills out default - * values for what's not defined. - */ - function getSlidifyOptions( options ) { - - options = options || {}; - options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR; - options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR; - options.attributes = options.attributes || ''; - - return options; - - } - - /** - * Helper function for constructing a markdown slide. - */ - function createMarkdownSlide( content, options ) { - - options = getSlidifyOptions( options ); - - var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) ); - - if( notesMatch.length === 2 ) { - content = notesMatch[0] + '<aside class="notes" data-markdown>' + notesMatch[1].trim() + '</aside>'; - } - - // prevent script end tags in the content from interfering - // with parsing - content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER ); - - return '<script type="text/template">' + content + '</script>'; - - } - - /** - * Parses a data string into multiple slides based - * on the passed in separator arguments. - */ - function slidify( markdown, options ) { - - options = getSlidifyOptions( options ); - - var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ), - horizontalSeparatorRegex = new RegExp( options.separator ); - - var matches, - lastIndex = 0, - isHorizontal, - wasHorizontal = true, - content, - sectionStack = []; - - // iterate until all blocks between separators are stacked up - while( matches = separatorRegex.exec( markdown ) ) { - notes = null; - - // determine direction (horizontal by default) - isHorizontal = horizontalSeparatorRegex.test( matches[0] ); - - if( !isHorizontal && wasHorizontal ) { - // create vertical stack - sectionStack.push( [] ); - } - - // pluck slide content from markdown input - content = markdown.substring( lastIndex, matches.index ); - - if( isHorizontal && wasHorizontal ) { - // add to horizontal stack - sectionStack.push( content ); - } - else { - // add to vertical stack - sectionStack[sectionStack.length-1].push( content ); - } - - lastIndex = separatorRegex.lastIndex; - wasHorizontal = isHorizontal; - } - - // add the remaining slide - ( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) ); - - var markdownSections = ''; - - // flatten the hierarchical stack, and insert <section data-markdown> tags - for( var i = 0, len = sectionStack.length; i < len; i++ ) { - // vertical - if( sectionStack[i] instanceof Array ) { - markdownSections += '<section '+ options.attributes +'>'; - - sectionStack[i].forEach( function( child ) { - markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>'; - } ); - - markdownSections += '</section>'; - } - else { - markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>'; - } - } - - return markdownSections; - - } - - /** - * Parses any current data-markdown slides, splits - * multi-slide markdown into separate sections and - * handles loading of external markdown. - */ - function processSlides() { - - var sections = document.querySelectorAll( '[data-markdown]'), - section; - - for( var i = 0, len = sections.length; i < len; i++ ) { - - section = sections[i]; - - if( section.getAttribute( 'data-markdown' ).length ) { - - var xhr = new XMLHttpRequest(), - url = section.getAttribute( 'data-markdown' ); - - datacharset = section.getAttribute( 'data-charset' ); - - // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes - if( datacharset != null && datacharset != '' ) { - xhr.overrideMimeType( 'text/html; charset=' + datacharset ); - } - - xhr.onreadystatechange = function() { - if( xhr.readyState === 4 ) { - // file protocol yields status code 0 (useful for local debug, mobile applications etc.) - if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) { - - section.outerHTML = slidify( xhr.responseText, { - separator: section.getAttribute( 'data-separator' ), - verticalSeparator: section.getAttribute( 'data-separator-vertical' ), - notesSeparator: section.getAttribute( 'data-separator-notes' ), - attributes: getForwardedAttributes( section ) - }); - - } - else { - - section.outerHTML = '<section data-state="alert">' + - 'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' + - 'Check your browser\'s JavaScript console for more details.' + - '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' + - '</section>'; - - } - } - }; - - xhr.open( 'GET', url, false ); - - try { - xhr.send(); - } - catch ( e ) { - alert( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e ); - } - - } - else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) { - - section.outerHTML = slidify( getMarkdownFromSlide( section ), { - separator: section.getAttribute( 'data-separator' ), - verticalSeparator: section.getAttribute( 'data-separator-vertical' ), - notesSeparator: section.getAttribute( 'data-separator-notes' ), - attributes: getForwardedAttributes( section ) - }); - - } - else { - section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) ); - } - } - - } - - /** - * Check if a node value has the attributes pattern. - * If yes, extract it and add that value as one or several attributes - * the the terget element. - * - * You need Cache Killer on Chrome to see the effect on any FOM transformation - * directly on refresh (F5) - * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277 - */ - function addAttributeInElement( node, elementTarget, separator ) { - - var mardownClassesInElementsRegex = new RegExp( separator, 'mg' ); - var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"=]+?)\"", 'mg' ); - var nodeValue = node.nodeValue; - if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) { - - var classes = matches[1]; - nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex ); - node.nodeValue = nodeValue; - while( matchesClass = mardownClassRegex.exec( classes ) ) { - var name = matchesClass[1]; - var value = matchesClass[2]; - if (name.substr(0, 5) === 'data-' || whiteListAttr.indexOf(name) !== -1) - elementTarget.setAttribute( name, filterXSS.escapeAttrValue(value) ); - } - return true; - } - return false; - } - - /** - * Add attributes to the parent element of a text node, - * or the element of an attribute node. - */ - function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) { - - if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) { - previousParentElement = element; - for( var i = 0; i < element.childNodes.length; i++ ) { - childElement = element.childNodes[i]; - if ( i > 0 ) { - j = i - 1; - while ( j >= 0 ) { - aPreviousChildElement = element.childNodes[j]; - if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) { - previousParentElement = aPreviousChildElement; - break; - } - j = j - 1; - } - } - parentSection = section; - if( childElement.nodeName == "section" ) { - parentSection = childElement ; - previousParentElement = childElement ; - } - if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) { - addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes ); - } - } - } - - if ( element.nodeType == Node.COMMENT_NODE ) { - if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) { - addAttributeInElement( element, section, separatorSectionAttributes ); - } - } - } - - /** - * Converts any current data-markdown slides in the - * DOM to HTML. - */ - function convertSlides() { - - var sections = document.querySelectorAll( '[data-markdown]'); - - for( var i = 0, len = sections.length; i < len; i++ ) { - - var section = sections[i]; - - // Only parse the same slide once - if( !section.getAttribute( 'data-markdown-parsed' ) ) { - - section.setAttribute( 'data-markdown-parsed', true ) - - var notes = section.querySelector( 'aside.notes' ); - var markdown = getMarkdownFromSlide( section ); - - var rendered = md.render(markdown); - rendered = preventXSS(rendered); - var result = postProcess(rendered); - section.innerHTML = result[0].outerHTML; - addAttributes( section, section, null, section.getAttribute( 'data-element-attributes' ) || - section.parentNode.getAttribute( 'data-element-attributes' ) || - DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR, - section.getAttribute( 'data-attributes' ) || - section.parentNode.getAttribute( 'data-attributes' ) || - DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR); - - // If there were notes, we need to re-add them after - // having overwritten the section's HTML - if( notes ) { - section.appendChild( notes ); - } - - } - - } - - } - - // API - return { - - initialize: function() { - processSlides(); - convertSlides(); - }, - - // TODO: Do these belong in the API? - processSlides: processSlides, - convertSlides: convertSlides, - slidify: slidify - - }; - -})); +(function (root, factory) { + if (typeof exports === 'object') { + module.exports = factory() + } else { + // Browser globals (root is window) + root.RevealMarkdown = factory() + root.RevealMarkdown.initialize() + } +}(this, function () { + var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$' + var DEFAULT_NOTES_SEPARATOR = 'note:' + var DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\.element\\s*?(.+?)$' + var DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\.slide:\\s*?(\\S.+?)$' + + var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__' + + /** + * Retrieves the markdown contents of a slide section + * element. Normalizes leading tabs/whitespace. + */ + function getMarkdownFromSlide (section) { + var template = section.querySelector('script') + + // strip leading whitespace so it isn't evaluated as code + var text = (template || section).textContent + + // restore script end tags + text = text.replace(new RegExp(SCRIPT_END_PLACEHOLDER, 'g'), '</script>') + + var leadingWs = text.match(/^\n?(\s*)/)[1].length + var leadingTabs = text.match(/^\n?(\t*)/)[1].length + + if (leadingTabs > 0) { + text = text.replace(new RegExp('\\n?\\t{' + leadingTabs + '}', 'g'), '\n') + } else if (leadingWs > 1) { + text = text.replace(new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n') + } + + return text + } + + /** + * Given a markdown slide section element, this will + * return all arguments that aren't related to markdown + * parsing. Used to forward any other user-defined arguments + * to the output markdown slide. + */ + function getForwardedAttributes (section) { + var attributes = section.attributes + var result = [] + + for (var i = 0, len = attributes.length; i < len; i++) { + var name = attributes[i].name + var value = attributes[i].value + + // disregard attributes that are used for markdown loading/parsing + if (/data-(markdown|separator|vertical|notes)/gi.test(name)) continue + + if (value) { + result.push(name + '="' + value + '"') + } else { + result.push(name) + } + } + + return result.join(' ') + } + + /** + * Inspects the given options and fills out default + * values for what's not defined. + */ + function getSlidifyOptions (options) { + options = options || {} + options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR + options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR + options.attributes = options.attributes || '' + + return options + } + + /** + * Helper function for constructing a markdown slide. + */ + function createMarkdownSlide (content, options) { + options = getSlidifyOptions(options) + + var notesMatch = content.split(new RegExp(options.notesSeparator, 'mgi')) + + if (notesMatch.length === 2) { + content = notesMatch[0] + '<aside class="notes" data-markdown>' + notesMatch[1].trim() + '</aside>' + } + + // prevent script end tags in the content from interfering + // with parsing + content = content.replace(/<\/script>/g, SCRIPT_END_PLACEHOLDER) + + return '<script type="text/template">' + content + '</script>' + } + + /** + * Parses a data string into multiple slides based + * on the passed in separator arguments. + */ + function slidify (markdown, options) { + options = getSlidifyOptions(options) + + var separatorRegex = new RegExp(options.separator + (options.verticalSeparator ? '|' + options.verticalSeparator : ''), 'mg') + var horizontalSeparatorRegex = new RegExp(options.separator) + + var matches + var lastIndex = 0 + var isHorizontal + var wasHorizontal = true + var content + var sectionStack = [] + + // iterate until all blocks between separators are stacked up + while ((matches = separatorRegex.exec(markdown)) !== null) { + // determine direction (horizontal by default) + isHorizontal = horizontalSeparatorRegex.test(matches[0]) + + if (!isHorizontal && wasHorizontal) { + // create vertical stack + sectionStack.push([]) + } + + // pluck slide content from markdown input + content = markdown.substring(lastIndex, matches.index) + + if (isHorizontal && wasHorizontal) { + // add to horizontal stack + sectionStack.push(content) + } else { + // add to vertical stack + sectionStack[sectionStack.length - 1].push(content) + } + + lastIndex = separatorRegex.lastIndex + wasHorizontal = isHorizontal + } + + // add the remaining slide + (wasHorizontal ? sectionStack : sectionStack[sectionStack.length - 1]).push(markdown.substring(lastIndex)) + + var markdownSections = '' + + // flatten the hierarchical stack, and insert <section data-markdown> tags + for (var i = 0, len = sectionStack.length; i < len; i++) { + // vertical + if (sectionStack[i] instanceof Array) { + markdownSections += '<section ' + options.attributes + '>' + + sectionStack[i].forEach(function (child) { + markdownSections += '<section data-markdown>' + createMarkdownSlide(child, options) + '</section>' + }) + + markdownSections += '</section>' + } else { + markdownSections += '<section ' + options.attributes + ' data-markdown>' + createMarkdownSlide(sectionStack[i], options) + '</section>' + } + } + + return markdownSections + } + + /** + * Parses any current data-markdown slides, splits + * multi-slide markdown into separate sections and + * handles loading of external markdown. + */ + function processSlides () { + var sections = document.querySelectorAll('[data-markdown]') + var section + + for (var i = 0, len = sections.length; i < len; i++) { + section = sections[i] + + if (section.getAttribute('data-markdown').length) { + var xhr = new XMLHttpRequest() + var url = section.getAttribute('data-markdown') + + var datacharset = section.getAttribute('data-charset') + + // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes + if (datacharset !== null && datacharset !== '') { + xhr.overrideMimeType('text/html; charset=' + datacharset) + } + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + // file protocol yields status code 0 (useful for local debug, mobile applications etc.) + if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) { + section.outerHTML = slidify(xhr.responseText, { + separator: section.getAttribute('data-separator'), + verticalSeparator: section.getAttribute('data-separator-vertical'), + notesSeparator: section.getAttribute('data-separator-notes'), + attributes: getForwardedAttributes(section) + }) + } else { + section.outerHTML = '<section data-state="alert">' + + 'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' + + 'Check your browser\'s JavaScript console for more details.' + + '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' + + '</section>' + } + } + } + + xhr.open('GET', url, false) + + try { + xhr.send() + } catch (e) { + alert('Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e) + } + } else if (section.getAttribute('data-separator') || section.getAttribute('data-separator-vertical') || section.getAttribute('data-separator-notes')) { + section.outerHTML = slidify(getMarkdownFromSlide(section), { + separator: section.getAttribute('data-separator'), + verticalSeparator: section.getAttribute('data-separator-vertical'), + notesSeparator: section.getAttribute('data-separator-notes'), + attributes: getForwardedAttributes(section) + }) + } else { + section.innerHTML = createMarkdownSlide(getMarkdownFromSlide(section)) + } + } + } + + /** + * Check if a node value has the attributes pattern. + * If yes, extract it and add that value as one or several attributes + * the the terget element. + * + * You need Cache Killer on Chrome to see the effect on any FOM transformation + * directly on refresh (F5) + * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277 + */ + function addAttributeInElement (node, elementTarget, separator) { + var mardownClassesInElementsRegex = new RegExp(separator, 'mg') + var mardownClassRegex = new RegExp('([^"= ]+?)="([^"=]+?)"', 'mg') + var nodeValue = node.nodeValue + var matches + var matchesClass + if ((matches = mardownClassesInElementsRegex.exec(nodeValue))) { + var classes = matches[1] + nodeValue = nodeValue.substring(0, matches.index) + nodeValue.substring(mardownClassesInElementsRegex.lastIndex) + node.nodeValue = nodeValue + while ((matchesClass = mardownClassRegex.exec(classes))) { + var name = matchesClass[1] + var value = matchesClass[2] + if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) { elementTarget.setAttribute(name, window.filterXSS.escapeAttrValue(value)) } + } + return true + } + return false + } + + /** + * Add attributes to the parent element of a text node, + * or the element of an attribute node. + */ + function addAttributes (section, element, previousElement, separatorElementAttributes, separatorSectionAttributes) { + if (element != null && element.childNodes !== undefined && element.childNodes.length > 0) { + var previousParentElement = element + for (var i = 0; i < element.childNodes.length; i++) { + var childElement = element.childNodes[i] + if (i > 0) { + let j = i - 1 + while (j >= 0) { + var aPreviousChildElement = element.childNodes[j] + if (typeof aPreviousChildElement.setAttribute === 'function' && aPreviousChildElement.tagName !== 'BR') { + previousParentElement = aPreviousChildElement + break + } + j = j - 1 + } + } + var parentSection = section + if (childElement.nodeName === 'section') { + parentSection = childElement + previousParentElement = childElement + } + if (typeof childElement.setAttribute === 'function' || childElement.nodeType === Node.COMMENT_NODE) { + addAttributes(parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes) + } + } + } + + if (element.nodeType === Node.COMMENT_NODE) { + if (addAttributeInElement(element, previousElement, separatorElementAttributes) === false) { + addAttributeInElement(element, section, separatorSectionAttributes) + } + } + } + + /** + * Converts any current data-markdown slides in the + * DOM to HTML. + */ + function convertSlides () { + var sections = document.querySelectorAll('[data-markdown]') + + for (var i = 0, len = sections.length; i < len; i++) { + var section = sections[i] + + // Only parse the same slide once + if (!section.getAttribute('data-markdown-parsed')) { + section.setAttribute('data-markdown-parsed', true) + + var notes = section.querySelector('aside.notes') + var markdown = getMarkdownFromSlide(section) + + var rendered = md.render(markdown) + rendered = preventXSS(rendered) + var result = window.postProcess(rendered) + section.innerHTML = result[0].outerHTML + addAttributes(section, section, null, section.getAttribute('data-element-attributes') || + section.parentNode.getAttribute('data-element-attributes') || + DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR, + section.getAttribute('data-attributes') || + section.parentNode.getAttribute('data-attributes') || + DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR) + + // If there were notes, we need to re-add them after + // having overwritten the section's HTML + if (notes) { + section.appendChild(notes) + } + } + } + } + + // API + return { + initialize: function () { + processSlides() + convertSlides() + }, + // TODO: Do these belong in the API? + processSlides: processSlides, + convertSlides: convertSlides, + slidify: slidify + } +})) diff --git a/public/js/slide.js b/public/js/slide.js index 63cf64c6..e743bb55 100644 --- a/public/js/slide.js +++ b/public/js/slide.js @@ -1,138 +1,139 @@ -require('../css/extra.css'); -require('../css/site.css'); +/* eslint-env browser, jquery */ +/* global serverurl, Reveal */ -import { md, updateLastChange, finishView } from './extra'; +require('../css/extra.css') +require('../css/site.css') -import { preventXSS } from './render'; +import { md, updateLastChange, finishView } from './extra' -const body = $(".slides").text(); +const body = $('.slides').text() -createtime = lastchangeui.time.attr('data-createtime'); -lastchangetime = lastchangeui.time.attr('data-updatetime'); -updateLastChange(); -const url = window.location.pathname; -$('.ui-edit').attr('href', `${url}/edit`); +window.createtime = window.lastchangeui.time.attr('data-createtime') +window.lastchangetime = window.lastchangeui.time.attr('data-updatetime') +updateLastChange() +const url = window.location.pathname +$('.ui-edit').attr('href', `${url}/edit`) $(document).ready(() => { - //tooltip - $('[data-toggle="tooltip"]').tooltip(); -}); - -function extend() { - const target = {}; - - for (const source of arguments) { - for (const key in source) { - if (source.hasOwnProperty(key)) { - target[key] = source[key]; - } - } + // tooltip + $('[data-toggle="tooltip"]').tooltip() +}) + +function extend () { + const target = {} + + for (const source of arguments) { + for (const key in source) { + if (source.hasOwnProperty(key)) { + target[key] = source[key] + } } + } - return target; + return target } // Optional libraries used to extend on reveal.js const deps = [{ - src: `${serverurl}/build/reveal.js/lib/js/classList.js`, - condition() { - return !document.body.classList; - } + src: `${serverurl}/build/reveal.js/lib/js/classList.js`, + condition () { + return !document.body.classList + } }, { - src: `${serverurl}/js/reveal-markdown.js`, - callback() { - const slideOptions = { - separator: '^(\r\n?|\n)---(\r\n?|\n)$', - verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' - }; - const slides = RevealMarkdown.slidify(body, slideOptions); - $(".slides").html(slides); - RevealMarkdown.initialize(); - $(".slides").show(); + src: `${serverurl}/js/reveal-markdown.js`, + callback () { + const slideOptions = { + separator: '^(\r\n?|\n)---(\r\n?|\n)$', + verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' } + const slides = window.RevealMarkdown.slidify(body, slideOptions) + $('.slides').html(slides) + window.RevealMarkdown.initialize() + $('.slides').show() + } }, { - src: `${serverurl}/build/reveal.js/plugin/notes/notes.js`, - async: true, - condition() { - return !!document.body.classList; - } -}]; + src: `${serverurl}/build/reveal.js/plugin/notes/notes.js`, + async: true, + condition () { + return !!document.body.classList + } +}] // default options to init reveal.js const defaultOptions = { - controls: true, - progress: true, - slideNumber: true, - history: true, - center: true, - transition: 'none', - dependencies: deps -}; + controls: true, + progress: true, + slideNumber: true, + history: true, + center: true, + transition: 'none', + dependencies: deps +} // options from yaml meta -const meta = JSON.parse($("#meta").text()); -var options = meta.slideOptions || {}; +const meta = JSON.parse($('#meta').text()) +var options = meta.slideOptions || {} -const view = $('.reveal'); +const view = $('.reveal') -//text language -if (meta.lang && typeof meta.lang == "string") { - view.attr('lang', meta.lang); +// text language +if (meta.lang && typeof meta.lang === 'string') { + view.attr('lang', meta.lang) } else { - view.removeAttr('lang'); + view.removeAttr('lang') } -//text direction -if (meta.dir && typeof meta.dir == "string" && meta.dir == "rtl") { - options.rtl = true; +// text direction +if (meta.dir && typeof meta.dir === 'string' && meta.dir === 'rtl') { + options.rtl = true } else { - options.rtl = false; + options.rtl = false } -//breaks +// breaks if (typeof meta.breaks === 'boolean' && !meta.breaks) { - md.options.breaks = false; + md.options.breaks = false } else { - md.options.breaks = true; + md.options.breaks = true } // options from URL query string -const queryOptions = Reveal.getQueryHash() || {}; +const queryOptions = Reveal.getQueryHash() || {} -var options = extend(defaultOptions, options, queryOptions); -Reveal.initialize(options); +options = extend(defaultOptions, options, queryOptions) +Reveal.initialize(options) window.viewAjaxCallback = () => { - Reveal.layout(); -}; - -function renderSlide(event) { - if (window.location.search.match( /print-pdf/gi )) { - const slides = $('.slides'); - var title = document.title; - finishView(slides); - document.title = title; - Reveal.layout(); - } else { - const markdown = $(event.currentSlide); - if (!markdown.attr('data-rendered')) { - var title = document.title; - finishView(markdown); - markdown.attr('data-rendered', 'true'); - document.title = title; - Reveal.layout(); - } + Reveal.layout() +} + +function renderSlide (event) { + if (window.location.search.match(/print-pdf/gi)) { + const slides = $('.slides') + let title = document.title + finishView(slides) + document.title = title + Reveal.layout() + } else { + const markdown = $(event.currentSlide) + if (!markdown.attr('data-rendered')) { + let title = document.title + finishView(markdown) + markdown.attr('data-rendered', 'true') + document.title = title + Reveal.layout() } + } } Reveal.addEventListener('ready', event => { - renderSlide(event); - const markdown = $(event.currentSlide); + renderSlide(event) + const markdown = $(event.currentSlide) // force browser redraw - setTimeout(() => { - markdown.hide().show(0); - }, 0); -}); -Reveal.addEventListener('slidechanged', renderSlide); + setTimeout(() => { + markdown.hide().show(0) + }, 0) +}) +Reveal.addEventListener('slidechanged', renderSlide) -const isMacLike = navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) ? true : false; +const isMacLike = !!navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) -if (!isMacLike) $('.container').addClass('hidescrollbar'); +if (!isMacLike) $('.container').addClass('hidescrollbar') diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js index c9693176..c227f83f 100644 --- a/public/js/syncscroll.js +++ b/public/js/syncscroll.js @@ -1,365 +1,367 @@ +/* eslint-env browser, jquery */ +/* global _ */ // Inject line numbers for sync scroll. -import markdownitContainer from 'markdown-it-container'; +import markdownitContainer from 'markdown-it-container' -import { md } from './extra'; +import { md } from './extra' -function addPart(tokens, idx) { - if (tokens[idx].map && tokens[idx].level === 0) { - const startline = tokens[idx].map[0] + 1; - const endline = tokens[idx].map[1]; - tokens[idx].attrJoin('class', 'part'); - tokens[idx].attrJoin('data-startline', startline); - tokens[idx].attrJoin('data-endline', endline); - } +function addPart (tokens, idx) { + if (tokens[idx].map && tokens[idx].level === 0) { + const startline = tokens[idx].map[0] + 1 + const endline = tokens[idx].map[1] + tokens[idx].attrJoin('class', 'part') + tokens[idx].attrJoin('data-startline', startline) + tokens[idx].attrJoin('data-endline', endline) + } } md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw'); - addPart(tokens, idx); - return self.renderToken(...arguments); -}; + tokens[idx].attrJoin('class', 'raw') + addPart(tokens, idx) + return self.renderToken(...arguments) +} md.renderer.rules.table_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx); - return self.renderToken(...arguments); -}; + addPart(tokens, idx) + return self.renderToken(...arguments) +} md.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx); - return self.renderToken(...arguments); -}; + addPart(tokens, idx) + return self.renderToken(...arguments) +} md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw'); - if (tokens[idx].map) { - const startline = tokens[idx].map[0] + 1; - const endline = tokens[idx].map[1]; - tokens[idx].attrJoin('data-startline', startline); - tokens[idx].attrJoin('data-endline', endline); - } - return self.renderToken(...arguments); -}; + tokens[idx].attrJoin('class', 'raw') + if (tokens[idx].map) { + const startline = tokens[idx].map[0] + 1 + const endline = tokens[idx].map[1] + tokens[idx].attrJoin('data-startline', startline) + tokens[idx].attrJoin('data-endline', endline) + } + return self.renderToken(...arguments) +} md.renderer.rules.ordered_list_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx); - return self.renderToken(...arguments); -}; + addPart(tokens, idx) + return self.renderToken(...arguments) +} md.renderer.rules.link_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx); - return self.renderToken(...arguments); -}; + addPart(tokens, idx) + return self.renderToken(...arguments) +} md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx); - return self.renderToken(...arguments); -}; + addPart(tokens, idx) + return self.renderToken(...arguments) +} md.renderer.rules.heading_open = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw'); - addPart(tokens, idx); - return self.renderToken(...arguments); -}; + tokens[idx].attrJoin('class', 'raw') + addPart(tokens, idx) + return self.renderToken(...arguments) +} md.renderer.rules.fence = (tokens, idx, options, env, self) => { - const token = tokens[idx]; - const info = token.info ? md.utils.unescapeAll(token.info).trim() : ''; - let langName = ''; - let highlighted; - - if (info) { - langName = info.split(/\s+/g)[0]; - if (/\!$/.test(info)) token.attrJoin('class', 'wrap'); - token.attrJoin('class', options.langPrefix + langName.replace(/\=$|\=\d+$|\=\+$|\!$|\=\!/, '')); - token.attrJoin('class', 'hljs'); - token.attrJoin('class', 'raw'); - } - - if (options.highlight) { - highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content); - } else { - highlighted = md.utils.escapeHtml(token.content); - } - - if (highlighted.indexOf('<pre') === 0) { - return `${highlighted}\n`; - } - - if (tokens[idx].map && tokens[idx].level === 0) { - const startline = tokens[idx].map[0] + 1; - const endline = tokens[idx].map[1]; - return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`; - } - - return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`; -}; + const token = tokens[idx] + const info = token.info ? md.utils.unescapeAll(token.info).trim() : '' + let langName = '' + let highlighted + + if (info) { + langName = info.split(/\s+/g)[0] + if (/!$/.test(info)) token.attrJoin('class', 'wrap') + token.attrJoin('class', options.langPrefix + langName.replace(/=$|=\d+$|=\+$|!$|=!/, '')) + token.attrJoin('class', 'hljs') + token.attrJoin('class', 'raw') + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content) + } else { + highlighted = md.utils.escapeHtml(token.content) + } + + if (highlighted.indexOf('<pre') === 0) { + return `${highlighted}\n` + } + + if (tokens[idx].map && tokens[idx].level === 0) { + const startline = tokens[idx].map[0] + 1 + const endline = tokens[idx].map[1] + return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n` + } + + return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n` +} md.renderer.rules.code_block = (tokens, idx, options, env, self) => { - if (tokens[idx].map && tokens[idx].level === 0) { - const startline = tokens[idx].map[0] + 1; - const endline = tokens[idx].map[1]; - return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n`; - } - return `<pre><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n`; -}; -function renderContainer(tokens, idx, options, env, self) { - tokens[idx].attrJoin('role', 'alert'); - tokens[idx].attrJoin('class', 'alert'); - tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`); - addPart(tokens, idx); - return self.renderToken(...arguments); + if (tokens[idx].map && tokens[idx].level === 0) { + const startline = tokens[idx].map[0] + 1 + const endline = tokens[idx].map[1] + return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n` + } + return `<pre><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n` +} +function renderContainer (tokens, idx, options, env, self) { + tokens[idx].attrJoin('role', 'alert') + tokens[idx].attrJoin('class', 'alert') + tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`) + addPart(tokens, idx) + return self.renderToken(...arguments) } -md.use(markdownitContainer, 'success', { render: renderContainer }); -md.use(markdownitContainer, 'info', { render: renderContainer }); -md.use(markdownitContainer, 'warning', { render: renderContainer }); -md.use(markdownitContainer, 'danger', { render: renderContainer }); +md.use(markdownitContainer, 'success', { render: renderContainer }) +md.use(markdownitContainer, 'info', { render: renderContainer }) +md.use(markdownitContainer, 'warning', { render: renderContainer }) +md.use(markdownitContainer, 'danger', { render: renderContainer }) // FIXME: expose syncscroll to window -window.syncscroll = true; +window.syncscroll = true -window.preventSyncScrollToEdit = false; -window.preventSyncScrollToView = false; +window.preventSyncScrollToEdit = false +window.preventSyncScrollToView = false -const editScrollThrottle = 5; -const viewScrollThrottle = 5; -const buildMapThrottle = 100; +const editScrollThrottle = 5 +const viewScrollThrottle = 5 +const buildMapThrottle = 100 -let viewScrolling = false; -let editScrolling = false; +let viewScrolling = false +let editScrolling = false -let editArea = null; -let viewArea = null; -let markdownArea = null; +let editArea = null +let viewArea = null +let markdownArea = null -export function setupSyncAreas(edit, view, markdown) { - editArea = edit; - viewArea = view; - markdownArea = markdown; - editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle)); - viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle)); +export function setupSyncAreas (edit, view, markdown) { + editArea = edit + viewArea = view + markdownArea = markdown + editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle)) + viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle)) } -let scrollMap, lineHeightMap, viewTop, viewBottom; +let scrollMap, lineHeightMap, viewTop, viewBottom -export function clearMap() { - scrollMap = null; - lineHeightMap = null; - viewTop = null; - viewBottom = null; +export function clearMap () { + scrollMap = null + lineHeightMap = null + viewTop = null + viewBottom = null } -window.viewAjaxCallback = clearMap; +window.viewAjaxCallback = clearMap -const buildMap = _.throttle(buildMapInner, buildMapThrottle); +const buildMap = _.throttle(buildMapInner, buildMapThrottle) // 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 buildMapInner(callback) { - if (!viewArea || !markdownArea) return; - let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap; - - offset = viewArea.scrollTop() - viewArea.offset().top; - _scrollMap = []; - nonEmptyList = []; - _lineHeightMap = []; - viewTop = 0; - viewBottom = viewArea[0].scrollHeight - viewArea.height(); - - acc = 0; - const lines = editor.getValue().split('\n'); - const lineHeight = editor.defaultTextHeight(); - for (i = 0; i < lines.length; i++) { - const str = lines[i]; - - _lineHeightMap.push(acc); - - if (str.length === 0) { - acc++; - continue; - } - - const h = editor.heightAtLine(i + 1) - editor.heightAtLine(i); - acc += Math.round(h / lineHeight); +function buildMapInner (callback) { + if (!viewArea || !markdownArea) return + let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap + + offset = viewArea.scrollTop() - viewArea.offset().top + _scrollMap = [] + nonEmptyList = [] + _lineHeightMap = [] + viewTop = 0 + viewBottom = viewArea[0].scrollHeight - viewArea.height() + + acc = 0 + const lines = window.editor.getValue().split('\n') + const lineHeight = window.editor.defaultTextHeight() + for (i = 0; i < lines.length; i++) { + const str = lines[i] + + _lineHeightMap.push(acc) + + if (str.length === 0) { + acc++ + continue } - _lineHeightMap.push(acc); - linesCount = acc; - for (i = 0; i < linesCount; i++) { - _scrollMap.push(-1); - } + const h = window.editor.heightAtLine(i + 1) - window.editor.heightAtLine(i) + acc += Math.round(h / lineHeight) + } + _lineHeightMap.push(acc) + linesCount = acc + + for (i = 0; i < linesCount; i++) { + _scrollMap.push(-1) + } - nonEmptyList.push(0); + nonEmptyList.push(0) // make the first line go top - _scrollMap[0] = viewTop; - - const parts = markdownArea.find('.part').toArray(); - for (i = 0; i < parts.length; i++) { - const $el = $(parts[i]); - let t = $el.attr('data-startline') - 1; - if (t === '') { - return; - } - t = _lineHeightMap[t]; - if (t !== 0 && t !== nonEmptyList[nonEmptyList.length - 1]) { - nonEmptyList.push(t); - } - _scrollMap[t] = Math.round($el.offset().top + offset - 10); + _scrollMap[0] = viewTop + + const parts = markdownArea.find('.part').toArray() + for (i = 0; i < parts.length; i++) { + const $el = $(parts[i]) + let t = $el.attr('data-startline') - 1 + if (t === '') { + return } + t = _lineHeightMap[t] + if (t !== 0 && t !== nonEmptyList[nonEmptyList.length - 1]) { + nonEmptyList.push(t) + } + _scrollMap[t] = Math.round($el.offset().top + offset - 10) + } - nonEmptyList.push(linesCount); - _scrollMap[linesCount] = viewArea[0].scrollHeight; - - pos = 0; - for (i = 1; i < linesCount; i++) { - if (_scrollMap[i] !== -1) { - pos++; - continue; - } + nonEmptyList.push(linesCount) + _scrollMap[linesCount] = viewArea[0].scrollHeight - a = nonEmptyList[pos]; - b = nonEmptyList[pos + 1]; - _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a)); + pos = 0 + for (i = 1; i < linesCount; i++) { + if (_scrollMap[i] !== -1) { + pos++ + continue } - _scrollMap[0] = 0; + a = nonEmptyList[pos] + b = nonEmptyList[pos + 1] + _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a)) + } + + _scrollMap[0] = 0 - scrollMap = _scrollMap; - lineHeightMap = _lineHeightMap; + scrollMap = _scrollMap + lineHeightMap = _lineHeightMap - if (loaded && callback) callback(); + if (window.loaded && callback) callback() } // sync view scroll progress to edit -let viewScrollingTimer = null; - -export function syncScrollToEdit(event, preventAnimate) { - if (currentMode != modeType.both || !syncscroll || !editArea) return; - if (preventSyncScrollToEdit) { - if (typeof preventSyncScrollToEdit === 'number') { - preventSyncScrollToEdit--; - } else { - preventSyncScrollToEdit = false; - } - return; - } - if (!scrollMap || !lineHeightMap) { - buildMap(() => { - syncScrollToEdit(event, preventAnimate); - }); - return; - } - if (editScrolling) return; - - const scrollTop = viewArea[0].scrollTop; - let lineIndex = 0; - for (var i = 0, l = scrollMap.length; i < l; i++) { - if (scrollMap[i] > scrollTop) { - break; - } else { - lineIndex = i; - } - } - let lineNo = 0; - let lineDiff = 0; - for (var i = 0, l = lineHeightMap.length; i < l; i++) { - if (lineHeightMap[i] > lineIndex) { - break; - } else { - lineNo = lineHeightMap[i]; - lineDiff = lineHeightMap[i + 1] - lineNo; - } - } +let viewScrollingTimer = null - let posTo = 0; - let topDiffPercent = 0; - let posToNextDiff = 0; - const scrollInfo = editor.getScrollInfo(); - const textHeight = editor.defaultTextHeight(); - const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight; - const preLastLineNo = Math.round(preLastLineHeight / textHeight); - const preLastLinePos = scrollMap[preLastLineNo]; - - if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) { - posTo = preLastLineHeight; - topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos); - posToNextDiff = textHeight * topDiffPercent; - posTo += Math.ceil(posToNextDiff); +export function syncScrollToEdit (event, preventAnimate) { + if (window.currentMode !== window.modeType.both || !window.syncscroll || !editArea) return + if (window.preventSyncScrollToEdit) { + if (typeof window.preventSyncScrollToEdit === 'number') { + window.preventSyncScrollToEdit-- } else { - posTo = lineNo * textHeight; - topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]); - posToNextDiff = textHeight * lineDiff * topDiffPercent; - posTo += Math.ceil(posToNextDiff); + window.preventSyncScrollToEdit = false } - - if (preventAnimate) { - editArea.scrollTop(posTo); + return + } + if (!scrollMap || !lineHeightMap) { + buildMap(() => { + syncScrollToEdit(event, preventAnimate) + }) + return + } + if (editScrolling) return + + const scrollTop = viewArea[0].scrollTop + let lineIndex = 0 + for (let i = 0, l = scrollMap.length; i < l; i++) { + if (scrollMap[i] > scrollTop) { + break } else { - const posDiff = Math.abs(scrollInfo.top - posTo); - var duration = posDiff / 50; - duration = duration >= 100 ? duration : 100; - editArea.stop(true, true).animate({ - scrollTop: posTo - }, duration, "linear"); + lineIndex = i } - - viewScrolling = true; - clearTimeout(viewScrollingTimer); - viewScrollingTimer = setTimeout(viewScrollingTimeoutInner, duration * 1.5); + } + let lineNo = 0 + let lineDiff = 0 + for (let i = 0, l = lineHeightMap.length; i < l; i++) { + if (lineHeightMap[i] > lineIndex) { + break + } else { + lineNo = lineHeightMap[i] + lineDiff = lineHeightMap[i + 1] - lineNo + } + } + + let posTo = 0 + let topDiffPercent = 0 + let posToNextDiff = 0 + const scrollInfo = window.editor.getScrollInfo() + const textHeight = window.editor.defaultTextHeight() + const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight + const preLastLineNo = Math.round(preLastLineHeight / textHeight) + const preLastLinePos = scrollMap[preLastLineNo] + + if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) { + posTo = preLastLineHeight + topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos) + posToNextDiff = textHeight * topDiffPercent + posTo += Math.ceil(posToNextDiff) + } else { + posTo = lineNo * textHeight + topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]) + posToNextDiff = textHeight * lineDiff * topDiffPercent + posTo += Math.ceil(posToNextDiff) + } + + if (preventAnimate) { + editArea.scrollTop(posTo) + } else { + const posDiff = Math.abs(scrollInfo.top - posTo) + var duration = posDiff / 50 + duration = duration >= 100 ? duration : 100 + editArea.stop(true, true).animate({ + scrollTop: posTo + }, duration, 'linear') + } + + viewScrolling = true + clearTimeout(viewScrollingTimer) + viewScrollingTimer = setTimeout(viewScrollingTimeoutInner, duration * 1.5) } -function viewScrollingTimeoutInner() { - viewScrolling = false; +function viewScrollingTimeoutInner () { + viewScrolling = false } // sync edit scroll progress to view -let editScrollingTimer = null; - -export function syncScrollToView(event, preventAnimate) { - if (currentMode != modeType.both || !syncscroll || !viewArea) return; - if (preventSyncScrollToView) { - if (typeof preventSyncScrollToView === 'number') { - preventSyncScrollToView--; - } else { - preventSyncScrollToView = false; - } - return; - } - if (!scrollMap || !lineHeightMap) { - buildMap(() => { - syncScrollToView(event, preventAnimate); - }); - return; - } - if (viewScrolling) return; - - let lineNo, posTo; - let topDiffPercent, posToNextDiff; - const scrollInfo = editor.getScrollInfo(); - const textHeight = editor.defaultTextHeight(); - lineNo = Math.floor(scrollInfo.top / textHeight); - // if reach the last line, will start lerp to the bottom - const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight); - if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) { - topDiffPercent = diffToBottom / textHeight; - posTo = scrollMap[lineNo + 1]; - posToNextDiff = (viewBottom - posTo) * topDiffPercent; - posTo += Math.floor(posToNextDiff); - } else { - topDiffPercent = (scrollInfo.top % textHeight) / textHeight; - posTo = scrollMap[lineNo]; - posToNextDiff = (scrollMap[lineNo + 1] - posTo) * topDiffPercent; - posTo += Math.floor(posToNextDiff); - } +let editScrollingTimer = null - if (preventAnimate) { - viewArea.scrollTop(posTo); +export function syncScrollToView (event, preventAnimate) { + if (window.currentMode !== window.modeType.both || !window.syncscroll || !viewArea) return + if (window.preventSyncScrollToView) { + if (typeof preventSyncScrollToView === 'number') { + window.preventSyncScrollToView-- } else { - const posDiff = Math.abs(viewArea.scrollTop() - posTo); - var duration = posDiff / 50; - duration = duration >= 100 ? duration : 100; - viewArea.stop(true, true).animate({ - scrollTop: posTo - }, duration, "linear"); + window.preventSyncScrollToView = false } - - editScrolling = true; - clearTimeout(editScrollingTimer); - editScrollingTimer = setTimeout(editScrollingTimeoutInner, duration * 1.5); + return + } + if (!scrollMap || !lineHeightMap) { + buildMap(() => { + syncScrollToView(event, preventAnimate) + }) + return + } + if (viewScrolling) return + + let lineNo, posTo + let topDiffPercent, posToNextDiff + const scrollInfo = window.editor.getScrollInfo() + const textHeight = window.editor.defaultTextHeight() + lineNo = Math.floor(scrollInfo.top / textHeight) + // if reach the last line, will start lerp to the bottom + const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight) + if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) { + topDiffPercent = diffToBottom / textHeight + posTo = scrollMap[lineNo + 1] + posToNextDiff = (viewBottom - posTo) * topDiffPercent + posTo += Math.floor(posToNextDiff) + } else { + topDiffPercent = (scrollInfo.top % textHeight) / textHeight + posTo = scrollMap[lineNo] + posToNextDiff = (scrollMap[lineNo + 1] - posTo) * topDiffPercent + posTo += Math.floor(posToNextDiff) + } + + if (preventAnimate) { + viewArea.scrollTop(posTo) + } else { + const posDiff = Math.abs(viewArea.scrollTop() - posTo) + var duration = posDiff / 50 + duration = duration >= 100 ? duration : 100 + viewArea.stop(true, true).animate({ + scrollTop: posTo + }, duration, 'linear') + } + + editScrolling = true + clearTimeout(editScrollingTimer) + editScrollingTimer = setTimeout(editScrollingTimeoutInner, duration * 1.5) } -function editScrollingTimeoutInner() { - editScrolling = false; +function editScrollingTimeoutInner () { + editScrolling = false } diff --git a/public/vendor/md-toc.js b/public/vendor/md-toc.js index 200275a5..f93f7921 100755 --- a/public/vendor/md-toc.js +++ b/public/vendor/md-toc.js @@ -1,129 +1,123 @@ +/* eslint-env browser, jquery */ /** * md-toc.js v1.0.2 * https://github.com/yijian166/md-toc.js */ (function (window) { - function Toc(id, options) { - this.el = document.getElementById(id); - if (!this.el) return; - this.options = options || {}; - this.tocLevel = parseInt(options.level) || 0; - this.tocClass = options['class'] || 'toc'; - this.ulClass = options['ulClass']; - this.tocTop = parseInt(options.top) || 0; - this.elChilds = this.el.children; - this.process = options['process']; - if (!this.elChilds.length) return; - this._init(); - } + function Toc (id, options) { + this.el = document.getElementById(id) + if (!this.el) return + this.options = options || {} + this.tocLevel = parseInt(options.level) || 0 + this.tocClass = options['class'] || 'toc' + this.ulClass = options['ulClass'] + this.tocTop = parseInt(options.top) || 0 + this.elChilds = this.el.children + this.process = options['process'] + if (!this.elChilds.length) return + this._init() + } - Toc.prototype._init = function () { - this._collectTitleElements(); - this._createTocContent(); - this._showToc(); - }; + Toc.prototype._init = function () { + this._collectTitleElements() + this._createTocContent() + this._showToc() + } - Toc.prototype._collectTitleElements = function () { - this._elTitlesNames = [], - this.elTitleElements = []; - for (var i = 1; i < 7; i++) { - if (this.el.getElementsByTagName('h' + i).length) { - this._elTitlesNames.push('h' + i); - } - } + Toc.prototype._collectTitleElements = function () { + this._elTitlesNames = [] + this.elTitleElements = [] + for (var i = 1; i < 7; i++) { + if (this.el.getElementsByTagName('h' + i).length) { + this._elTitlesNames.push('h' + i) + } + } - this._elTitlesNames.length = this._elTitlesNames.length > this.tocLevel ? this.tocLevel : this._elTitlesNames.length; + this._elTitlesNames.length = this._elTitlesNames.length > this.tocLevel ? this.tocLevel : this._elTitlesNames.length - for (var j = 0; j < this.elChilds.length; j++) { - this._elChildName = this.elChilds[j].tagName.toLowerCase(); - if (this._elTitlesNames.toString().match(this._elChildName)) { - this.elTitleElements.push(this.elChilds[j]); - } - } - }; + for (var j = 0; j < this.elChilds.length; j++) { + this._elChildName = this.elChilds[j].tagName.toLowerCase() + if (this._elTitlesNames.toString().match(this._elChildName)) { + this.elTitleElements.push(this.elChilds[j]) + } + } + } - Toc.prototype._createTocContent = function () { - this._elTitleElementsLen = this.elTitleElements.length; - if (!this._elTitleElementsLen) return; - this.tocContent = ''; - this._tempLists = []; + Toc.prototype._createTocContent = function () { + this._elTitleElementsLen = this.elTitleElements.length + if (!this._elTitleElementsLen) return + this.tocContent = '' + this._tempLists = [] - var url = location.origin + location.pathname; - for (var i = 0; i < this._elTitleElementsLen; i++) { - var j = i + 1; - this._elTitleElement = this.elTitleElements[i]; - this._elTitleElementName = this._elTitleElement.tagName; - this._elTitleElementText = (typeof this.process === 'function' ? this.process(this._elTitleElement) : this._elTitleElement.innerHTML).replace(/<(?:.|\n)*?>/gm, ''); - var id = this._elTitleElement.getAttribute('id'); - if (!id) { - this._elTitleElement.setAttribute('id', 'tip' + i); - id = '#tip' + i; - } else { - id = '#' + id; - } + for (var i = 0; i < this._elTitleElementsLen; i++) { + var j = i + 1 + this._elTitleElement = this.elTitleElements[i] + this._elTitleElementName = this._elTitleElement.tagName + this._elTitleElementText = (typeof this.process === 'function' ? this.process(this._elTitleElement) : this._elTitleElement.innerHTML).replace(/<(?:.|\n)*?>/gm, '') + var id = this._elTitleElement.getAttribute('id') + if (!id) { + this._elTitleElement.setAttribute('id', 'tip' + i) + id = '#tip' + i + } else { + id = '#' + id + } - this.tocContent += '<li><a href="' + id + '">' + this._elTitleElementText + '</a>'; + this.tocContent += '<li><a href="' + id + '">' + this._elTitleElementText + '</a>' - if (j != this._elTitleElementsLen) { - this._elNextTitleElementName = this.elTitleElements[j].tagName; - if (this._elTitleElementName != this._elNextTitleElementName) { - var checkColse = false, - y = 1; - for (var t = this._tempLists.length - 1; t >= 0; t--) { - if (this._tempLists[t].tagName == this._elNextTitleElementName) { - checkColse = true; - break; - } - y++; - } - if (checkColse) { - this.tocContent += new Array(y + 1).join('</li></ul>'); - this._tempLists.length = this._tempLists.length - y; - } else { - this._tempLists.push(this._elTitleElement); - if (this.ulClass) - this.tocContent += '<ul class="' + this.ulClass + '">'; - else - this.tocContent += '<ul>'; - } - } else { - this.tocContent += '</li>'; - } - } else { - if (this._tempLists.length) { - this.tocContent += new Array(this._tempLists.length + 1).join('</li></ul>'); - } else { - this.tocContent += '</li>'; - } + if (j !== this._elTitleElementsLen) { + this._elNextTitleElementName = this.elTitleElements[j].tagName + if (this._elTitleElementName !== this._elNextTitleElementName) { + var checkColse = false + var y = 1 + for (var t = this._tempLists.length - 1; t >= 0; t--) { + if (this._tempLists[t].tagName === this._elNextTitleElementName) { + checkColse = true + break } + y++ + } + if (checkColse) { + this.tocContent += new Array(y + 1).join('</li></ul>') + this._tempLists.length = this._tempLists.length - y + } else { + this._tempLists.push(this._elTitleElement) + if (this.ulClass) { this.tocContent += '<ul class="' + this.ulClass + '">' } else { this.tocContent += '<ul>' } + } + } else { + this.tocContent += '</li>' } - if (this.ulClass) - this.tocContent = '<ul class="' + this.ulClass + '">' + this.tocContent + '</ul>'; - else - this.tocContent = '<ul>' + this.tocContent + '</ul>'; - }; - - Toc.prototype._showToc = function () { - this.toc = document.createElement('div'); - this.toc.innerHTML = this.tocContent; - this.toc.setAttribute('class', this.tocClass); - if (!this.options.targetId) { - this.el.appendChild(this.toc); + } else { + if (this._tempLists.length) { + this.tocContent += new Array(this._tempLists.length + 1).join('</li></ul>') } else { - document.getElementById(this.options.targetId).appendChild(this.toc); + this.tocContent += '</li>' } - var self = this; - if (this.tocTop > -1) { - window.onscroll = function () { - var t = document.documentElement.scrollTop || document.body.scrollTop; - if (t < self.tocTop) { - self.toc.setAttribute('style', 'position:absolute;top:' + self.tocTop + 'px;'); - } else { - self.toc.setAttribute('style', 'position:fixed;top:10px;'); - } - } + } + } + if (this.ulClass) { this.tocContent = '<ul class="' + this.ulClass + '">' + this.tocContent + '</ul>' } else { this.tocContent = '<ul>' + this.tocContent + '</ul>' } + } + + Toc.prototype._showToc = function () { + this.toc = document.createElement('div') + this.toc.innerHTML = this.tocContent + this.toc.setAttribute('class', this.tocClass) + if (!this.options.targetId) { + this.el.appendChild(this.toc) + } else { + document.getElementById(this.options.targetId).appendChild(this.toc) + } + var self = this + if (this.tocTop > -1) { + window.onscroll = function () { + var t = document.documentElement.scrollTop || document.body.scrollTop + if (t < self.tocTop) { + self.toc.setAttribute('style', 'position:absolute;top:' + self.tocTop + 'px;') + } else { + self.toc.setAttribute('style', 'position:fixed;top:10px;') } - }; - window.Toc = Toc; -})(window);
\ No newline at end of file + } + } + } + window.Toc = Toc +})(window) |