1
0
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:
Nguyen Viet Hung
2020-02-05 13:28:27 +13:00
committed by GitHub
parent 051ce9e208
commit 592aca1539
186 changed files with 9233 additions and 5565 deletions

View File

@@ -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' })
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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 {}
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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: [

View File

@@ -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())

View File

@@ -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
}

View File

@@ -1,4 +1,4 @@
export default function convertModeName (name) {
export default function convertModeName(name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'

View File

@@ -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']
})

View File

@@ -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.')

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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
})

View File

@@ -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()
}

View File

@@ -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']
})
}

View File

@@ -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])
}
}

View File

@@ -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']
})
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -6,7 +6,7 @@
* @param {string} input
* @return {string}
*/
export function strip (input) {
export function strip(input) {
let output = input
try {
output = output

View File

@@ -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')
})

View File

@@ -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(', ')

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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]
}

View File

@@ -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')

View File

@@ -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'