This commit is contained in:
Raccoon 2021-07-31 15:56:50 +08:00
parent e4855cdb3c
commit f83b4b77fd
No known key found for this signature in database
GPG Key ID: 06770355DC9ECD38
13 changed files with 672 additions and 441 deletions

58
package-lock.json generated
View File

@ -3973,6 +3973,21 @@
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz", "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz",
"integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==" "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ=="
}, },
"@types/jquery": {
"version": "3.5.6",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.6.tgz",
"integrity": "sha512-SmgCQRzGPId4MZQKDj9Hqc6kSXFNWZFHpELkyK8AQhf8Zr6HKfCzFv9ZC1Fv3FyQttJZOlap3qYb12h61iZAIg==",
"dev": true,
"requires": {
"@types/sizzle": "*"
}
},
"@types/js-cookie": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz",
"integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==",
"dev": true
},
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.7", "version": "7.0.7",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
@ -4014,6 +4029,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/list.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@types/list.js/-/list.js-2.3.1.tgz",
"integrity": "sha512-yKwk95g7K5mKl9i5/rrkrbwoXvRtiC/gd4b1qU8mbfxanSkPsCSqAvmgaXS+tdj66+l5BMvdargh57pdOH+yBg==",
"dev": true
},
"@types/lodash": { "@types/lodash": {
"version": "4.14.170", "version": "4.14.170",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
@ -4118,6 +4139,15 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
}, },
"@types/select2": {
"version": "4.0.54",
"resolved": "https://registry.npmjs.org/@types/select2/-/select2-4.0.54.tgz",
"integrity": "sha512-xq8c3zsKktym2b9XbDLUWBDyX0WyWjKzeFsDQHkM9p6NfxJza4g2Ud1bheTKNXpHNyVPUrfGwDEMN7JBe3gQbw==",
"dev": true,
"requires": {
"@types/jquery": "*"
}
},
"@types/serve-static": { "@types/serve-static": {
"version": "1.13.9", "version": "1.13.9",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz",
@ -4127,6 +4157,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/sizzle": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true
},
"@types/socket.io": { "@types/socket.io": {
"version": "2.1.13", "version": "2.1.13",
"resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.13.tgz", "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.13.tgz",
@ -4147,6 +4183,12 @@
"socket.io-parser": "*" "socket.io-parser": "*"
} }
}, },
"@types/store": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@types/store/-/store-2.0.2.tgz",
"integrity": "sha512-ZPHnXkzmGMfk+pHqAGzTSpA9CbsHmJLgkvOl5w52LZ0XTxB1ZIHWZzQ7lEtjTNWScBbsQekg8TjApMXkMe4nkw==",
"dev": true
},
"@types/validator": { "@types/validator": {
"version": "13.1.4", "version": "13.1.4",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.4.tgz",
@ -19059,13 +19101,21 @@
"integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
}, },
"xss": { "xss": {
"version": "1.0.6", "version": "1.0.9",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.6.tgz", "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.9.tgz",
"integrity": "sha512-6Q9TPBeNyoTRxgZFk5Ggaepk/4vUOYdOsIUYvLehcsIZTFjaavbVnsuAkLA5lIFuug5hw8zxcB9tm01gsjph2A==", "integrity": "sha512-2t7FahYnGJys6DpHLhajusId7R0Pm2yTmuL0GV9+mV0ZlaLSnb2toBmppATfg5sWIhZQGlsTLoecSzya+l4EAQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"commander": "^2.9.0", "commander": "^2.20.3",
"cssfilter": "0.0.10" "cssfilter": "0.0.10"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
}
} }
}, },
"xtend": { "xtend": {

View File

@ -119,6 +119,9 @@
"@types/express": "4.17.9", "@types/express": "4.17.9",
"@types/express-flash": "0.0.2", "@types/express-flash": "0.0.2",
"@types/express-session": "^1.17.3", "@types/express-session": "^1.17.3",
"@types/jquery": "^3.5.6",
"@types/js-cookie": "^2.2.7",
"@types/list.js": "^2.3.1",
"@types/lodash": "^4.14.170", "@types/lodash": "^4.14.170",
"@types/markdown-pdf": "^9.0.0", "@types/markdown-pdf": "^9.0.0",
"@types/mime-types": "^2.1.0", "@types/mime-types": "^2.1.0",
@ -128,7 +131,9 @@
"@types/passport.socketio": "^3.7.5", "@types/passport.socketio": "^3.7.5",
"@types/qs": "^6.9.6", "@types/qs": "^6.9.6",
"@types/randomcolor": "^0.5.5", "@types/randomcolor": "^0.5.5",
"@types/select2": "^4.0.54",
"@types/socket.io": "^2.1.13", "@types/socket.io": "^2.1.13",
"@types/store": "^2.0.2",
"@types/validator": "^13.1.4", "@types/validator": "^13.1.4",
"@typescript-eslint/eslint-plugin": "^4.26.1", "@typescript-eslint/eslint-plugin": "^4.26.1",
"@typescript-eslint/parser": "^4.26.1", "@typescript-eslint/parser": "^4.26.1",
@ -222,7 +227,7 @@
"webpack-cli": "~4.7.2", "webpack-cli": "~4.7.2",
"webpack-merge": "~5.8.0", "webpack-merge": "~5.8.0",
"wurl": "~2.5.3", "wurl": "~2.5.3",
"xss": "~1.0.6" "xss": "~1.0.9"
}, },
"optionalDependencies": { "optionalDependencies": {
"bufferutil": "~4.0.0", "bufferutil": "~4.0.0",

View File

@ -1,38 +1,115 @@
/* eslint-env browser, jquery */ import {saveAs} from 'file-saver'
/* global moment, serverurl */ import $ from 'jquery'
import List from 'list.js'
import unescapeHTML from 'lodash/unescape'
import moment from 'moment'
import { import '../css/cover.css'
checkIfAuth, import '../css/site.css'
clearLoginState,
getLoginState,
resetCheckAuth,
setloginStateChangeEvent
} from './lib/common/login'
import { import {
clearDuplicatedHistory, clearDuplicatedHistory,
deleteServerHistory, clearServerHistoryAsync,
deleteServerHistoryAsync,
getHistory, getHistory,
getHistoryAsync,
getStorageHistory, getStorageHistory,
NoteHistory,
parseHistory, parseHistory,
parseServerToHistory, parseServerToHistory,
parseStorageToHistory, parseStorageToHistory,
postHistoryToServer, postHistoryToServerAsync,
removeHistory, removeHistory,
saveHistory, saveHistory,
saveStorageHistoryToServer saveStorageHistoryToServer
} from './history' } from './history'
import { saveAs } from 'file-saver' import {
import List from 'list.js' checkIfAuth,
import unescapeHTML from 'lodash/unescape' clearLoginState,
getLoginState,
getLoginUserProfile, isLogin,
resetCheckAuth,
setLoginStateChangeEvent
} from './lib/common/login'
import {serverurl} from "./lib/config";
require('./locale') import {initializeLocaleDropdown} from "./locale";
require('../css/cover.css') initializeLocaleDropdown()
require('../css/site.css')
const options = { setLoginStateChangeEvent(pageInit)
void pageInit()
async function pageInit() {
try {
const profile = await getLoginUserProfile()
$('.ui-signin').hide()
$('.ui-or').hide()
$('.ui-welcome').show()
if (profile.photo) $('.ui-avatar').prop('src', profile.photo).show()
else $('.ui-avatar').prop('src', '').hide()
$('.ui-name').html(profile.name)
$('.ui-signout').show()
navSection.historyPageBtn.trigger('click')
parseServerToHistory(historyList, parseHistoryCallback)
} catch (err) {
$('.ui-signin').show()
$('.ui-or').show()
$('.ui-welcome').hide()
$('.ui-avatar').prop('src', '').hide()
$('.ui-name').html('')
$('.ui-signout').hide()
parseStorageToHistory(historyList, parseHistoryCallback)
}
}
// prevent empty link change hash
$('a[href="#"]').on('click', function (e) {
e.preventDefault()
})
/**
* masthead nav section
*/
const navSection = {
introPageBtn: $('.ui-home'),
historyPageBtn: $('.ui-history'),
deleteUserModalCancel: $('.ui-delete-user-modal-cancel'),
logoutBtn: $('.ui-logout')
}
const introSection = $('#home')
const historySection = $('#history')
navSection.introPageBtn.on('click', function (e) {
if (!introSection.is(':visible')) {
$('.section:visible').hide()
introSection.fadeIn()
}
})
navSection.historyPageBtn.on('click', () => {
if (!historySection.is(':visible')) {
$('.section:visible').hide()
historySection.fadeIn()
}
})
navSection.deleteUserModalCancel.on('click', () => {
$('.ui-delete-user').parent().removeClass('active')
})
navSection.logoutBtn.on('click', () => {
clearLoginState()
location.href = `${serverurl}/logout`
})
/**
* History Section
*/
const options: List.ListOptions = {
valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'], valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'],
item: `<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4"> item: `<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">
<span class="id" style="display:none;"></span> <span class="id" style="display:none;"></span>
@ -54,87 +131,169 @@ const options = {
</a> </a>
</li>`, </li>`,
page: 18, page: 18,
pagination: [{ pagination: {
outerWindow: 1 outerWindow: 1
}] }
} }
const historyList = new List('history', options) const historyList = new List('history', options)
window.migrateHistoryFromTempCallback = pageInit /**
setloginStateChangeEvent(pageInit) * History Tool Bar
*/
pageInit() const historyListContainer = $('#history-list')
const historyPagination = $('.pagination')
function pageInit () { const historyToolbar = {
checkIfAuth( importFromBrowserBtn: $('.ui-import-from-browser'),
data => { clearHistoryBtn: $('.ui-clear-history'),
$('.ui-signin').hide() saveHistoryBtn: $('.ui-save-history'),
$('.ui-or').hide() openHistoryFile: $('.ui-open-history'),
$('.ui-welcome').show() tagFilter: $('.ui-use-tags')
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)
}
)
} }
$('.masthead-nav li').click(function () { /**
$(this).siblings().removeClass('active') * clear all history
$(this).addClass('active') */
historyToolbar.clearHistoryBtn.on('click', () => {
$('.ui-delete-history-modal-msg').text('Do you really want to clear all history?')
$('.ui-delete-history-modal-item').html('There is no turning back.')
isClearAllHistory = true
deleteHistoryModalTargetId = null
}) })
// prevent empty link change hash historyToolbar.importFromBrowserBtn.on('click', () => {
$('a[href="#"]').click(function (e) { saveStorageHistoryToServer(() => {
e.preventDefault() parseStorageToHistory(historyList, parseHistoryCallback)
})
}) })
$('.ui-home').click(function (e) { historyToolbar.saveHistoryBtn.on('click', async function () {
if (!$('#home').is(':visible')) { const history = JSON.stringify(await getHistoryAsync())
$('.section:visible').hide() const blob = new Blob([history], {
$('#home').fadeIn() type: 'application/json;charset=utf-8'
} })
saveAs(blob, `codimd_history_${moment().format('YYYYMMDDHHmmss')}`, true)
}) })
$('.ui-history').click(() => { historyToolbar.openHistoryFile.on('change', function (e) {
if (!$('#history').is(':visible')) { const files = e.target.files || e.dataTransfer.files
$('.section:visible').hide() const file = files[0]
$('#history').fadeIn() const reader = new FileReader()
} reader.onload = () => {
}) const notehistory = JSON.parse(reader.result)
// console.log(notehistory);
function checkHistoryList () { if (!reader.result) return
if ($('#history-list').children().length > 0) { getHistory(data => {
$('.pagination').show() let mergedata = data.concat(notehistory)
$('.ui-nohistory').hide() mergedata = clearDuplicatedHistory(mergedata)
$('.ui-import-from-browser').hide() saveHistory(mergedata)
} else if ($('#history-list').children().length === 0) { parseHistory(historyList, parseHistoryCallback)
$('.pagination').hide()
$('.ui-nohistory').slideDown()
getStorageHistory(data => {
if (data && data.length > 0 && getLoginState() && historyList.items.length === 0) {
$('.ui-import-from-browser').slideDown()
}
}) })
historyToolbar.openHistoryFile.replaceWith(historyToolbar.openHistoryFile.val('').clone(true))
} }
reader.readAsText(file)
})
$('.ui-refresh-history').on('click', () => {
const lastTags = historyToolbar.tagFilter.select2('val')
historyToolbar.tagFilter.select2('val', '')
historyList.filter()
const lastKeyword = $('.search').val()
$('.search').val('')
historyList.search()
historyListContainer.slideUp('fast')
historyPagination.hide()
resetCheckAuth()
historyList.clear()
parseHistory(historyList, (list, notehistory) => {
parseHistoryCallback(list, notehistory)
historyToolbar.tagFilter.select2('val', lastTags)
historyToolbar.tagFilter.trigger('change')
historyList.search(lastKeyword)
$('.search').val(lastKeyword)
refreshHistoryUIElementVisibility()
historyListContainer.slideDown('fast')
})
})
let filtertags = []
historyToolbar.tagFilter.select2({
placeholder: historyToolbar.tagFilter.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: unescapeHTML(tags[i])
}
}
filtertags = tags
} }
function parseHistoryCallback (list, notehistory) {
checkHistoryList() historyToolbar.tagFilter.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()
}
refreshHistoryUIElementVisibility()
})
$('.search').on('keyup', () => {
refreshHistoryUIElementVisibility()
})
// ----------------------------------------------------------------------------------------------------------------
function refreshHistoryUIElementVisibility() {
if (historyListContainer.children().length > 0) {
historyPagination.show()
$('.ui-nohistory').hide()
historyToolbar.importFromBrowserBtn.hide()
return
}
historyPagination.hide()
$('.ui-nohistory').slideDown()
getStorageHistory(data => {
if (data && data.length > 0 && getLoginState() && historyList.items.length === 0) {
historyToolbar.importFromBrowserBtn.slideDown()
}
})
}
function parseHistoryCallback(list, notehistory) {
refreshHistoryUIElementVisibility()
// sort by pinned then timestamp // sort by pinned then timestamp
list.sort('', { list.sort('', {
sortFunction (a, b) { sortFunction(a, b) {
const notea = a.values() const notea = a.values()
const noteb = b.values() const noteb = b.values()
if (notea.pinned && !noteb.pinned) { if (notea.pinned && !noteb.pinned) {
@ -160,8 +319,12 @@ function parseHistoryCallback (list, notehistory) {
for (let j = 0; j < tags.length; j++) { for (let j = 0; j < tags.length; j++) {
// push info filtertags if not found // push info filtertags if not found
let found = false let found = false
if (filtertags.includes(tags[j])) { found = true } if (filtertags.includes(tags[j])) {
if (!found) { filtertags.push(tags[j]) } found = true
}
if (!found) {
filtertags.push(tags[j])
}
} }
} }
} }
@ -169,7 +332,7 @@ function parseHistoryCallback (list, notehistory) {
} }
// update items whenever list updated // update items whenever list updated
historyList.on('updated', e => { historyList.on('updated', function (e) {
for (let i = 0, l = e.items.length; i < l; i++) { for (let i = 0, l = e.items.length; i < l; i++) {
const item = e.items[i] const item = e.items[i]
if (item.visible()) { if (item.visible()) {
@ -189,7 +352,7 @@ historyList.on('updated', e => {
// parse tags // parse tags
const tags = values.tags const tags = values.tags
if (tags && tags.length > 0 && tagsEl.children().length <= 0) { if (tags && tags.length > 0 && tagsEl.children().length <= 0) {
const labels = [] const labels: string[] = []
for (let j = 0; j < tags.length; j++) { for (let j = 0; j < tags.length; j++) {
// push into the item label // push into the item label
labels.push(`<span class='label label-default'>${tags[j]}</span>`) labels.push(`<span class='label label-default'>${tags[j]}</span>`)
@ -199,22 +362,66 @@ historyList.on('updated', e => {
} }
} }
$('.ui-history-close').off('click') $('.ui-history-close').off('click')
$('.ui-history-close').on('click', historyCloseClick) $('.ui-history-close').on('click', onHistoryCloseClick)
$('.ui-history-pin').off('click') $('.ui-history-pin').off('click')
$('.ui-history-pin').on('click', historyPinClick) $('.ui-history-pin').on('click', historyPinClick)
}) })
function historyCloseClick (e) { /**
* Delete History Modal
*/
let isClearAllHistory: boolean = false
let deleteHistoryModalTargetId: string | null = null
function onHistoryCloseClick(this: HTMLElement, e) {
e.preventDefault() e.preventDefault()
const id = $(this).closest('a').siblings('span').html() const id: string = $(this).closest('a').siblings('span').html()
const value = historyList.get('id', id)[0]._values const value: NoteHistory = historyList.get('id', id)[0].values()
$('.ui-delete-history-modal-msg').text('Do you really want to delete below history?') $('.ui-delete-history-modal-msg').text('Do you really want to delete below history?')
$('.ui-delete-history-modal-item').html(`<i class="fa fa-file-text"></i> ${value.text}<br><i class="fa fa-clock-o"></i> ${value.time}`) $('.ui-delete-history-modal-item').html(`<i class="fa fa-file-text"></i> ${value.text}<br><i class="fa fa-clock-o"></i> ${value.time}`)
clearHistory = false isClearAllHistory = false
deleteId = id deleteHistoryModalTargetId = id
} }
function historyPinClick (e) { $('.ui-delete-history-modal-confirm').on('click', async function () {
if (await isLogin()) {
if (isClearAllHistory) {
await clearServerHistoryAsync()
} else {
if (deleteHistoryModalTargetId) {
await deleteServerHistoryAsync(deleteHistoryModalTargetId)
}
}
refreshHistoryUIElementVisibility()
$('.delete-history-modal').modal('hide')
deleteHistoryModalTargetId = null
isClearAllHistory = false
} else {
if (isClearAllHistory) {
saveHistory([])
historyList.clear()
refreshHistoryUIElementVisibility()
deleteHistoryModalTargetId = null
} else {
if (!deleteHistoryModalTargetId) return
getHistory(notehistory => {
const newnotehistory = removeHistory(deleteHistoryModalTargetId, notehistory)
saveHistory(newnotehistory)
historyList.remove('id', deleteHistoryModalTargetId)
refreshHistoryUIElementVisibility()
deleteHistoryModalTargetId = null
})
}
$('.delete-history-modal').modal('hide')
isClearAllHistory = false
}
})
/**
* Pin History
*/
function historyPinClick(e) {
e.preventDefault() e.preventDefault()
const $this = $(this) const $this = $(this)
const id = $this.closest('a').siblings('span').html() const id = $this.closest('a').siblings('span').html()
@ -229,13 +436,17 @@ function historyPinClick (e) {
item._values.pinned = false item._values.pinned = false
} }
checkIfAuth(() => { checkIfAuth(() => {
postHistoryToServer(id, { postHistoryToServerAsync(id, {pinned})
pinned .then(() => {
}, (err, result) => { if (pinned) {
if (!err) { $this.addClass('active')
if (pinned) { $this.addClass('active') } else { $this.removeClass('active') } } else {
} $this.removeClass('active')
}) }
})
.catch(err => {
console.error(err)
})
}, () => { }, () => {
getHistory(notehistory => { getHistory(notehistory => {
for (let i = 0; i < notehistory.length; i++) { for (let i = 0; i < notehistory.length; i++) {
@ -245,7 +456,11 @@ function historyPinClick (e) {
} }
} }
saveHistory(notehistory) saveHistory(notehistory)
if (pinned) { $this.addClass('active') } else { $this.removeClass('active') } if (pinned) {
$this.addClass('active')
} else {
$this.removeClass('active')
}
}) })
}) })
} }
@ -253,7 +468,7 @@ function historyPinClick (e) {
// auto update item fromNow every minutes // auto update item fromNow every minutes
setInterval(updateItemFromNow, 60000) setInterval(updateItemFromNow, 60000)
function updateItemFromNow () { function updateItemFromNow() {
const items = $('.item').toArray() const items = $('.item').toArray()
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const item = $(items[i]) const item = $(items[i])
@ -261,170 +476,3 @@ function updateItemFromNow () {
item.find('.fromNow').text(moment(timestamp).fromNow()) 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-history-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-history-modal').modal('hide')
clearHistory = false
})
}
$('.ui-delete-history-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, `codimd_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-history-modal-msg').text('Do you really want to clear all history?')
$('.ui-delete-history-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-delete-user-modal-cancel').click(() => {
$('.ui-delete-user').parent().removeClass('active')
})
$('.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: unescapeHTML(tags[i])
}
}
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()
})

View File

@ -44,6 +44,8 @@ require('prismjs/components/prism-gherkin')
require('./lib/common/login') require('./lib/common/login')
require('../vendor/md-toc') require('../vendor/md-toc')
let viz = new window.Viz() let viz = new window.Viz()
const plantumlEncoder = require('plantuml-encoder') const plantumlEncoder = require('plantuml-encoder')

View File

@ -1,9 +1,10 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
/* global serverurl, moment */ /* global serverurl, moment */
import $ from 'jquery'
import store from 'store' import store from 'store'
import LZString from '@hackmd/lz-string' import LZString from '@hackmd/lz-string'
import {serverurl} from "./lib/config";
import escapeHTML from 'lodash/escape' import escapeHTML from 'lodash/escape'
import { import {
@ -11,13 +12,11 @@ import {
encodeNoteId encodeNoteId
} from './utils' } from './utils'
import { checkIfAuth } from './lib/common/login' import {checkIfAuth, isLogin} from './lib/common/login'
import { urlpath } from './lib/config' import {urlpath} from './lib/config'
window.migrateHistoryFromTempCallback = null export function saveHistory(notehistory) {
export function saveHistory (notehistory) {
checkIfAuth( checkIfAuth(
() => { () => {
saveHistoryToServer(notehistory) saveHistoryToServer(notehistory)
@ -28,17 +27,17 @@ export function saveHistory (notehistory) {
) )
} }
function saveHistoryToStorage (notehistory) { function saveHistoryToStorage(notehistory) {
store.set('notehistory', JSON.stringify(notehistory)) store.set('notehistory', JSON.stringify(notehistory))
} }
function saveHistoryToServer (notehistory) { function saveHistoryToServer(notehistory) {
$.post(`${serverurl}/history`, { $.post(`${serverurl}/history`, {
history: JSON.stringify(notehistory) history: JSON.stringify(notehistory)
}) })
} }
export function saveStorageHistoryToServer (callback) { export function saveStorageHistoryToServer(callback) {
const data = store.get('notehistory') const data = store.get('notehistory')
if (data) { if (data) {
$.post(`${serverurl}/history`, { $.post(`${serverurl}/history`, {
@ -50,7 +49,7 @@ export function saveStorageHistoryToServer (callback) {
} }
} }
export function clearDuplicatedHistory (notehistory) { export function clearDuplicatedHistory(notehistory) {
const newnotehistory = [] const newnotehistory = []
for (let i = 0; i < notehistory.length; i++) { for (let i = 0; i < notehistory.length; i++) {
let found = false let found = false
@ -67,12 +66,14 @@ export function clearDuplicatedHistory (notehistory) {
break break
} }
} }
if (!found) { newnotehistory.push(notehistory[i]) } if (!found) {
newnotehistory.push(notehistory[i])
}
} }
return newnotehistory return newnotehistory
} }
function addHistory (id, text, time, tags, pinned, notehistory) { function addHistory(id, text, time, tags, pinned, notehistory) {
// only add when note id exists // only add when note id exists
if (id) { if (id) {
notehistory.push({ notehistory.push({
@ -86,7 +87,7 @@ function addHistory (id, text, time, tags, pinned, notehistory) {
return notehistory return notehistory
} }
export function removeHistory (id, notehistory) { export function removeHistory(id, notehistory) {
for (let i = 0; i < notehistory.length; i++) { for (let i = 0; i < notehistory.length; i++) {
if (notehistory[i].id === id) { if (notehistory[i].id === id) {
notehistory.splice(i, 1) notehistory.splice(i, 1)
@ -97,7 +98,7 @@ export function removeHistory (id, notehistory) {
} }
// used for inner // used for inner
export function writeHistory (title, tags) { export function writeHistory(title, tags) {
checkIfAuth( checkIfAuth(
() => { () => {
// no need to do this anymore, this will count from server-side // no need to do this anymore, this will count from server-side
@ -109,7 +110,7 @@ export function writeHistory (title, tags) {
) )
} }
function writeHistoryToStorage (title, tags) { function writeHistoryToStorage(title, tags) {
const data = store.get('notehistory') const data = store.get('notehistory')
let notehistory let notehistory
if (data && typeof data === 'string') { if (data && typeof data === 'string') {
@ -126,7 +127,7 @@ 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) { function renderHistory(title, tags) {
// console.debug(tags); // console.debug(tags);
const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1] const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1]
return { return {
@ -137,7 +138,7 @@ function renderHistory (title, tags) {
} }
} }
function generateHistory (title, tags, notehistory) { function generateHistory(title, tags, notehistory) {
const info = renderHistory(title, tags) const info = renderHistory(title, tags)
// keep any pinned data // keep any pinned data
let pinned = false let pinned = false
@ -154,22 +155,42 @@ function generateHistory (title, tags, notehistory) {
} }
// used for outer // used for outer
export function getHistory (callback) {
checkIfAuth( export async function getHistoryAsync(): Promise<NoteHistory[]> {
() => { if (await isLogin()) {
getServerHistory(callback) return await getServerHistoryAsync()
}, }
() => { return getStorageHistoryAsync();
getStorageHistory(callback)
}
)
} }
function getServerHistory (callback) { export function getHistory(callback: (_: NoteHistory[]) => void) {
getHistoryAsync()
.then((data) => {
callback(data)
})
}
export interface NoteHistory {
id?: string
text?: string
time?: Date | number
tags?: string[]
pinned?: boolean
}
function getServerHistoryAsync(): Promise<NoteHistory[]> {
return new Promise((resolve) => {
getServerHistory(function (data: NoteHistory[]) {
resolve(data)
})
})
}
function getServerHistory(callback) {
$.get(`${serverurl}/history`) $.get(`${serverurl}/history`)
.done(data => { .done(data => {
if (data.history) { if (data.history) {
callback(data.history) callback(data.history as NoteHistory[])
} }
}) })
.fail((xhr, status, error) => { .fail((xhr, status, error) => {
@ -177,17 +198,30 @@ function getServerHistory (callback) {
}) })
} }
export function getStorageHistory (callback) { function getStorageHistoryAsync(): NoteHistory[] {
let data = store.get('notehistory') let data = store.get('notehistory')
if (data) { if (data) {
if (typeof data === 'string') { data = JSON.parse(data) } if (typeof data === 'string') {
data = JSON.parse(data)
}
return data
}
return []
}
export function getStorageHistory(callback) {
let data = store.get('notehistory')
if (data) {
if (typeof data === 'string') {
data = JSON.parse(data)
}
callback(data) callback(data)
} }
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
callback([]) callback([])
} }
export function parseHistory (list, callback) { export function parseHistory(list, callback) {
checkIfAuth( checkIfAuth(
() => { () => {
parseServerToHistory(list, callback) parseServerToHistory(list, callback)
@ -198,7 +232,7 @@ export function parseHistory (list, callback) {
) )
} }
export function parseServerToHistory (list, callback) { export function parseServerToHistory(list, callback) {
$.get(`${serverurl}/history`) $.get(`${serverurl}/history`)
.done(data => { .done(data => {
if (data.history) { if (data.history) {
@ -210,16 +244,18 @@ export function parseServerToHistory (list, callback) {
}) })
} }
export function parseStorageToHistory (list, callback) { export function parseStorageToHistory(list, callback) {
let data = store.get('notehistory') let data = store.get('notehistory')
if (data) { if (data) {
if (typeof data === 'string') { data = JSON.parse(data) } if (typeof data === 'string') {
data = JSON.parse(data)
}
parseToHistory(list, data, callback) parseToHistory(list, data, callback)
} }
parseToHistory(list, [], callback) parseToHistory(list, [], callback)
} }
function parseToHistory (list, notehistory, callback) { function parseToHistory(list, notehistory, callback) {
if (!callback) return if (!callback) return
else if (!list || !notehistory) callback(list, notehistory) else if (!list || !notehistory) callback(list, notehistory)
else if (notehistory && notehistory.length > 0) { else if (notehistory && notehistory.length > 0) {
@ -242,29 +278,53 @@ function parseToHistory (list, notehistory, callback) {
notehistory[i].text = escapeHTML(notehistory[i].text) notehistory[i].text = escapeHTML(notehistory[i].text)
notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? escapeHTML(notehistory[i].tags).split(',') : [] notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? escapeHTML(notehistory[i].tags).split(',') : []
// add to list // 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) { interface HistoryUpdatePayload {
$.post(`${serverurl}/history/${noteId}`, data) pinned: boolean
.done(result => callback(null, result))
.fail((xhr, status, error) => {
console.error(xhr.responseText)
return callback(error, null)
})
} }
export function deleteServerHistory (noteId, callback) { export function postHistoryToServerAsync(noteId: string, data ?: HistoryUpdatePayload) {
$.ajax({ return new Promise((resolve, reject) => {
url: `${serverurl}/history${noteId ? '/' + noteId : ''}`, $.post(`${serverurl}/history/${noteId}`, data as any)
type: 'DELETE' .done(result => resolve(result))
.fail((xhr, status, error) => {
console.error(xhr.responseText)
return reject(error)
})
})
}
export function deleteServerHistoryAsync(noteId: string) {
return new Promise((resolve, reject) => {
$.ajax({
url: `${serverurl}/history/${noteId}`,
type: 'DELETE'
})
.done(result => resolve(result))
.fail((xhr, status, error) => {
console.error(xhr.responseText)
return reject(error)
})
})
}
export function clearServerHistoryAsync() {
return new Promise((resolve, reject) => {
$.ajax({
url: `${serverurl}/history`,
type: 'DELETE'
})
.done(result => resolve(result))
.fail((xhr, status, error) => {
console.error(xhr.responseText)
return reject(error)
})
}) })
.done(result => callback(null, result))
.fail((xhr, status, error) => {
console.error(xhr.responseText)
return callback(error, null)
})
} }

View File

@ -23,14 +23,14 @@ import { Spinner } from 'spin.js'
import { import {
checkLoginStateChanged, checkLoginStateChanged,
setloginStateChangeEvent setLoginStateChangeEvent
} from './lib/common/login' } from './lib/common/login'
import { import {
debug, debug,
DROPBOX_APP_KEY, DROPBOX_APP_KEY,
noteid, noteid,
noteurl, noteurl, serverurl,
urlpath, urlpath,
version version
} from './lib/config' } from './lib/config'
@ -66,10 +66,9 @@ import {
import { import {
writeHistory, writeHistory,
deleteServerHistory,
getHistory, getHistory,
saveHistory, saveHistory,
removeHistory removeHistory, deleteServerHistoryAsync
} from './history' } from './history'
import { preventXSS } from './render' import { preventXSS } from './render'
@ -396,7 +395,7 @@ function setNeedRefresh () {
showStatus(statusType.offline) showStatus(statusType.offline)
} }
setloginStateChangeEvent(function () { setLoginStateChangeEvent(function () {
setRefreshModal('user-state-changed') setRefreshModal('user-state-changed')
setNeedRefresh() setNeedRefresh()
}) })
@ -1752,11 +1751,14 @@ socket.on('error', function (data) {
console.error(data) console.error(data)
if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' } if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' }
}) })
socket.on('delete', function () { socket.on('delete', async function () {
if (personalInfo.login) { if (personalInfo.login) {
deleteServerHistory(noteid, function (err, data) { try {
if (!err) location.href = serverurl await deleteServerHistoryAsync(noteid)
}) location.href = serverurl
} catch (err) {
console.error(err)
}
} else { } else {
getHistory(function (notehistory) { getHistory(function (notehistory) {
var newnotehistory = removeHistory(noteid, notehistory) var newnotehistory = removeHistory(noteid, notehistory)

View File

@ -1,24 +1,28 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
/* global Cookies */ /* global Cookies */
import { serverurl } from '../config' import Cookies from 'js-cookie'
let checkAuth = false import {serverurl} from '../config'
type PureFunction = () => void
let checkAuth: boolean = false
let profile = null let profile = null
let lastLoginState = getLoginState() let lastLoginState = getLoginState()
let lastUserId = getUserId() let lastUserId: string | null = getUserId()
let loginStateChangeEvent = null let loginStateChangeEvent: PureFunction | null = null
export function setloginStateChangeEvent (func) { export function setLoginStateChangeEvent(func: PureFunction): void {
loginStateChangeEvent = func loginStateChangeEvent = func
} }
export function resetCheckAuth () { export function resetCheckAuth(): void {
checkAuth = false checkAuth = false
} }
export function setLoginState (bool, id) { function setLoginState(bool: boolean, id?: string) {
Cookies.set('loginstate', bool, { Cookies.set('loginstate', String(bool), {
expires: 365 expires: 365
}) })
if (id) { if (id) {
@ -29,64 +33,81 @@ export function setLoginState (bool, id) {
Cookies.remove('userid') Cookies.remove('userid')
} }
lastLoginState = bool lastLoginState = bool
lastUserId = id lastUserId = id || null
checkLoginStateChanged() checkLoginStateChanged()
} }
export function checkLoginStateChanged () { export function checkLoginStateChanged(): boolean {
if (getLoginState() !== lastLoginState || getUserId() !== lastUserId) { if (getLoginState() !== lastLoginState || getUserId() !== lastUserId) {
if (loginStateChangeEvent) setTimeout(loginStateChangeEvent, 100) if (loginStateChangeEvent) setTimeout(loginStateChangeEvent, 100)
return true return true
} else { }
return false
}
export function getLoginState(): boolean {
const state = Cookies.get('loginstate')
return state === 'true'
}
function getUserId(): string {
return Cookies.get('userid') || ""
}
export function clearLoginState(): void {
Cookies.remove('loginstate')
}
type YesCallbackFunc = (profile: any) => void
/**
* getLoginUserProfile
* @throws Error when user not login
* @return profile user profile
*/
export async function getLoginUserProfile(): Promise<any> {
return new Promise(function (resolve, reject) {
const cookieLoginState = getLoginState()
if (checkLoginStateChanged()) checkAuth = false
if (!checkAuth || typeof cookieLoginState === 'undefined') {
$.get(`${serverurl}/me`)
.done(data => {
if (data && data.status === 'ok') {
profile = data
setLoginState(true, data.id)
return resolve(profile)
} else {
setLoginState(false)
return reject(new Error('user not login'))
}
})
.fail(() => {
return reject(new Error('user not login'))
})
.always(() => {
checkAuth = true
})
} else if (cookieLoginState) {
return resolve(profile)
} else {
return reject(new Error('user not login'))
}
})
}
export async function isLogin(): Promise<boolean> {
try {
await getLoginUserProfile()
return true
} catch (e) {
return false return false
} }
} }
export function getLoginState () { export function checkIfAuth(yesCallback: YesCallbackFunc, noCallback: PureFunction) {
const state = Cookies.get('loginstate') getLoginUserProfile().then((profile) => {
return state === 'true' || state === true
}
export function getUserId () {
return Cookies.get('userid')
}
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`)
.done(data => {
if (data && data.status === 'ok') {
profile = data
yesCallback(profile)
setLoginState(true, data.id)
} else {
noCallback()
setLoginState(false)
}
})
.fail(() => {
noCallback()
})
.always(() => {
checkAuth = true
})
} else if (cookieLoginState) {
yesCallback(profile) yesCallback(profile)
} else { }).catch(() => {
noCallback() noCallback()
} })
}
export default {
checkAuth,
profile,
lastLoginState,
lastUserId,
loginStateChangeEvent
} }

View File

@ -1,12 +1,3 @@
declare interface Window {
DROPBOX_APP_KEY: string,
domain: string,
urlpath: string,
debug: boolean,
serverurl: string,
version: string
USE_CDN: boolean
}
export const DROPBOX_APP_KEY = window.DROPBOX_APP_KEY || '' export const DROPBOX_APP_KEY = window.DROPBOX_APP_KEY || ''

View File

@ -1,31 +1,48 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
/* global Cookies */ /* global Cookies */
var lang = 'en' import $ from 'jquery'
var userLang = navigator.language || navigator.userLanguage import Cookies from 'js-cookie'
var userLangCode = userLang.split('-')[0]
var locale = $('.ui-locale') const userLang = navigator.language || navigator.userLanguage
var supportLangs = [] const userLangCode = userLang.split('-')[0]
$('.ui-locale option').each(function () {
supportLangs.push($(this).val()) let lang = 'en'
}) let supportLangs: string[] = []
if (Cookies.get('locale')) {
lang = Cookies.get('locale') function reloadLanguageFromCookie () {
if (lang === 'zh') { if (Cookies.get('locale')) {
lang = 'zh-TW' lang = Cookies.get('locale') as string
if (lang === 'zh') {
lang = 'zh-TW'
}
} else if (supportLangs.indexOf(userLang) !== -1) {
lang = supportLangs[supportLangs.indexOf(userLang)]
} else if (supportLangs.indexOf(userLangCode) !== -1) {
lang = supportLangs[supportLangs.indexOf(userLangCode)]
} }
} else if (supportLangs.indexOf(userLang) !== -1) {
lang = supportLangs[supportLangs.indexOf(userLang)]
} else if (supportLangs.indexOf(userLangCode) !== -1) {
lang = supportLangs[supportLangs.indexOf(userLangCode)]
} }
locale.val(lang) function fillSupportLanguageArrayFromDropdownOptions () {
$('select.ui-locale option[value="' + lang + '"]').attr('selected', 'selected') const $langOptions = $('.ui-locale option');
$langOptions.each(function () {
locale.change(function () { supportLangs.push($(this).val() as string)
Cookies.set('locale', $(this).val(), {
expires: 365
}) })
window.location.reload() }
})
export function initializeLocaleDropdown() {
const $locale = $('.ui-locale')
fillSupportLanguageArrayFromDropdownOptions()
reloadLanguageFromCookie()
$locale.val(lang)
$('select.ui-locale option[value="' + lang + '"]').attr('selected', 'selected')
$locale.on('change', function () {
Cookies.set('locale', $(this).val() as string, {
expires: 365
})
window.location.reload()
})
}

View File

@ -1,16 +1,15 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
// allow some attributes // allow some attributes
var filterXSS = require('xss') import {filterXSS, escapeAttrValue, whiteList} from 'xss'
var whiteListAttr = ['id', 'class', 'style'] const whiteListAttr = ['id', 'class', 'style']
window.whiteListAttr = whiteListAttr window.whiteListAttr = whiteListAttr
// allow link starts with '.', '/' and custom protocol with '://', exclude link starts with javascript:// // allow link starts with '.', '/' and custom protocol with '://', exclude link starts with javascript://
var linkRegex = /^(?!javascript:\/\/)([\w|-]+:\/\/)|^([.|/])+/i const linkRegex = /^(?!javascript:\/\/)([\w|-]+:\/\/)|^([.|/])+/i
// allow data uri, from https://gist.github.com/bgrins/6194623 // 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 const dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*)\s*$/i
// custom white list // custom white list
var whiteList = filterXSS.whiteList
// allow ol specify start number // allow ol specify start number
whiteList.ol = ['start'] whiteList.ol = ['start']
// allow li specify value number // allow li specify value number
@ -34,7 +33,7 @@ whiteList.figure = []
// allow figcaption tag // allow figcaption tag
whiteList.figcaption = [] whiteList.figcaption = []
var filterXSSOptions = { const filterXSSOptions = {
allowCommentTag: true, allowCommentTag: true,
whiteList: whiteList, whiteList: whiteList,
escapeHtml: function (html) { escapeHtml: function (html) {
@ -51,28 +50,25 @@ var filterXSSOptions = {
onTagAttr: function (tag, name, value, isWhiteAttr) { onTagAttr: function (tag, name, value, isWhiteAttr) {
// allow href and src that match linkRegex // allow href and src that match linkRegex
if (isWhiteAttr && (name === 'href' || name === 'src') && linkRegex.test(value)) { if (isWhiteAttr && (name === 'href' || name === 'src') && linkRegex.test(value)) {
return name + '="' + filterXSS.escapeAttrValue(value) + '"' return name + '="' + escapeAttrValue(value) + '"'
} }
// allow data uri in img src // allow data uri in img src
if (isWhiteAttr && (tag === 'img' && name === 'src') && dataUriRegex.test(value)) { if (isWhiteAttr && (tag === 'img' && name === 'src') && dataUriRegex.test(value)) {
return name + '="' + filterXSS.escapeAttrValue(value) + '"' return name + '="' + escapeAttrValue(value) + '"'
} }
}, },
onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) { onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {
// allow attr start with 'data-' or in the whiteListAttr // allow attr start with 'data-' or in the whiteListAttr
if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) { if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) {
// escape its value using built-in escapeAttrValue function // escape its value using built-in escapeAttrValue function
return name + '="' + filterXSS.escapeAttrValue(value) + '"' return name + '="' + escapeAttrValue(value) + '"'
} }
} }
} }
function preventXSS (html) { export function preventXSS (html) {
return filterXSS(html, filterXSSOptions) return filterXSS(html, filterXSSOptions)
} }
window.preventXSS = preventXSS window.preventXSS = preventXSS
module.exports = { export {escapeAttrValue} from 'xss'
preventXSS: preventXSS,
escapeAttrValue: filterXSS.escapeAttrValue
}

27
public/js/tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"lib": ["DOM", "ESNext"],
"sourceMap": true,
"baseUrl": ".",
"allowJs": true,
"strict": false,
"strictNullChecks": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"alwaysStrict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"typeRoots": ["../../node_modules/@types",
"./typings"
]
},
"include": [
"./"
]
}

12
public/js/typings/index.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
declare global {
interface Window {
Viz: any
createtime: any
lastchangetime: any
lastchangeui: any
owner: any
ownerprofile: any
whiteListAttr: any
preventXSS: any
}
}

View File

@ -2,23 +2,23 @@ import base64url from 'base64url'
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
export function checkNoteIdValid (id) { export function checkNoteIdValid (id: string) {
const result = id.match(uuidRegex) const result = id.match(uuidRegex)
return !!(result && result.length === 1) return !!(result && result.length === 1)
} }
export function encodeNoteId (id) { export function encodeNoteId (id: string) {
// remove dashes in UUID and encode in url-safe base64 // remove dashes in UUID and encode in url-safe base64
const str = id.replace(/-/g, '') const str = id.replace(/-/g, '')
const hexStr = Buffer.from(str, 'hex') const hexStr = Buffer.from(str, 'hex')
return base64url.encode(hexStr) return base64url.encode(hexStr)
} }
export function decodeNoteId (encodedId) { export function decodeNoteId (encodedId: string) {
// decode from url-safe base64 // decode from url-safe base64
const id = base64url.toBuffer(encodedId).toString('hex') const id = base64url.toBuffer(encodedId).toString('hex')
// add dashes between the UUID string parts // add dashes between the UUID string parts
const idParts = [] const idParts: string[] = []
idParts.push(id.substr(0, 8)) idParts.push(id.substr(0, 8))
idParts.push(id.substr(8, 4)) idParts.push(id.substr(8, 4))
idParts.push(id.substr(12, 4)) idParts.push(id.substr(12, 4))