diff --git a/package-lock.json b/package-lock.json
index 90a132c0..27f548b4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3973,6 +3973,21 @@
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz",
"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": {
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
@@ -4014,6 +4029,12 @@
"@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": {
"version": "4.14.170",
"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",
"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": {
"version": "1.13.9",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz",
@@ -4127,6 +4157,12 @@
"@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": {
"version": "2.1.13",
"resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.13.tgz",
@@ -4147,6 +4183,12 @@
"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": {
"version": "13.1.4",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.4.tgz",
@@ -19059,13 +19101,21 @@
"integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
},
"xss": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.6.tgz",
- "integrity": "sha512-6Q9TPBeNyoTRxgZFk5Ggaepk/4vUOYdOsIUYvLehcsIZTFjaavbVnsuAkLA5lIFuug5hw8zxcB9tm01gsjph2A==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.9.tgz",
+ "integrity": "sha512-2t7FahYnGJys6DpHLhajusId7R0Pm2yTmuL0GV9+mV0ZlaLSnb2toBmppATfg5sWIhZQGlsTLoecSzya+l4EAQ==",
"dev": true,
"requires": {
- "commander": "^2.9.0",
+ "commander": "^2.20.3",
"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": {
diff --git a/package.json b/package.json
index b1c14f3f..039f6afc 100644
--- a/package.json
+++ b/package.json
@@ -119,6 +119,9 @@
"@types/express": "4.17.9",
"@types/express-flash": "0.0.2",
"@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/markdown-pdf": "^9.0.0",
"@types/mime-types": "^2.1.0",
@@ -128,7 +131,9 @@
"@types/passport.socketio": "^3.7.5",
"@types/qs": "^6.9.6",
"@types/randomcolor": "^0.5.5",
+ "@types/select2": "^4.0.54",
"@types/socket.io": "^2.1.13",
+ "@types/store": "^2.0.2",
"@types/validator": "^13.1.4",
"@typescript-eslint/eslint-plugin": "^4.26.1",
"@typescript-eslint/parser": "^4.26.1",
@@ -222,7 +227,7 @@
"webpack-cli": "~4.7.2",
"webpack-merge": "~5.8.0",
"wurl": "~2.5.3",
- "xss": "~1.0.6"
+ "xss": "~1.0.9"
},
"optionalDependencies": {
"bufferutil": "~4.0.0",
diff --git a/public/js/cover.ts b/public/js/cover.ts
index 10111bfa..d91ba597 100644
--- a/public/js/cover.ts
+++ b/public/js/cover.ts
@@ -1,38 +1,115 @@
-/* eslint-env browser, jquery */
-/* global moment, serverurl */
+import {saveAs} from 'file-saver'
+import $ from 'jquery'
+import List from 'list.js'
+import unescapeHTML from 'lodash/unescape'
+import moment from 'moment'
-import {
- checkIfAuth,
- clearLoginState,
- getLoginState,
- resetCheckAuth,
- setloginStateChangeEvent
-} from './lib/common/login'
+import '../css/cover.css'
+import '../css/site.css'
import {
clearDuplicatedHistory,
- deleteServerHistory,
+ clearServerHistoryAsync,
+ deleteServerHistoryAsync,
getHistory,
+ getHistoryAsync,
getStorageHistory,
+ NoteHistory,
parseHistory,
parseServerToHistory,
parseStorageToHistory,
- postHistoryToServer,
+ postHistoryToServerAsync,
removeHistory,
saveHistory,
saveStorageHistoryToServer
} from './history'
-import { saveAs } from 'file-saver'
-import List from 'list.js'
-import unescapeHTML from 'lodash/unescape'
+import {
+ checkIfAuth,
+ 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')
-require('../css/site.css')
+initializeLocaleDropdown()
-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'],
item: `
@@ -54,87 +131,169 @@ const options = {
`,
page: 18,
- pagination: [{
+ pagination: {
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').show()
- $('.ui-or').show()
- $('.ui-welcome').hide()
- $('.ui-avatar').prop('src', '').hide()
- $('.ui-name').html('')
- $('.ui-signout').hide()
- parseStorageToHistory(historyList, parseHistoryCallback)
- }
- )
+/**
+ * History Tool Bar
+ */
+const historyListContainer = $('#history-list')
+const historyPagination = $('.pagination')
+const historyToolbar = {
+ importFromBrowserBtn: $('.ui-import-from-browser'),
+ clearHistoryBtn: $('.ui-clear-history'),
+ saveHistoryBtn: $('.ui-save-history'),
+ openHistoryFile: $('.ui-open-history'),
+ tagFilter: $('.ui-use-tags')
}
-$('.masthead-nav li').click(function () {
- $(this).siblings().removeClass('active')
- $(this).addClass('active')
+/**
+ * clear all history
+ */
+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
-$('a[href="#"]').click(function (e) {
- e.preventDefault()
+historyToolbar.importFromBrowserBtn.on('click', () => {
+ saveStorageHistoryToServer(() => {
+ parseStorageToHistory(historyList, parseHistoryCallback)
+ })
})
-$('.ui-home').click(function (e) {
- if (!$('#home').is(':visible')) {
- $('.section:visible').hide()
- $('#home').fadeIn()
- }
+historyToolbar.saveHistoryBtn.on('click', async function () {
+ const history = JSON.stringify(await getHistoryAsync())
+ const blob = new Blob([history], {
+ type: 'application/json;charset=utf-8'
+ })
+ saveAs(blob, `codimd_history_${moment().format('YYYYMMDDHHmmss')}`, true)
})
-$('.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()
- }
+historyToolbar.openHistoryFile.on('change', function (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)
})
+ 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
list.sort('', {
- sortFunction (a, b) {
+ sortFunction(a, b) {
const notea = a.values()
const noteb = b.values()
if (notea.pinned && !noteb.pinned) {
@@ -160,8 +319,12 @@ function parseHistoryCallback (list, notehistory) {
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]) }
+ if (filtertags.includes(tags[j])) {
+ found = true
+ }
+ if (!found) {
+ filtertags.push(tags[j])
+ }
}
}
}
@@ -169,7 +332,7 @@ function parseHistoryCallback (list, notehistory) {
}
// 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++) {
const item = e.items[i]
if (item.visible()) {
@@ -189,7 +352,7 @@ historyList.on('updated', e => {
// parse tags
const tags = values.tags
if (tags && tags.length > 0 && tagsEl.children().length <= 0) {
- const labels = []
+ const labels: string[] = []
for (let j = 0; j < tags.length; j++) {
// push into the item label
labels.push(`${tags[j]}`)
@@ -199,22 +362,66 @@ historyList.on('updated', e => {
}
}
$('.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').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()
- const id = $(this).closest('a').siblings('span').html()
- const value = historyList.get('id', id)[0]._values
+ const id: string = $(this).closest('a').siblings('span').html()
+ 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-item').html(` ${value.text}
${value.time}`)
- clearHistory = false
- deleteId = id
+ isClearAllHistory = false
+ 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()
const $this = $(this)
const id = $this.closest('a').siblings('span').html()
@@ -229,13 +436,17 @@ function historyPinClick (e) {
item._values.pinned = false
}
checkIfAuth(() => {
- postHistoryToServer(id, {
- pinned
- }, (err, result) => {
- if (!err) {
- if (pinned) { $this.addClass('active') } else { $this.removeClass('active') }
- }
- })
+ postHistoryToServerAsync(id, {pinned})
+ .then(() => {
+ if (pinned) {
+ $this.addClass('active')
+ } else {
+ $this.removeClass('active')
+ }
+ })
+ .catch(err => {
+ console.error(err)
+ })
}, () => {
getHistory(notehistory => {
for (let i = 0; i < notehistory.length; i++) {
@@ -245,7 +456,11 @@ function historyPinClick (e) {
}
}
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
setInterval(updateItemFromNow, 60000)
-function updateItemFromNow () {
+function updateItemFromNow() {
const items = $('.item').toArray()
for (let i = 0; i < items.length; i++) {
const item = $(items[i])
@@ -261,170 +476,3 @@ function updateItemFromNow () {
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()
-})
diff --git a/public/js/extra.ts b/public/js/extra.ts
index f076d1bb..84722051 100644
--- a/public/js/extra.ts
+++ b/public/js/extra.ts
@@ -44,6 +44,8 @@ require('prismjs/components/prism-gherkin')
require('./lib/common/login')
require('../vendor/md-toc')
+
+
let viz = new window.Viz()
const plantumlEncoder = require('plantuml-encoder')
diff --git a/public/js/history.ts b/public/js/history.ts
index da39f095..945d6a14 100644
--- a/public/js/history.ts
+++ b/public/js/history.ts
@@ -1,9 +1,10 @@
/* eslint-env browser, jquery */
/* global serverurl, moment */
+import $ from 'jquery'
import store from 'store'
import LZString from '@hackmd/lz-string'
-
+import {serverurl} from "./lib/config";
import escapeHTML from 'lodash/escape'
import {
@@ -11,13 +12,11 @@ import {
encodeNoteId
} 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(
() => {
saveHistoryToServer(notehistory)
@@ -28,17 +27,17 @@ export function saveHistory (notehistory) {
)
}
-function saveHistoryToStorage (notehistory) {
+function saveHistoryToStorage(notehistory) {
store.set('notehistory', JSON.stringify(notehistory))
}
-function saveHistoryToServer (notehistory) {
+function saveHistoryToServer(notehistory) {
$.post(`${serverurl}/history`, {
history: JSON.stringify(notehistory)
})
}
-export function saveStorageHistoryToServer (callback) {
+export function saveStorageHistoryToServer(callback) {
const data = store.get('notehistory')
if (data) {
$.post(`${serverurl}/history`, {
@@ -50,7 +49,7 @@ export function saveStorageHistoryToServer (callback) {
}
}
-export function clearDuplicatedHistory (notehistory) {
+export function clearDuplicatedHistory(notehistory) {
const newnotehistory = []
for (let i = 0; i < notehistory.length; i++) {
let found = false
@@ -67,12 +66,14 @@ export function clearDuplicatedHistory (notehistory) {
break
}
}
- if (!found) { newnotehistory.push(notehistory[i]) }
+ 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({
@@ -86,7 +87,7 @@ function addHistory (id, text, time, tags, pinned, notehistory) {
return notehistory
}
-export function removeHistory (id, notehistory) {
+export function removeHistory(id, notehistory) {
for (let i = 0; i < notehistory.length; i++) {
if (notehistory[i].id === id) {
notehistory.splice(i, 1)
@@ -97,7 +98,7 @@ export function removeHistory (id, notehistory) {
}
// used for inner
-export function writeHistory (title, tags) {
+export function writeHistory(title, tags) {
checkIfAuth(
() => {
// 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')
let notehistory
if (data && typeof data === 'string') {
@@ -126,7 +127,7 @@ if (!Array.isArray) {
Array.isArray = arg => Object.prototype.toString.call(arg) === '[object Array]'
}
-function renderHistory (title, 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 {
@@ -137,7 +138,7 @@ function renderHistory (title, tags) {
}
}
-function generateHistory (title, tags, notehistory) {
+function generateHistory(title, tags, notehistory) {
const info = renderHistory(title, tags)
// keep any pinned data
let pinned = false
@@ -154,22 +155,42 @@ function generateHistory (title, tags, notehistory) {
}
// used for outer
-export function getHistory (callback) {
- checkIfAuth(
- () => {
- getServerHistory(callback)
- },
- () => {
- getStorageHistory(callback)
- }
- )
+
+export async function getHistoryAsync(): Promise {
+ if (await isLogin()) {
+ return await getServerHistoryAsync()
+ }
+ return getStorageHistoryAsync();
}
-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 {
+ return new Promise((resolve) => {
+ getServerHistory(function (data: NoteHistory[]) {
+ resolve(data)
+ })
+ })
+}
+
+function getServerHistory(callback) {
$.get(`${serverurl}/history`)
.done(data => {
if (data.history) {
- callback(data.history)
+ callback(data.history as NoteHistory[])
}
})
.fail((xhr, status, error) => {
@@ -177,17 +198,30 @@ function getServerHistory (callback) {
})
}
-export function getStorageHistory (callback) {
+function getStorageHistoryAsync(): NoteHistory[] {
let data = store.get('notehistory')
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)
}
// eslint-disable-next-line standard/no-callback-literal
callback([])
}
-export function parseHistory (list, callback) {
+export function parseHistory(list, callback) {
checkIfAuth(
() => {
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`)
.done(data => {
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')
if (data) {
- if (typeof data === 'string') { data = JSON.parse(data) }
+ if (typeof data === 'string') {
+ data = JSON.parse(data)
+ }
parseToHistory(list, data, callback)
}
parseToHistory(list, [], callback)
}
-function parseToHistory (list, notehistory, callback) {
+function parseToHistory(list, notehistory, callback) {
if (!callback) return
else if (!list || !notehistory) callback(list, notehistory)
else if (notehistory && notehistory.length > 0) {
@@ -242,29 +278,53 @@ function parseToHistory (list, notehistory, callback) {
notehistory[i].text = escapeHTML(notehistory[i].text)
notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? escapeHTML(notehistory[i].tags).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)
}
-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)
- })
+interface HistoryUpdatePayload {
+ pinned: boolean
}
-export function deleteServerHistory (noteId, callback) {
- $.ajax({
- url: `${serverurl}/history${noteId ? '/' + noteId : ''}`,
- type: 'DELETE'
+export function postHistoryToServerAsync(noteId: string, data ?: HistoryUpdatePayload) {
+ return new Promise((resolve, reject) => {
+ $.post(`${serverurl}/history/${noteId}`, data as any)
+ .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)
- })
}
diff --git a/public/js/index.ts b/public/js/index.ts
index 82cbb357..9213ea4e 100644
--- a/public/js/index.ts
+++ b/public/js/index.ts
@@ -23,14 +23,14 @@ import { Spinner } from 'spin.js'
import {
checkLoginStateChanged,
- setloginStateChangeEvent
+ setLoginStateChangeEvent
} from './lib/common/login'
import {
debug,
DROPBOX_APP_KEY,
noteid,
- noteurl,
+ noteurl, serverurl,
urlpath,
version
} from './lib/config'
@@ -66,10 +66,9 @@ import {
import {
writeHistory,
- deleteServerHistory,
getHistory,
saveHistory,
- removeHistory
+ removeHistory, deleteServerHistoryAsync
} from './history'
import { preventXSS } from './render'
@@ -396,7 +395,7 @@ function setNeedRefresh () {
showStatus(statusType.offline)
}
-setloginStateChangeEvent(function () {
+setLoginStateChangeEvent(function () {
setRefreshModal('user-state-changed')
setNeedRefresh()
})
@@ -1752,11 +1751,14 @@ socket.on('error', function (data) {
console.error(data)
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) {
- deleteServerHistory(noteid, function (err, data) {
- if (!err) location.href = serverurl
- })
+ try {
+ await deleteServerHistoryAsync(noteid)
+ location.href = serverurl
+ } catch (err) {
+ console.error(err)
+ }
} else {
getHistory(function (notehistory) {
var newnotehistory = removeHistory(noteid, notehistory)
diff --git a/public/js/lib/common/login.ts b/public/js/lib/common/login.ts
index 15c64668..770ccd9a 100644
--- a/public/js/lib/common/login.ts
+++ b/public/js/lib/common/login.ts
@@ -1,24 +1,28 @@
/* eslint-env browser, jquery */
/* 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 lastLoginState = getLoginState()
-let lastUserId = getUserId()
-let loginStateChangeEvent = null
+let lastUserId: string | null = getUserId()
+let loginStateChangeEvent: PureFunction | null = null
-export function setloginStateChangeEvent (func) {
+export function setLoginStateChangeEvent(func: PureFunction): void {
loginStateChangeEvent = func
}
-export function resetCheckAuth () {
+export function resetCheckAuth(): void {
checkAuth = false
}
-export function setLoginState (bool, id) {
- Cookies.set('loginstate', bool, {
+function setLoginState(bool: boolean, id?: string) {
+ Cookies.set('loginstate', String(bool), {
expires: 365
})
if (id) {
@@ -29,64 +33,81 @@ export function setLoginState (bool, id) {
Cookies.remove('userid')
}
lastLoginState = bool
- lastUserId = id
+ lastUserId = id || null
checkLoginStateChanged()
}
-export function checkLoginStateChanged () {
+export function checkLoginStateChanged(): boolean {
if (getLoginState() !== lastLoginState || getUserId() !== lastUserId) {
if (loginStateChangeEvent) setTimeout(loginStateChangeEvent, 100)
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 {
+ 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 {
+ try {
+ await getLoginUserProfile()
+ return true
+ } catch (e) {
return false
}
}
-export function getLoginState () {
- const state = Cookies.get('loginstate')
- 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) {
+export function checkIfAuth(yesCallback: YesCallbackFunc, noCallback: PureFunction) {
+ getLoginUserProfile().then((profile) => {
yesCallback(profile)
- } else {
+ }).catch(() => {
noCallback()
- }
-}
-
-export default {
- checkAuth,
- profile,
- lastLoginState,
- lastUserId,
- loginStateChangeEvent
+ })
}
diff --git a/public/js/lib/config/index.ts b/public/js/lib/config/index.ts
index 89fe0bfb..04c631ba 100644
--- a/public/js/lib/config/index.ts
+++ b/public/js/lib/config/index.ts
@@ -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 || ''
diff --git a/public/js/locale.ts b/public/js/locale.ts
index 71c0f99f..89a93820 100644
--- a/public/js/locale.ts
+++ b/public/js/locale.ts
@@ -1,31 +1,48 @@
/* 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')
- if (lang === 'zh') {
- lang = 'zh-TW'
+import $ from 'jquery'
+import Cookies from 'js-cookie'
+
+const userLang = navigator.language || navigator.userLanguage
+const userLangCode = userLang.split('-')[0]
+
+let lang = 'en'
+let supportLangs: string[] = []
+
+function reloadLanguageFromCookie () {
+ if (Cookies.get('locale')) {
+ 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)
-$('select.ui-locale option[value="' + lang + '"]').attr('selected', 'selected')
-
-locale.change(function () {
- Cookies.set('locale', $(this).val(), {
- expires: 365
+function fillSupportLanguageArrayFromDropdownOptions () {
+ const $langOptions = $('.ui-locale option');
+ $langOptions.each(function () {
+ supportLangs.push($(this).val() as string)
})
- 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()
+ })
+}
diff --git a/public/js/render.ts b/public/js/render.ts
index 4a9c3b25..66ce713d 100644
--- a/public/js/render.ts
+++ b/public/js/render.ts
@@ -1,16 +1,15 @@
/* eslint-env browser, jquery */
// 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
// 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
-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
-var whiteList = filterXSS.whiteList
// allow ol specify start number
whiteList.ol = ['start']
// allow li specify value number
@@ -34,7 +33,7 @@ whiteList.figure = []
// allow figcaption tag
whiteList.figcaption = []
-var filterXSSOptions = {
+const filterXSSOptions = {
allowCommentTag: true,
whiteList: whiteList,
escapeHtml: function (html) {
@@ -51,28 +50,25 @@ var filterXSSOptions = {
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) + '"'
+ return name + '="' + escapeAttrValue(value) + '"'
}
// allow data uri in img src
if (isWhiteAttr && (tag === 'img' && name === 'src') && dataUriRegex.test(value)) {
- return name + '="' + filterXSS.escapeAttrValue(value) + '"'
+ return name + '="' + escapeAttrValue(value) + '"'
}
},
onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {
// allow attr start with 'data-' or in the whiteListAttr
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 + '="' + escapeAttrValue(value) + '"'
}
}
}
-function preventXSS (html) {
+export function preventXSS (html) {
return filterXSS(html, filterXSSOptions)
}
window.preventXSS = preventXSS
-module.exports = {
- preventXSS: preventXSS,
- escapeAttrValue: filterXSS.escapeAttrValue
-}
+export {escapeAttrValue} from 'xss'
diff --git a/public/js/tsconfig.json b/public/js/tsconfig.json
new file mode 100644
index 00000000..1ab62d9e
--- /dev/null
+++ b/public/js/tsconfig.json
@@ -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": [
+ "./"
+ ]
+}
diff --git a/public/js/typings/index.d.ts b/public/js/typings/index.d.ts
new file mode 100644
index 00000000..51da1516
--- /dev/null
+++ b/public/js/typings/index.d.ts
@@ -0,0 +1,12 @@
+declare global {
+ interface Window {
+ Viz: any
+ createtime: any
+ lastchangetime: any
+ lastchangeui: any
+ owner: any
+ ownerprofile: any
+ whiteListAttr: any
+ preventXSS: any
+ }
+}
diff --git a/public/js/utils.ts b/public/js/utils.ts
index 6a98ce9e..82183cbc 100644
--- a/public/js/utils.ts
+++ b/public/js/utils.ts
@@ -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
-export function checkNoteIdValid (id) {
+export function checkNoteIdValid (id: string) {
const result = id.match(uuidRegex)
return !!(result && result.length === 1)
}
-export function encodeNoteId (id) {
+export function encodeNoteId (id: string) {
// remove dashes in UUID and encode in url-safe base64
const str = id.replace(/-/g, '')
const hexStr = Buffer.from(str, 'hex')
return base64url.encode(hexStr)
}
-export function decodeNoteId (encodedId) {
+export function decodeNoteId (encodedId: string) {
// decode from url-safe base64
const id = base64url.toBuffer(encodedId).toString('hex')
// add dashes between the UUID string parts
- const idParts = []
+ const idParts: string[] = []
idParts.push(id.substr(0, 8))
idParts.push(id.substr(8, 4))
idParts.push(id.substr(12, 4))