mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 01:36:22 +00:00
Merge branch 'master' into bug-1992
This commit is contained in:
@@ -5,7 +5,7 @@ import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||
import convertModeName from 'browser/lib/convertModeName'
|
||||
import { options, TableEditor } from '@susisu/mte-kernel'
|
||||
import { options, TableEditor, Alignment } from '@susisu/mte-kernel'
|
||||
import TextEditorInterface from 'browser/lib/TextEditorInterface'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import iconv from 'iconv-lite'
|
||||
@@ -59,6 +59,7 @@ export default class CodeEditor extends React.Component {
|
||||
this.searchState = null
|
||||
|
||||
this.formatTable = () => this.handleFormatTable()
|
||||
this.editorActivityHandler = () => this.handleEditorActivity()
|
||||
}
|
||||
|
||||
handleSearch (msg) {
|
||||
@@ -99,6 +100,28 @@ export default class CodeEditor extends React.Component {
|
||||
this.tableEditor.formatAll(options({textWidthOptions: {}}))
|
||||
}
|
||||
|
||||
handleEditorActivity () {
|
||||
if (!this.textEditorInterface.transaction) {
|
||||
this.updateTableEditorState()
|
||||
}
|
||||
}
|
||||
|
||||
updateTableEditorState () {
|
||||
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
|
||||
if (active) {
|
||||
if (this.extraKeysMode !== 'editor') {
|
||||
this.extraKeysMode = 'editor'
|
||||
this.editor.setOption('extraKeys', this.editorKeyMap)
|
||||
}
|
||||
} else {
|
||||
if (this.extraKeysMode !== 'default') {
|
||||
this.extraKeysMode = 'default'
|
||||
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
||||
this.tableEditor.resetSmartCursor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { rulers, enableRulers } = this.props
|
||||
const expandSnippet = this.expandSnippet.bind(this)
|
||||
@@ -119,29 +142,7 @@ export default class CodeEditor extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
this.value = this.props.value
|
||||
this.editor = CodeMirror(this.refs.root, {
|
||||
rulers: buildCMRulers(rulers, enableRulers),
|
||||
value: this.props.value,
|
||||
lineNumbers: this.props.displayLineNumbers,
|
||||
lineWrapping: true,
|
||||
theme: this.props.theme,
|
||||
indentUnit: this.props.indentSize,
|
||||
tabSize: this.props.indentSize,
|
||||
indentWithTabs: this.props.indentType !== 'space',
|
||||
keyMap: this.props.keyMap,
|
||||
scrollPastEnd: this.props.scrollPastEnd,
|
||||
inputStyle: 'textarea',
|
||||
dragDrop: false,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
autoCloseBrackets: {
|
||||
pairs: '()[]{}\'\'""$$**``',
|
||||
triples: '```"""\'\'\'',
|
||||
explode: '[]{}``$$',
|
||||
override: true
|
||||
},
|
||||
extraKeys: {
|
||||
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
|
||||
Tab: function (cm) {
|
||||
const cursor = cm.getCursor()
|
||||
const line = cm.getLine(cursor.line)
|
||||
@@ -192,7 +193,31 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
return CodeMirror.Pass
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.value = this.props.value
|
||||
this.editor = CodeMirror(this.refs.root, {
|
||||
rulers: buildCMRulers(rulers, enableRulers),
|
||||
value: this.props.value,
|
||||
lineNumbers: this.props.displayLineNumbers,
|
||||
lineWrapping: true,
|
||||
theme: this.props.theme,
|
||||
indentUnit: this.props.indentSize,
|
||||
tabSize: this.props.indentSize,
|
||||
indentWithTabs: this.props.indentType !== 'space',
|
||||
keyMap: this.props.keyMap,
|
||||
scrollPastEnd: this.props.scrollPastEnd,
|
||||
inputStyle: 'textarea',
|
||||
dragDrop: false,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
autoCloseBrackets: {
|
||||
pairs: '()[]{}\'\'""$$**``',
|
||||
triples: '```"""\'\'\'',
|
||||
explode: '[]{}``$$',
|
||||
override: true
|
||||
},
|
||||
extraKeys: this.defaultKeyMap
|
||||
})
|
||||
|
||||
this.setMode(this.props.mode)
|
||||
@@ -215,8 +240,58 @@ export default class CodeEditor extends React.Component {
|
||||
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
||||
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||
|
||||
this.tableEditor = new TableEditor(new TextEditorInterface(this.editor))
|
||||
this.textEditorInterface = new TextEditorInterface(this.editor)
|
||||
this.tableEditor = new TableEditor(this.textEditorInterface)
|
||||
eventEmitter.on('code:format-table', this.formatTable)
|
||||
|
||||
this.tableEditorOptions = options({
|
||||
smartCursor: true
|
||||
})
|
||||
|
||||
this.editorKeyMap = CodeMirror.normalizeKeyMap({
|
||||
'Tab': () => { this.tableEditor.nextCell(this.tableEditorOptions) },
|
||||
'Shift-Tab': () => { this.tableEditor.previousCell(this.tableEditorOptions) },
|
||||
'Enter': () => { this.tableEditor.nextRow(this.tableEditorOptions) },
|
||||
'Ctrl-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) },
|
||||
'Cmd-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) },
|
||||
'Shift-Ctrl-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) },
|
||||
'Shift-Cmd-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) },
|
||||
'Shift-Ctrl-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) },
|
||||
'Shift-Cmd-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) },
|
||||
'Shift-Ctrl-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) },
|
||||
'Shift-Cmd-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) },
|
||||
'Shift-Ctrl-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) },
|
||||
'Shift-Cmd-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) },
|
||||
'Ctrl-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) },
|
||||
'Cmd-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) },
|
||||
'Ctrl-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) },
|
||||
'Cmd-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) },
|
||||
'Ctrl-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) },
|
||||
'Cmd-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) },
|
||||
'Ctrl-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) },
|
||||
'Cmd-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) },
|
||||
'Ctrl-K Ctrl-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) },
|
||||
'Cmd-K Cmd-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) },
|
||||
'Ctrl-L Ctrl-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) },
|
||||
'Cmd-L Cmd-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) },
|
||||
'Ctrl-K Ctrl-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) },
|
||||
'Cmd-K Cmd-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) },
|
||||
'Ctrl-L Ctrl-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) },
|
||||
'Cmd-L Cmd-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) },
|
||||
'Alt-Shift-Ctrl-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Cmd-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Ctrl-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Cmd-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Ctrl-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Cmd-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Ctrl-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) },
|
||||
'Alt-Shift-Cmd-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) }
|
||||
})
|
||||
|
||||
if (this.props.enableTableEditor) {
|
||||
this.editor.on('cursorActivity', this.editorActivityHandler)
|
||||
this.editor.on('changes', this.editorActivityHandler)
|
||||
}
|
||||
}
|
||||
|
||||
expandSnippet (line, cursor, cm, snippets) {
|
||||
@@ -353,6 +428,19 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
|
||||
}
|
||||
|
||||
if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
|
||||
if (this.props.enableTableEditor) {
|
||||
this.editor.on('cursorActivity', this.editorActivityHandler)
|
||||
this.editor.on('changes', this.editorActivityHandler)
|
||||
} else {
|
||||
this.editor.off('cursorActivity', this.editorActivityHandler)
|
||||
this.editor.off('changes', this.editorActivityHandler)
|
||||
}
|
||||
|
||||
this.extraKeysMode = 'default'
|
||||
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
||||
}
|
||||
|
||||
if (needRefresh) {
|
||||
this.editor.refresh()
|
||||
}
|
||||
@@ -516,7 +604,10 @@ export default class CodeEditor extends React.Component {
|
||||
body,
|
||||
'text/html'
|
||||
)
|
||||
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
|
||||
const escapePipe = (str) => {
|
||||
return str.replace('|', '\\|')
|
||||
}
|
||||
const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})`
|
||||
resolve(linkWithTitle)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
|
||||
@@ -265,6 +265,7 @@ class MarkdownEditor extends React.Component {
|
||||
storageKey={storageKey}
|
||||
noteKey={noteKey}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
enableTableEditor={config.editor.enableTableEditor}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onBlur={(e) => this.handleBlur(e)}
|
||||
/>
|
||||
|
||||
@@ -158,6 +158,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
rulers={config.editor.rulers}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
enableTableEditor={config.editor.enableTableEditor}
|
||||
storageKey={storageKey}
|
||||
noteKey={noteKey}
|
||||
onChange={this.handleOnChange.bind(this)}
|
||||
|
||||
@@ -3,51 +3,111 @@ import { Point } from '@susisu/mte-kernel'
|
||||
export default class TextEditorInterface {
|
||||
constructor (editor) {
|
||||
this.editor = editor
|
||||
this.doc = editor.getDoc()
|
||||
this.transaction = false
|
||||
}
|
||||
|
||||
getCursorPosition () {
|
||||
const pos = this.editor.getCursor()
|
||||
return new Point(pos.line, pos.ch)
|
||||
const { line, ch } = this.doc.getCursor()
|
||||
return new Point(line, ch)
|
||||
}
|
||||
|
||||
setCursorPosition (pos) {
|
||||
this.editor.setCursor({line: pos.row, ch: pos.column})
|
||||
}
|
||||
|
||||
setSelectionRange (range) {
|
||||
this.editor.setSelection({
|
||||
anchor: {line: range.start.row, ch: range.start.column},
|
||||
head: {line: range.end.row, ch: range.end.column}
|
||||
this.doc.setCursor({
|
||||
line: pos.row,
|
||||
ch: pos.column
|
||||
})
|
||||
}
|
||||
|
||||
getLastRow () {
|
||||
return this.editor.lastLine()
|
||||
setSelectionRange (range) {
|
||||
this.doc.setSelection(
|
||||
{ line: range.start.row, ch: range.start.column },
|
||||
{ line: range.end.row, ch: range.end.column }
|
||||
)
|
||||
}
|
||||
|
||||
acceptsTableEdit (row) {
|
||||
getLastRow () {
|
||||
return this.doc.lineCount() - 1
|
||||
}
|
||||
|
||||
acceptsTableEdit () {
|
||||
return true
|
||||
}
|
||||
|
||||
getLine (row) {
|
||||
return this.editor.getLine(row)
|
||||
return this.doc.getLine(row)
|
||||
}
|
||||
|
||||
insertLine (row, line) {
|
||||
this.editor.replaceRange(line, {line: row, ch: 0})
|
||||
const lastRow = this.getLastRow()
|
||||
if (row > lastRow) {
|
||||
const lastLine = this.getLine(lastRow)
|
||||
this.doc.replaceRange(
|
||||
'\n' + line,
|
||||
{ line: lastRow, ch: lastLine.length },
|
||||
{ line: lastRow, ch: lastLine.length }
|
||||
)
|
||||
} else {
|
||||
this.doc.replaceRange(
|
||||
line + '\n',
|
||||
{ line: row, ch: 0 },
|
||||
{ line: row, ch: 0 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
deleteLine (row) {
|
||||
this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length})
|
||||
const lastRow = this.getLastRow()
|
||||
if (row >= lastRow) {
|
||||
if (lastRow > 0) {
|
||||
const preLastLine = this.getLine(lastRow - 1)
|
||||
const lastLine = this.getLine(lastRow)
|
||||
this.doc.replaceRange(
|
||||
'',
|
||||
{ line: lastRow - 1, ch: preLastLine.length },
|
||||
{ line: lastRow, ch: lastLine.length }
|
||||
)
|
||||
} else {
|
||||
const lastLine = this.getLine(lastRow)
|
||||
this.doc.replaceRange(
|
||||
'',
|
||||
{ line: lastRow, ch: 0 },
|
||||
{ line: lastRow, ch: lastLine.length }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
this.doc.replaceRange(
|
||||
'',
|
||||
{ line: row, ch: 0 },
|
||||
{ line: row + 1, ch: 0 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
replaceLines (startRow, endRow, lines) {
|
||||
endRow-- // because endRow is a first line after a table.
|
||||
const endRowCh = this.editor.getLine(endRow).length
|
||||
this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh})
|
||||
const lastRow = this.getLastRow()
|
||||
if (endRow > lastRow) {
|
||||
const lastLine = this.getLine(lastRow)
|
||||
this.doc.replaceRange(
|
||||
lines.join('\n'),
|
||||
{ line: startRow, ch: 0 },
|
||||
{ line: lastRow, ch: lastLine.length }
|
||||
)
|
||||
} else {
|
||||
this.doc.replaceRange(
|
||||
lines.join('\n') + '\n',
|
||||
{ line: startRow, ch: 0 },
|
||||
{ line: endRow, ch: 0 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
transact (func) {
|
||||
this.transaction = true
|
||||
func()
|
||||
this.transaction = false
|
||||
if (this.onDidFinishTransaction) {
|
||||
this.onDidFinishTransaction.call(undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,17 @@ export function findNoteTitle (value) {
|
||||
let title = null
|
||||
let isInsideCodeBlock = false
|
||||
|
||||
if (splitted[0] === '---') {
|
||||
let line = 0
|
||||
while (++line < splitted.length) {
|
||||
if (splitted[line] === '---') {
|
||||
splitted.splice(0, line + 1)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
splitted.some((line, index) => {
|
||||
const trimmedLine = line.trim()
|
||||
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||
|
||||
24
browser/lib/markdown-it-frontmatter.js
Normal file
24
browser/lib/markdown-it-frontmatter.js
Normal file
@@ -0,0 +1,24 @@
|
||||
'use strict'
|
||||
|
||||
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]) === '---') {
|
||||
state.line = line + 1
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
md.block.ruler.before('table', 'frontmatter', frontmatter, {
|
||||
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
|
||||
})
|
||||
}
|
||||
100
browser/lib/markdown-toc-generator.js
Normal file
100
browser/lib/markdown-toc-generator.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @fileoverview Markdown table of contents generator
|
||||
*/
|
||||
|
||||
import toc from 'markdown-toc'
|
||||
import diacritics from 'diacritics-map'
|
||||
import stripColor from 'strip-color'
|
||||
|
||||
const EOL = require('os').EOL
|
||||
|
||||
/**
|
||||
* @caseSensitiveSlugify Custom slugify function
|
||||
* Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js),
|
||||
* but keeps original case to properly handle https://github.com/BoostIO/Boostnote/issues/2067
|
||||
*/
|
||||
function caseSensitiveSlugify (str) {
|
||||
function replaceDiacritics (str) {
|
||||
return str.replace(/[À-ž]/g, function (ch) {
|
||||
return diacritics[ch] || ch
|
||||
})
|
||||
}
|
||||
|
||||
function getTitle (str) {
|
||||
if (/^\[[^\]]+\]\(/.test(str)) {
|
||||
var m = /^\[([^\]]+)\]/.exec(str)
|
||||
if (m) return m[1]
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
str = getTitle(str)
|
||||
str = stripColor(str)
|
||||
// str = str.toLowerCase() //let's be case sensitive
|
||||
|
||||
// `.split()` is often (but not always) faster than `.replace()`
|
||||
str = str.split(' ').join('-')
|
||||
str = str.split(/\t/).join('--')
|
||||
str = str.split(/<\/?[^>]+>/).join('')
|
||||
str = str.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join('')
|
||||
str = str.split(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/).join('')
|
||||
str = replaceDiacritics(str)
|
||||
return str
|
||||
}
|
||||
|
||||
const TOC_MARKER_START = '<!-- toc -->'
|
||||
const TOC_MARKER_END = '<!-- tocstop -->'
|
||||
|
||||
/**
|
||||
* Takes care of proper updating given editor with TOC.
|
||||
* If TOC doesn't exit in the editor, it's inserted at current caret position.
|
||||
* Otherwise,TOC is updated in place.
|
||||
* @param editor CodeMirror editor to be updated with TOC
|
||||
*/
|
||||
export function generateInEditor (editor) {
|
||||
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
|
||||
|
||||
function tocExistsInEditor () {
|
||||
return tocRegex.test(editor.getValue())
|
||||
}
|
||||
|
||||
function updateExistingToc () {
|
||||
const toc = generate(editor.getValue())
|
||||
const search = editor.getSearchCursor(tocRegex)
|
||||
while (search.findNext()) {
|
||||
search.replace(toc)
|
||||
}
|
||||
}
|
||||
|
||||
function addTocAtCursorPosition () {
|
||||
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
|
||||
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
|
||||
}
|
||||
|
||||
if (tocExistsInEditor()) {
|
||||
updateExistingToc()
|
||||
} else {
|
||||
addTocAtCursorPosition()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates MD TOC based on MD document passed as string.
|
||||
* @param markdownText MD document
|
||||
* @returns generatedTOC String containing generated TOC
|
||||
*/
|
||||
export function generate (markdownText) {
|
||||
const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify})
|
||||
return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END
|
||||
}
|
||||
|
||||
function wrapTocWithEol (toc, editor) {
|
||||
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
|
||||
const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL
|
||||
return leftWrap + toc + rightWrap
|
||||
}
|
||||
|
||||
export default {
|
||||
generate,
|
||||
generateInEditor
|
||||
}
|
||||
@@ -153,6 +153,7 @@ class Markdown {
|
||||
})
|
||||
this.md.use(require('markdown-it-kbd'))
|
||||
this.md.use(require('markdown-it-admonition'))
|
||||
this.md.use(require('./markdown-it-frontmatter'))
|
||||
|
||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||
this.md.use(require('markdown-it-plantuml'), '', {
|
||||
|
||||
62
browser/lib/newNote.js
Normal file
62
browser/lib/newNote.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { hashHistory } from 'react-router'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
|
||||
export function createMarkdownNote (storage, folder, dispatch, location) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||
return dataApi
|
||||
.createNote(storage, {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: folder,
|
||||
title: '',
|
||||
content: ''
|
||||
})
|
||||
.then(note => {
|
||||
const noteHash = note.key
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: { key: noteHash }
|
||||
})
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
})
|
||||
}
|
||||
|
||||
export function createSnippetNote (storage, folder, dispatch, location, config) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||
return dataApi
|
||||
.createNote(storage, {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: folder,
|
||||
title: '',
|
||||
description: '',
|
||||
snippets: [
|
||||
{
|
||||
name: '',
|
||||
mode: config.editor.snippetDefaultLanguage || 'text',
|
||||
content: ''
|
||||
}
|
||||
]
|
||||
})
|
||||
.then(note => {
|
||||
const noteHash = note.key
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: { key: noteHash }
|
||||
})
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
})
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { formatDate } from 'browser/lib/date-formatter'
|
||||
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||
import striptags from 'striptags'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||
|
||||
class MarkdownNoteDetail extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -47,6 +48,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
this.dispatchTimer = null
|
||||
|
||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||
this.generateToc = () => this.handleGenerateToc()
|
||||
}
|
||||
|
||||
focus () {
|
||||
@@ -59,6 +61,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||
this.handleSwitchMode(reversedType)
|
||||
})
|
||||
ee.on('code:generate-toc', this.generateToc)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
@@ -75,6 +78,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
componentWillUnmount () {
|
||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||
ee.off('code:generate-toc', this.generateToc)
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
}
|
||||
|
||||
@@ -262,6 +266,11 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleGenerateToc () {
|
||||
const editor = this.refs.content.refs.code.editor
|
||||
markdownToc.generateInEditor(editor)
|
||||
}
|
||||
|
||||
handleFocus (e) {
|
||||
this.focus()
|
||||
}
|
||||
@@ -363,6 +372,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
data={data}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
/>
|
||||
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||
|
||||
@@ -13,6 +13,7 @@ $info-margin-under-border = 30px
|
||||
display flex
|
||||
align-items center
|
||||
padding 0 20px
|
||||
z-index 99
|
||||
|
||||
.info-left
|
||||
padding 0 10px
|
||||
|
||||
@@ -29,6 +29,7 @@ import InfoPanelTrashed from './InfoPanelTrashed'
|
||||
import { formatDate } from 'browser/lib/date-formatter'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
@@ -52,6 +53,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
this.scrollToNextTabThreshold = 0.7
|
||||
this.generateToc = () => this.handleGenerateToc()
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -65,6 +67,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
enableLeftArrow: allTabs.offsetLeft !== 0
|
||||
})
|
||||
}
|
||||
ee.on('code:generate-toc', this.generateToc)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
@@ -91,6 +94,16 @@ class SnippetNoteDetail extends React.Component {
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
ee.off('code:generate-toc', this.generateToc)
|
||||
}
|
||||
|
||||
handleGenerateToc () {
|
||||
const { note, snippetIndex } = this.state
|
||||
const currentMode = note.snippets[snippetIndex].mode
|
||||
if (currentMode.includes('Markdown')) {
|
||||
const currentEditor = this.refs[`code-${snippetIndex}`].refs.code.editor
|
||||
markdownToc.generateInEditor(currentEditor)
|
||||
}
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
@@ -441,7 +454,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
const isSuper = global.process.platform === 'darwin'
|
||||
? e.metaKey
|
||||
: e.ctrlKey
|
||||
if (isSuper && !e.shiftKey) {
|
||||
if (isSuper && !e.shiftKey && !e.altKey) {
|
||||
e.preventDefault()
|
||||
this.addSnippet()
|
||||
}
|
||||
@@ -692,6 +705,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
keyMap={config.editor.keyMap}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
enableTableEditor={config.editor.enableTableEditor}
|
||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||
ref={'code-' + index}
|
||||
/>
|
||||
|
||||
@@ -6,71 +6,33 @@ import _ from 'lodash'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import Autosuggest from 'react-autosuggest'
|
||||
|
||||
class TagSelect extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
newTag: ''
|
||||
}
|
||||
this.addtagHandler = this.handleAddTag.bind(this)
|
||||
newTag: '',
|
||||
suggestions: []
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.value = this.props.value
|
||||
ee.on('editor:add-tag', this.addtagHandler)
|
||||
this.handleAddTag = this.handleAddTag.bind(this)
|
||||
this.onInputBlur = this.onInputBlur.bind(this)
|
||||
this.onInputChange = this.onInputChange.bind(this)
|
||||
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
||||
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
|
||||
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
|
||||
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ee.off('editor:add-tag', this.addtagHandler)
|
||||
}
|
||||
|
||||
handleAddTag () {
|
||||
this.refs.newTag.focus()
|
||||
}
|
||||
|
||||
handleNewTagInputKeyDown (e) {
|
||||
switch (e.keyCode) {
|
||||
case 9:
|
||||
e.preventDefault()
|
||||
this.submitTag()
|
||||
break
|
||||
case 13:
|
||||
this.submitTag()
|
||||
break
|
||||
case 8:
|
||||
if (this.refs.newTag.value.length === 0) {
|
||||
this.removeLastTag()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleNewTagBlur (e) {
|
||||
this.submitTag()
|
||||
}
|
||||
|
||||
removeLastTag () {
|
||||
this.removeTagByCallback((value) => {
|
||||
value.pop()
|
||||
})
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.setState({
|
||||
newTag: ''
|
||||
})
|
||||
}
|
||||
|
||||
submitTag () {
|
||||
addNewTag (newTag) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
||||
let { value } = this.props
|
||||
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
||||
newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag
|
||||
|
||||
newTag = newTag.trim().replace(/ +/g, '_')
|
||||
if (newTag.charAt(0) === '#') {
|
||||
newTag.substring(1)
|
||||
}
|
||||
|
||||
if (newTag.length <= 0) {
|
||||
this.setState({
|
||||
@@ -79,6 +41,7 @@ class TagSelect extends React.Component {
|
||||
return
|
||||
}
|
||||
|
||||
let { value } = this.props
|
||||
value = _.isArray(value)
|
||||
? value.slice()
|
||||
: []
|
||||
@@ -93,10 +56,36 @@ class TagSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleNewTagInputChange (e) {
|
||||
this.setState({
|
||||
newTag: this.refs.newTag.value
|
||||
buildSuggestions () {
|
||||
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
|
||||
(tag, name) => ({
|
||||
name,
|
||||
nameLC: name.toLowerCase(),
|
||||
size: tag.size
|
||||
})
|
||||
).filter(
|
||||
tag => tag.size > 0
|
||||
), ['name'])
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.value = this.props.value
|
||||
|
||||
this.buildSuggestions()
|
||||
|
||||
ee.on('editor:add-tag', this.handleAddTag)
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ee.off('editor:add-tag', this.handleAddTag)
|
||||
}
|
||||
|
||||
handleAddTag () {
|
||||
this.refs.newTag.input.focus()
|
||||
}
|
||||
|
||||
handleTagRemoveButtonClick (tag) {
|
||||
@@ -105,6 +94,60 @@ class TagSelect extends React.Component {
|
||||
}, tag)
|
||||
}
|
||||
|
||||
onInputBlur (e) {
|
||||
this.submitNewTag()
|
||||
}
|
||||
|
||||
onInputChange (e, { newValue, method }) {
|
||||
this.setState({
|
||||
newTag: newValue
|
||||
})
|
||||
}
|
||||
|
||||
onInputKeyDown (e) {
|
||||
switch (e.keyCode) {
|
||||
case 9:
|
||||
e.preventDefault()
|
||||
this.submitNewTag()
|
||||
break
|
||||
case 13:
|
||||
this.submitNewTag()
|
||||
break
|
||||
case 8:
|
||||
if (this.state.newTag.length === 0) {
|
||||
this.removeLastTag()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSuggestionsClearRequested () {
|
||||
this.setState({
|
||||
suggestions: []
|
||||
})
|
||||
}
|
||||
|
||||
onSuggestionsFetchRequested ({ value }) {
|
||||
const valueLC = value.toLowerCase()
|
||||
const suggestions = _.filter(
|
||||
this.suggestions,
|
||||
tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
|
||||
)
|
||||
|
||||
this.setState({
|
||||
suggestions
|
||||
})
|
||||
}
|
||||
|
||||
onSuggestionSelected (event, { suggestion, suggestionValue }) {
|
||||
this.addNewTag(suggestionValue)
|
||||
}
|
||||
|
||||
removeLastTag () {
|
||||
this.removeTagByCallback((value) => {
|
||||
value.pop()
|
||||
})
|
||||
}
|
||||
|
||||
removeTagByCallback (callback, tag = null) {
|
||||
let { value } = this.props
|
||||
|
||||
@@ -118,6 +161,18 @@ class TagSelect extends React.Component {
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.buildSuggestions()
|
||||
|
||||
this.setState({
|
||||
newTag: ''
|
||||
})
|
||||
}
|
||||
|
||||
submitNewTag () {
|
||||
this.addNewTag(this.refs.newTag.input.value)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, className } = this.props
|
||||
|
||||
@@ -138,6 +193,8 @@ class TagSelect extends React.Component {
|
||||
})
|
||||
: []
|
||||
|
||||
const { newTag, suggestions } = this.state
|
||||
|
||||
return (
|
||||
<div className={_.isString(className)
|
||||
? 'TagSelect ' + className
|
||||
@@ -146,13 +203,25 @@ class TagSelect extends React.Component {
|
||||
styleName='root'
|
||||
>
|
||||
{tagList}
|
||||
<input styleName='newTag'
|
||||
<Autosuggest
|
||||
ref='newTag'
|
||||
value={this.state.newTag}
|
||||
placeholder={i18n.__('Add tag...')}
|
||||
onChange={(e) => this.handleNewTagInputChange(e)}
|
||||
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
|
||||
onBlur={(e) => this.handleNewTagBlur(e)}
|
||||
suggestions={suggestions}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
getSuggestionValue={suggestion => suggestion.name}
|
||||
renderSuggestion={suggestion => (
|
||||
<div>
|
||||
{suggestion.name}
|
||||
</div>
|
||||
)}
|
||||
inputProps={{
|
||||
placeholder: i18n.__('Add tag...'),
|
||||
value: newTag,
|
||||
onChange: this.onInputChange,
|
||||
onKeyDown: this.onInputKeyDown,
|
||||
onBlur: this.onInputBlur
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -163,7 +232,6 @@ TagSelect.propTypes = {
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.arrayOf(PropTypes.string),
|
||||
onChange: PropTypes.func
|
||||
|
||||
}
|
||||
|
||||
export default CSSModules(TagSelect, styles)
|
||||
|
||||
@@ -42,14 +42,6 @@
|
||||
color: $ui-text-color
|
||||
padding 4px 16px 4px 8px
|
||||
|
||||
.newTag
|
||||
box-sizing border-box
|
||||
border none
|
||||
background-color transparent
|
||||
outline none
|
||||
padding 0 4px
|
||||
font-size 13px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.tag
|
||||
background-color alpha($ui-dark-tag-backgroundColor, 60%)
|
||||
@@ -62,11 +54,6 @@ body[data-theme="dark"]
|
||||
.tag-label
|
||||
color $ui-dark-text-color
|
||||
|
||||
.newTag
|
||||
border-color none
|
||||
background-color transparent
|
||||
color $ui-dark-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.tag
|
||||
background-color $ui-solarized-dark-tag-backgroundColor
|
||||
@@ -78,11 +65,6 @@ body[data-theme="solarized-dark"]
|
||||
.tag-label
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.newTag
|
||||
border-color none
|
||||
background-color transparent
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.tag
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
@@ -93,8 +75,3 @@ body[data-theme="monokai"]
|
||||
|
||||
.tag-label
|
||||
color $ui-monokai-text-color
|
||||
|
||||
.newTag
|
||||
border-color none
|
||||
background-color transparent
|
||||
color $ui-monokai-text-color
|
||||
|
||||
@@ -7,6 +7,7 @@ import modal from 'browser/main/lib/modal'
|
||||
import NewNoteModal from 'browser/main/modals/NewNoteModal'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
@@ -37,6 +38,11 @@ class NewNoteButton extends React.Component {
|
||||
const { location, dispatch, config } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
|
||||
createMarkdownNote(storage.key, folder.key, dispatch, location)
|
||||
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
|
||||
createSnippetNote(storage.key, folder.key, dispatch, location, config)
|
||||
} else {
|
||||
modal.open(NewNoteModal, {
|
||||
storage: storage.key,
|
||||
folder: folder.key,
|
||||
@@ -45,6 +51,7 @@ class NewNoteButton extends React.Component {
|
||||
config
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, params } = this.props
|
||||
|
||||
@@ -80,6 +80,7 @@ class NoteList extends React.Component {
|
||||
this.getViewType = this.getViewType.bind(this)
|
||||
this.restoreNote = this.restoreNote.bind(this)
|
||||
this.copyNoteLink = this.copyNoteLink.bind(this)
|
||||
this.navigate = this.navigate.bind(this)
|
||||
|
||||
// TODO: not Selected noteKeys but SelectedNote(for reusing)
|
||||
this.state = {
|
||||
@@ -98,6 +99,7 @@ class NoteList extends React.Component {
|
||||
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
|
||||
ee.on('import:file', this.importFromFileHandler)
|
||||
ee.on('list:jump', this.jumpNoteByHash)
|
||||
ee.on('list:navigate', this.navigate)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
@@ -687,6 +689,16 @@ class NoteList extends React.Component {
|
||||
return copy(noteLink)
|
||||
}
|
||||
|
||||
navigate (sender, pathname) {
|
||||
const { router } = this.context
|
||||
router.push({
|
||||
pathname,
|
||||
query: {
|
||||
// key: noteKey
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
save (note) {
|
||||
const { dispatch } = this.props
|
||||
dataApi
|
||||
|
||||
@@ -38,6 +38,22 @@ class StorageItem extends React.Component {
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: i18n.__('Export Storage'),
|
||||
submenu: [
|
||||
{
|
||||
label: i18n.__('Export as txt'),
|
||||
click: (e) => this.handleExportStorageClick(e, 'txt')
|
||||
},
|
||||
{
|
||||
label: i18n.__('Export as md'),
|
||||
click: (e) => this.handleExportStorageClick(e, 'md')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: i18n.__('Unlink Storage'),
|
||||
click: (e) => this.handleUnlinkStorageClick(e)
|
||||
@@ -68,6 +84,30 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleExportStorageClick (e, fileType) {
|
||||
const options = {
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
buttonLabel: i18n.__('Select directory'),
|
||||
title: i18n.__('Select a folder to export the files to'),
|
||||
multiSelections: false
|
||||
}
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options,
|
||||
(paths) => {
|
||||
if (paths && paths.length === 1) {
|
||||
const { storage, dispatch } = this.props
|
||||
dataApi
|
||||
.exportStorage(storage.key, fileType, paths[0])
|
||||
.then(data => {
|
||||
dispatch({
|
||||
type: 'EXPORT_STORAGE',
|
||||
storage: data.storage,
|
||||
fileType: data.fileType
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleToggleButtonClick (e) {
|
||||
const { storage, dispatch } = this.props
|
||||
const isOpen = !this.state.isOpen
|
||||
|
||||
@@ -198,12 +198,12 @@ class SideNav extends React.Component {
|
||||
const tags = pathSegments[pathSegments.length - 1]
|
||||
return (tags === 'alltags')
|
||||
? []
|
||||
: tags.split(' ')
|
||||
: tags.split(' ').map(tag => decodeURIComponent(tag))
|
||||
}
|
||||
|
||||
handleClickTagListItem (name) {
|
||||
const { router } = this.context
|
||||
router.push(`/tags/${name}`)
|
||||
router.push(`/tags/${encodeURIComponent(name)}`)
|
||||
}
|
||||
|
||||
handleSortTagsByChange (e) {
|
||||
@@ -230,7 +230,7 @@ class SideNav extends React.Component {
|
||||
} else {
|
||||
listOfTags.push(tag)
|
||||
}
|
||||
router.push(`/tags/${listOfTags.join(' ')}`)
|
||||
router.push(`/tags/${listOfTags.map(tag => encodeURIComponent(tag)).join(' ')}`)
|
||||
}
|
||||
|
||||
emptyTrash (entries) {
|
||||
|
||||
@@ -132,6 +132,15 @@ body[data-theme="dark"]
|
||||
.CodeMirror-foldgutter-folded:after
|
||||
content: "\25B8"
|
||||
|
||||
.CodeMirror-hover
|
||||
padding 2px 4px 0 4px
|
||||
position absolute
|
||||
z-index 99
|
||||
|
||||
.CodeMirror-hyperlink
|
||||
cursor pointer
|
||||
|
||||
|
||||
.sortableItemHelper
|
||||
z-index modalZIndex + 5
|
||||
|
||||
@@ -156,3 +165,5 @@ body[data-theme="monokai"]
|
||||
body[data-theme="default"]
|
||||
.SideNav ::-webkit-scrollbar-thumb
|
||||
background-color rgba(255, 255, 255, 0.3)
|
||||
|
||||
@import '../styles/Detail/TagSelect.styl'
|
||||
@@ -46,7 +46,8 @@ export const DEFAULT_CONFIG = {
|
||||
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
||||
scrollPastEnd: false,
|
||||
type: 'SPLIT',
|
||||
fetchUrlTitle: true
|
||||
fetchUrlTitle: true,
|
||||
enableTableEditor: false
|
||||
},
|
||||
preview: {
|
||||
fontSize: '14',
|
||||
|
||||
63
browser/main/lib/dataApi/exportStorage.js
Normal file
63
browser/main/lib/dataApi/exportStorage.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
import resolveStorageData from './resolveStorageData'
|
||||
import resolveStorageNotes from './resolveStorageNotes'
|
||||
import filenamify from 'filenamify'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
|
||||
/**
|
||||
* @param {String} storageKey
|
||||
* @param {String} fileType
|
||||
* @param {String} exportDir
|
||||
*
|
||||
* @return {Object}
|
||||
* ```
|
||||
* {
|
||||
* storage: Object,
|
||||
* fileType: String,
|
||||
* exportDir: String
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
function exportStorage (storageKey, fileType, exportDir) {
|
||||
let targetStorage
|
||||
try {
|
||||
targetStorage = findStorage(storageKey)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(storage => (
|
||||
resolveStorageNotes(storage).then(notes => ({storage, notes}))
|
||||
))
|
||||
.then(function exportNotes (data) {
|
||||
const { storage, notes } = data
|
||||
const folderNamesMapping = {}
|
||||
storage.folders.forEach(folder => {
|
||||
const folderExportedDir = path.join(exportDir, filenamify(folder.name, {replacement: '_'}))
|
||||
folderNamesMapping[folder.key] = folderExportedDir
|
||||
// make sure directory exists
|
||||
try {
|
||||
fs.mkdirSync(folderExportedDir)
|
||||
} catch (e) {}
|
||||
})
|
||||
notes
|
||||
.filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE')
|
||||
.forEach(markdownNote => {
|
||||
const folderExportedDir = folderNamesMapping[markdownNote.folder]
|
||||
const snippetName = `${filenamify(markdownNote.title, {replacement: '_'})}.${fileType}`
|
||||
const notePath = path.join(folderExportedDir, snippetName)
|
||||
fs.writeFileSync(notePath, markdownNote.content)
|
||||
})
|
||||
|
||||
return {
|
||||
storage,
|
||||
fileType,
|
||||
exportDir
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = exportStorage
|
||||
@@ -9,6 +9,7 @@ const dataApi = {
|
||||
deleteFolder: require('./deleteFolder'),
|
||||
reorderFolder: require('./reorderFolder'),
|
||||
exportFolder: require('./exportFolder'),
|
||||
exportStorage: require('./exportStorage'),
|
||||
createNote: require('./createNote'),
|
||||
updateNote: require('./updateNote'),
|
||||
deleteNote: require('./deleteNote'),
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './NewNoteModal.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import { hashHistory } from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
|
||||
|
||||
class NewNoteModal extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -24,29 +21,8 @@ class NewNoteModal extends React.Component {
|
||||
}
|
||||
|
||||
handleMarkdownNoteButtonClick (e) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||
const { storage, folder, dispatch, location } = this.props
|
||||
dataApi
|
||||
.createNote(storage, {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: folder,
|
||||
title: '',
|
||||
content: ''
|
||||
})
|
||||
.then(note => {
|
||||
const noteHash = note.key
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: { key: noteHash }
|
||||
})
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
createMarkdownNote(storage, folder, dispatch, location).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
@@ -59,36 +35,8 @@ class NewNoteModal extends React.Component {
|
||||
}
|
||||
|
||||
handleSnippetNoteButtonClick (e) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||
const { storage, folder, dispatch, location, config } = this.props
|
||||
|
||||
dataApi
|
||||
.createNote(storage, {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: folder,
|
||||
title: '',
|
||||
description: '',
|
||||
snippets: [
|
||||
{
|
||||
name: '',
|
||||
mode: config.editor.snippetDefaultLanguage || 'text',
|
||||
content: ''
|
||||
}
|
||||
]
|
||||
})
|
||||
.then(note => {
|
||||
const noteHash = note.key
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: { key: noteHash }
|
||||
})
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
createSnippetNote(storage, folder, dispatch, location, config).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ class UiTab extends React.Component {
|
||||
ui: {
|
||||
theme: this.refs.uiTheme.value,
|
||||
language: this.refs.uiLanguage.value,
|
||||
defaultNote: this.refs.defaultNote.value,
|
||||
showCopyNotification: this.refs.showCopyNotification.checked,
|
||||
confirmDeletion: this.refs.confirmDeletion.checked,
|
||||
showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked,
|
||||
@@ -87,7 +88,8 @@ class UiTab extends React.Component {
|
||||
keyMap: this.refs.editorKeyMap.value,
|
||||
snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value,
|
||||
scrollPastEnd: this.refs.scrollPastEnd.checked,
|
||||
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
|
||||
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked,
|
||||
enableTableEditor: this.refs.enableTableEditor.checked
|
||||
},
|
||||
preview: {
|
||||
fontSize: this.refs.previewFontSize.value,
|
||||
@@ -173,7 +175,9 @@ class UiTab extends React.Component {
|
||||
<div styleName='group-header'>{i18n.__('Interface')}</div>
|
||||
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Interface Theme')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<select value={config.ui.theme}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
@@ -189,7 +193,9 @@ class UiTab extends React.Component {
|
||||
</div>
|
||||
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Language')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<select value={config.ui.language}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
@@ -202,6 +208,22 @@ class UiTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Default New Note')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<select value={config.ui.defaultNote}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
ref='defaultNote'
|
||||
>
|
||||
<option value='ALWAYS_ASK'>{i18n.__('Always Ask')}</option>
|
||||
<option value='MARKDOWN_NOTE'>{i18n.__('Markdown Note')}</option>
|
||||
<option value='SNIPPET_NOTE'>{i18n.__('Snippet Note')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
@@ -437,6 +459,17 @@ class UiTab extends React.Component {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
checked={this.state.config.editor.enableTableEditor}
|
||||
ref='enableTableEditor'
|
||||
type='checkbox'
|
||||
/>
|
||||
{i18n.__('Enable smart table editor')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-header2'>{i18n.__('Preview')}</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
|
||||
@@ -216,16 +216,10 @@ function data (state = defaultDataMap(), action) {
|
||||
return state
|
||||
}
|
||||
case 'UPDATE_FOLDER':
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
return state
|
||||
case 'REORDER_FOLDER':
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
return state
|
||||
case 'EXPORT_FOLDER':
|
||||
case 'RENAME_STORAGE':
|
||||
case 'EXPORT_STORAGE':
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
@@ -355,11 +349,6 @@ function data (state = defaultDataMap(), action) {
|
||||
})
|
||||
}
|
||||
return state
|
||||
case 'RENAME_STORAGE':
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
return state
|
||||
case 'EXPAND_STORAGE':
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
|
||||
109
browser/styles/Detail/TagSelect.styl
Normal file
109
browser/styles/Detail/TagSelect.styl
Normal file
@@ -0,0 +1,109 @@
|
||||
.TagSelect
|
||||
.react-autosuggest__input
|
||||
box-sizing border-box
|
||||
border none
|
||||
background-color transparent
|
||||
outline none
|
||||
padding 0 4px
|
||||
font-size 13px
|
||||
|
||||
ul
|
||||
position fixed
|
||||
z-index 999
|
||||
box-sizing border-box
|
||||
list-style none
|
||||
padding 0
|
||||
margin 0
|
||||
|
||||
border-radius 4px
|
||||
margin .2em 0 0
|
||||
background-color $ui-noteList-backgroundColor
|
||||
border 1px solid rgba(0,0,0,.3)
|
||||
box-shadow .05em .2em .6em rgba(0,0,0,.2)
|
||||
text-shadow none
|
||||
|
||||
&:empty,
|
||||
&[hidden]
|
||||
display none
|
||||
|
||||
&:before
|
||||
content ""
|
||||
position absolute
|
||||
top -7px
|
||||
left 14px
|
||||
width 0 height 0
|
||||
padding 6px
|
||||
background-color $ui-noteList-backgroundColor
|
||||
border inherit
|
||||
border-right 0
|
||||
border-bottom 0
|
||||
-webkit-transform rotate(45deg)
|
||||
transform rotate(45deg)
|
||||
|
||||
li
|
||||
position relative
|
||||
padding 6px 18px 6px 10px
|
||||
cursor pointer
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
|
||||
body[data-theme="dark"]
|
||||
.TagSelect
|
||||
.react-autosuggest__input
|
||||
color $ui-dark-text-color
|
||||
|
||||
ul
|
||||
border-color $ui-dark-borderColor
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
|
||||
&:before
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.TagSelect
|
||||
.react-autosuggest__input
|
||||
color $ui-monokai-text-color
|
||||
|
||||
ul
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
|
||||
&:before
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.TagSelect
|
||||
.react-autosuggest__input
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
ul
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
&:before
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
body[data-theme="white"]
|
||||
.TagSelect
|
||||
ul
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color $ui-button--active-backgroundColor
|
||||
@@ -87,3 +87,22 @@ Pull requestをすることはその変化分のコードの著作権をBoostIO
|
||||
|
||||
这并不表示Boostnote会成为一个需要付费的软件。如果我们想获得收益,我们会尝试一些其他的方法,比如说云存储、绑定手机软件等。
|
||||
因为GPLv3过于严格,不能和其他的一些协议兼容,所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议,比如说BSD、MIT。
|
||||
|
||||
---
|
||||
|
||||
# Contributing to Boostnote (Français)
|
||||
|
||||
### Lorsque vous signalez un problème ou un bug
|
||||
Il n'y a pas de modèle pour un signaler problème. Mais nous vous demandons :
|
||||
|
||||
**Merci de founir une capture d'écran de Boostnote avec l'outil de développement ouvert**
|
||||
(vous pouvez l'ouvrir avec `Ctrl+Shift+I`)
|
||||
|
||||
Merci en avance pour votre aide.
|
||||
|
||||
### À propos des droits d'auteurs et des requêtes (`Pull Request`)
|
||||
|
||||
Si vous faites une requête, vous acceptez de transmettre les modifications du code à BoostIO.
|
||||
|
||||
Cela ne veut pas dire que Boostnote deviendra une application payante. Si nous voulons gagner de l'argent, nous trouverons un autre moyen, comme un service de sauvegarde sur le Cloud, une application mobile ou des options payantes.
|
||||
Puisque GPL v3 est trop strict pour être compatible avec n'importe quelle autre licence, nous pensons avoir un jour besoin de la remplacer avec une licence bien plus libre (comme BSD, MIT).
|
||||
|
||||
127
extra_scripts/codemirror/addon/hyperlink/hyperlink.js
vendored
Executable file
127
extra_scripts/codemirror/addon/hyperlink/hyperlink.js
vendored
Executable file
@@ -0,0 +1,127 @@
|
||||
(function (mod) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') { // Common JS
|
||||
mod(require('../codemirror/lib/codemirror'))
|
||||
} else if (typeof define === 'function' && define.amd) { // AMD
|
||||
define(['../codemirror/lib/codemirror'], mod)
|
||||
} else { // Plain browser env
|
||||
mod(CodeMirror)
|
||||
}
|
||||
})(function (CodeMirror) {
|
||||
'use strict'
|
||||
|
||||
const shell = require('electron').shell
|
||||
const yOffset = 2
|
||||
|
||||
const macOS = global.process.platform === 'darwin'
|
||||
const modifier = macOS ? 'metaKey' : 'ctrlKey'
|
||||
|
||||
class HyperLink {
|
||||
constructor(cm) {
|
||||
this.cm = cm
|
||||
this.lineDiv = cm.display.lineDiv
|
||||
|
||||
this.onMouseDown = this.onMouseDown.bind(this)
|
||||
this.onMouseEnter = this.onMouseEnter.bind(this)
|
||||
this.onMouseLeave = this.onMouseLeave.bind(this)
|
||||
this.onMouseMove = this.onMouseMove.bind(this)
|
||||
|
||||
this.tooltip = document.createElement('div')
|
||||
this.tooltipContent = document.createElement('div')
|
||||
this.tooltipIndicator = document.createElement('div')
|
||||
this.tooltip.setAttribute('class', 'CodeMirror-hover CodeMirror-matchingbracket CodeMirror-selected')
|
||||
this.tooltip.setAttribute('cm-ignore-events', 'true')
|
||||
this.tooltip.appendChild(this.tooltipContent)
|
||||
this.tooltip.appendChild(this.tooltipIndicator)
|
||||
this.tooltipContent.textContent = `${macOS ? 'Cmd(⌘)' : 'Ctrl(^)'} + click to follow link`
|
||||
|
||||
this.lineDiv.addEventListener('mousedown', this.onMouseDown)
|
||||
this.lineDiv.addEventListener('mouseenter', this.onMouseEnter, {
|
||||
capture: true,
|
||||
passive: true
|
||||
})
|
||||
this.lineDiv.addEventListener('mouseleave', this.onMouseLeave, {
|
||||
capture: true,
|
||||
passive: true
|
||||
})
|
||||
this.lineDiv.addEventListener('mousemove', this.onMouseMove, {
|
||||
passive: true
|
||||
})
|
||||
}
|
||||
getUrl(el) {
|
||||
const className = el.className.split(' ')
|
||||
|
||||
if (className.indexOf('cm-url') !== -1) {
|
||||
const match = /^\((.*)\)|\[(.*)\]|(.*)$/.exec(el.textContent)
|
||||
return match[1] || match[2] || match[3]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
onMouseDown(e) {
|
||||
const { target } = e
|
||||
if (!e[modifier]) {
|
||||
return
|
||||
}
|
||||
|
||||
const url = this.getUrl(target)
|
||||
if (url) {
|
||||
e.preventDefault()
|
||||
|
||||
shell.openExternal(url)
|
||||
}
|
||||
}
|
||||
onMouseEnter(e) {
|
||||
const { target } = e
|
||||
|
||||
const url = this.getUrl(target)
|
||||
if (url) {
|
||||
if (e[modifier]) {
|
||||
target.classList.add('CodeMirror-activeline-background', 'CodeMirror-hyperlink')
|
||||
}
|
||||
else {
|
||||
target.classList.add('CodeMirror-activeline-background')
|
||||
}
|
||||
|
||||
this.showInfo(target)
|
||||
}
|
||||
}
|
||||
onMouseLeave(e) {
|
||||
if (this.tooltip.parentElement === this.lineDiv) {
|
||||
e.target.classList.remove('CodeMirror-activeline-background', 'CodeMirror-hyperlink')
|
||||
|
||||
this.lineDiv.removeChild(this.tooltip)
|
||||
}
|
||||
}
|
||||
onMouseMove(e) {
|
||||
if (this.tooltip.parentElement === this.lineDiv) {
|
||||
if (e[modifier]) {
|
||||
e.target.classList.add('CodeMirror-hyperlink')
|
||||
}
|
||||
else {
|
||||
e.target.classList.remove('CodeMirror-hyperlink')
|
||||
}
|
||||
}
|
||||
}
|
||||
showInfo(relatedTo) {
|
||||
const b1 = relatedTo.getBoundingClientRect()
|
||||
const b2 = this.lineDiv.getBoundingClientRect()
|
||||
const tdiv = this.tooltip
|
||||
|
||||
tdiv.style.left = (b1.left - b2.left) + 'px'
|
||||
this.lineDiv.appendChild(tdiv)
|
||||
|
||||
const b3 = tdiv.getBoundingClientRect()
|
||||
const top = b1.top - b2.top - b3.height - yOffset
|
||||
if (top < 0) {
|
||||
tdiv.style.top = (b1.top - b2.top + b1.height + yOffset) + 'px'
|
||||
}
|
||||
else {
|
||||
tdiv.style.top = top + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodeMirror.defineOption('hyperlink', true, (cm) => {
|
||||
const addon = new HyperLink(cm)
|
||||
})
|
||||
})
|
||||
@@ -78,9 +78,11 @@ app.on('ready', function () {
|
||||
|
||||
var template = require('./main-menu')
|
||||
var menu = Menu.buildFromTemplate(template)
|
||||
var touchBarMenu = require('./touchbar-menu')
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
Menu.setApplicationMenu(menu)
|
||||
mainWindow.setTouchBar(touchBarMenu)
|
||||
break
|
||||
case 'win32':
|
||||
mainWindow.setMenu(menu)
|
||||
|
||||
@@ -145,6 +145,16 @@ const file = {
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Generate/Update Markdown TOC',
|
||||
accelerator: 'Shift+Ctrl+T',
|
||||
click () {
|
||||
mainWindow.webContents.send('code:generate-toc')
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Print',
|
||||
accelerator: 'CommandOrControl+P',
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
|
||||
<link rel="stylesheet" href="../node_modules/katex/dist/katex.min.css">
|
||||
<link rel="stylesheet" href="../node_modules/codemirror/addon/dialog/dialog.css">
|
||||
|
||||
<title>Boostnote</title>
|
||||
|
||||
<style>
|
||||
@@ -95,6 +96,7 @@
|
||||
<script src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
|
||||
|
||||
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
|
||||
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
|
||||
|
||||
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
|
||||
|
||||
41
lib/touchbar-menu.js
Normal file
41
lib/touchbar-menu.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const {TouchBar} = require('electron')
|
||||
const {TouchBarButton, TouchBarSpacer} = TouchBar
|
||||
const mainWindow = require('./main-window')
|
||||
|
||||
const allNotes = new TouchBarButton({
|
||||
label: '📒',
|
||||
click: () => {
|
||||
mainWindow.webContents.send('list:navigate', '/home')
|
||||
}
|
||||
})
|
||||
|
||||
const starredNotes = new TouchBarButton({
|
||||
label: '⭐️',
|
||||
click: () => {
|
||||
mainWindow.webContents.send('list:navigate', '/starred')
|
||||
}
|
||||
})
|
||||
|
||||
const trash = new TouchBarButton({
|
||||
label: '🗑',
|
||||
click: () => {
|
||||
mainWindow.webContents.send('list:navigate', '/trashed')
|
||||
}
|
||||
})
|
||||
|
||||
const newNote = new TouchBarButton({
|
||||
label: '✎',
|
||||
click: () => {
|
||||
mainWindow.webContents.send('list:navigate', '/home')
|
||||
mainWindow.webContents.send('top:new-note')
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = new TouchBar([
|
||||
allNotes,
|
||||
starredNotes,
|
||||
trash,
|
||||
new TouchBarSpacer({size: 'small'}),
|
||||
newNote
|
||||
])
|
||||
|
||||
@@ -176,5 +176,6 @@
|
||||
"Allow dangerous html tags": "Allow dangerous html tags",
|
||||
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Enable smart table editor": "Enable smart table editor",
|
||||
"Snippet Default Language": "Snippet Default Language"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"Notes": "Notas",
|
||||
"Tags": "Etiquetas",
|
||||
"Preferences": "Preferencias",
|
||||
"Make a note": "Tomar una nota",
|
||||
"Make a note": "Crear nota",
|
||||
"Ctrl": "Ctrl",
|
||||
"Ctrl(^)": "Ctrl",
|
||||
"to create a new note": "para crear una nueva nota",
|
||||
@@ -14,7 +14,7 @@
|
||||
"STORAGE": "ALMACENAMIENTO",
|
||||
"FOLDER": "CARPETA",
|
||||
"CREATION DATE": "FECHA DE CREACIÓN",
|
||||
"NOTE LINK": "ENLACE A LA NOTA",
|
||||
"NOTE LINK": "ENLACE DE LA NOTA",
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
@@ -23,7 +23,7 @@
|
||||
"Storages": "Almacenamientos",
|
||||
"Add Storage Location": "Añadir ubicación de almacenamiento",
|
||||
"Add Folder": "Añadir carpeta",
|
||||
"Open Storage folder": "Añadir carpeta de almacenamiento",
|
||||
"Open Storage folder": "Abrir carpeta de almacenamiento",
|
||||
"Unlink": "Desvincular",
|
||||
"Edit": "Editar",
|
||||
"Delete": "Eliminar",
|
||||
@@ -39,8 +39,8 @@
|
||||
"Editor Font Family": "Fuente del editor",
|
||||
"Editor Indent Style": "Estilo de indentado del editor",
|
||||
"Spaces": "Espacios",
|
||||
"Tabs": "Tabulación",
|
||||
"Switch to Preview": "Cambiar a Previsualización",
|
||||
"Tabs": "Tabulaciones",
|
||||
"Switch to Preview": "Cambiar a previsualización",
|
||||
"When Editor Blurred": "Cuando el editor pierde el foco",
|
||||
"When Editor Blurred, Edit On Double Click": "Cuando el editor pierde el foco, editar con doble clic",
|
||||
"On Right Click": "Al hacer clic derecho",
|
||||
@@ -48,20 +48,20 @@
|
||||
"default": "por defecto",
|
||||
"vim": "vim",
|
||||
"emacs": "emacs",
|
||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Reinicie boostnote después de cambiar el mapeo de teclas",
|
||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Por favor reinicie boostnote después de cambiar el mapeo de teclas",
|
||||
"Show line numbers in the editor": "Mostrar números de línea en el editor",
|
||||
"Allow editor to scroll past the last line": "Permitir al editor desplazarse más allá de la última línea",
|
||||
"Bring in web page title when pasting URL on editor": "Al pegar una URL en el editor, insertar el título de la web automáticamente",
|
||||
"Preview": "Previsualización",
|
||||
"Bring in web page title when pasting URL on editor": "Al pegar una URL en el editor, insertar el título de la web",
|
||||
"Preview": "Previsualizar",
|
||||
"Preview Font Size": "Previsualizar tamaño de la fuente",
|
||||
"Preview Font Family": "Previsualizar fuente",
|
||||
"Code block Theme": "Tema de los bloques de código",
|
||||
"Allow preview to scroll past the last line": "Permitir a la previsualización desplazarse más allá de la última línea",
|
||||
"Show line numbers for preview code blocks": "Mostar números de línea al previsualizar bloques de código",
|
||||
"LaTeX Inline Open Delimiter": "Delimitador de apertura LaTeX en línea",
|
||||
"LaTeX Inline Close Delimiter": "Delimitador de cierre LaTeX en línea",
|
||||
"LaTeX Block Open Delimiter": "Delimitado de apertura bloque LaTeX",
|
||||
"LaTeX Block Close Delimiter": "Delimitador de cierre bloque LaTeX",
|
||||
"Show line numbers for preview code blocks": "Mostrar números de línea al previsualizar bloques de código",
|
||||
"LaTeX Inline Open Delimiter": "Delimitador de apertura de LaTeX en línea",
|
||||
"LaTeX Inline Close Delimiter": "Delimitador de cierre de LaTeX en línea",
|
||||
"LaTeX Block Open Delimiter": "Delimitador de apertura de bloque LaTeX",
|
||||
"LaTeX Block Close Delimiter": "Delimitador de cierre de bloque LaTeX",
|
||||
"PlantUML Server": "PlantUML Server",
|
||||
"Community": "Comunidad",
|
||||
"Subscribe to Newsletter": "Suscribirse al boletín",
|
||||
@@ -71,20 +71,20 @@
|
||||
"Twitter": "Twitter",
|
||||
"About": "Sobre",
|
||||
"Boostnote": "Boostnote",
|
||||
"An open source note-taking app made for programmers just like you.": "Una aplicación para tomar notas de código abieto para programadores como tú.",
|
||||
"An open source note-taking app made for programmers just like you.": "Una aplicación de código abierto para tomar notas hecho para programadores como tú.",
|
||||
"Website": "Página web",
|
||||
"Development": "Desarrollo",
|
||||
" : Development configurations for Boostnote.": " : Configuraciones de desarrollo para Boostnote.",
|
||||
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
|
||||
"License: GPL v3": "Licencia: GPL v3",
|
||||
"Analytics": "Analítica",
|
||||
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote recopila datos anónimos con el único propósito de mejorar la aplicación. No recopila ninguna información personal, como puede ser el contenido de sus notas.",
|
||||
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote recopila datos anónimos con el único propósito de mejorar la aplicación, y de ninguna manera recopila información personal como el contenido de sus notas.",
|
||||
"You can see how it works on ": "Puedes ver cómo funciona en ",
|
||||
"You can choose to enable or disable this option.": "Puedes elegir activar o desactivar esta opción.",
|
||||
"Enable analytics to help improve Boostnote": "Activa analítica para ayudar a mejorar Boostnote",
|
||||
"Enable analytics to help improve Boostnote": "Activar recopilación de datos para ayudar a mejorar Boostnote",
|
||||
"Crowdfunding": "Crowdfunding",
|
||||
"Dear everyone,": "Hola a todos,",
|
||||
"Thank you for using Boostnote!": "Gracias por usar Boostnote!",
|
||||
"Thank you for using Boostnote!": "¡Gracias por usar Boostnote!",
|
||||
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote es utilizado en alrededor de 200 países y regiones diferentes por una increíble comunidad de desarrolladores.",
|
||||
"To continue supporting this growth, and to satisfy community expectations,": "Para continuar apoyando este crecimiento y satisfacer las expectativas de la comunidad,",
|
||||
"we would like to invest more time and resources in this project.": "nos gustaría invertir más tiempo y recursos en este proyecto.",
|
||||
@@ -99,7 +99,7 @@
|
||||
"Show \"Saved to Clipboard\" notification when copying": "Mostrar la notificaión \"Guardado en Portapapeles\" al copiar",
|
||||
"All Notes": "Todas las notas",
|
||||
"Starred": "Destacado",
|
||||
"Are you sure to ": "Estás seguro de ",
|
||||
"Are you sure to ": "¿Estás seguro de ",
|
||||
" delete": " eliminar",
|
||||
"this folder?": "esta carpeta?",
|
||||
"Confirm": "Confirmar",
|
||||
@@ -118,7 +118,7 @@
|
||||
"Blog Type": "Tipo de blog",
|
||||
"Blog Address": "Dirección del blog",
|
||||
"Save": "Guardar",
|
||||
"Auth": "Auth",
|
||||
"Auth": "Autentificación",
|
||||
"Authentication Method": "Método de autentificación",
|
||||
"JWT": "JWT",
|
||||
"USER": "USUARIO",
|
||||
@@ -129,8 +129,8 @@
|
||||
"Restore": "Restaurar",
|
||||
"Permanent Delete": "Eliminar permanentemente",
|
||||
"Confirm note deletion": "Confirmar eliminación de nota",
|
||||
"This will permanently remove this note.": "La nota se eliminará permanentemente.",
|
||||
"Successfully applied!": "Aplicado con éxito!",
|
||||
"This will permanently remove this note.": "Esto eliminará la nota permanentemente.",
|
||||
"Successfully applied!": "¡Aplicado con éxito!",
|
||||
"Albanian": "Albanés",
|
||||
"Chinese (zh-CN)": "Chino - China",
|
||||
"Chinese (zh-TW)": "Chino - Taiwan",
|
||||
@@ -139,11 +139,11 @@
|
||||
"Korean": "Coreano",
|
||||
"Norwegian": "Noruego",
|
||||
"Polish": "Polaco",
|
||||
"Portuguese": "Portugues",
|
||||
"Portuguese": "Portugués",
|
||||
"Spanish": "Español",
|
||||
"You have to save!": "Tienes que guardar!",
|
||||
"You have to save!": "¡Tienes que guardar!",
|
||||
"Russian": "Ruso",
|
||||
"Command(⌘)": "Command(⌘)",
|
||||
"Command(⌘)": "Comando(⌘)",
|
||||
"Editor Rulers": "Reglas del editor",
|
||||
"Enable": "Activar",
|
||||
"Disable": "Desactivar",
|
||||
@@ -151,6 +151,7 @@
|
||||
"Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)",
|
||||
"Allow styles": "Permitir estilos",
|
||||
"Allow dangerous html tags": "Permitir etiquetas html peligrosas",
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown."
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Ha pegado un enlace a un archivo adjunto que no se puede encontrar en el almacenamiento de esta nota. Pegar enlaces a archivos adjuntos solo está soportado si el origen y el destino son el mismo almacenamiento. ¡Por favor, mejor arrastre el archivo! ⚠",
|
||||
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir flechas textuales a símbolos bonitos. ⚠ Esto interferirá cuando use comentarios HTML en Markdown.",
|
||||
"Snippet Default Language": "Lenguaje por defecto de los fragmentos de código"
|
||||
}
|
||||
|
||||
@@ -153,5 +153,6 @@
|
||||
"Allow dangerous html tags": "Accepter les tags html dangereux",
|
||||
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.",
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠",
|
||||
"Enable smart table editor": "Activer l'intelligent éditeur de tableaux",
|
||||
"Snippet Default Language": "Langage par défaut d'un snippet"
|
||||
}
|
||||
|
||||
271
locales/pl.json
271
locales/pl.json
@@ -1,155 +1,164 @@
|
||||
{
|
||||
"Notes": "Notes",
|
||||
"Tags": "Tags",
|
||||
"Preferences": "Preferences",
|
||||
"Make a note": "Make a note",
|
||||
"Notes": "Notatki",
|
||||
"Tags": "Tagi",
|
||||
"Preferences": "Ustawienia",
|
||||
"Make a note": "Stwórz notatkę",
|
||||
"Ctrl": "Ctrl",
|
||||
"Ctrl(^)": "Ctrl",
|
||||
"to create a new note": "to create a new note",
|
||||
"Toggle Mode": "Toggle Mode",
|
||||
"Trash": "Trash",
|
||||
"MODIFICATION DATE": "MODIFICATION DATE",
|
||||
"Words": "Words",
|
||||
"Letters": "Letters",
|
||||
"STORAGE": "STORAGE",
|
||||
"to create a new note": "Aby stworzyć nową notatkę",
|
||||
"Toggle Mode": "Przełącz tryb",
|
||||
"Trash": "Kosz",
|
||||
"MODIFICATION DATE": "DATA MODYFIKACJI",
|
||||
"Words": "Słowa",
|
||||
"Letters": "Litery",
|
||||
"STORAGE": "MIEJSCE ZAPISU",
|
||||
"FOLDER": "FOLDER",
|
||||
"CREATION DATE": "CREATION DATE",
|
||||
"NOTE LINK": "NOTE LINK",
|
||||
"CREATION DATE": "DATA UTWORZENIA",
|
||||
"NOTE LINK": "LINK NOTATKI",
|
||||
"Toggle editor mode": "Przełączanie trybu edytora",
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
"Print": "Print",
|
||||
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
||||
"Print": "Drukuj",
|
||||
"Help": "Pomoc",
|
||||
"Your preferences for Boostnote": "Twoje ustawienia dla Boostnote",
|
||||
"Storages": "Storages",
|
||||
"Add Storage Location": "Add Storage Location",
|
||||
"Add Folder": "Add Folder",
|
||||
"Open Storage folder": "Open Storage folder",
|
||||
"Unlink": "Unlink",
|
||||
"Edit": "Edit",
|
||||
"Delete": "Delete",
|
||||
"Interface": "Interface",
|
||||
"Interface Theme": "Interface Theme",
|
||||
"Default": "Default",
|
||||
"White": "White",
|
||||
"Add Storage Location": "Dodaj miejsce zapisu",
|
||||
"Add Folder": "Dodaj Folder",
|
||||
"Open Storage folder": "Otwórz folder zapisu",
|
||||
"Unlink": "Odlinkuj",
|
||||
"Edit": "Edytuj",
|
||||
"Delete": "Usuń",
|
||||
"Interface": "Interfejs",
|
||||
"Interface Theme": "Styl interfejsu",
|
||||
"Default": "Domyślny",
|
||||
"White": "Biały",
|
||||
"Solarized Dark": "Solarized Dark",
|
||||
"Dark": "Dark",
|
||||
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
|
||||
"Editor Theme": "Editor Theme",
|
||||
"Editor Font Size": "Editor Font Size",
|
||||
"Editor Font Family": "Editor Font Family",
|
||||
"Editor Indent Style": "Editor Indent Style",
|
||||
"Spaces": "Spaces",
|
||||
"Tabs": "Tabs",
|
||||
"Switch to Preview": "Switch to Preview",
|
||||
"When Editor Blurred": "When Editor Blurred",
|
||||
"When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click",
|
||||
"On Right Click": "On Right Click",
|
||||
"Editor Keymap": "Editor Keymap",
|
||||
"default": "default",
|
||||
"Dark": "Ciemny",
|
||||
"Show a confirmation dialog when deleting notes": "Zatwierdzaj usunięcie notatek",
|
||||
"Show only related tags": "Pokazuj tylko powiązane tagi",
|
||||
"Editor Theme": "Wygląd edytora",
|
||||
"Editor Font Size": "Rozmiar czcionki",
|
||||
"Editor Font Family": "Czcionka",
|
||||
"Snippet Default Language": "Domyślny język snippetów kodu",
|
||||
"Editor Indent Style": "Rodzaj wcięć",
|
||||
"Spaces": "Spacje",
|
||||
"Tabs": "Tabulatory",
|
||||
"Switch to Preview": "Przełącz na podgląd",
|
||||
"When Editor Blurred": "Gdy edytor jest w tle",
|
||||
"When Editor Blurred, Edit On Double Click": "Gdy edytor jest w tle, edytuj za pomocą podwójnego kliknięcia",
|
||||
"On Right Click": "Kliknięcie prawego przycisku myszy",
|
||||
"Editor Keymap": "Układ klawiszy edytora",
|
||||
"default": "domyślny",
|
||||
"vim": "vim",
|
||||
"emacs": "emacs",
|
||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
|
||||
"Show line numbers in the editor": "Show line numbers in the editor",
|
||||
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
|
||||
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
|
||||
"Preview": "Preview",
|
||||
"Preview Font Size": "Preview Font Size",
|
||||
"Preview Font Family": "Preview Font Family",
|
||||
"Code block Theme": "Code block Theme",
|
||||
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
|
||||
"Show line numbers for preview code blocks": "Show line numbers for preview code blocks",
|
||||
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
|
||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||
"PlantUML Server": "PlantUML Server",
|
||||
"Community": "Community",
|
||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Musisz zrestartować Boostnote po zmianie ustawień klawiszy",
|
||||
"Show line numbers in the editor": "Pokazuj numery lini w edytorze",
|
||||
"Allow editor to scroll past the last line": "Pozwalaj edytorowi na przewijanie poza końcową linię",
|
||||
"Bring in web page title when pasting URL on editor": "Wprowadź tytuł wklejanej strony WWW do edytora",
|
||||
"Preview": "Podgląd",
|
||||
"Preview Font Size": "Rozmiar czcionki",
|
||||
"Enable smart quotes": "Włącz inteligentne cytowanie",
|
||||
"Render newlines in Markdown paragraphs as <br>": "Dodawaj nowe linie w notatce jako znacznik <br>",
|
||||
"Preview Font Family": "Czcionka",
|
||||
"Code block Theme": "Styl bloku kodu",
|
||||
"Allow preview to scroll past the last line": "Pozwalaj podglądowi na przewijanie poza końcową linię",
|
||||
"Show line numbers for preview code blocks": "Pokazuj numery lini dla podglądu bloków kodu",
|
||||
"LaTeX Inline Open Delimiter": "Otwarcie liniowego ogranicznika LaTeX",
|
||||
"LaTeX Inline Close Delimiter": "Zamknięcie liniowego ogranicznika LaTeX",
|
||||
"LaTeX Block Open Delimiter": "Otwarcie blokowego ogranicznika LaTeX",
|
||||
"LaTeX Block Close Delimiter": "Zamknięcie blokowego ogranicznika LaTeX",
|
||||
"PlantUML Server": "Serwer PlantUML",
|
||||
"Community": "Społeczność",
|
||||
"Subscribe to Newsletter": "Zapisz się do Newslettera",
|
||||
"GitHub": "GitHub",
|
||||
"Blog": "Blog",
|
||||
"Facebook Group": "Facebook Group",
|
||||
"Facebook Group": "Grupa na Facebooku",
|
||||
"Twitter": "Twitter",
|
||||
"About": "About",
|
||||
"About": "O nas",
|
||||
"Boostnote": "Boostnote",
|
||||
"An open source note-taking app made for programmers just like you.": "An open source note-taking app made for programmers just like you.",
|
||||
"Website": "Website",
|
||||
"An open source note-taking app made for programmers just like you.": "Aplikacja open-source do przechowywania notatek, stworzona dla programistów takich jak Ty.",
|
||||
"Website": "Strona WWW",
|
||||
"Development": "Development",
|
||||
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
|
||||
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
|
||||
"License: GPL v3": "License: GPL v3",
|
||||
"Analytics": "Analytics",
|
||||
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",
|
||||
"You can see how it works on ": "You can see how it works on ",
|
||||
"You can choose to enable or disable this option.": "You can choose to enable or disable this option.",
|
||||
"Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote",
|
||||
"License: GPL v3": "Licencja: GPL v3",
|
||||
"Analytics": "Statystyki",
|
||||
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote zbiera anonimowe dane wyłącznie w celu poprawy działania aplikacji, lecz nigdy nie pobiera prywatnych danych z Twoich notatek.",
|
||||
"You can see how it works on ": "Możesz zobaczyć jak to działa na platformie",
|
||||
"You can choose to enable or disable this option.": "Możesz włączyć lub wyłączyć zbieranie danych tutaj:",
|
||||
"Enable analytics to help improve Boostnote": "Zbieraj dane by pomóc w ulepszaniu Boostnote",
|
||||
"Crowdfunding": "Crowdfunding",
|
||||
"Dear everyone,": "Dear everyone,",
|
||||
"Thank you for using Boostnote!": "Thank you for using Boostnote!",
|
||||
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.",
|
||||
"To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,",
|
||||
"we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.",
|
||||
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!",
|
||||
"Thanks,": "Thanks,",
|
||||
"Boostnote maintainers": "Boostnote maintainers",
|
||||
"Support via OpenCollective": "Support via OpenCollective",
|
||||
"Language": "Language",
|
||||
"English": "English",
|
||||
"German": "German",
|
||||
"French": "French",
|
||||
"Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying",
|
||||
"All Notes": "All Notes",
|
||||
"Starred": "Starred",
|
||||
"Are you sure to ": "Are you sure to ",
|
||||
" delete": " delete",
|
||||
"this folder?": "this folder?",
|
||||
"Confirm": "Confirm",
|
||||
"Cancel": "Cancel",
|
||||
"Markdown Note": "Markdown Note",
|
||||
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.",
|
||||
"Snippet Note": "Snippet Note",
|
||||
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "This format is for creating code snippets. Multiple snippets can be grouped into a single note.",
|
||||
"Tab to switch format": "Tab to switch format",
|
||||
"Updated": "Updated",
|
||||
"Created": "Created",
|
||||
"Alphabetically": "Alphabetically",
|
||||
"Default View": "Default View",
|
||||
"Compressed View": "Compressed View",
|
||||
"Search": "Search",
|
||||
"Blog Type": "Blog Type",
|
||||
"Blog Address": "Blog Address",
|
||||
"Save": "Save",
|
||||
"Auth": "Auth",
|
||||
"Authentication Method": "Authentication Method",
|
||||
"Dear everyone,": "Droga społeczności,",
|
||||
"Thank you for using Boostnote!": "Dziękujemy za używanie Boostnote!",
|
||||
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote jest używany w około 200 krajach i regionach przez wspaniałą społeczność programistów.",
|
||||
"To continue supporting this growth, and to satisfy community expectations,": "Chcielibyśmy poświęcić więcej czasu na rozwój naszego projektu",
|
||||
"we would like to invest more time and resources in this project.": "aby popularność i satysfakcja naszej społeczności ciągle wzrastała.",
|
||||
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Jeśli podoba Ci się naszy projekt i lubisz go używać, możesz wspomóc nasz przez OpenCollective!",
|
||||
"Thanks,": "Dzięki,",
|
||||
"Boostnote maintainers": "Kontrybutorzy Boostnote",
|
||||
"Support via OpenCollective": "Wspomóż przez OpenCollective",
|
||||
"Language": "Język",
|
||||
"English": "Angielski",
|
||||
"German": "Niemiecki",
|
||||
"French": "Francuski",
|
||||
"Show \"Saved to Clipboard\" notification when copying": "Pokazuj \"Skopiowano do schowka\" podczas kopiowania",
|
||||
"All Notes": "Notatki",
|
||||
"Starred": "Oznaczone",
|
||||
"Are you sure to ": "Czy na pewno chcesz ",
|
||||
" delete": " usunąc",
|
||||
"this folder?": "ten folder?",
|
||||
"Confirm": "Potwierdź",
|
||||
"Cancel": "Anuluj",
|
||||
"Markdown Note": "Notatka Markdown",
|
||||
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Ten format pozwala na tworzenie dokumentów tekstowych, list zadań, bloków kodu, czy bloków Latex.",
|
||||
"Snippet Note": "Snippet Kodu",
|
||||
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Ten format zezwala na tworzenia snippetów kodu. Kilka snippetów można zgrupować w jedną notatkę.",
|
||||
"Tab to switch format": "Tabulator by zmienić format",
|
||||
"Updated": "Ostatnio aktualizowane",
|
||||
"Created": "Data utworzenia",
|
||||
"Alphabetically": "Alfabetycznie",
|
||||
"Default View": "Widok domyślny",
|
||||
"Compressed View": "Widok skompresowany",
|
||||
"Search": "Szukaj",
|
||||
"Blog Type": "Typ bloga",
|
||||
"Blog Address": "Adres bloga",
|
||||
"Save": "Zapisz",
|
||||
"Auth": "Autoryzacja",
|
||||
"Authentication Method": "Metoda Autoryzacji",
|
||||
"JWT": "JWT",
|
||||
"USER": "USER",
|
||||
"USER": "Użutkownik",
|
||||
"Token": "Token",
|
||||
"Storage": "Storage",
|
||||
"Hotkeys": "Hotkeys",
|
||||
"Show/Hide Boostnote": "Show/Hide Boostnote",
|
||||
"Restore": "Restore",
|
||||
"Permanent Delete": "Permanent Delete",
|
||||
"Confirm note deletion": "Confirm note deletion",
|
||||
"This will permanently remove this note.": "This will permanently remove this note.",
|
||||
"Successfully applied!": "Successfully applied!",
|
||||
"Albanian": "Albanian",
|
||||
"Chinese (zh-CN)": "Chinese (zh-CN)",
|
||||
"Chinese (zh-TW)": "Chinese (zh-TW)",
|
||||
"Danish": "Danish",
|
||||
"Japanese": "Japanese",
|
||||
"Korean": "Korean",
|
||||
"Norwegian": "Norwegian",
|
||||
"Polish": "Polish",
|
||||
"Portuguese": "Portuguese",
|
||||
"Spanish": "Spanish",
|
||||
"You have to save!": "You have to save!",
|
||||
"Russian": "Russian",
|
||||
"Editor Rulers": "Editor Rulers",
|
||||
"Enable": "Enable",
|
||||
"Disable": "Disable",
|
||||
"Hotkeys": "Skróty klawiszowe",
|
||||
"Show/Hide Boostnote": "Pokaż/Ukryj Boostnote",
|
||||
"Restore": "Przywróć",
|
||||
"Permanent Delete": "Usuń trwale",
|
||||
"Confirm note deletion": "Potwierdź usunięcie notatki",
|
||||
"This will permanently remove this note.": "Spodowoduje to trwałe usunięcie notatki",
|
||||
"Successfully applied!": "Sukces!",
|
||||
"Albanian": "Albański",
|
||||
"Chinese (zh-CN)": "Chiński (zh-CN)",
|
||||
"Chinese (zh-TW)": "Chiński (zh-TW)",
|
||||
"Danish": "Duński",
|
||||
"Japanese": "Japoński",
|
||||
"Korean": "Koreański",
|
||||
"Norwegian": "Norweski",
|
||||
"Polish": "Polski",
|
||||
"Portuguese": "Portugalski",
|
||||
"Spanish": "Hiszpański",
|
||||
"You have to save!": "Musisz zapisać!",
|
||||
"Russian": "Rosyjski",
|
||||
"Editor Rulers": "Margines",
|
||||
"Enable": "Włącz",
|
||||
"Disable": "Wyłącz",
|
||||
"Sanitization": "Sanitization",
|
||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||
"Allow styles": "Allow styles",
|
||||
"Allow dangerous html tags": "Allow dangerous html tags",
|
||||
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
||||
"Only allow secure html tags (recommended)": "Zezwól tylko na bezpieczne tagi HTML (zalecane)",
|
||||
"Allow styles": "Zezwól na style",
|
||||
"Allow dangerous html tags": "Zezwól na niebezpieczne tagi HTML",
|
||||
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Konwertuje tekstowe strzałki na znaki. ⚠ Wpłynie to na używanie komentarzy HTML w Twojej notatce.",
|
||||
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Wkleiłes link odnoszący się do załącznika, ktory nie może zostać znaleziony. Wklejanie linków do załączników jest możliwe tylko gdy notatka i załącznik są w tym samym folderze. Używaj opcji 'Przeciągaj i upuść' załącznik! ⚠",
|
||||
"Star": "Oznacz",
|
||||
"Fullscreen": "Pełen ekran",
|
||||
"Add tag...": "Dodaj tag..."
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"markdown-it-named-headers": "^0.0.4",
|
||||
"markdown-it-plantuml": "^1.1.0",
|
||||
"markdown-it-smartarrows": "^1.0.1",
|
||||
"markdown-toc": "^1.2.0",
|
||||
"mdurl": "^1.0.1",
|
||||
"mermaid": "^8.0.0-rc.8",
|
||||
"moment": "^2.10.3",
|
||||
@@ -88,6 +89,7 @@
|
||||
"node-ipc": "^8.1.0",
|
||||
"raphael": "^2.2.7",
|
||||
"react": "^15.5.4",
|
||||
"react-autosuggest": "^9.4.0",
|
||||
"react-codemirror": "^0.3.0",
|
||||
"react-debounce-render": "^4.0.1",
|
||||
"react-dom": "^15.0.2",
|
||||
|
||||
@@ -22,7 +22,8 @@ Thank you to all the people who already contributed to Boostnote!
|
||||
Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers.
|
||||
|
||||
Any issues on Boostnote can be funded by anyone and that money will be distributed to contributors and maintainers. If you'd like to join them, please consider:
|
||||
- [Become a backer on IssueHunt](https://issuehunt.io/repos/53266139).
|
||||
|
||||
[](https://issuehunt.io/repos/53266139)
|
||||
|
||||
## Community
|
||||
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
||||
|
||||
52
tests/dataApi/exportStorage-test.js
Normal file
52
tests/dataApi/exportStorage-test.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const test = require('ava')
|
||||
const exportStorage = require('browser/main/lib/dataApi/exportStorage')
|
||||
|
||||
global.document = require('jsdom').jsdom('<body></body>')
|
||||
global.window = document.defaultView
|
||||
global.navigator = window.navigator
|
||||
|
||||
const Storage = require('dom-storage')
|
||||
const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
|
||||
const path = require('path')
|
||||
const TestDummy = require('../fixtures/TestDummy')
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const sander = require('sander')
|
||||
|
||||
test.beforeEach(t => {
|
||||
t.context.storageDir = path.join(os.tmpdir(), 'test/export-storage')
|
||||
t.context.storage = TestDummy.dummyStorage(t.context.storageDir)
|
||||
t.context.exportDir = path.join(os.tmpdir(), 'test/export-storage-output')
|
||||
try { fs.mkdirSync(t.context.exportDir) } catch (e) {}
|
||||
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
||||
})
|
||||
|
||||
test.serial('Export a storage', t => {
|
||||
const storageKey = t.context.storage.cache.key
|
||||
const folders = t.context.storage.json.folders
|
||||
const notes = t.context.storage.notes
|
||||
const exportDir = t.context.exportDir
|
||||
const folderKeyToName = folders.reduce(
|
||||
(acc, folder) => {
|
||||
acc[folder.key] = folder.name
|
||||
return acc
|
||||
}, {})
|
||||
return exportStorage(storageKey, 'md', exportDir)
|
||||
.then(() => {
|
||||
notes.forEach(note => {
|
||||
const noteDir = path.join(exportDir, folderKeyToName[note.folder], `${note.title}.md`)
|
||||
if (note.type === 'MARKDOWN_NOTE') {
|
||||
t.true(fs.existsSync(noteDir))
|
||||
t.is(fs.readFileSync(noteDir, 'utf8'), note.content)
|
||||
} else if (note.type === 'SNIPPET_NOTE') {
|
||||
t.false(fs.existsSync(noteDir))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test.afterEach.always(t => {
|
||||
localStorage.clear()
|
||||
sander.rimrafSync(t.context.storageDir)
|
||||
sander.rimrafSync(t.context.exportDir)
|
||||
})
|
||||
@@ -1,5 +1,23 @@
|
||||
import browserEnv from 'browser-env'
|
||||
browserEnv(['window', 'document'])
|
||||
browserEnv(['window', 'document', 'navigator'])
|
||||
|
||||
// for CodeMirror mockup
|
||||
document.body.createTextRange = function () {
|
||||
return {
|
||||
setEnd: function () {},
|
||||
setStart: function () {},
|
||||
getBoundingClientRect: function () {
|
||||
return {right: 0}
|
||||
},
|
||||
getClientRects: function () {
|
||||
return {
|
||||
length: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.localStorage = {
|
||||
// polyfill
|
||||
|
||||
@@ -14,7 +14,8 @@ test('findNoteTitle#find should return a correct title (string)', t => {
|
||||
['hoge\n====\nfuga', 'hoge'],
|
||||
['====', '===='],
|
||||
['```\n# hoge\n```', '```'],
|
||||
['hoge', 'hoge']
|
||||
['hoge', 'hoge'],
|
||||
['---\nlayout: test\n---\n # hoge', '# hoge']
|
||||
]
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
|
||||
668
tests/lib/markdown-toc-generator-test.js
Normal file
668
tests/lib/markdown-toc-generator-test.js
Normal file
@@ -0,0 +1,668 @@
|
||||
/**
|
||||
* @fileoverview Unit test for browser/lib/markdown-toc-generator
|
||||
*/
|
||||
|
||||
import CodeMirror from 'codemirror'
|
||||
require('codemirror/addon/search/searchcursor.js')
|
||||
const test = require('ava')
|
||||
const markdownToc = require('browser/lib/markdown-toc-generator')
|
||||
const EOL = require('os').EOL
|
||||
|
||||
test(t => {
|
||||
/**
|
||||
* Contains array of test cases in format :
|
||||
* [
|
||||
* test title
|
||||
* input markdown,
|
||||
* expected toc
|
||||
* ]
|
||||
* @type {*[]}
|
||||
*/
|
||||
const testCases = [
|
||||
[
|
||||
'***************************** empty note',
|
||||
`
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
'***************************** single level',
|
||||
`
|
||||
# one
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
'***************************** two levels',
|
||||
`
|
||||
# one
|
||||
# two
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
- [two](#two)
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
'***************************** 3 levels with children',
|
||||
`
|
||||
# one
|
||||
## one one
|
||||
# two
|
||||
## two two
|
||||
# three
|
||||
## three three
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
- [three](#three)
|
||||
* [three three](#three-three)
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
'***************************** 3 levels, 3rd with 6 sub-levels',
|
||||
`
|
||||
# one
|
||||
## one one
|
||||
# two
|
||||
## two two
|
||||
# three
|
||||
## three three
|
||||
### three three three
|
||||
#### three three three three
|
||||
##### three three three three three
|
||||
###### three three three three three three
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
- [three](#three)
|
||||
* [three three](#three-three)
|
||||
+ [three three three](#three-three-three)
|
||||
- [three three three three](#three-three-three-three)
|
||||
* [three three three three three](#three-three-three-three-three)
|
||||
+ [three three three three three three](#three-three-three-three-three-three)
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
'***************************** multilevel with texts in between',
|
||||
`
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
# three
|
||||
this is a level three three text
|
||||
this is a level three three text
|
||||
## three three
|
||||
this is a text
|
||||
this is a text
|
||||
### three three three
|
||||
this is a text
|
||||
this is a text
|
||||
### three three three 2
|
||||
this is a text
|
||||
this is a text
|
||||
#### three three three three
|
||||
this is a text
|
||||
this is a text
|
||||
#### three three three three 2
|
||||
this is a text
|
||||
this is a text
|
||||
##### three three three three three
|
||||
this is a text
|
||||
this is a text
|
||||
##### three three three three three 2
|
||||
this is a text
|
||||
this is a text
|
||||
###### three three three three three three
|
||||
this is a text
|
||||
this is a text
|
||||
this is a text
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
- [three](#three)
|
||||
* [three three](#three-three)
|
||||
+ [three three three](#three-three-three)
|
||||
+ [three three three 2](#three-three-three-2)
|
||||
- [three three three three](#three-three-three-three)
|
||||
- [three three three three 2](#three-three-three-three-2)
|
||||
* [three three three three three](#three-three-three-three-three)
|
||||
* [three three three three three 2](#three-three-three-three-three-2)
|
||||
+ [three three three three three three](#three-three-three-three-three-three)
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
'***************************** outdated TOC',
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
# one modified
|
||||
## one one
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [one modified](#one-modified)
|
||||
* [one one](#one-one)
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
'***************************** properly generated case sensitive TOC',
|
||||
`
|
||||
# onE
|
||||
## oNe one
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [onE](#onE)
|
||||
* [oNe one](#oNe-one)
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
'***************************** position of TOC is stable (do not use elements above toc marker)',
|
||||
`
|
||||
# title
|
||||
|
||||
this is a text
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [onE](#onE)
|
||||
* [oNe one](#oNe-one)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
# onE
|
||||
## oNe one
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [onE](#onE)
|
||||
* [oNe one](#oNe-one)
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
'***************************** properly handle generation of not completed TOC',
|
||||
`
|
||||
# hoge
|
||||
|
||||
##
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [hoge](#hoge)
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
]
|
||||
]
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
const title = testCase[0]
|
||||
const inputMd = testCase[1].trim()
|
||||
const expectedToc = testCase[2].trim()
|
||||
const generatedToc = markdownToc.generate(inputMd)
|
||||
|
||||
t.is(generatedToc, expectedToc, `generate test : ${title} , generated : ${EOL}${generatedToc}, expected : ${EOL}${expectedToc}`)
|
||||
})
|
||||
})
|
||||
|
||||
test(t => {
|
||||
/**
|
||||
* Contains array of test cases in format :
|
||||
* [
|
||||
* title
|
||||
* cursor
|
||||
* inputMd
|
||||
* expectedMd
|
||||
* ]
|
||||
* @type {*[]}
|
||||
*/
|
||||
const testCases = [
|
||||
[
|
||||
`***************************** Empty note, cursor at the top`,
|
||||
{line: 0, ch: 0},
|
||||
``,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
`***************************** Two level note,TOC at the beginning `,
|
||||
{line: 0, ch: 0},
|
||||
`
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`,
|
||||
`
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
|
||||
<!-- tocstop -->
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`
|
||||
],
|
||||
[
|
||||
`***************************** Two level note, cursor just after 'header text' `,
|
||||
{line: 1, ch: 12},
|
||||
`
|
||||
# header
|
||||
header text
|
||||
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`,
|
||||
`
|
||||
# header
|
||||
header text
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`
|
||||
],
|
||||
[
|
||||
`***************************** Two level note, cursor at empty line under 'header text' `,
|
||||
{line: 2, ch: 0},
|
||||
`
|
||||
# header
|
||||
header text
|
||||
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`,
|
||||
`
|
||||
# header
|
||||
header text
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
|
||||
<!-- tocstop -->
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`
|
||||
],
|
||||
[
|
||||
`***************************** Two level note, cursor just before 'text' word`,
|
||||
{line: 1, ch: 8},
|
||||
`
|
||||
# header
|
||||
header text
|
||||
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`,
|
||||
`
|
||||
# header
|
||||
header
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
|
||||
<!-- tocstop -->
|
||||
text
|
||||
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`
|
||||
],
|
||||
[
|
||||
`***************************** Already generated TOC without header file, regenerate TOC in place, no changes`,
|
||||
{line: 13, ch: 0},
|
||||
`
|
||||
# header
|
||||
header text
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
|
||||
<!-- tocstop -->
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`,
|
||||
`
|
||||
# header
|
||||
header text
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
|
||||
<!-- tocstop -->
|
||||
# one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`
|
||||
],
|
||||
[
|
||||
`***************************** Already generated TOC, needs updating in place`,
|
||||
{line: 0, ch: 0},
|
||||
`
|
||||
# header
|
||||
header text
|
||||
<!-- toc -->
|
||||
|
||||
- [one](#one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
|
||||
<!-- tocstop -->
|
||||
# This is the one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`,
|
||||
`
|
||||
# header
|
||||
header text
|
||||
<!-- toc -->
|
||||
|
||||
- [This is the one](#This-is-the-one)
|
||||
* [one one](#one-one)
|
||||
- [two](#two)
|
||||
* [two two](#two-two)
|
||||
|
||||
<!-- tocstop -->
|
||||
# This is the one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`
|
||||
],
|
||||
[
|
||||
`***************************** Document with cursor at the last line, expecting empty TOC `,
|
||||
{line: 13, ch: 30},
|
||||
`
|
||||
# header
|
||||
header text
|
||||
|
||||
# This is the one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
`,
|
||||
`
|
||||
# header
|
||||
header text
|
||||
|
||||
# This is the one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
<!-- toc -->
|
||||
|
||||
|
||||
|
||||
<!-- tocstop -->
|
||||
`
|
||||
],
|
||||
[
|
||||
`***************************** Empty, not actual TOC , should be supplemented with two new points beneath`,
|
||||
{line: 0, ch: 0},
|
||||
`
|
||||
# header
|
||||
header text
|
||||
|
||||
# This is the one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
<!-- toc -->
|
||||
|
||||
|
||||
|
||||
<!-- tocstop -->
|
||||
# new point included in toc
|
||||
## new subpoint
|
||||
`,
|
||||
`
|
||||
# header
|
||||
header text
|
||||
|
||||
# This is the one
|
||||
this is a level one text
|
||||
this is a level one text
|
||||
|
||||
## one one
|
||||
# two
|
||||
this is a level two text
|
||||
this is a level two text
|
||||
## two two
|
||||
this is a level two two text
|
||||
this is a level two two text
|
||||
<!-- toc -->
|
||||
|
||||
- [new point included in toc](#new-point-included-in-toc)
|
||||
* [new subpoint](#new-subpoint)
|
||||
|
||||
<!-- tocstop -->
|
||||
# new point included in toc
|
||||
## new subpoint
|
||||
`
|
||||
]
|
||||
]
|
||||
testCases.forEach(testCase => {
|
||||
const title = testCase[0]
|
||||
const cursor = testCase[1]
|
||||
const inputMd = testCase[2].trim()
|
||||
const expectedMd = testCase[3].trim()
|
||||
|
||||
const editor = CodeMirror()
|
||||
editor.setValue(inputMd)
|
||||
editor.setCursor(cursor)
|
||||
markdownToc.generateInEditor(editor)
|
||||
|
||||
t.is(expectedMd, editor.getValue(), `generateInEditor test : ${title} , generated : ${EOL}${editor.getValue()}, expected : ${EOL}${expectedMd}`)
|
||||
})
|
||||
})
|
||||
@@ -41,6 +41,7 @@ var config = {
|
||||
'markdown-it-kbd',
|
||||
'markdown-it-plantuml',
|
||||
'markdown-it-admonition',
|
||||
'markdown-toc',
|
||||
'devtron',
|
||||
'@rokt33r/season',
|
||||
{
|
||||
|
||||
34
yarn.lock
34
yarn.lock
@@ -6352,6 +6352,10 @@ oauth-sign@~0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
|
||||
|
||||
object-assign@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
|
||||
|
||||
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
@@ -7190,6 +7194,22 @@ rcedit@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-1.1.0.tgz#ae21c28d4efdd78e95fcab7309a5dd084920b16a"
|
||||
|
||||
react-autosuggest@^9.4.0:
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-9.4.0.tgz#3146bc9afa4f171bed067c542421edec5ca94294"
|
||||
dependencies:
|
||||
prop-types "^15.5.10"
|
||||
react-autowhatever "^10.1.2"
|
||||
shallow-equal "^1.0.0"
|
||||
|
||||
react-autowhatever@^10.1.2:
|
||||
version "10.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-autowhatever/-/react-autowhatever-10.1.2.tgz#200ffc41373b2189e3f6140ac7bdb82363a79fd3"
|
||||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
react-themeable "^1.1.0"
|
||||
section-iterator "^2.0.0"
|
||||
|
||||
react-codemirror@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-codemirror/-/react-codemirror-0.3.0.tgz#cd6bd6ef458ec1e035cfd8b3fe7b30c8c7883c6c"
|
||||
@@ -7290,6 +7310,12 @@ react-test-renderer@^15.6.2:
|
||||
fbjs "^0.8.9"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
react-themeable@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-themeable/-/react-themeable-1.1.0.tgz#7d4466dd9b2b5fa75058727825e9f152ba379a0e"
|
||||
dependencies:
|
||||
object-assign "^3.0.0"
|
||||
|
||||
react-transform-catch-errors@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-transform-catch-errors/-/react-transform-catch-errors-1.0.2.tgz#1b4d4a76e97271896fc16fe3086c793ec88a9eeb"
|
||||
@@ -7792,6 +7818,10 @@ scope-css@^1.0.5:
|
||||
version "1.1.0"
|
||||
resolved "http://registry.npm.taobao.org/scope-css/download/scope-css-1.1.0.tgz#74eff45461bc9d3f3b29ed575b798cd722fa1256"
|
||||
|
||||
section-iterator@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
|
||||
|
||||
semver-diff@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
||||
@@ -7887,6 +7917,10 @@ sha.js@2.2.6:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba"
|
||||
|
||||
shallow-equal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.0.0.tgz#508d1838b3de590ab8757b011b25e430900945f7"
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
|
||||
Reference in New Issue
Block a user