mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-12 17:26:17 +00:00
fixed eslint error & integrated with prettier as well as formatted the whole codebase (#3450)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import CSSModules from 'react-css-modules'
|
||||
|
||||
export default function (component, styles) {
|
||||
return CSSModules(component, styles, {handleNotFoundStyleName: 'log'})
|
||||
export default function(component, styles) {
|
||||
return CSSModules(component, styles, { handleNotFoundStyleName: 'log' })
|
||||
}
|
||||
|
||||
@@ -78,13 +78,13 @@ const languages = [
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
getLocales () {
|
||||
return languages.reduce(function (localeList, locale) {
|
||||
getLocales() {
|
||||
return languages.reduce(function(localeList, locale) {
|
||||
localeList.push(locale.locale)
|
||||
return localeList
|
||||
}, [])
|
||||
},
|
||||
getLanguages () {
|
||||
getLanguages() {
|
||||
return languages
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
class MutableMap {
|
||||
constructor (iterable) {
|
||||
constructor(iterable) {
|
||||
this._map = new Map(iterable)
|
||||
Object.defineProperty(this, 'size', {
|
||||
get: () => this._map.size,
|
||||
set: function (value) {
|
||||
set: function(value) {
|
||||
this['size'] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get (...args) {
|
||||
get(...args) {
|
||||
return this._map.get(...args)
|
||||
}
|
||||
|
||||
set (...args) {
|
||||
set(...args) {
|
||||
return this._map.set(...args)
|
||||
}
|
||||
|
||||
delete (...args) {
|
||||
delete(...args) {
|
||||
return this._map.delete(...args)
|
||||
}
|
||||
|
||||
has (...args) {
|
||||
has(...args) {
|
||||
return this._map.has(...args)
|
||||
}
|
||||
|
||||
clear (...args) {
|
||||
clear(...args) {
|
||||
return this._map.clear(...args)
|
||||
}
|
||||
|
||||
forEach (...args) {
|
||||
forEach(...args) {
|
||||
return this._map.forEach(...args)
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
[Symbol.iterator]() {
|
||||
return this._map[Symbol.iterator]()
|
||||
}
|
||||
|
||||
map (cb) {
|
||||
map(cb) {
|
||||
const result = []
|
||||
for (const [key, value] of this._map) {
|
||||
result.push(cb(value, key))
|
||||
@@ -45,7 +45,7 @@ class MutableMap {
|
||||
return result
|
||||
}
|
||||
|
||||
toJS () {
|
||||
toJS() {
|
||||
const result = {}
|
||||
for (let [key, value] of this._map) {
|
||||
if (value instanceof MutableSet || value instanceof MutableMap) {
|
||||
@@ -58,42 +58,42 @@ class MutableMap {
|
||||
}
|
||||
|
||||
class MutableSet {
|
||||
constructor (iterable) {
|
||||
constructor(iterable) {
|
||||
this._set = new Set(iterable)
|
||||
Object.defineProperty(this, 'size', {
|
||||
get: () => this._set.size,
|
||||
set: function (value) {
|
||||
set: function(value) {
|
||||
this['size'] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
add (...args) {
|
||||
add(...args) {
|
||||
return this._set.add(...args)
|
||||
}
|
||||
|
||||
delete (...args) {
|
||||
delete(...args) {
|
||||
return this._set.delete(...args)
|
||||
}
|
||||
|
||||
forEach (...args) {
|
||||
forEach(...args) {
|
||||
return this._set.forEach(...args)
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
[Symbol.iterator]() {
|
||||
return this._set[Symbol.iterator]()
|
||||
}
|
||||
|
||||
map (cb) {
|
||||
map(cb) {
|
||||
const result = []
|
||||
this._set.forEach(function (value, key) {
|
||||
this._set.forEach(function(value, key) {
|
||||
result.push(cb(value, key))
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
toJS () {
|
||||
toJS() {
|
||||
return Array.from(this._set)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ const BOOSTNOTERC = '.boostnoterc'
|
||||
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
|
||||
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
|
||||
|
||||
export function parse (boostnotercPath = _boostnotercPath) {
|
||||
export function parse(boostnotercPath = _boostnotercPath) {
|
||||
if (!sander.existsSync(boostnotercPath)) return {}
|
||||
try {
|
||||
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
console.warn('Your .boostnoterc is broken so it\'s not used.')
|
||||
console.warn("Your .boostnoterc is broken so it's not used.")
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import fs from 'fs'
|
||||
import consts from './consts'
|
||||
|
||||
class SnippetManager {
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.defaultSnippet = [
|
||||
{
|
||||
id: crypto.randomBytes(16).toString('hex'),
|
||||
name: 'Dummy text',
|
||||
prefix: ['lorem', 'ipsum'],
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||
content:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||
}
|
||||
]
|
||||
this.snippets = []
|
||||
@@ -18,7 +19,7 @@ class SnippetManager {
|
||||
this.assignSnippets = this.assignSnippets.bind(this)
|
||||
}
|
||||
|
||||
init () {
|
||||
init() {
|
||||
if (fs.existsSync(consts.SNIPPET_FILE)) {
|
||||
try {
|
||||
this.snippets = JSON.parse(
|
||||
@@ -37,11 +38,11 @@ class SnippetManager {
|
||||
this.snippets = this.defaultSnippet
|
||||
}
|
||||
|
||||
assignSnippets (snippets) {
|
||||
assignSnippets(snippets) {
|
||||
this.snippets = snippets
|
||||
}
|
||||
|
||||
expandSnippet (wordBeforeCursor, cursor, cm) {
|
||||
expandSnippet(wordBeforeCursor, cursor, cm) {
|
||||
const templateCursorString = ':{}'
|
||||
for (let i = 0; i < this.snippets.length; i++) {
|
||||
if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) {
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import { Point } from '@susisu/mte-kernel'
|
||||
|
||||
export default class TextEditorInterface {
|
||||
constructor (editor) {
|
||||
constructor(editor) {
|
||||
this.editor = editor
|
||||
this.doc = editor.getDoc()
|
||||
this.transaction = false
|
||||
}
|
||||
|
||||
getCursorPosition () {
|
||||
getCursorPosition() {
|
||||
const { line, ch } = this.doc.getCursor()
|
||||
return new Point(line, ch)
|
||||
}
|
||||
|
||||
setCursorPosition (pos) {
|
||||
setCursorPosition(pos) {
|
||||
this.doc.setCursor({
|
||||
line: pos.row,
|
||||
ch: pos.column
|
||||
})
|
||||
}
|
||||
|
||||
setSelectionRange (range) {
|
||||
setSelectionRange(range) {
|
||||
this.doc.setSelection(
|
||||
{ line: range.start.row, ch: range.start.column },
|
||||
{ line: range.end.row, ch: range.end.column }
|
||||
)
|
||||
}
|
||||
|
||||
getLastRow () {
|
||||
getLastRow() {
|
||||
return this.doc.lineCount() - 1
|
||||
}
|
||||
|
||||
acceptsTableEdit () {
|
||||
acceptsTableEdit() {
|
||||
return true
|
||||
}
|
||||
|
||||
getLine (row) {
|
||||
getLine(row) {
|
||||
return this.doc.getLine(row)
|
||||
}
|
||||
|
||||
insertLine (row, line) {
|
||||
insertLine(row, line) {
|
||||
const lastRow = this.getLastRow()
|
||||
if (row > lastRow) {
|
||||
const lastLine = this.getLine(lastRow)
|
||||
@@ -56,7 +56,7 @@ export default class TextEditorInterface {
|
||||
}
|
||||
}
|
||||
|
||||
deleteLine (row) {
|
||||
deleteLine(row) {
|
||||
const lastRow = this.getLastRow()
|
||||
if (row >= lastRow) {
|
||||
if (lastRow > 0) {
|
||||
@@ -76,15 +76,11 @@ export default class TextEditorInterface {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
this.doc.replaceRange(
|
||||
'',
|
||||
{ line: row, ch: 0 },
|
||||
{ line: row + 1, ch: 0 }
|
||||
)
|
||||
this.doc.replaceRange('', { line: row, ch: 0 }, { line: row + 1, ch: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
replaceLines (startRow, endRow, lines) {
|
||||
replaceLines(startRow, endRow, lines) {
|
||||
const lastRow = this.getLastRow()
|
||||
if (endRow > lastRow) {
|
||||
const lastLine = this.getLine(lastRow)
|
||||
@@ -102,7 +98,7 @@ export default class TextEditorInterface {
|
||||
}
|
||||
}
|
||||
|
||||
transact (func) {
|
||||
transact(func) {
|
||||
this.transaction = true
|
||||
func()
|
||||
this.transaction = false
|
||||
|
||||
@@ -3,7 +3,7 @@ import i18n from 'browser/lib/i18n'
|
||||
const { remote } = electron
|
||||
const { dialog } = remote
|
||||
|
||||
export function confirmDeleteNote (confirmDeletion, permanent) {
|
||||
export function confirmDeleteNote(confirmDeletion, permanent) {
|
||||
if (confirmDeletion || permanent) {
|
||||
const alertConfig = {
|
||||
type: 'warning',
|
||||
@@ -13,7 +13,8 @@ export function confirmDeleteNote (confirmDeletion, permanent) {
|
||||
}
|
||||
|
||||
const dialogButtonIndex = dialog.showMessageBox(
|
||||
remote.getCurrentWindow(), alertConfig
|
||||
remote.getCurrentWindow(),
|
||||
alertConfig
|
||||
)
|
||||
|
||||
return dialogButtonIndex === 0
|
||||
|
||||
@@ -9,41 +9,53 @@ const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
|
||||
const paths = [
|
||||
isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH),
|
||||
isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
|
||||
isProduction
|
||||
? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH)
|
||||
: path.resolve(CODEMIRROR_THEME_PATH),
|
||||
isProduction
|
||||
? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH)
|
||||
: path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
|
||||
]
|
||||
|
||||
const themes = paths
|
||||
.map(directory => fs.readdirSync(directory).map(file => {
|
||||
const name = file.substring(0, file.lastIndexOf('.'))
|
||||
.map(directory =>
|
||||
fs.readdirSync(directory).map(file => {
|
||||
const name = file.substring(0, file.lastIndexOf('.'))
|
||||
|
||||
return {
|
||||
name,
|
||||
path: path.join(directory, file),
|
||||
className: `cm-s-${name}`
|
||||
}
|
||||
}))
|
||||
return {
|
||||
name,
|
||||
path: path.join(directory, file),
|
||||
className: `cm-s-${name}`
|
||||
}
|
||||
})
|
||||
)
|
||||
.reduce((accumulator, value) => accumulator.concat(value), [])
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
|
||||
name: 'solarized dark',
|
||||
path: path.join(paths[0], 'solarized.css'),
|
||||
className: `cm-s-solarized cm-s-dark`
|
||||
}, {
|
||||
name: 'solarized light',
|
||||
path: path.join(paths[0], 'solarized.css'),
|
||||
className: `cm-s-solarized cm-s-light`
|
||||
})
|
||||
themes.splice(
|
||||
themes.findIndex(({ name }) => name === 'solarized'),
|
||||
1,
|
||||
{
|
||||
name: 'solarized dark',
|
||||
path: path.join(paths[0], 'solarized.css'),
|
||||
className: `cm-s-solarized cm-s-dark`
|
||||
},
|
||||
{
|
||||
name: 'solarized light',
|
||||
path: path.join(paths[0], 'solarized.css'),
|
||||
className: `cm-s-solarized cm-s-light`
|
||||
}
|
||||
)
|
||||
themes.splice(0, 0, {
|
||||
name: 'default',
|
||||
path: path.join(paths[0], 'elegant.css'),
|
||||
className: `cm-s-default`
|
||||
})
|
||||
|
||||
const snippetFile = process.env.NODE_ENV !== 'test'
|
||||
? path.join(app.getPath('userData'), 'snippets.json')
|
||||
: '' // return nothing as we specified different path to snippets.json in test
|
||||
const snippetFile =
|
||||
process.env.NODE_ENV !== 'test'
|
||||
? path.join(app.getPath('userData'), 'snippets.json')
|
||||
: '' // return nothing as we specified different path to snippets.json in test
|
||||
|
||||
const consts = {
|
||||
FOLDER_COLORS: [
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const { remote } = require('electron')
|
||||
const { Menu, MenuItem } = remote
|
||||
|
||||
function popup (templates) {
|
||||
function popup(templates) {
|
||||
const menu = new Menu()
|
||||
templates.forEach((item) => {
|
||||
templates.forEach(item => {
|
||||
menu.append(new MenuItem(item))
|
||||
})
|
||||
menu.popup(remote.getCurrentWindow())
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import fs from 'fs'
|
||||
|
||||
const {remote} = require('electron')
|
||||
const {Menu} = remote.require('electron')
|
||||
const {clipboard} = remote.require('electron')
|
||||
const {shell} = remote.require('electron')
|
||||
const { remote } = require('electron')
|
||||
const { Menu } = remote.require('electron')
|
||||
const { clipboard } = remote.require('electron')
|
||||
const { shell } = remote.require('electron')
|
||||
const spellcheck = require('./spellcheck')
|
||||
const uri2path = require('file-uri-to-path')
|
||||
|
||||
@@ -16,11 +16,16 @@ const uri2path = require('file-uri-to-path')
|
||||
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||
* @returns {Electron.Menu} The created electron context menu
|
||||
*/
|
||||
const buildEditorContextMenu = function (editor, event) {
|
||||
if (editor == null || event == null || event.pageX == null || event.pageY == null) {
|
||||
const buildEditorContextMenu = function(editor, event) {
|
||||
if (
|
||||
editor == null ||
|
||||
event == null ||
|
||||
event.pageX == null ||
|
||||
event.pageY == null
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const cursor = editor.coordsChar({left: event.pageX, top: event.pageY})
|
||||
const cursor = editor.coordsChar({ left: event.pageX, top: event.pageY })
|
||||
const wordRange = editor.findWordAt(cursor)
|
||||
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
||||
const existingMarks = editor.findMarks(wordRange.anchor, wordRange.head) || []
|
||||
@@ -40,30 +45,44 @@ const buildEditorContextMenu = function (editor, event) {
|
||||
isMisspelled: isMisspelled,
|
||||
spellingSuggestions: suggestion
|
||||
}
|
||||
const template = [{
|
||||
role: 'cut'
|
||||
}, {
|
||||
role: 'copy'
|
||||
}, {
|
||||
role: 'paste'
|
||||
}, {
|
||||
role: 'selectall'
|
||||
}]
|
||||
const template = [
|
||||
{
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
role: 'paste'
|
||||
},
|
||||
{
|
||||
role: 'selectall'
|
||||
}
|
||||
]
|
||||
|
||||
if (selection.isMisspelled) {
|
||||
const suggestions = selection.spellingSuggestions
|
||||
template.unshift.apply(template, suggestions.map(function (suggestion) {
|
||||
return {
|
||||
label: suggestion,
|
||||
click: function (suggestion) {
|
||||
if (editor != null) {
|
||||
editor.replaceRange(suggestion.label, wordRange.anchor, wordRange.head)
|
||||
template.unshift.apply(
|
||||
template,
|
||||
suggestions
|
||||
.map(function(suggestion) {
|
||||
return {
|
||||
label: suggestion,
|
||||
click: function(suggestion) {
|
||||
if (editor != null) {
|
||||
editor.replaceRange(
|
||||
suggestion.label,
|
||||
wordRange.anchor,
|
||||
wordRange.head
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).concat({
|
||||
type: 'separator'
|
||||
}))
|
||||
})
|
||||
.concat({
|
||||
type: 'separator'
|
||||
})
|
||||
)
|
||||
}
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
@@ -74,19 +93,30 @@ const buildEditorContextMenu = function (editor, event) {
|
||||
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||
* @returns {Electron.Menu} The created electron context menu
|
||||
*/
|
||||
const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
|
||||
if (markdownPreview == null || event == null || event.pageX == null || event.pageY == null) {
|
||||
const buildMarkdownPreviewContextMenu = function(markdownPreview, event) {
|
||||
if (
|
||||
markdownPreview == null ||
|
||||
event == null ||
|
||||
event.pageX == null ||
|
||||
event.pageY == null
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Default context menu inclusions
|
||||
const template = [{
|
||||
role: 'copy'
|
||||
}, {
|
||||
role: 'selectall'
|
||||
}]
|
||||
const template = [
|
||||
{
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
role: 'selectall'
|
||||
}
|
||||
]
|
||||
|
||||
if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
|
||||
if (
|
||||
event.target.tagName.toLowerCase() === 'a' &&
|
||||
event.target.getAttribute('href')
|
||||
) {
|
||||
// Link opener for files on the local system pointed to by href
|
||||
const href = event.target.href
|
||||
const isLocalFile = href.startsWith('file:')
|
||||
@@ -94,31 +124,29 @@ const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
|
||||
const absPath = uri2path(href)
|
||||
try {
|
||||
if (fs.lstatSync(absPath).isFile()) {
|
||||
template.push(
|
||||
{
|
||||
label: i18n.__('Show in explorer'),
|
||||
click: (e) => shell.showItemInFolder(absPath)
|
||||
}
|
||||
)
|
||||
template.push({
|
||||
label: i18n.__('Show in explorer'),
|
||||
click: e => shell.showItemInFolder(absPath)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error while evaluating if the file is locally available', e)
|
||||
console.log(
|
||||
'Error while evaluating if the file is locally available',
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Add option to context menu to copy url
|
||||
template.push(
|
||||
{
|
||||
label: i18n.__('Copy Url'),
|
||||
click: (e) => clipboard.writeText(href)
|
||||
}
|
||||
)
|
||||
template.push({
|
||||
label: i18n.__('Copy Url'),
|
||||
click: e => clipboard.writeText(href)
|
||||
})
|
||||
}
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
|
||||
module.exports =
|
||||
{
|
||||
module.exports = {
|
||||
buildEditorContextMenu: buildEditorContextMenu,
|
||||
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function convertModeName (name) {
|
||||
export default function convertModeName(name) {
|
||||
switch (name) {
|
||||
case 'ejs':
|
||||
return 'Embedded Javascript'
|
||||
|
||||
@@ -3,8 +3,19 @@ import 'codemirror-mode-elixir'
|
||||
|
||||
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
|
||||
if (stylusCodeInfo == null) {
|
||||
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
||||
CodeMirror.modeInfo.push({
|
||||
name: 'Stylus',
|
||||
mime: 'text/x-styl',
|
||||
mode: 'stylus',
|
||||
ext: ['styl'],
|
||||
alias: ['styl']
|
||||
})
|
||||
} else {
|
||||
stylusCodeInfo.alias = ['styl']
|
||||
}
|
||||
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})
|
||||
CodeMirror.modeInfo.push({
|
||||
name: 'Elixir',
|
||||
mime: 'text/x-elixir',
|
||||
mode: 'elixir',
|
||||
ext: ['ex']
|
||||
})
|
||||
|
||||
@@ -8,7 +8,7 @@ import moment from 'moment'
|
||||
* @param {mixed}
|
||||
* @return {string}
|
||||
*/
|
||||
export function formatDate (date) {
|
||||
export function formatDate(date) {
|
||||
const m = moment(date)
|
||||
if (!m.isValid()) {
|
||||
throw Error('Invalid argument.')
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') {
|
||||
export function findNoteTitle(
|
||||
value,
|
||||
enableFrontMatterTitle,
|
||||
frontMatterTitleField = 'title'
|
||||
) {
|
||||
const splitted = value.split('\n')
|
||||
let title = null
|
||||
let isInsideCodeBlock = false
|
||||
@@ -6,8 +10,13 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
|
||||
if (splitted[0] === '---') {
|
||||
let line = 0
|
||||
while (++line < splitted.length) {
|
||||
if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) {
|
||||
title = splitted[line].substring(frontMatterTitleField.length + 1).trim()
|
||||
if (
|
||||
enableFrontMatterTitle &&
|
||||
splitted[line].startsWith(frontMatterTitleField + ':')
|
||||
) {
|
||||
title = splitted[line]
|
||||
.substring(frontMatterTitleField.length + 1)
|
||||
.trim()
|
||||
|
||||
break
|
||||
}
|
||||
@@ -22,11 +31,15 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
|
||||
if (title === null) {
|
||||
splitted.some((line, index) => {
|
||||
const trimmedLine = line.trim()
|
||||
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||
const trimmedNextLine =
|
||||
splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||
if (trimmedLine.match('```')) {
|
||||
isInsideCodeBlock = !isInsideCodeBlock
|
||||
}
|
||||
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
|
||||
if (
|
||||
isInsideCodeBlock === false &&
|
||||
(trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))
|
||||
) {
|
||||
title = trimmedLine
|
||||
return true
|
||||
}
|
||||
@@ -35,7 +48,7 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
|
||||
|
||||
if (title === null) {
|
||||
title = ''
|
||||
splitted.some((line) => {
|
||||
splitted.some(line => {
|
||||
if (line.trim().length > 0) {
|
||||
title = line.trim()
|
||||
return true
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
export function findStorage (storageKey) {
|
||||
export function findStorage(storageKey) {
|
||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
const storage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
||||
if (!_.isArray(cachedStorageList))
|
||||
throw new Error("Target storage doesn't exist.")
|
||||
const storage = _.find(cachedStorageList, { key: storageKey })
|
||||
if (storage === undefined) throw new Error("Target storage doesn't exist.")
|
||||
|
||||
return storage
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export function getTodoStatus (content) {
|
||||
export function getTodoStatus(content) {
|
||||
const splitted = content.split('\n')
|
||||
let numberOfTodo = 0
|
||||
let numberOfCompletedTodo = 0
|
||||
|
||||
splitted.forEach((line) => {
|
||||
splitted.forEach(line => {
|
||||
const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
|
||||
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
|
||||
numberOfTodo++
|
||||
@@ -19,7 +19,7 @@ export function getTodoStatus (content) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getTodoPercentageOfCompleted (content) {
|
||||
export function getTodoPercentageOfCompleted(content) {
|
||||
const state = getTodoStatus(content)
|
||||
return Math.floor(state.completed / state.total * 100)
|
||||
return Math.floor((state.completed / state.total) * 100)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
* @return {string}
|
||||
*/
|
||||
|
||||
export function decodeEntities (text) {
|
||||
export function decodeEntities(text) {
|
||||
var entities = [
|
||||
['apos', '\''],
|
||||
['apos', "'"],
|
||||
['amp', '&'],
|
||||
['lt', '<'],
|
||||
['gt', '>'],
|
||||
@@ -24,16 +24,16 @@ export function decodeEntities (text) {
|
||||
return text
|
||||
}
|
||||
|
||||
export function encodeEntities (text) {
|
||||
export function encodeEntities(text) {
|
||||
const entities = [
|
||||
['\'', 'apos'],
|
||||
["'", 'apos'],
|
||||
['<', 'lt'],
|
||||
['>', 'gt'],
|
||||
['\\?', '#63'],
|
||||
['\\$', '#36']
|
||||
]
|
||||
|
||||
entities.forEach((entity) => {
|
||||
entities.forEach(entity => {
|
||||
text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
|
||||
})
|
||||
return text
|
||||
|
||||
@@ -8,9 +8,10 @@ const i18n = new (require('i18n-2'))({
|
||||
// setup some locales - other locales default to the first locale
|
||||
locales: getLocales(),
|
||||
extension: '.json',
|
||||
directory: process.env.NODE_ENV === 'production'
|
||||
? path.join(app.getAppPath(), './locales')
|
||||
: path.resolve('./locales'),
|
||||
directory:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? path.join(app.getAppPath(), './locales')
|
||||
: path.resolve('./locales'),
|
||||
devMode: false
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const crypto = require('crypto')
|
||||
const uuidv4 = require('uuid/v4')
|
||||
|
||||
module.exports = function (uuid) {
|
||||
module.exports = function(uuid) {
|
||||
if (typeof uuid === typeof true && uuid) {
|
||||
return uuidv4()
|
||||
}
|
||||
|
||||
@@ -1,35 +1,44 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function definitionListPlugin (md) {
|
||||
module.exports = function definitionListPlugin(md) {
|
||||
var isSpace = md.utils.isSpace
|
||||
|
||||
// Search `[:~][\n ]`, returns next pos after marker on success
|
||||
// or -1 on fail.
|
||||
function skipMarker (state, line) {
|
||||
function skipMarker(state, line) {
|
||||
let start = state.bMarks[line] + state.tShift[line]
|
||||
const max = state.eMarks[line]
|
||||
|
||||
if (start >= max) { return -1 }
|
||||
if (start >= max) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Check bullet
|
||||
const marker = state.src.charCodeAt(start++)
|
||||
if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1 }
|
||||
if (marker !== 0x7e /* ~ */ && marker !== 0x3a /* : */) {
|
||||
return -1
|
||||
}
|
||||
|
||||
const pos = state.skipSpaces(start)
|
||||
|
||||
// require space after ":"
|
||||
if (start === pos) { return -1 }
|
||||
if (start === pos) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return start
|
||||
}
|
||||
|
||||
function markTightParagraphs (state, idx) {
|
||||
function markTightParagraphs(state, idx) {
|
||||
const level = state.level + 2
|
||||
|
||||
let i
|
||||
let l
|
||||
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
|
||||
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
|
||||
if (
|
||||
state.tokens[i].level === level &&
|
||||
state.tokens[i].type === 'paragraph_open'
|
||||
) {
|
||||
state.tokens[i + 2].hidden = true
|
||||
state.tokens[i].hidden = true
|
||||
i += 2
|
||||
@@ -37,7 +46,7 @@ module.exports = function definitionListPlugin (md) {
|
||||
}
|
||||
}
|
||||
|
||||
function deflist (state, startLine, endLine, silent) {
|
||||
function deflist(state, startLine, endLine, silent) {
|
||||
var ch,
|
||||
contentStart,
|
||||
ddLine,
|
||||
@@ -63,28 +72,38 @@ module.exports = function definitionListPlugin (md) {
|
||||
|
||||
if (silent) {
|
||||
// quirk: validation mode validates a dd block only, not a whole deflist
|
||||
if (state.ddIndent < 0) { return false }
|
||||
if (state.ddIndent < 0) {
|
||||
return false
|
||||
}
|
||||
return skipMarker(state, startLine) >= 0
|
||||
}
|
||||
|
||||
nextLine = startLine + 1
|
||||
if (nextLine >= endLine) { return false }
|
||||
if (nextLine >= endLine) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (state.isEmpty(nextLine)) {
|
||||
nextLine++
|
||||
if (nextLine >= endLine) { return false }
|
||||
if (nextLine >= endLine) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (state.sCount[nextLine] < state.blkIndent) { return false }
|
||||
if (state.sCount[nextLine] < state.blkIndent) {
|
||||
return false
|
||||
}
|
||||
contentStart = skipMarker(state, nextLine)
|
||||
if (contentStart < 0) { return false }
|
||||
if (contentStart < 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Start list
|
||||
listTokIdx = state.tokens.length
|
||||
tight = true
|
||||
|
||||
token = state.push('dl_open', 'dl', 1)
|
||||
token.map = listLines = [ startLine, 0 ]
|
||||
token.map = listLines = [startLine, 0]
|
||||
|
||||
//
|
||||
// Iterate list items
|
||||
@@ -100,34 +119,38 @@ module.exports = function definitionListPlugin (md) {
|
||||
// needed to break out of the second one
|
||||
//
|
||||
/* eslint no-labels:0,block-scoped-var:0 */
|
||||
OUTER:
|
||||
for (;;) {
|
||||
OUTER: for (;;) {
|
||||
prevEmptyEnd = false
|
||||
|
||||
token = state.push('dt_open', 'dt', 1)
|
||||
token.map = [ dtLine, dtLine ]
|
||||
token.map = [dtLine, dtLine]
|
||||
|
||||
token = state.push('inline', '', 0)
|
||||
token.map = [ dtLine, dtLine ]
|
||||
token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim()
|
||||
token.map = [dtLine, dtLine]
|
||||
token.content = state
|
||||
.getLines(dtLine, dtLine + 1, state.blkIndent, false)
|
||||
.trim()
|
||||
token.children = []
|
||||
|
||||
token = state.push('dt_close', 'dt', -1)
|
||||
|
||||
for (;;) {
|
||||
token = state.push('dd_open', 'dd', 1)
|
||||
token.map = itemLines = [ ddLine, 0 ]
|
||||
token.map = itemLines = [ddLine, 0]
|
||||
|
||||
pos = contentStart
|
||||
max = state.eMarks[ddLine]
|
||||
offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine])
|
||||
offset =
|
||||
state.sCount[ddLine] +
|
||||
contentStart -
|
||||
(state.bMarks[ddLine] + state.tShift[ddLine])
|
||||
|
||||
while (pos < max) {
|
||||
ch = state.src.charCodeAt(pos)
|
||||
|
||||
if (isSpace(ch)) {
|
||||
if (ch === 0x09) {
|
||||
offset += 4 - offset % 4
|
||||
offset += 4 - (offset % 4)
|
||||
} else {
|
||||
offset++
|
||||
}
|
||||
@@ -153,8 +176,11 @@ module.exports = function definitionListPlugin (md) {
|
||||
state.parentType = 'deflist'
|
||||
|
||||
newEndLine = ddLine
|
||||
while (++newEndLine < endLine && (state.sCount[newEndLine] >= state.sCount[ddLine] || state.isEmpty(newEndLine))) {
|
||||
}
|
||||
while (
|
||||
++newEndLine < endLine &&
|
||||
(state.sCount[newEndLine] >= state.sCount[ddLine] ||
|
||||
state.isEmpty(newEndLine))
|
||||
) {}
|
||||
|
||||
oldLineMax = state.lineMax
|
||||
state.lineMax = newEndLine
|
||||
@@ -169,7 +195,7 @@ module.exports = function definitionListPlugin (md) {
|
||||
}
|
||||
// Item become loose if finish with empty line,
|
||||
// but we should filter last element, because it means list finish
|
||||
prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1)
|
||||
prevEmptyEnd = state.line - ddLine > 1 && state.isEmpty(state.line - 1)
|
||||
|
||||
state.tShift[ddLine] = oldTShift
|
||||
state.sCount[ddLine] = oldSCount
|
||||
@@ -182,11 +208,17 @@ module.exports = function definitionListPlugin (md) {
|
||||
|
||||
itemLines[1] = nextLine = state.line
|
||||
|
||||
if (nextLine >= endLine) { break OUTER }
|
||||
if (nextLine >= endLine) {
|
||||
break OUTER
|
||||
}
|
||||
|
||||
if (state.sCount[nextLine] < state.blkIndent) { break OUTER }
|
||||
if (state.sCount[nextLine] < state.blkIndent) {
|
||||
break OUTER
|
||||
}
|
||||
contentStart = skipMarker(state, nextLine)
|
||||
if (contentStart < 0) { break }
|
||||
if (contentStart < 0) {
|
||||
break
|
||||
}
|
||||
|
||||
ddLine = nextLine
|
||||
|
||||
@@ -194,20 +226,36 @@ module.exports = function definitionListPlugin (md) {
|
||||
// insert DD tag and repeat checking
|
||||
}
|
||||
|
||||
if (nextLine >= endLine) { break }
|
||||
if (nextLine >= endLine) {
|
||||
break
|
||||
}
|
||||
dtLine = nextLine
|
||||
|
||||
if (state.isEmpty(dtLine)) { break }
|
||||
if (state.sCount[dtLine] < state.blkIndent) { break }
|
||||
if (state.isEmpty(dtLine)) {
|
||||
break
|
||||
}
|
||||
if (state.sCount[dtLine] < state.blkIndent) {
|
||||
break
|
||||
}
|
||||
|
||||
ddLine = dtLine + 1
|
||||
if (ddLine >= endLine) { break }
|
||||
if (state.isEmpty(ddLine)) { ddLine++ }
|
||||
if (ddLine >= endLine) { break }
|
||||
if (ddLine >= endLine) {
|
||||
break
|
||||
}
|
||||
if (state.isEmpty(ddLine)) {
|
||||
ddLine++
|
||||
}
|
||||
if (ddLine >= endLine) {
|
||||
break
|
||||
}
|
||||
|
||||
if (state.sCount[ddLine] < state.blkIndent) { break }
|
||||
if (state.sCount[ddLine] < state.blkIndent) {
|
||||
break
|
||||
}
|
||||
contentStart = skipMarker(state, ddLine)
|
||||
if (contentStart < 0) { break }
|
||||
if (contentStart < 0) {
|
||||
break
|
||||
}
|
||||
|
||||
// go to the next loop iteration:
|
||||
// insert DT and DD tags and repeat checking
|
||||
@@ -228,5 +276,7 @@ module.exports = function definitionListPlugin (md) {
|
||||
return true
|
||||
}
|
||||
|
||||
md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference' ] })
|
||||
md.block.ruler.before('paragraph', 'deflist', deflist, {
|
||||
alt: ['paragraph', 'reference']
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function (md, renderers, defaultRenderer) {
|
||||
module.exports = function(md, renderers, defaultRenderer) {
|
||||
const paramsRE = /^[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/
|
||||
|
||||
function fence (state, startLine, endLine, silent) {
|
||||
function fence(state, startLine, endLine, silent) {
|
||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
let max = state.eMarks[startLine]
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = function (md, renderers, defaultRenderer) {
|
||||
}
|
||||
|
||||
const marker = state.src.charCodeAt(pos)
|
||||
if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
|
||||
if (marker !== 0x7e /* ~ */ && marker !== 0x60 /* ` */) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -46,7 +46,10 @@ module.exports = function (md, renderers, defaultRenderer) {
|
||||
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
|
||||
break
|
||||
}
|
||||
if (state.src.charCodeAt(pos) !== marker || state.sCount[nextLine] - state.blkIndent >= 4) {
|
||||
if (
|
||||
state.src.charCodeAt(pos) !== marker ||
|
||||
state.sCount[nextLine] - state.blkIndent >= 4
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -127,10 +130,12 @@ module.exports = function (md, renderers, defaultRenderer) {
|
||||
})
|
||||
|
||||
for (const name in renderers) {
|
||||
md.renderer.rules[`${name}_fence`] = (tokens, index) => renderers[name](tokens[index])
|
||||
md.renderer.rules[`${name}_fence`] = (tokens, index) =>
|
||||
renderers[name](tokens[index])
|
||||
}
|
||||
|
||||
if (defaultRenderer) {
|
||||
md.renderer.rules['_fence'] = (tokens, index) => defaultRenderer(tokens[index])
|
||||
md.renderer.rules['_fence'] = (tokens, index) =>
|
||||
defaultRenderer(tokens[index])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function frontMatterPlugin (md) {
|
||||
function frontmatter (state, startLine, endLine, silent) {
|
||||
if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') {
|
||||
module.exports = function frontMatterPlugin(md) {
|
||||
function frontmatter(state, startLine, endLine, silent) {
|
||||
if (
|
||||
startLine !== 0 ||
|
||||
state.src.substr(startLine, state.eMarks[0]) !== '---'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
let line = 0
|
||||
while (++line < state.lineMax) {
|
||||
if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') {
|
||||
if (
|
||||
state.src.substring(state.bMarks[line], state.eMarks[line]) === '---'
|
||||
) {
|
||||
state.line = line + 1
|
||||
|
||||
return true
|
||||
@@ -19,6 +24,6 @@ module.exports = function frontMatterPlugin (md) {
|
||||
}
|
||||
|
||||
md.block.ruler.before('table', 'frontmatter', frontmatter, {
|
||||
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import sanitizeHtml from 'sanitize-html'
|
||||
import { escapeHtmlCharacters } from './utils'
|
||||
import url from 'url'
|
||||
|
||||
module.exports = function sanitizePlugin (md, options) {
|
||||
module.exports = function sanitizePlugin(md, options) {
|
||||
options = options || {}
|
||||
|
||||
md.core.ruler.after('linkify', 'sanitize_inline', state => {
|
||||
@@ -38,15 +38,20 @@ module.exports = function sanitizePlugin (md, options) {
|
||||
}
|
||||
|
||||
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
|
||||
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
|
||||
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/gi
|
||||
|
||||
function sanitizeInline (html, options) {
|
||||
function sanitizeInline(html, options) {
|
||||
let match = tagRegex.exec(html)
|
||||
if (!match) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
|
||||
const {
|
||||
allowedTags,
|
||||
allowedAttributes,
|
||||
selfClosing,
|
||||
allowedSchemesAppliedToAttributes
|
||||
} = options
|
||||
|
||||
if (match[1] !== undefined) {
|
||||
// opening tag
|
||||
@@ -65,9 +70,17 @@ function sanitizeInline (html, options) {
|
||||
name = match[1].toLowerCase()
|
||||
value = match[3]
|
||||
|
||||
if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) {
|
||||
if (
|
||||
allowedAttributes['*'].indexOf(name) !== -1 ||
|
||||
(allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)
|
||||
) {
|
||||
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
|
||||
if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
|
||||
if (
|
||||
naughtyHRef(value, options) ||
|
||||
(tag === 'iframe' &&
|
||||
name === 'src' &&
|
||||
naughtyIFrame(value, options))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -94,7 +107,7 @@ function sanitizeInline (html, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function naughtyHRef (href, options) {
|
||||
function naughtyHRef(href, options) {
|
||||
// href = href.replace(/[\x00-\x20]+/g, '')
|
||||
if (!href) {
|
||||
// No href
|
||||
@@ -117,7 +130,7 @@ function naughtyHRef (href, options) {
|
||||
return options.allowedSchemes.indexOf(scheme) === -1
|
||||
}
|
||||
|
||||
function naughtyIFrame (src, options) {
|
||||
function naughtyIFrame(src, options) {
|
||||
try {
|
||||
const parsed = url.parse(src, false, true)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const hasProp = Object.prototype.hasOwnProperty
|
||||
/**
|
||||
* From @enyaxu/markdown-it-anchor
|
||||
*/
|
||||
function uniqueSlug (slug, slugs, opts) {
|
||||
function uniqueSlug(slug, slugs, opts) {
|
||||
let uniq = slug
|
||||
let i = opts.uniqueSlugStartIndex
|
||||
while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
|
||||
@@ -20,7 +20,7 @@ function uniqueSlug (slug, slugs, opts) {
|
||||
return uniq
|
||||
}
|
||||
|
||||
function linkify (token) {
|
||||
function linkify(token) {
|
||||
token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
|
||||
return token
|
||||
}
|
||||
@@ -36,8 +36,8 @@ const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
|
||||
* Otherwise,TOC is updated in place.
|
||||
* @param editor CodeMirror editor to be updated with TOC
|
||||
*/
|
||||
export function generateInEditor (editor) {
|
||||
function updateExistingToc () {
|
||||
export function generateInEditor(editor) {
|
||||
function updateExistingToc() {
|
||||
const toc = generate(editor.getValue())
|
||||
const search = editor.getSearchCursor(tocRegex)
|
||||
while (search.findNext()) {
|
||||
@@ -45,8 +45,10 @@ export function generateInEditor (editor) {
|
||||
}
|
||||
}
|
||||
|
||||
function addTocAtCursorPosition () {
|
||||
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
|
||||
function addTocAtCursorPosition() {
|
||||
const toc = generate(
|
||||
editor.getRange(editor.getCursor(), { line: Infinity })
|
||||
)
|
||||
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
|
||||
}
|
||||
|
||||
@@ -57,7 +59,7 @@ export function generateInEditor (editor) {
|
||||
}
|
||||
}
|
||||
|
||||
export function tocExistsInEditor (editor) {
|
||||
export function tocExistsInEditor(editor) {
|
||||
return tocRegex.test(editor.getValue())
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ export function tocExistsInEditor (editor) {
|
||||
* @param markdownText MD document
|
||||
* @returns generatedTOC String containing generated TOC
|
||||
*/
|
||||
export function generate (markdownText) {
|
||||
export function generate(markdownText) {
|
||||
const slugs = {}
|
||||
const opts = {
|
||||
uniqueSlugStartIndex: 1
|
||||
@@ -86,9 +88,12 @@ export function generate (markdownText) {
|
||||
return TOC_MARKER_START + EOL + EOL + md + EOL + EOL + TOC_MARKER_END
|
||||
}
|
||||
|
||||
function wrapTocWithEol (toc, editor) {
|
||||
function wrapTocWithEol(toc, editor) {
|
||||
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
|
||||
const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL
|
||||
const rightWrap =
|
||||
editor.getLine(editor.getCursor().line).length === editor.getCursor().ch
|
||||
? ''
|
||||
: EOL
|
||||
return leftWrap + toc + rightWrap
|
||||
}
|
||||
|
||||
|
||||
@@ -10,18 +10,20 @@ import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import katex from 'katex'
|
||||
import { lastFindInArray } from './utils'
|
||||
|
||||
function createGutter (str, firstLineNumber) {
|
||||
function createGutter(str, firstLineNumber) {
|
||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
|
||||
const lines = []
|
||||
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
|
||||
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
||||
}
|
||||
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
||||
return (
|
||||
'<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
||||
)
|
||||
}
|
||||
|
||||
class Markdown {
|
||||
constructor (options = {}) {
|
||||
constructor(options = {}) {
|
||||
const config = ConfigManager.get()
|
||||
const defaultOptions = {
|
||||
typographer: config.preview.smartQuotes,
|
||||
@@ -37,29 +39,129 @@ class Markdown {
|
||||
this.md.linkify.set({ fuzzyLink: false })
|
||||
|
||||
if (updatedOptions.sanitize !== 'NONE') {
|
||||
const allowedTags = ['iframe', 'input', 'b',
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
|
||||
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
|
||||
'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
|
||||
const allowedTags = [
|
||||
'iframe',
|
||||
'input',
|
||||
'b',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'h7',
|
||||
'h8',
|
||||
'br',
|
||||
'b',
|
||||
'i',
|
||||
'strong',
|
||||
'em',
|
||||
'a',
|
||||
'pre',
|
||||
'code',
|
||||
'img',
|
||||
'tt',
|
||||
'div',
|
||||
'ins',
|
||||
'del',
|
||||
'sup',
|
||||
'sub',
|
||||
'p',
|
||||
'ol',
|
||||
'ul',
|
||||
'table',
|
||||
'thead',
|
||||
'tbody',
|
||||
'tfoot',
|
||||
'blockquote',
|
||||
'dl',
|
||||
'dt',
|
||||
'dd',
|
||||
'kbd',
|
||||
'q',
|
||||
'samp',
|
||||
'var',
|
||||
'hr',
|
||||
'ruby',
|
||||
'rt',
|
||||
'rp',
|
||||
'li',
|
||||
'tr',
|
||||
'td',
|
||||
'th',
|
||||
's',
|
||||
'strike',
|
||||
'summary',
|
||||
'details'
|
||||
]
|
||||
const allowedAttributes = [
|
||||
'abbr', 'accept', 'accept-charset',
|
||||
'accesskey', 'action', 'align', 'alt', 'axis',
|
||||
'border', 'cellpadding', 'cellspacing', 'char',
|
||||
'charoff', 'charset', 'checked',
|
||||
'clear', 'cols', 'colspan', 'color',
|
||||
'compact', 'coords', 'datetime', 'dir',
|
||||
'disabled', 'enctype', 'for', 'frame',
|
||||
'headers', 'height', 'hreflang',
|
||||
'hspace', 'ismap', 'label', 'lang',
|
||||
'maxlength', 'media', 'method',
|
||||
'multiple', 'name', 'nohref', 'noshade',
|
||||
'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
|
||||
'rows', 'rowspan', 'rules', 'scope',
|
||||
'selected', 'shape', 'size', 'span',
|
||||
'start', 'summary', 'tabindex', 'target',
|
||||
'title', 'type', 'usemap', 'valign', 'value',
|
||||
'vspace', 'width', 'itemprop'
|
||||
'abbr',
|
||||
'accept',
|
||||
'accept-charset',
|
||||
'accesskey',
|
||||
'action',
|
||||
'align',
|
||||
'alt',
|
||||
'axis',
|
||||
'border',
|
||||
'cellpadding',
|
||||
'cellspacing',
|
||||
'char',
|
||||
'charoff',
|
||||
'charset',
|
||||
'checked',
|
||||
'clear',
|
||||
'cols',
|
||||
'colspan',
|
||||
'color',
|
||||
'compact',
|
||||
'coords',
|
||||
'datetime',
|
||||
'dir',
|
||||
'disabled',
|
||||
'enctype',
|
||||
'for',
|
||||
'frame',
|
||||
'headers',
|
||||
'height',
|
||||
'hreflang',
|
||||
'hspace',
|
||||
'ismap',
|
||||
'label',
|
||||
'lang',
|
||||
'maxlength',
|
||||
'media',
|
||||
'method',
|
||||
'multiple',
|
||||
'name',
|
||||
'nohref',
|
||||
'noshade',
|
||||
'nowrap',
|
||||
'open',
|
||||
'prompt',
|
||||
'readonly',
|
||||
'rel',
|
||||
'rev',
|
||||
'rows',
|
||||
'rowspan',
|
||||
'rules',
|
||||
'scope',
|
||||
'selected',
|
||||
'shape',
|
||||
'size',
|
||||
'span',
|
||||
'start',
|
||||
'summary',
|
||||
'tabindex',
|
||||
'target',
|
||||
'title',
|
||||
'type',
|
||||
'usemap',
|
||||
'valign',
|
||||
'value',
|
||||
'vspace',
|
||||
'width',
|
||||
'itemprop'
|
||||
]
|
||||
|
||||
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
|
||||
@@ -72,20 +174,20 @@ class Markdown {
|
||||
allowedTags,
|
||||
allowedAttributes: {
|
||||
'*': allowedAttributes,
|
||||
'a': ['href'],
|
||||
'div': ['itemscope', 'itemtype'],
|
||||
'blockquote': ['cite'],
|
||||
'del': ['cite'],
|
||||
'ins': ['cite'],
|
||||
'q': ['cite'],
|
||||
'img': ['src', 'width', 'height'],
|
||||
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||
'input': ['type', 'id', 'checked']
|
||||
a: ['href'],
|
||||
div: ['itemscope', 'itemtype'],
|
||||
blockquote: ['cite'],
|
||||
del: ['cite'],
|
||||
ins: ['cite'],
|
||||
q: ['cite'],
|
||||
img: ['src', 'width', 'height'],
|
||||
iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||
input: ['type', 'id', 'checked']
|
||||
},
|
||||
allowedIframeHostnames: ['www.youtube.com'],
|
||||
selfClosing: [ 'img', 'br', 'hr', 'input' ],
|
||||
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
|
||||
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
|
||||
selfClosing: ['img', 'br', 'hr', 'input'],
|
||||
allowedSchemes: ['http', 'https', 'ftp', 'mailto'],
|
||||
allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
|
||||
allowProtocolRelative: true
|
||||
})
|
||||
}
|
||||
@@ -98,7 +200,7 @@ class Markdown {
|
||||
inlineClose: config.preview.latexInlineClose,
|
||||
blockOpen: config.preview.latexBlockOpen,
|
||||
blockClose: config.preview.latexBlockClose,
|
||||
inlineRenderer: function (str) {
|
||||
inlineRenderer: function(str) {
|
||||
let output = ''
|
||||
try {
|
||||
output = katex.renderToString(str.trim())
|
||||
@@ -107,7 +209,7 @@ class Markdown {
|
||||
}
|
||||
return output
|
||||
},
|
||||
blockRenderer: function (str) {
|
||||
blockRenderer: function(str) {
|
||||
let output = ''
|
||||
try {
|
||||
output = katex.renderToString(str.trim(), { displayMode: true })
|
||||
@@ -124,7 +226,19 @@ class Markdown {
|
||||
slugify: require('./slugify')
|
||||
})
|
||||
this.md.use(require('markdown-it-kbd'))
|
||||
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error', 'quote', 'abstract', 'question']})
|
||||
this.md.use(require('markdown-it-admonition'), {
|
||||
types: [
|
||||
'note',
|
||||
'hint',
|
||||
'attention',
|
||||
'caution',
|
||||
'danger',
|
||||
'error',
|
||||
'quote',
|
||||
'abstract',
|
||||
'question'
|
||||
]
|
||||
})
|
||||
this.md.use(require('markdown-it-abbr'))
|
||||
this.md.use(require('markdown-it-sub'))
|
||||
this.md.use(require('markdown-it-sup'))
|
||||
@@ -144,63 +258,86 @@ class Markdown {
|
||||
this.md.use(require('./markdown-it-deflist'))
|
||||
this.md.use(require('./markdown-it-frontmatter'))
|
||||
|
||||
this.md.use(require('./markdown-it-fence'), {
|
||||
chart: token => {
|
||||
if (token.parameters.hasOwnProperty('yaml')) {
|
||||
token.parameters.format = 'yaml'
|
||||
}
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
|
||||
</pre>`
|
||||
},
|
||||
flowchart: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
|
||||
</pre>`
|
||||
},
|
||||
gallery: token => {
|
||||
const content = token.content.split('\n').slice(0, -1).map(line => {
|
||||
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
||||
if (match) {
|
||||
return mdurl.encode(match[1])
|
||||
} else {
|
||||
return mdurl.encode(line)
|
||||
this.md.use(
|
||||
require('./markdown-it-fence'),
|
||||
{
|
||||
chart: token => {
|
||||
if (token.parameters.hasOwnProperty('yaml')) {
|
||||
token.parameters.format = 'yaml'
|
||||
}
|
||||
}).join('\n')
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="gallery" data-autoplay="${token.parameters.autoplay}" data-height="${token.parameters.height}">${content}</div>
|
||||
<div class="chart" data-height="${
|
||||
token.parameters.height
|
||||
}" data-format="${token.parameters.format || 'json'}">${
|
||||
token.content
|
||||
}</div>
|
||||
</pre>`
|
||||
},
|
||||
flowchart: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="flowchart" data-height="${token.parameters.height}">${
|
||||
token.content
|
||||
}</div>
|
||||
</pre>`
|
||||
},
|
||||
gallery: token => {
|
||||
const content = token.content
|
||||
.split('\n')
|
||||
.slice(0, -1)
|
||||
.map(line => {
|
||||
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
||||
if (match) {
|
||||
return mdurl.encode(match[1])
|
||||
} else {
|
||||
return mdurl.encode(line)
|
||||
}
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="gallery" data-autoplay="${
|
||||
token.parameters.autoplay
|
||||
}" data-height="${token.parameters.height}">${content}</div>
|
||||
</pre>`
|
||||
},
|
||||
mermaid: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="mermaid" data-height="${token.parameters.height}">${
|
||||
token.content
|
||||
}</div>
|
||||
</pre>`
|
||||
},
|
||||
sequence: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="sequence" data-height="${token.parameters.height}">${
|
||||
token.content
|
||||
}</div>
|
||||
</pre>`
|
||||
}
|
||||
},
|
||||
mermaid: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
|
||||
</pre>`
|
||||
},
|
||||
sequence: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
|
||||
</pre>`
|
||||
}
|
||||
}, token => {
|
||||
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
||||
token => {
|
||||
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
${createGutter(token.content, token.firstLineNumber)}
|
||||
<code class="${token.langType}">${token.content}</code>
|
||||
</pre>`
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||
const plantuml = require('markdown-it-plantuml')
|
||||
const plantUmlStripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
||||
const plantUmlServerAddress = plantUmlStripTrailingSlash(config.preview.plantUMLServerAddress)
|
||||
const parsePlantUml = function (umlCode, openMarker, closeMarker, type) {
|
||||
const plantUmlStripTrailingSlash = url =>
|
||||
url.endsWith('/') ? url.slice(0, -1) : url
|
||||
const plantUmlServerAddress = plantUmlStripTrailingSlash(
|
||||
config.preview.plantUMLServerAddress
|
||||
)
|
||||
const parsePlantUml = function(umlCode, openMarker, closeMarker, type) {
|
||||
const s = unescape(encodeURIComponent(umlCode))
|
||||
const zippedCode = deflate.encode64(
|
||||
deflate.zip_deflate(`${openMarker}\n${s}\n${closeMarker}`, 9)
|
||||
@@ -209,39 +346,47 @@ class Markdown {
|
||||
}
|
||||
|
||||
this.md.use(plantuml, {
|
||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
|
||||
generateSource: umlCode =>
|
||||
parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
|
||||
})
|
||||
|
||||
// Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
|
||||
this.md.use(plantuml, {
|
||||
openMarker: '@startditaa',
|
||||
closeMarker: '@endditaa',
|
||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
|
||||
generateSource: umlCode =>
|
||||
parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
|
||||
})
|
||||
|
||||
// Mindmap support
|
||||
this.md.use(plantuml, {
|
||||
openMarker: '@startmindmap',
|
||||
closeMarker: '@endmindmap',
|
||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
|
||||
generateSource: umlCode =>
|
||||
parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
|
||||
})
|
||||
|
||||
// WBS support
|
||||
this.md.use(plantuml, {
|
||||
openMarker: '@startwbs',
|
||||
closeMarker: '@endwbs',
|
||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
|
||||
generateSource: umlCode =>
|
||||
parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
|
||||
})
|
||||
|
||||
// Gantt support
|
||||
this.md.use(plantuml, {
|
||||
openMarker: '@startgantt',
|
||||
closeMarker: '@endgantt',
|
||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startgantt', '@endgantt', 'svg')
|
||||
generateSource: umlCode =>
|
||||
parsePlantUml(umlCode, '@startgantt', '@endgantt', 'svg')
|
||||
})
|
||||
|
||||
// Override task item
|
||||
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||
this.md.block.ruler.at('paragraph', function(
|
||||
state,
|
||||
startLine /*, endLine */
|
||||
) {
|
||||
let content, terminate, i, l, token
|
||||
let nextLine = startLine + 1
|
||||
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||
@@ -251,10 +396,14 @@ class Markdown {
|
||||
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||
// this would be a code block normally, but after paragraph
|
||||
// it's considered a lazy continuation regardless of what's there
|
||||
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
|
||||
if (state.sCount[nextLine] - state.blkIndent > 3) {
|
||||
continue
|
||||
}
|
||||
|
||||
// quirk for blockquotes, this line should already be checked by that rule
|
||||
if (state.sCount[nextLine] < 0) { continue }
|
||||
if (state.sCount[nextLine] < 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Some tags can terminate paragraph without empty line.
|
||||
terminate = false
|
||||
@@ -264,10 +413,14 @@ class Markdown {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (terminate) { break }
|
||||
if (terminate) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
|
||||
content = state
|
||||
.getLines(startLine, nextLine, state.blkIndent, false)
|
||||
.trim()
|
||||
|
||||
state.line = nextLine
|
||||
|
||||
@@ -277,18 +430,31 @@ class Markdown {
|
||||
if (state.parentType === 'list') {
|
||||
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
||||
if (match) {
|
||||
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
|
||||
const liToken = lastFindInArray(
|
||||
state.tokens,
|
||||
token => token.type === 'list_item_open'
|
||||
)
|
||||
if (liToken) {
|
||||
if (!liToken.attrs) {
|
||||
liToken.attrs = []
|
||||
}
|
||||
if (config.preview.lineThroughCheckbox) {
|
||||
liToken.attrs.push(['class', `taskListItem${match[1] !== ' ' ? ' checked' : ''}`])
|
||||
liToken.attrs.push([
|
||||
'class',
|
||||
`taskListItem${match[1] !== ' ' ? ' checked' : ''}`
|
||||
])
|
||||
} else {
|
||||
liToken.attrs.push(['class', 'taskListItem'])
|
||||
}
|
||||
}
|
||||
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
|
||||
content = `<label class='taskListItem${
|
||||
match[1] !== ' ' ? ' checked' : ''
|
||||
}' for='checkbox-${startLine + 1}'><input type='checkbox'${
|
||||
match[1] !== ' ' ? ' checked' : ''
|
||||
} id='checkbox-${startLine + 1}'/> ${content.substring(
|
||||
4,
|
||||
content.length
|
||||
)}</label>`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +475,7 @@ class Markdown {
|
||||
// Add line number attribute for scrolling
|
||||
const originalRender = this.md.renderer.render
|
||||
this.md.renderer.render = (tokens, options, env) => {
|
||||
tokens.forEach((token) => {
|
||||
tokens.forEach(token => {
|
||||
switch (token.type) {
|
||||
case 'blockquote_open':
|
||||
case 'dd_open':
|
||||
@@ -330,7 +496,7 @@ class Markdown {
|
||||
window.md = this.md
|
||||
}
|
||||
|
||||
render (content) {
|
||||
render(content) {
|
||||
if (!_.isString(content)) content = ''
|
||||
return this.md.render(content)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @param {string} input
|
||||
* @return {string}
|
||||
*/
|
||||
export function strip (input) {
|
||||
export function strip(input) {
|
||||
let output = input
|
||||
try {
|
||||
output = output
|
||||
|
||||
@@ -4,12 +4,22 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import queryString from 'query-string'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
export function createMarkdownNote (storage, folder, dispatch, location, params, config) {
|
||||
export function createMarkdownNote(
|
||||
storage,
|
||||
folder,
|
||||
dispatch,
|
||||
location,
|
||||
params,
|
||||
config
|
||||
) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||
|
||||
let tags = []
|
||||
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
||||
if (
|
||||
config.ui.tagNewNoteWithFilteringTags &&
|
||||
location.pathname.match(/\/tags/)
|
||||
) {
|
||||
tags = params.tagname.split(' ')
|
||||
}
|
||||
|
||||
@@ -29,25 +39,40 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
|
||||
note: note
|
||||
})
|
||||
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
}))
|
||||
dispatch(
|
||||
push({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
})
|
||||
)
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
})
|
||||
}
|
||||
|
||||
export function createSnippetNote (storage, folder, dispatch, location, params, config) {
|
||||
export function createSnippetNote(
|
||||
storage,
|
||||
folder,
|
||||
dispatch,
|
||||
location,
|
||||
params,
|
||||
config
|
||||
) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||
|
||||
let tags = []
|
||||
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
||||
if (
|
||||
config.ui.tagNewNoteWithFilteringTags &&
|
||||
location.pathname.match(/\/tags/)
|
||||
) {
|
||||
tags = params.tagname.split(' ')
|
||||
}
|
||||
|
||||
const defaultLanguage = config.editor.snippetDefaultLanguage === 'Auto Detect' ? null : config.editor.snippetDefaultLanguage
|
||||
const defaultLanguage =
|
||||
config.editor.snippetDefaultLanguage === 'Auto Detect'
|
||||
? null
|
||||
: config.editor.snippetDefaultLanguage
|
||||
|
||||
return dataApi
|
||||
.createNote(storage, {
|
||||
@@ -71,10 +96,12 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
}))
|
||||
dispatch(
|
||||
push({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
})
|
||||
)
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import consts from 'browser/lib/consts'
|
||||
import isString from 'lodash/isString'
|
||||
|
||||
export default function normalizeEditorFontFamily (fontFamily) {
|
||||
export default function normalizeEditorFontFamily(fontFamily) {
|
||||
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
||||
return isString(fontFamily) && fontFamily.length > 0
|
||||
? [fontFamily].concat(defaultEditorFontFamily).join(', ')
|
||||
|
||||
@@ -1,31 +1,38 @@
|
||||
import _ from 'lodash'
|
||||
|
||||
export default function searchFromNotes (notes, search) {
|
||||
export default function searchFromNotes(notes, search) {
|
||||
if (search.trim().length === 0) return []
|
||||
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
|
||||
const searchBlocks = search.split(' ').filter(block => {
|
||||
return block !== ''
|
||||
})
|
||||
|
||||
let foundNotes = notes
|
||||
searchBlocks.forEach((block) => {
|
||||
searchBlocks.forEach(block => {
|
||||
foundNotes = findByWordOrTag(foundNotes, block)
|
||||
})
|
||||
return foundNotes
|
||||
}
|
||||
|
||||
function findByWordOrTag (notes, block) {
|
||||
function findByWordOrTag(notes, block) {
|
||||
let tag = block
|
||||
if (tag.match(/^#.+/)) {
|
||||
tag = tag.match(/#(.+)/)[1]
|
||||
}
|
||||
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
|
||||
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
|
||||
return notes.filter((note) => {
|
||||
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) {
|
||||
return notes.filter(note => {
|
||||
if (_.isArray(note.tags) && note.tags.some(_tag => _tag.match(tagRegExp))) {
|
||||
return true
|
||||
}
|
||||
if (note.type === 'SNIPPET_NOTE') {
|
||||
return note.description.match(wordRegExp) || note.snippets.some((snippet) => {
|
||||
return snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
|
||||
})
|
||||
return (
|
||||
note.description.match(wordRegExp) ||
|
||||
note.snippets.some(snippet => {
|
||||
return (
|
||||
snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
|
||||
)
|
||||
})
|
||||
)
|
||||
} else if (note.type === 'MARKDOWN_NOTE') {
|
||||
return note.content.match(wordRegExp)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
module.exports = function slugify (title) {
|
||||
module.exports = function slugify(title) {
|
||||
const slug = encodeURI(
|
||||
title.trim()
|
||||
title
|
||||
.trim()
|
||||
.replace(/^\s+/, '')
|
||||
.replace(/\s+$/, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g, '')
|
||||
.replace(
|
||||
/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g,
|
||||
''
|
||||
)
|
||||
)
|
||||
|
||||
return slug
|
||||
|
||||
@@ -12,19 +12,19 @@ const MILLISECONDS_TILL_LIVECHECK = 500
|
||||
let dictionary = null
|
||||
let self
|
||||
|
||||
function getAvailableDictionaries () {
|
||||
function getAvailableDictionaries() {
|
||||
return [
|
||||
{label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED},
|
||||
{label: i18n.__('English'), value: 'en_GB'},
|
||||
{label: i18n.__('German'), value: 'de_DE'},
|
||||
{label: i18n.__('French'), value: 'fr_FR'}
|
||||
{ label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED },
|
||||
{ label: i18n.__('English'), value: 'en_GB' },
|
||||
{ label: i18n.__('German'), value: 'de_DE' },
|
||||
{ label: i18n.__('French'), value: 'fr_FR' }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Only to be used in the tests :)
|
||||
*/
|
||||
function setDictionaryForTestsOnly (newDictionary) {
|
||||
function setDictionaryForTestsOnly(newDictionary) {
|
||||
dictionary = newDictionary
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ function setDictionaryForTestsOnly (newDictionary) {
|
||||
* @param {Codemirror} editor CodeMirror-Editor
|
||||
* @param {String} lang on of the values from getAvailableDictionaries()-Method
|
||||
*/
|
||||
function setLanguage (editor, lang) {
|
||||
function setLanguage(editor, lang) {
|
||||
self = this
|
||||
dictionary = null
|
||||
|
||||
@@ -50,8 +50,7 @@ function setLanguage (editor, lang) {
|
||||
dictionary = new Typo(lang, false, false, {
|
||||
dictionaryPath: DICTIONARY_PATH,
|
||||
asyncLoad: true,
|
||||
loadedCallback: () =>
|
||||
checkWholeDocument(editor)
|
||||
loadedCallback: () => checkWholeDocument(editor)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -60,12 +59,12 @@ function setLanguage (editor, lang) {
|
||||
* Checks the whole content of the editor for typos
|
||||
* @param {Codemirror} editor CodeMirror-Editor
|
||||
*/
|
||||
function checkWholeDocument (editor) {
|
||||
function checkWholeDocument(editor) {
|
||||
const lastLine = editor.lineCount() - 1
|
||||
const textOfLastLine = editor.getLine(lastLine) || ''
|
||||
const lastChar = textOfLastLine.length
|
||||
const from = {line: 0, ch: 0}
|
||||
const to = {line: lastLine, ch: lastChar}
|
||||
const from = { line: 0, ch: 0 }
|
||||
const to = { line: lastLine, ch: lastChar }
|
||||
checkMultiLineRange(editor, from, to)
|
||||
}
|
||||
|
||||
@@ -75,15 +74,18 @@ function checkWholeDocument (editor) {
|
||||
* @param {line, ch} from starting position of the spellcheck
|
||||
* @param {line, ch} to end position of the spellcheck
|
||||
*/
|
||||
function checkMultiLineRange (editor, from, to) {
|
||||
function sortRange (pos1, pos2) {
|
||||
if (pos1.line > pos2.line || (pos1.line === pos2.line && pos1.ch > pos2.ch)) {
|
||||
return {from: pos2, to: pos1}
|
||||
function checkMultiLineRange(editor, from, to) {
|
||||
function sortRange(pos1, pos2) {
|
||||
if (
|
||||
pos1.line > pos2.line ||
|
||||
(pos1.line === pos2.line && pos1.ch > pos2.ch)
|
||||
) {
|
||||
return { from: pos2, to: pos1 }
|
||||
}
|
||||
return {from: pos1, to: pos2}
|
||||
return { from: pos1, to: pos2 }
|
||||
}
|
||||
|
||||
const {from: smallerPos, to: higherPos} = sortRange(from, to)
|
||||
const { from: smallerPos, to: higherPos } = sortRange(from, to)
|
||||
for (let l = smallerPos.line; l <= higherPos.line; l++) {
|
||||
const line = editor.getLine(l) || ''
|
||||
let w = 0
|
||||
@@ -95,9 +97,9 @@ function checkMultiLineRange (editor, from, to) {
|
||||
wEnd = higherPos.ch
|
||||
}
|
||||
while (w <= wEnd) {
|
||||
const wordRange = editor.findWordAt({line: l, ch: w})
|
||||
const wordRange = editor.findWordAt({ line: l, ch: w })
|
||||
self.checkWord(editor, wordRange)
|
||||
w += (wordRange.head.ch - wordRange.anchor.ch) + 1
|
||||
w += wordRange.head.ch - wordRange.anchor.ch + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,13 +112,15 @@ function checkMultiLineRange (editor, from, to) {
|
||||
* @param wordRange Object specifying the range that should be checked.
|
||||
* Having the following structure: <code>{anchor: {line: integer, ch: integer}, head: {line: integer, ch: integer}}</code>
|
||||
*/
|
||||
function checkWord (editor, wordRange) {
|
||||
function checkWord(editor, wordRange) {
|
||||
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
||||
if (word == null || word.length <= 3) {
|
||||
return
|
||||
}
|
||||
if (!dictionary.check(word)) {
|
||||
editor.markText(wordRange.anchor, wordRange.head, {className: styles[CSS_ERROR_CLASS]})
|
||||
editor.markText(wordRange.anchor, wordRange.head, {
|
||||
className: styles[CSS_ERROR_CLASS]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,32 +130,40 @@ function checkWord (editor, wordRange) {
|
||||
* @param fromChangeObject codeMirror changeObject describing the start of the editing
|
||||
* @param toChangeObject codeMirror changeObject describing the end of the editing
|
||||
*/
|
||||
function checkChangeRange (editor, fromChangeObject, toChangeObject) {
|
||||
function checkChangeRange(editor, fromChangeObject, toChangeObject) {
|
||||
/**
|
||||
* Calculate the smallest respectively largest position as a start, resp. end, position and return it
|
||||
* @param start CodeMirror change object
|
||||
* @param end CodeMirror change object
|
||||
* @returns {{start: {line: *, ch: *}, end: {line: *, ch: *}}}
|
||||
*/
|
||||
function getStartAndEnd (start, end) {
|
||||
function getStartAndEnd(start, end) {
|
||||
const possiblePositions = [start.from, start.to, end.from, end.to]
|
||||
let smallest = start.from
|
||||
let biggest = end.to
|
||||
for (const currentPos of possiblePositions) {
|
||||
if (currentPos.line < smallest.line || (currentPos.line === smallest.line && currentPos.ch < smallest.ch)) {
|
||||
if (
|
||||
currentPos.line < smallest.line ||
|
||||
(currentPos.line === smallest.line && currentPos.ch < smallest.ch)
|
||||
) {
|
||||
smallest = currentPos
|
||||
}
|
||||
if (currentPos.line > biggest.line || (currentPos.line === biggest.line && currentPos.ch > biggest.ch)) {
|
||||
if (
|
||||
currentPos.line > biggest.line ||
|
||||
(currentPos.line === biggest.line && currentPos.ch > biggest.ch)
|
||||
) {
|
||||
biggest = currentPos
|
||||
}
|
||||
}
|
||||
return {start: smallest, end: biggest}
|
||||
return { start: smallest, end: biggest }
|
||||
}
|
||||
|
||||
if (dictionary === null || editor == null) { return }
|
||||
if (dictionary === null || editor == null) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const {start, end} = getStartAndEnd(fromChangeObject, toChangeObject)
|
||||
const { start, end } = getStartAndEnd(fromChangeObject, toChangeObject)
|
||||
|
||||
// Expand the range to include words after/before whitespaces
|
||||
start.ch = Math.max(start.ch - 1, 0)
|
||||
@@ -165,29 +177,40 @@ function checkChangeRange (editor, fromChangeObject, toChangeObject) {
|
||||
|
||||
self.checkMultiLineRange(editor, start, end)
|
||||
} catch (e) {
|
||||
console.info('Error during the spell check. It might be due to problems figuring out the range of the new text..', e)
|
||||
console.info(
|
||||
'Error during the spell check. It might be due to problems figuring out the range of the new text..',
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function saveLiveSpellCheckFrom (changeObject) {
|
||||
function saveLiveSpellCheckFrom(changeObject) {
|
||||
liveSpellCheckFrom = changeObject
|
||||
}
|
||||
let liveSpellCheckFrom
|
||||
const debouncedSpellCheckLeading = _.debounce(saveLiveSpellCheckFrom, MILLISECONDS_TILL_LIVECHECK, {
|
||||
'leading': true,
|
||||
'trailing': false
|
||||
})
|
||||
const debouncedSpellCheck = _.debounce(checkChangeRange, MILLISECONDS_TILL_LIVECHECK, {
|
||||
'leading': false,
|
||||
'trailing': true
|
||||
})
|
||||
const debouncedSpellCheckLeading = _.debounce(
|
||||
saveLiveSpellCheckFrom,
|
||||
MILLISECONDS_TILL_LIVECHECK,
|
||||
{
|
||||
leading: true,
|
||||
trailing: false
|
||||
}
|
||||
)
|
||||
const debouncedSpellCheck = _.debounce(
|
||||
checkChangeRange,
|
||||
MILLISECONDS_TILL_LIVECHECK,
|
||||
{
|
||||
leading: false,
|
||||
trailing: true
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Handles a keystroke. Buffers the input and performs a live spell check after a certain time. Uses _debounce from lodash to buffer the input
|
||||
* @param {Codemirror} editor CodeMirror-Editor
|
||||
* @param changeObject codeMirror changeObject
|
||||
*/
|
||||
function handleChange (editor, changeObject) {
|
||||
function handleChange(editor, changeObject) {
|
||||
if (dictionary === null) {
|
||||
return
|
||||
}
|
||||
@@ -201,7 +224,7 @@ function handleChange (editor, changeObject) {
|
||||
* @param word word to be checked
|
||||
* @returns {String[]} Array of suggestions
|
||||
*/
|
||||
function getSpellingSuggestion (word) {
|
||||
function getSpellingSuggestion(word) {
|
||||
if (dictionary == null || word == null) {
|
||||
return []
|
||||
}
|
||||
@@ -211,7 +234,7 @@ function getSpellingSuggestion (word) {
|
||||
/**
|
||||
* Returns the name of the CSS class used for errors
|
||||
*/
|
||||
function getCSSClassName () {
|
||||
function getCSSClassName() {
|
||||
return styles[CSS_ERROR_CLASS]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const TurndownService = require('turndown')
|
||||
const { gfm } = require('turndown-plugin-gfm')
|
||||
|
||||
export const createTurndownService = function () {
|
||||
export const createTurndownService = function() {
|
||||
const turndown = new TurndownService()
|
||||
turndown.use(gfm)
|
||||
turndown.remove('script')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export function lastFindInArray (array, callback) {
|
||||
export function lastFindInArray(array, callback) {
|
||||
for (let i = array.length - 1; i >= 0; --i) {
|
||||
if (callback(array[i], i, array)) {
|
||||
return array[i]
|
||||
@@ -6,7 +6,7 @@ export function lastFindInArray (array, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
export function escapeHtmlCharacters (
|
||||
export function escapeHtmlCharacters(
|
||||
html,
|
||||
opt = { detectCodeBlock: false, skipSingleQuote: false }
|
||||
) {
|
||||
@@ -115,7 +115,7 @@ export function escapeHtmlCharacters (
|
||||
return html
|
||||
}
|
||||
|
||||
export function isObjectEqual (a, b) {
|
||||
export function isObjectEqual(a, b) {
|
||||
const aProps = Object.getOwnPropertyNames(a)
|
||||
const bProps = Object.getOwnPropertyNames(b)
|
||||
|
||||
@@ -132,11 +132,13 @@ export function isObjectEqual (a, b) {
|
||||
return true
|
||||
}
|
||||
|
||||
export function isMarkdownTitleURL (str) {
|
||||
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
|
||||
export function isMarkdownTitleURL(str) {
|
||||
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(
|
||||
str
|
||||
)
|
||||
}
|
||||
|
||||
export function humanFileSize (bytes) {
|
||||
export function humanFileSize(bytes) {
|
||||
const threshold = 1000
|
||||
if (Math.abs(bytes) < threshold) {
|
||||
return bytes + ' B'
|
||||
|
||||
Reference in New Issue
Block a user