mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46:22 +00:00
Merge remote-tracking branch 'upstream/master' into allow-no-html-escape
This commit is contained in:
@@ -20,6 +20,6 @@ If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/
|
|||||||
- OS Version and name :
|
- OS Version and name :
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Love Boostnote? Please consider supporting us via OpenCollective:
|
Love Boostnote? Please consider supporting us on IssueHunt:
|
||||||
👉 https://opencollective.com/boostnoteio
|
👉 https://issuehunt.io/repos/53266139
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
|||||||
import convertModeName from 'browser/lib/convertModeName'
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import iconv from 'iconv-lite'
|
import iconv from 'iconv-lite'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import fs from 'fs'
|
||||||
const { ipcRenderer } = require('electron')
|
const { ipcRenderer } = require('electron')
|
||||||
|
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||||
|
|
||||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
|
||||||
const buildCMRulers = (rulers, enableRulers) =>
|
const buildCMRulers = (rulers, enableRulers) =>
|
||||||
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
|
enableRulers ? rulers.map(ruler => ({column: ruler})) : []
|
||||||
|
|
||||||
export default class CodeEditor extends React.Component {
|
export default class CodeEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -81,8 +83,21 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { rulers, enableRulers } = this.props
|
const { rulers, enableRulers } = this.props
|
||||||
this.value = this.props.value
|
const expandSnippet = this.expandSnippet.bind(this)
|
||||||
|
|
||||||
|
const defaultSnippet = [
|
||||||
|
{
|
||||||
|
id: crypto.randomBytes(16).toString('hex'),
|
||||||
|
name: 'Dummy text',
|
||||||
|
prefix: ['lorem', 'ipsum'],
|
||||||
|
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
||||||
|
fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.value = this.props.value
|
||||||
this.editor = CodeMirror(this.refs.root, {
|
this.editor = CodeMirror(this.refs.root, {
|
||||||
rulers: buildCMRulers(rulers, enableRulers),
|
rulers: buildCMRulers(rulers, enableRulers),
|
||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
@@ -103,6 +118,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
Tab: function (cm) {
|
Tab: function (cm) {
|
||||||
const cursor = cm.getCursor()
|
const cursor = cm.getCursor()
|
||||||
const line = cm.getLine(cursor.line)
|
const line = cm.getLine(cursor.line)
|
||||||
|
const cursorPosition = cursor.ch
|
||||||
|
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
|
||||||
if (cm.somethingSelected()) cm.indentSelection('add')
|
if (cm.somethingSelected()) cm.indentSelection('add')
|
||||||
else {
|
else {
|
||||||
const tabs = cm.getOption('indentWithTabs')
|
const tabs = cm.getOption('indentWithTabs')
|
||||||
@@ -114,6 +131,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
cm.execCommand('insertSoftTab')
|
cm.execCommand('insertSoftTab')
|
||||||
}
|
}
|
||||||
cm.execCommand('goLineEnd')
|
cm.execCommand('goLineEnd')
|
||||||
|
} else if (!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
|
||||||
|
// text expansion on tab key if the char before is alphabet
|
||||||
|
const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
|
||||||
|
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
||||||
|
if (tabs) {
|
||||||
|
cm.execCommand('insertTab')
|
||||||
|
} else {
|
||||||
|
cm.execCommand('insertSoftTab')
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
cm.execCommand('insertTab')
|
cm.execCommand('insertTab')
|
||||||
@@ -157,6 +184,73 @@ export default class CodeEditor extends React.Component {
|
|||||||
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandSnippet (line, cursor, cm, snippets) {
|
||||||
|
const wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch)
|
||||||
|
const templateCursorString = ':{}'
|
||||||
|
for (let i = 0; i < snippets.length; i++) {
|
||||||
|
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
||||||
|
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
|
||||||
|
const snippetLines = snippets[i].content.split('\n')
|
||||||
|
let cursorLineNumber = 0
|
||||||
|
let cursorLinePosition = 0
|
||||||
|
for (let j = 0; j < snippetLines.length; j++) {
|
||||||
|
const cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
||||||
|
if (cursorIndex !== -1) {
|
||||||
|
cursorLineNumber = j
|
||||||
|
cursorLinePosition = cursorIndex
|
||||||
|
cm.replaceRange(
|
||||||
|
snippets[i].content.replace(templateCursorString, ''),
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cm.replaceRange(
|
||||||
|
snippets[i].content,
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
getWordBeforeCursor (line, lineNumber, cursorPosition) {
|
||||||
|
let wordBeforeCursor = ''
|
||||||
|
const originCursorPosition = cursorPosition
|
||||||
|
const emptyChars = /\t|\s|\r|\n/
|
||||||
|
|
||||||
|
// to prevent the word to expand is long that will crash the whole app
|
||||||
|
// the safeStop is there to stop user to expand words that longer than 20 chars
|
||||||
|
const safeStop = 20
|
||||||
|
|
||||||
|
while (cursorPosition > 0) {
|
||||||
|
const currentChar = line.substr(cursorPosition - 1, 1)
|
||||||
|
// if char is not an empty char
|
||||||
|
if (!emptyChars.test(currentChar)) {
|
||||||
|
wordBeforeCursor = currentChar + wordBeforeCursor
|
||||||
|
} else if (wordBeforeCursor.length >= safeStop) {
|
||||||
|
throw new Error('Your snippet trigger is too long !')
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cursorPosition--
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: wordBeforeCursor,
|
||||||
|
range: {
|
||||||
|
from: {line: lineNumber, ch: originCursorPosition},
|
||||||
|
to: {line: lineNumber, ch: cursorPosition}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
quitEditor () {
|
quitEditor () {
|
||||||
document.querySelector('textarea').blur()
|
document.querySelector('textarea').blur()
|
||||||
}
|
}
|
||||||
@@ -174,7 +268,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate (prevProps, prevState) {
|
componentDidUpdate (prevProps, prevState) {
|
||||||
let needRefresh = false
|
let needRefresh = false
|
||||||
const { rulers, enableRulers } = this.props
|
const {rulers, enableRulers} = this.props
|
||||||
if (prevProps.mode !== this.props.mode) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
this.setMode(this.props.mode)
|
this.setMode(this.props.mode)
|
||||||
}
|
}
|
||||||
@@ -274,6 +368,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
handlePaste (editor, e) {
|
handlePaste (editor, e) {
|
||||||
const clipboardData = e.clipboardData
|
const clipboardData = e.clipboardData
|
||||||
|
const {storageKey, noteKey} = this.props
|
||||||
const dataTransferItem = clipboardData.items[0]
|
const dataTransferItem = clipboardData.items[0]
|
||||||
const pastedTxt = clipboardData.getData('text')
|
const pastedTxt = clipboardData.getData('text')
|
||||||
const isURL = (str) => {
|
const isURL = (str) => {
|
||||||
@@ -283,22 +378,28 @@ export default class CodeEditor extends React.Component {
|
|||||||
const isInLinkTag = (editor) => {
|
const isInLinkTag = (editor) => {
|
||||||
const startCursor = editor.getCursor('start')
|
const startCursor = editor.getCursor('start')
|
||||||
const prevChar = editor.getRange(
|
const prevChar = editor.getRange(
|
||||||
{ line: startCursor.line, ch: startCursor.ch - 2 },
|
{line: startCursor.line, ch: startCursor.ch - 2},
|
||||||
{ line: startCursor.line, ch: startCursor.ch }
|
{line: startCursor.line, ch: startCursor.ch}
|
||||||
)
|
)
|
||||||
const endCursor = editor.getCursor('end')
|
const endCursor = editor.getCursor('end')
|
||||||
const nextChar = editor.getRange(
|
const nextChar = editor.getRange(
|
||||||
{ line: endCursor.line, ch: endCursor.ch },
|
{line: endCursor.line, ch: endCursor.ch},
|
||||||
{ line: endCursor.line, ch: endCursor.ch + 1 }
|
{line: endCursor.line, ch: endCursor.ch + 1}
|
||||||
)
|
)
|
||||||
return prevChar === '](' && nextChar === ')'
|
return prevChar === '](' && nextChar === ')'
|
||||||
}
|
}
|
||||||
if (dataTransferItem.type.match('image')) {
|
if (dataTransferItem.type.match('image')) {
|
||||||
const {storageKey, noteKey} = this.props
|
|
||||||
attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem)
|
attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem)
|
||||||
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
||||||
this.handlePasteUrl(e, editor, pastedTxt)
|
this.handlePasteUrl(e, editor, pastedTxt)
|
||||||
}
|
}
|
||||||
|
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
||||||
|
attachmentManagement.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
||||||
|
.then((modifiedText) => {
|
||||||
|
this.editor.replaceSelection(modifiedText)
|
||||||
|
})
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleScroll (e) {
|
||||||
@@ -312,24 +413,58 @@ export default class CodeEditor extends React.Component {
|
|||||||
const taggedUrl = `<${pastedTxt}>`
|
const taggedUrl = `<${pastedTxt}>`
|
||||||
editor.replaceSelection(taggedUrl)
|
editor.replaceSelection(taggedUrl)
|
||||||
|
|
||||||
|
const isImageReponse = (response) => {
|
||||||
|
return response.headers.has('content-type') &&
|
||||||
|
response.headers.get('content-type').match(/^image\/.+$/)
|
||||||
|
}
|
||||||
|
const replaceTaggedUrl = (replacement) => {
|
||||||
|
const value = editor.getValue()
|
||||||
|
const cursor = editor.getCursor()
|
||||||
|
const newValue = value.replace(taggedUrl, replacement)
|
||||||
|
const newCursor = Object.assign({}, cursor, { ch: cursor.ch + newValue.length - value.length })
|
||||||
|
editor.setValue(newValue)
|
||||||
|
editor.setCursor(newCursor)
|
||||||
|
}
|
||||||
|
|
||||||
fetch(pastedTxt, {
|
fetch(pastedTxt, {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
return this.decodeResponse(response)
|
if (isImageReponse(response)) {
|
||||||
}).then((response) => {
|
return this.mapImageResponse(response, pastedTxt)
|
||||||
const parsedResponse = (new window.DOMParser()).parseFromString(response, 'text/html')
|
} else {
|
||||||
const value = editor.getValue()
|
return this.mapNormalResponse(response, pastedTxt)
|
||||||
const cursor = editor.getCursor()
|
}
|
||||||
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})`
|
}).then((replacement) => {
|
||||||
const newValue = value.replace(taggedUrl, LinkWithTitle)
|
replaceTaggedUrl(replacement)
|
||||||
editor.setValue(newValue)
|
|
||||||
editor.setCursor(cursor)
|
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
const value = editor.getValue()
|
replaceTaggedUrl(pastedTxt)
|
||||||
const newValue = value.replace(taggedUrl, pastedTxt)
|
})
|
||||||
const cursor = editor.getCursor()
|
}
|
||||||
editor.setValue(newValue)
|
|
||||||
editor.setCursor(cursor)
|
mapNormalResponse (response, pastedTxt) {
|
||||||
|
return this.decodeResponse(response).then((body) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const parsedBody = (new window.DOMParser()).parseFromString(body, 'text/html')
|
||||||
|
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
|
||||||
|
resolve(linkWithTitle)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mapImageResponse (response, pastedTxt) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const url = response.url
|
||||||
|
const name = url.substring(url.lastIndexOf('/') + 1)
|
||||||
|
const imageLinkWithName = ``
|
||||||
|
resolve(imageLinkWithName)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,11 +494,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { className, fontSize } = this.props
|
const {className, fontSize} = this.props
|
||||||
let fontFamily = this.props.fontFamily
|
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
const width = this.props.width
|
||||||
? [fontFamily].concat(defaultEditorFontFamily)
|
|
||||||
: defaultEditorFontFamily
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className == null
|
className={className == null
|
||||||
@@ -373,8 +506,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
ref='root'
|
ref='root'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
style={{
|
style={{
|
||||||
fontFamily: fontFamily.join(', '),
|
fontFamily,
|
||||||
fontSize: fontSize
|
fontSize: fontSize,
|
||||||
|
width: width
|
||||||
}}
|
}}
|
||||||
onDrop={(e) => this.handleDropImage(e)}
|
onDrop={(e) => this.handleDropImage(e)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -283,6 +283,8 @@ class MarkdownEditor extends React.Component {
|
|||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
smartQuotes={config.preview.smartQuotes}
|
smartQuotes={config.preview.smartQuotes}
|
||||||
|
smartArrows={config.preview.smartArrows}
|
||||||
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
@@ -295,6 +297,8 @@ class MarkdownEditor extends React.Component {
|
|||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
|
customCSS={config.preview.customCSS}
|
||||||
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,10 +22,12 @@ const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
|||||||
|
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const fileUrl = require('file-url')
|
||||||
|
|
||||||
const dialog = remote.dialog
|
const dialog = remote.dialog
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||||
const appPath = 'file://' + (process.env.NODE_ENV === 'production'
|
const appPath = fileUrl(process.env.NODE_ENV === 'production'
|
||||||
? app.getAppPath()
|
? app.getAppPath()
|
||||||
: path.resolve())
|
: path.resolve())
|
||||||
const CSS_FILES = [
|
const CSS_FILES = [
|
||||||
@@ -33,7 +35,7 @@ const CSS_FILES = [
|
|||||||
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
||||||
]
|
]
|
||||||
|
|
||||||
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) {
|
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) {
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
@@ -53,7 +55,19 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scro
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Material Icons'),
|
||||||
|
local('MaterialIcons-Regular'),
|
||||||
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff2') format('woff2'),
|
||||||
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
||||||
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
${allowCustomCSS ? customCSS : ''}
|
||||||
${markdownStyle}
|
${markdownStyle}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: '${fontFamily.join("','")}';
|
font-family: '${fontFamily.join("','")}';
|
||||||
font-size: ${fontSize}px;
|
font-size: ${fontSize}px;
|
||||||
@@ -111,6 +125,9 @@ body p {
|
|||||||
color: #000;
|
color: #000;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
.clipboardButton {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
@@ -133,7 +150,6 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
||||||
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
|
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
|
||||||
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
||||||
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
|
|
||||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||||
@@ -146,29 +162,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initMarkdown () {
|
initMarkdown () {
|
||||||
const { smartQuotes, sanitize } = this.props
|
const { smartQuotes, sanitize, breaks } = this.props
|
||||||
this.markdown = new Markdown({
|
this.markdown = new Markdown({
|
||||||
typographer: smartQuotes,
|
typographer: smartQuotes,
|
||||||
sanitize
|
sanitize,
|
||||||
|
breaks
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewAnchorClick (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
const anchor = e.target.closest('a')
|
|
||||||
const href = anchor.getAttribute('href')
|
|
||||||
if (_.isString(href) && href.match(/^#/)) {
|
|
||||||
const targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
|
|
||||||
if (targetElement != null) {
|
|
||||||
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
shell.openExternal(href)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCheckboxClick (e) {
|
handleCheckboxClick (e) {
|
||||||
this.props.onCheckboxClick(e)
|
this.props.onCheckboxClick(e)
|
||||||
}
|
}
|
||||||
@@ -216,9 +217,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
handleSaveAsHtml () {
|
handleSaveAsHtml () {
|
||||||
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
||||||
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams()
|
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
|
||||||
|
|
||||||
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme)
|
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
|
||||||
let body = this.markdown.render(escapeHtmlCharacters(noteContent))
|
let body = this.markdown.render(escapeHtmlCharacters(noteContent))
|
||||||
|
|
||||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
@@ -341,7 +342,10 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||||
if (prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize) {
|
if (prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||||
|
prevProps.sanitize !== this.props.sanitize ||
|
||||||
|
prevProps.smartArrows !== this.props.smartArrows ||
|
||||||
|
prevProps.breaks !== this.props.breaks) {
|
||||||
this.initMarkdown()
|
this.initMarkdown()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
@@ -352,14 +356,16 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
prevProps.lineNumber !== this.props.lineNumber ||
|
prevProps.lineNumber !== this.props.lineNumber ||
|
||||||
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
||||||
prevProps.theme !== this.props.theme ||
|
prevProps.theme !== this.props.theme ||
|
||||||
prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
||||||
|
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
||||||
|
prevProps.customCSS !== this.props.customCSS) {
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleParams () {
|
getStyleParams () {
|
||||||
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme } = this.props
|
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = this.props
|
||||||
let { fontFamily, codeBlockFontFamily } = this.props
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
||||||
@@ -368,14 +374,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
||||||
: defaultCodeBlockFontFamily
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme}
|
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyle () {
|
applyStyle () {
|
||||||
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams()
|
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
|
||||||
|
|
||||||
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
|
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
|
||||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme)
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
|
||||||
}
|
}
|
||||||
|
|
||||||
GetCodeThemeLink (theme) {
|
GetCodeThemeLink (theme) {
|
||||||
@@ -388,9 +394,6 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rewriteIframe () {
|
rewriteIframe () {
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
|
||||||
el.removeEventListener('click', this.anchorClickHandler)
|
|
||||||
})
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
el.removeEventListener('click', this.checkboxClickHandler)
|
el.removeEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
@@ -399,7 +402,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.removeEventListener('click', this.linkClickHandler)
|
el.removeEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
const { theme, indentSize, showCopyNotification, storagePath } = this.props
|
const { theme, indentSize, showCopyNotification, storagePath, noteKey } = this.props
|
||||||
let { value, codeBlockTheme } = this.props
|
let { value, codeBlockTheme } = this.props
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
@@ -411,18 +414,15 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
let renderedHTML = this.markdown.render(value)
|
let renderedHTML = this.markdown.render(value)
|
||||||
|
attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
|
||||||
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
|
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
|
||||||
this.fixDecodedURI(el)
|
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
|
||||||
})
|
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
el.addEventListener('click', this.checkboxClickHandler)
|
el.addEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
this.fixDecodedURI(el)
|
||||||
el.addEventListener('click', this.linkClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -473,7 +473,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
diagram.drawSVG(el, opts)
|
diagram.drawSVG(el, opts)
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -489,7 +489,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
diagram.drawSVG(el, {theme: 'simple'})
|
diagram.drawSVG(el, {theme: 'simple'})
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -538,11 +538,6 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
const href = e.target.href
|
const href = e.target.href
|
||||||
if (href.match(/^http/i)) {
|
|
||||||
shell.openExternal(href)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const linkHash = href.split('/').pop()
|
const linkHash = href.split('/').pop()
|
||||||
|
|
||||||
const regexNoteInternalLink = /main.html#(.+)/
|
const regexNoteInternalLink = /main.html#(.+)/
|
||||||
@@ -574,6 +569,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
eventEmitter.emit('list:jump', linkHash.split('-')[1])
|
eventEmitter.emit('list:jump', linkHash.split('-')[1])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// other case
|
||||||
|
shell.openExternal(href)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@@ -596,9 +594,12 @@ MarkdownPreview.propTypes = {
|
|||||||
onDoubleClick: PropTypes.func,
|
onDoubleClick: PropTypes.func,
|
||||||
onMouseUp: PropTypes.func,
|
onMouseUp: PropTypes.func,
|
||||||
onMouseDown: PropTypes.func,
|
onMouseDown: PropTypes.func,
|
||||||
|
onContextMenu: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
showCopyNotification: PropTypes.bool,
|
showCopyNotification: PropTypes.bool,
|
||||||
storagePath: PropTypes.string,
|
storagePath: PropTypes.string,
|
||||||
smartQuotes: PropTypes.bool
|
smartQuotes: PropTypes.bool,
|
||||||
|
smartArrows: PropTypes.bool,
|
||||||
|
breaks: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
this.focus = () => this.refs.code.focus()
|
this.focus = () => this.refs.code.focus()
|
||||||
this.reload = () => this.refs.code.reload()
|
this.reload = () => this.refs.code.reload()
|
||||||
this.userScroll = true
|
this.userScroll = true
|
||||||
|
this.state = {
|
||||||
|
isSliderFocused: false,
|
||||||
|
codeEditorWidthInPercent: 50
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnChange () {
|
handleOnChange () {
|
||||||
@@ -87,6 +91,42 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMouseMove (e) {
|
||||||
|
if (this.state.isSliderFocused) {
|
||||||
|
const rootRect = this.refs.root.getBoundingClientRect()
|
||||||
|
const rootWidth = rootRect.width
|
||||||
|
const offset = rootRect.left
|
||||||
|
let newCodeEditorWidthInPercent = (e.pageX - offset) / rootWidth * 100
|
||||||
|
|
||||||
|
// limit minSize to 10%, maxSize to 90%
|
||||||
|
if (newCodeEditorWidthInPercent <= 10) {
|
||||||
|
newCodeEditorWidthInPercent = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCodeEditorWidthInPercent >= 90) {
|
||||||
|
newCodeEditorWidthInPercent = 90
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
codeEditorWidthInPercent: newCodeEditorWidthInPercent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
isSliderFocused: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseDown (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
isSliderFocused: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {config, value, storageKey, noteKey} = this.props
|
const {config, value, storageKey, noteKey} = this.props
|
||||||
const storage = findStorage(storageKey)
|
const storage = findStorage(storageKey)
|
||||||
@@ -95,12 +135,16 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
const previewStyle = {}
|
const previewStyle = {}
|
||||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
|
||||||
|
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
|
||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root' ref='root'
|
||||||
|
onMouseMove={e => this.handleMouseMove(e)}
|
||||||
|
onMouseUp={e => this.handleMouseUp(e)}>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
styleName='codeEditor'
|
styleName='codeEditor'
|
||||||
ref='code'
|
ref='code'
|
||||||
|
width={this.state.codeEditorWidthInPercent + '%'}
|
||||||
mode='GitHub Flavored Markdown'
|
mode='GitHub Flavored Markdown'
|
||||||
value={value}
|
value={value}
|
||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
@@ -119,6 +163,9 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
onChange={this.handleOnChange.bind(this)}
|
onChange={this.handleOnChange.bind(this)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleScroll.bind(this)}
|
||||||
/>
|
/>
|
||||||
|
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||||
|
<div styleName='slider-hitbox' />
|
||||||
|
</div>
|
||||||
<MarkdownPreview
|
<MarkdownPreview
|
||||||
style={previewStyle}
|
style={previewStyle}
|
||||||
styleName='preview'
|
styleName='preview'
|
||||||
@@ -131,6 +178,8 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
smartQuotes={config.preview.smartQuotes}
|
smartQuotes={config.preview.smartQuotes}
|
||||||
|
smartArrows={config.preview.smartArrows}
|
||||||
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
tabInde='0'
|
tabInde='0'
|
||||||
@@ -140,6 +189,8 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
|
customCSS={config.preview.customCSS}
|
||||||
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,14 @@
|
|||||||
height 100%
|
height 100%
|
||||||
font-size 30px
|
font-size 30px
|
||||||
display flex
|
display flex
|
||||||
.codeEditor
|
.slider
|
||||||
width 50%
|
absolute top bottom
|
||||||
.preview
|
top -2px
|
||||||
width 50%
|
width 0
|
||||||
|
z-index 0
|
||||||
|
.slider-hitbox
|
||||||
|
absolute top bottom left right
|
||||||
|
width 7px
|
||||||
|
left -3px
|
||||||
|
z-index 10
|
||||||
|
cursor col-resize
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ body[data-theme="dark"]
|
|||||||
.item-simple-wrapper
|
.item-simple-wrapper
|
||||||
border-color transparent
|
border-color transparent
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
.iconWrap
|
.iconWrap
|
||||||
width 20px
|
width 20px
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
.counters
|
.counters
|
||||||
float right
|
float right
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
@@ -68,10 +68,9 @@
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
position fixed
|
position fixed
|
||||||
display inline-block
|
display inline-block
|
||||||
height 32px
|
height 36px
|
||||||
left 44px
|
left 44px
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
margin-top -8px
|
|
||||||
margin-left 0
|
margin-left 0
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
z-index 10
|
z-index 10
|
||||||
|
|||||||
@@ -58,8 +58,8 @@
|
|||||||
opacity 0
|
opacity 0
|
||||||
border-top-right-radius 2px
|
border-top-right-radius 2px
|
||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
height 26px
|
height 34px
|
||||||
line-height 26px
|
line-height 32px
|
||||||
|
|
||||||
.folderList-item:hover, .folderList-item--active:hover
|
.folderList-item:hover, .folderList-item--active:hover
|
||||||
.folderList-item-tooltip
|
.folderList-item-tooltip
|
||||||
|
|||||||
@@ -293,6 +293,84 @@ kbd
|
|||||||
line-height 1
|
line-height 1
|
||||||
padding 3px 5px
|
padding 3px 5px
|
||||||
|
|
||||||
|
$admonition
|
||||||
|
box-shadow 0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2)
|
||||||
|
position relative
|
||||||
|
margin 1.5625em 0
|
||||||
|
padding 0 1.2rem
|
||||||
|
border-left .4rem solid #448aff
|
||||||
|
border-radius .2rem
|
||||||
|
overflow auto
|
||||||
|
|
||||||
|
html .admonition>:last-child
|
||||||
|
margin-bottom 1.2rem
|
||||||
|
|
||||||
|
.admonition .admonition
|
||||||
|
margin 1em 0
|
||||||
|
|
||||||
|
.admonition p
|
||||||
|
margin-top: 0.5em
|
||||||
|
|
||||||
|
$admonition-icon
|
||||||
|
position absolute
|
||||||
|
left 1.2rem
|
||||||
|
font-family: "Material Icons"
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 24px
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: ltr;
|
||||||
|
|
||||||
|
/* Support for all WebKit browsers. */
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
/* Support for Safari and Chrome. */
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
|
||||||
|
/* Support for Firefox. */
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
||||||
|
/* Support for IE. */
|
||||||
|
font-feature-settings: 'liga';
|
||||||
|
|
||||||
|
$admonition-title
|
||||||
|
margin 0 -1.2rem
|
||||||
|
padding .8rem 1.2rem .8rem 4rem
|
||||||
|
border-bottom .1rem solid rgba(68,138,255,.1)
|
||||||
|
background-color rgba(68,138,255,.1)
|
||||||
|
font-weight 700
|
||||||
|
|
||||||
|
.admonition>.admonition-title:last-child
|
||||||
|
margin-bottom 0
|
||||||
|
|
||||||
|
admonition_types = {
|
||||||
|
note: {color: #0288D1, icon: "note"},
|
||||||
|
hint: {color: #009688, icon: "info_outline"},
|
||||||
|
danger: {color: #c2185b, icon: "block"},
|
||||||
|
caution: {color: #ffa726, icon: "warning"},
|
||||||
|
error: {color: #d32f2f, icon: "error_outline"},
|
||||||
|
attention: {color: #455a64, icon: "priority_high"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, val in admonition_types
|
||||||
|
.admonition.{name}
|
||||||
|
@extend $admonition
|
||||||
|
border-left-color: val[color]
|
||||||
|
|
||||||
|
.admonition.{name}>.admonition-title
|
||||||
|
@extend $admonition-title
|
||||||
|
border-bottom-color: .1rem solid rgba(val[color], 0.2)
|
||||||
|
background-color: rgba(val[color], 0.2)
|
||||||
|
|
||||||
|
.admonition.{name}>.admonition-title:before
|
||||||
|
@extend $admonition-icon
|
||||||
|
color: val[color]
|
||||||
|
content: val[icon]
|
||||||
|
|
||||||
themeDarkBackground = darken(#21252B, 10%)
|
themeDarkBackground = darken(#21252B, 10%)
|
||||||
themeDarkText = #f9f9f9
|
themeDarkText = #f9f9f9
|
||||||
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||||
@@ -396,4 +474,6 @@ body[data-theme="monokai"]
|
|||||||
td
|
td
|
||||||
border-color themeMonokaiTableBorder
|
border-color themeMonokaiTableBorder
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px themeMonokaiTableBorder
|
border-right solid 1px themeMonokaiTableBorder
|
||||||
|
kbd
|
||||||
|
background-color themeDarkBackground
|
||||||
@@ -58,6 +58,9 @@ const languages = [
|
|||||||
{
|
{
|
||||||
name: 'Spanish',
|
name: 'Spanish',
|
||||||
locale: 'es-ES'
|
locale: 'es-ES'
|
||||||
|
}, {
|
||||||
|
name: 'Turkish',
|
||||||
|
locale: 'tr'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ const themes = fs.readdirSync(themePath)
|
|||||||
})
|
})
|
||||||
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
||||||
|
|
||||||
|
const snippetFile = process.env.NODE_ENV !== 'test'
|
||||||
|
? path.join(app.getPath('userData'), 'snippets.json')
|
||||||
|
: '' // return nothing as we specified different path to snippets.json in test
|
||||||
|
|
||||||
const consts = {
|
const consts = {
|
||||||
FOLDER_COLORS: [
|
FOLDER_COLORS: [
|
||||||
'#E10051',
|
'#E10051',
|
||||||
@@ -31,7 +35,16 @@ const consts = {
|
|||||||
'Dodger Blue',
|
'Dodger Blue',
|
||||||
'Violet Eggplant'
|
'Violet Eggplant'
|
||||||
],
|
],
|
||||||
THEMES: ['default'].concat(themes)
|
THEMES: ['default'].concat(themes),
|
||||||
|
SNIPPET_FILE: snippetFile,
|
||||||
|
DEFAULT_EDITOR_FONT_FAMILY: [
|
||||||
|
'Monaco',
|
||||||
|
'Menlo',
|
||||||
|
'Ubuntu Mono',
|
||||||
|
'Consolas',
|
||||||
|
'source-code-pro',
|
||||||
|
'monospace'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = consts
|
module.exports = consts
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import markdownit from 'markdown-it'
|
|||||||
import sanitize from './markdown-it-sanitize-html'
|
import sanitize from './markdown-it-sanitize-html'
|
||||||
import emoji from 'markdown-it-emoji'
|
import emoji from 'markdown-it-emoji'
|
||||||
import math from '@rokt33r/markdown-it-math'
|
import math from '@rokt33r/markdown-it-math'
|
||||||
|
import smartArrows from 'markdown-it-smartarrows'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
@@ -25,7 +26,7 @@ class Markdown {
|
|||||||
linkify: true,
|
linkify: true,
|
||||||
html: true,
|
html: true,
|
||||||
xhtmlOut: true,
|
xhtmlOut: true,
|
||||||
breaks: true,
|
breaks: config.preview.breaks,
|
||||||
highlight: function (str, lang) {
|
highlight: function (str, lang) {
|
||||||
const delimiter = ':'
|
const delimiter = ':'
|
||||||
const langInfo = lang.split(delimiter)
|
const langInfo = lang.split(delimiter)
|
||||||
@@ -141,6 +142,7 @@ class Markdown {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.md.use(require('markdown-it-kbd'))
|
this.md.use(require('markdown-it-kbd'))
|
||||||
|
this.md.use(require('markdown-it-admonition'))
|
||||||
|
|
||||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||||
this.md.use(require('markdown-it-plantuml'), '', {
|
this.md.use(require('markdown-it-plantuml'), '', {
|
||||||
@@ -213,6 +215,10 @@ class Markdown {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (config.preview.smartArrows) {
|
||||||
|
this.md.use(smartArrows)
|
||||||
|
}
|
||||||
|
|
||||||
// Add line number attribute for scrolling
|
// Add line number attribute for scrolling
|
||||||
const originalRender = this.md.renderer.render
|
const originalRender = this.md.renderer.render
|
||||||
this.md.renderer.render = (tokens, options, env) => {
|
this.md.renderer.render = (tokens, options, env) => {
|
||||||
|
|||||||
0
browser/lib/markdown2.js
Normal file
0
browser/lib/markdown2.js
Normal file
9
browser/lib/normalizeEditorFontFamily.js
Normal file
9
browser/lib/normalizeEditorFontFamily.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import isString from 'lodash/isString'
|
||||||
|
|
||||||
|
export default function normalizeEditorFontFamily (fontFamily) {
|
||||||
|
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
||||||
|
return isString(fontFamily) && fontFamily.length > 0
|
||||||
|
? [fontFamily].concat(defaultEditorFontFamily).join(', ')
|
||||||
|
: defaultEditorFontFamily.join(', ')
|
||||||
|
}
|
||||||
@@ -47,7 +47,25 @@ function escapeHtmlCharacters (html) {
|
|||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isObjectEqual (a, b) {
|
||||||
|
const aProps = Object.getOwnPropertyNames(a)
|
||||||
|
const bProps = Object.getOwnPropertyNames(b)
|
||||||
|
|
||||||
|
if (aProps.length !== bProps.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < aProps.length; i++) {
|
||||||
|
const propName = aProps[i]
|
||||||
|
if (a[propName] !== b[propName]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
lastFindInArray,
|
lastFindInArray,
|
||||||
escapeHtmlCharacters
|
escapeHtmlCharacters,
|
||||||
|
isObjectEqual
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
.control-infoButton-panel
|
.control-infoButton-panel
|
||||||
z-index 200
|
z-index 200
|
||||||
margin-top 0px
|
margin-top 0px
|
||||||
|
top: 50px
|
||||||
right 25px
|
right 25px
|
||||||
position absolute
|
position absolute
|
||||||
padding 20px 25px 0 25px
|
padding 20px 25px 0 25px
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
ee.on('topbar:togglemodebutton', () => {
|
||||||
|
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||||
|
this.handleSwitchMode(reversedType)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
@@ -273,6 +277,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
handleSwitchMode (type) {
|
handleSwitchMode (type) {
|
||||||
this.setState({ editorType: type }, () => {
|
this.setState({ editorType: type }, () => {
|
||||||
|
this.focus()
|
||||||
const newConfig = Object.assign({}, this.props.config)
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
newConfig.editor.type = type
|
newConfig.editor.type = type
|
||||||
ConfigManager.set(newConfig)
|
ConfigManager.set(newConfig)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
|||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { dialog } = remote
|
||||||
|
|
||||||
class SnippetNoteDetail extends React.Component {
|
class SnippetNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -451,14 +451,14 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleModeButtonClick (e, index) {
|
handleModeButtonClick (e, index) {
|
||||||
const menu = new Menu()
|
const templetes = []
|
||||||
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
|
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
|
||||||
menu.append(new MenuItem({
|
templetes.push({
|
||||||
label: mode.name,
|
label: mode.name,
|
||||||
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
||||||
}))
|
})
|
||||||
})
|
})
|
||||||
menu.popup(remote.getCurrentWindow())
|
context.popup(templetes)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIndentTypeButtonClick (e) {
|
handleIndentTypeButtonClick (e) {
|
||||||
|
|||||||
@@ -44,16 +44,9 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeLastTag () {
|
removeLastTag () {
|
||||||
let { value } = this.props
|
this.removeTagByCallback((value) => {
|
||||||
|
value.pop()
|
||||||
value = _.isArray(value)
|
})
|
||||||
? value.slice()
|
|
||||||
: []
|
|
||||||
value.pop()
|
|
||||||
value = _.uniq(value)
|
|
||||||
|
|
||||||
this.value = value
|
|
||||||
this.props.onChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset () {
|
reset () {
|
||||||
@@ -96,15 +89,22 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTagRemoveButtonClick (tag) {
|
handleTagRemoveButtonClick (tag) {
|
||||||
return (e) => {
|
this.removeTagByCallback((value, tag) => {
|
||||||
let { value } = this.props
|
|
||||||
|
|
||||||
value.splice(value.indexOf(tag), 1)
|
value.splice(value.indexOf(tag), 1)
|
||||||
value = _.uniq(value)
|
}, tag)
|
||||||
|
}
|
||||||
|
|
||||||
this.value = value
|
removeTagByCallback (callback, tag = null) {
|
||||||
this.props.onChange()
|
let { value } = this.props
|
||||||
}
|
|
||||||
|
value = _.isArray(value)
|
||||||
|
? value.slice()
|
||||||
|
: []
|
||||||
|
callback(value, tag)
|
||||||
|
value = _.uniq(value)
|
||||||
|
|
||||||
|
this.value = value
|
||||||
|
this.props.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@@ -118,7 +118,7 @@ class TagSelect extends React.Component {
|
|||||||
>
|
>
|
||||||
<span styleName='tag-label'>#{tag}</span>
|
<span styleName='tag-label'>#{tag}</span>
|
||||||
<button styleName='tag-removeButton'
|
<button styleName='tag-removeButton'
|
||||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
|
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
||||||
>
|
>
|
||||||
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import SnippetNoteDetail from './SnippetNoteDetail'
|
|||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import debounceRender from 'react-debounce-render'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -99,4 +100,4 @@ Detail.propTypes = {
|
|||||||
ignorePreviewPointerEvents: PropTypes.bool
|
ignorePreviewPointerEvents: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(Detail, styles)
|
export default debounceRender(CSSModules(Detail, styles))
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { hashHistory } from 'react-router'
|
|||||||
import store from 'browser/main/store'
|
import store from 'browser/main/store'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import { getLocales } from 'browser/lib/Languages'
|
import { getLocales } from 'browser/lib/Languages'
|
||||||
|
import applyShortcuts from 'browser/main/lib/shortcutManager'
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
@@ -159,7 +160,7 @@ class Main extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
i18n.setLocale('en')
|
i18n.setLocale('en')
|
||||||
}
|
}
|
||||||
|
applyShortcuts()
|
||||||
// Reload all data
|
// Reload all data
|
||||||
dataApi.init()
|
dataApi.init()
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import debounceRender from 'react-debounce-render'
|
||||||
import styles from './NoteList.styl'
|
import styles from './NoteList.styl'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import NoteItem from 'browser/components/NoteItem'
|
import NoteItem from 'browser/components/NoteItem'
|
||||||
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
||||||
@@ -19,9 +21,10 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
|||||||
import Markdown from '../../lib/markdown'
|
import Markdown from '../../lib/markdown'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { dialog } = remote
|
||||||
const WP_POST_PATH = '/wp/v2/posts'
|
const WP_POST_PATH = '/wp/v2/posts'
|
||||||
|
|
||||||
function sortByCreatedAt (a, b) {
|
function sortByCreatedAt (a, b) {
|
||||||
@@ -489,55 +492,51 @@ class NoteList extends React.Component {
|
|||||||
const updateLabel = i18n.__('Update Blog')
|
const updateLabel = i18n.__('Update Blog')
|
||||||
const openBlogLabel = i18n.__('Open Blog')
|
const openBlogLabel = i18n.__('Open Blog')
|
||||||
|
|
||||||
const menu = new Menu()
|
const templates = []
|
||||||
|
|
||||||
if (location.pathname.match(/\/trash/)) {
|
if (location.pathname.match(/\/trash/)) {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: restoreNote,
|
label: restoreNote,
|
||||||
click: this.restoreNote
|
click: this.restoreNote
|
||||||
}))
|
}, {
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: deleteLabel,
|
label: deleteLabel,
|
||||||
click: this.deleteNote
|
click: this.deleteNote
|
||||||
}))
|
})
|
||||||
} else {
|
} else {
|
||||||
if (!location.pathname.match(/\/starred/)) {
|
if (!location.pathname.match(/\/starred/)) {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: pinLabel,
|
label: pinLabel,
|
||||||
click: this.pinToTop
|
click: this.pinToTop
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: deleteLabel,
|
label: deleteLabel,
|
||||||
click: this.deleteNote
|
click: this.deleteNote
|
||||||
}))
|
}, {
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: cloneNote,
|
label: cloneNote,
|
||||||
click: this.cloneNote.bind(this)
|
click: this.cloneNote.bind(this)
|
||||||
}))
|
}, {
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: copyNoteLink,
|
label: copyNoteLink,
|
||||||
click: this.copyNoteLink(note)
|
click: this.copyNoteLink(note)
|
||||||
}))
|
})
|
||||||
if (note.type === 'MARKDOWN_NOTE') {
|
if (note.type === 'MARKDOWN_NOTE') {
|
||||||
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: updateLabel,
|
label: updateLabel,
|
||||||
click: this.publishMarkdown.bind(this)
|
click: this.publishMarkdown.bind(this)
|
||||||
}))
|
}, {
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: openBlogLabel,
|
label: openBlogLabel,
|
||||||
click: () => this.openBlog.bind(this)(note)
|
click: () => this.openBlog.bind(this)(note)
|
||||||
}))
|
})
|
||||||
} else {
|
} else {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: publishLabel,
|
label: publishLabel,
|
||||||
click: this.publishMarkdown.bind(this)
|
click: this.publishMarkdown.bind(this)
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
menu.popup()
|
context.popup(templates)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedNotes (updateFunc, cleanSelection = true) {
|
updateSelectedNotes (updateFunc, cleanSelection = true) {
|
||||||
@@ -662,6 +661,10 @@ class NoteList extends React.Component {
|
|||||||
title: firstNote.title + ' ' + i18n.__('copy'),
|
title: firstNote.title + ' ' + i18n.__('copy'),
|
||||||
content: firstNote.content
|
content: firstNote.content
|
||||||
})
|
})
|
||||||
|
.then((note) => {
|
||||||
|
attachmentManagement.cloneAttachments(firstNote, note)
|
||||||
|
return note
|
||||||
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
@@ -943,15 +946,24 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
const viewType = this.getViewType()
|
const viewType = this.getViewType()
|
||||||
|
|
||||||
|
const autoSelectFirst =
|
||||||
|
notes.length === 1 ||
|
||||||
|
selectedNoteKeys.length === 0 ||
|
||||||
|
notes.every(note => !selectedNoteKeys.includes(note.key))
|
||||||
|
|
||||||
const noteList = notes
|
const noteList = notes
|
||||||
.map(note => {
|
.map((note, index) => {
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDefault = config.listStyle === 'DEFAULT'
|
const isDefault = config.listStyle === 'DEFAULT'
|
||||||
const uniqueKey = getNoteKey(note)
|
const uniqueKey = getNoteKey(note)
|
||||||
const isActive = selectedNoteKeys.includes(uniqueKey)
|
|
||||||
|
const isActive =
|
||||||
|
selectedNoteKeys.includes(uniqueKey) ||
|
||||||
|
notes.length === 1 ||
|
||||||
|
(autoSelectFirst && index === 0)
|
||||||
const dateDisplay = moment(
|
const dateDisplay = moment(
|
||||||
config.sortBy === 'CREATED_AT'
|
config.sortBy === 'CREATED_AT'
|
||||||
? note.createdAt : note.updatedAt
|
? note.createdAt : note.updatedAt
|
||||||
@@ -1053,4 +1065,4 @@ NoteList.propTypes = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(NoteList, styles)
|
export default debounceRender(CSSModules(NoteList, styles))
|
||||||
|
|||||||
@@ -48,4 +48,5 @@ body[data-theme="dark"]
|
|||||||
line-height normal
|
line-height normal
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
white-space nowrap
|
||||||
|
|||||||
@@ -11,21 +11,26 @@ import StorageItemChild from 'browser/components/StorageItem'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { SortableElement } from 'react-sortable-hoc'
|
import { SortableElement } from 'react-sortable-hoc'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, dialog } = remote
|
const { dialog } = remote
|
||||||
|
const escapeStringRegexp = require('escape-string-regexp')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
class StorageItem extends React.Component {
|
class StorageItem extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
const { storage } = this.props
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpen: true
|
isOpen: !!storage.isOpen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHeaderContextMenu (e) {
|
handleHeaderContextMenu (e) {
|
||||||
const menu = Menu.buildFromTemplate([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: i18n.__('Add Folder'),
|
label: i18n.__('Add Folder'),
|
||||||
click: (e) => this.handleAddFolderButtonClick(e)
|
click: (e) => this.handleAddFolderButtonClick(e)
|
||||||
@@ -38,8 +43,6 @@ class StorageItem extends React.Component {
|
|||||||
click: (e) => this.handleUnlinkStorageClick(e)
|
click: (e) => this.handleUnlinkStorageClick(e)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
menu.popup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUnlinkStorageClick (e) {
|
handleUnlinkStorageClick (e) {
|
||||||
@@ -66,8 +69,18 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleToggleButtonClick (e) {
|
handleToggleButtonClick (e) {
|
||||||
|
const { storage, dispatch } = this.props
|
||||||
|
const isOpen = !this.state.isOpen
|
||||||
|
dataApi.toggleStorage(storage.key, isOpen)
|
||||||
|
.then((storage) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'EXPAND_STORAGE',
|
||||||
|
storage,
|
||||||
|
isOpen
|
||||||
|
})
|
||||||
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpen: !this.state.isOpen
|
isOpen: isOpen
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +105,7 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFolderButtonContextMenu (e, folder) {
|
handleFolderButtonContextMenu (e, folder) {
|
||||||
const menu = Menu.buildFromTemplate([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: i18n.__('Rename Folder'),
|
label: i18n.__('Rename Folder'),
|
||||||
click: (e) => this.handleRenameFolderClick(e, folder)
|
click: (e) => this.handleRenameFolderClick(e, folder)
|
||||||
@@ -121,8 +134,6 @@ class StorageItem extends React.Component {
|
|||||||
click: (e) => this.handleFolderDeleteClick(e, folder)
|
click: (e) => this.handleFolderDeleteClick(e, folder)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
menu.popup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRenameFolderClick (e, folder) {
|
handleRenameFolderClick (e, folder) {
|
||||||
@@ -201,7 +212,7 @@ class StorageItem extends React.Component {
|
|||||||
createdNoteData.forEach((newNote) => {
|
createdNoteData.forEach((newNote) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: noteData.find((note) => note.content === newNote.content),
|
originNote: noteData.find((note) => note.content === newNote.oldContent),
|
||||||
note: newNote
|
note: newNote
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -223,7 +234,8 @@ class StorageItem extends React.Component {
|
|||||||
const { folderNoteMap, trashedSet } = data
|
const { folderNoteMap, trashedSet } = data
|
||||||
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
||||||
const folderList = storage.folders.map((folder, index) => {
|
const folderList = storage.folders.map((folder, index) => {
|
||||||
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
|
let folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
|
||||||
|
const isActive = !!(location.pathname.match(folderRegex))
|
||||||
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||||
|
|
||||||
let noteCount = 0
|
let noteCount = 0
|
||||||
@@ -253,7 +265,7 @@ class StorageItem extends React.Component {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
|
const isActive = location.pathname.match(new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + '$'))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName={isFolded ? 'root--folded' : 'root'}
|
<div styleName={isFolded ? 'root--folded' : 'root'}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
height 36px
|
height 36px
|
||||||
padding-left 25px
|
padding-left 25px
|
||||||
padding-right 15px
|
padding-right 15px
|
||||||
line-height 22px
|
line-height 36px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
font-size 14px
|
font-size 14px
|
||||||
border none
|
border none
|
||||||
@@ -147,7 +147,7 @@ body[data-theme="dark"]
|
|||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
&:active
|
&:active
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
.header--active
|
.header--active
|
||||||
.header-addFolderButton
|
.header-addFolderButton
|
||||||
@@ -180,7 +180,7 @@ body[data-theme="dark"]
|
|||||||
&:active, &:active:hover
|
&:active, &:active:hover
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
border-radius 2px
|
border-radius 2px
|
||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
white-space nowrap
|
||||||
|
|
||||||
body[data-theme="white"]
|
body[data-theme="white"]
|
||||||
.non-active-button
|
.non-active-button
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
const { remote } = require('electron')
|
|
||||||
const { Menu } = remote
|
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import styles from './SideNav.styl'
|
import styles from './SideNav.styl'
|
||||||
import { openModal } from 'browser/main/lib/modal'
|
import { openModal } from 'browser/main/lib/modal'
|
||||||
@@ -19,6 +17,7 @@ import ListButton from './ListButton'
|
|||||||
import TagButton from './TagButton'
|
import TagButton from './TagButton'
|
||||||
import {SortableContainer} from 'react-sortable-hoc'
|
import {SortableContainer} from 'react-sortable-hoc'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
class SideNav extends React.Component {
|
class SideNav extends React.Component {
|
||||||
// TODO: should not use electron stuff v0.7
|
// TODO: should not use electron stuff v0.7
|
||||||
@@ -185,7 +184,7 @@ class SideNav extends React.Component {
|
|||||||
).filter(
|
).filter(
|
||||||
note => activeTags.every(tag => note.tags.includes(tag))
|
note => activeTags.every(tag => note.tags.includes(tag))
|
||||||
)
|
)
|
||||||
let relatedTags = new Set()
|
const relatedTags = new Set()
|
||||||
relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag)))
|
relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag)))
|
||||||
return relatedTags
|
return relatedTags
|
||||||
}
|
}
|
||||||
@@ -224,7 +223,7 @@ class SideNav extends React.Component {
|
|||||||
handleClickNarrowToTag (tag) {
|
handleClickNarrowToTag (tag) {
|
||||||
const { router } = this.context
|
const { router } = this.context
|
||||||
const { location } = this.props
|
const { location } = this.props
|
||||||
let listOfTags = this.getActiveTags(location.pathname)
|
const listOfTags = this.getActiveTags(location.pathname)
|
||||||
const indexOfTag = listOfTags.indexOf(tag)
|
const indexOfTag = listOfTags.indexOf(tag)
|
||||||
if (indexOfTag > -1) {
|
if (indexOfTag > -1) {
|
||||||
listOfTags.splice(indexOfTag, 1)
|
listOfTags.splice(indexOfTag, 1)
|
||||||
@@ -254,10 +253,9 @@ class SideNav extends React.Component {
|
|||||||
handleFilterButtonContextMenu (event) {
|
handleFilterButtonContextMenu (event) {
|
||||||
const { data } = this.props
|
const { data } = this.props
|
||||||
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
const menu = Menu.buildFromTemplate([
|
context.popup([
|
||||||
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
|
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
|
||||||
])
|
])
|
||||||
menu.popup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './StatusBar.styl'
|
import styles from './StatusBar.styl'
|
||||||
import ZoomManager from 'browser/main/lib/ZoomManager'
|
import ZoomManager from 'browser/main/lib/ZoomManager'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote, ipcRenderer } = electron
|
const { remote, ipcRenderer } = electron
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { dialog } = remote
|
||||||
|
|
||||||
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
|
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
|
||||||
|
|
||||||
@@ -26,16 +27,16 @@ class StatusBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleZoomButtonClick (e) {
|
handleZoomButtonClick (e) {
|
||||||
const menu = new Menu()
|
const templates = []
|
||||||
|
|
||||||
zoomOptions.forEach((zoom) => {
|
zoomOptions.forEach((zoom) => {
|
||||||
menu.append(new MenuItem({
|
templates.push({
|
||||||
label: Math.floor(zoom * 100) + '%',
|
label: Math.floor(zoom * 100) + '%',
|
||||||
click: () => this.handleZoomMenuItemClick(zoom)
|
click: () => this.handleZoomMenuItemClick(zoom)
|
||||||
}))
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
menu.popup(remote.getCurrentWindow())
|
context.popup(templates)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleZoomMenuItemClick (zoomFactor) {
|
handleZoomMenuItemClick (zoomFactor) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import RcParser from 'browser/lib/RcParser'
|
import RcParser from 'browser/lib/RcParser'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
const win = global.process.platform === 'win32'
|
const win = global.process.platform === 'win32'
|
||||||
@@ -20,7 +21,8 @@ export const DEFAULT_CONFIG = {
|
|||||||
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
||||||
amaEnabled: true,
|
amaEnabled: true,
|
||||||
hotkey: {
|
hotkey: {
|
||||||
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
|
||||||
|
toggleMode: OSX ? 'Command + M' : 'Ctrl + M'
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
language: 'en',
|
language: 'en',
|
||||||
@@ -56,6 +58,10 @@ export const DEFAULT_CONFIG = {
|
|||||||
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
|
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
|
||||||
scrollPastEnd: false,
|
scrollPastEnd: false,
|
||||||
smartQuotes: true,
|
smartQuotes: true,
|
||||||
|
breaks: true,
|
||||||
|
smartArrows: false,
|
||||||
|
allowCustomCSS: false,
|
||||||
|
customCSS: '',
|
||||||
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
@@ -166,6 +172,7 @@ function set (updates) {
|
|||||||
ipcRenderer.send('config-renew', {
|
ipcRenderer.send('config-renew', {
|
||||||
config: get()
|
config: get()
|
||||||
})
|
})
|
||||||
|
ee.emit('config-renew')
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignConfigValues (originalConfig, rcConfig) {
|
function assignConfigValues (originalConfig, rcConfig) {
|
||||||
@@ -175,6 +182,17 @@ function assignConfigValues (originalConfig, rcConfig) {
|
|||||||
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
||||||
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
||||||
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
|
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
|
||||||
|
|
||||||
|
rewriteHotkey(config)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteHotkey (config) {
|
||||||
|
const keys = [...Object.keys(config.hotkey)]
|
||||||
|
keys.forEach(key => {
|
||||||
|
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command')
|
||||||
|
})
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ function addStorage (input) {
|
|||||||
key,
|
key,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
type: input.type,
|
type: input.type,
|
||||||
path: input.path
|
path: input.path,
|
||||||
|
isOpen: false
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(newStorage)
|
return Promise.resolve(newStorage)
|
||||||
@@ -48,7 +49,8 @@ function addStorage (input) {
|
|||||||
key: newStorage.key,
|
key: newStorage.key,
|
||||||
type: newStorage.type,
|
type: newStorage.type,
|
||||||
name: newStorage.name,
|
name: newStorage.name,
|
||||||
path: newStorage.path
|
path: newStorage.path,
|
||||||
|
isOpen: false
|
||||||
})
|
})
|
||||||
|
|
||||||
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ const fs = require('fs')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const findStorage = require('browser/lib/findStorage')
|
const findStorage = require('browser/lib/findStorage')
|
||||||
const mdurl = require('mdurl')
|
const mdurl = require('mdurl')
|
||||||
|
const fse = require('fs-extra')
|
||||||
const escapeStringRegexp = require('escape-string-regexp')
|
const escapeStringRegexp = require('escape-string-regexp')
|
||||||
|
const sander = require('sander')
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
||||||
const DESTINATION_FOLDER = 'attachments'
|
const DESTINATION_FOLDER = 'attachments'
|
||||||
@@ -40,7 +43,7 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
|
|||||||
|
|
||||||
const targetStorage = findStorage.findStorage(storageKey)
|
const targetStorage = findStorage.findStorage(storageKey)
|
||||||
|
|
||||||
const inputFile = fs.createReadStream(sourceFilePath)
|
const inputFileStream = fs.createReadStream(sourceFilePath)
|
||||||
let destinationName
|
let destinationName
|
||||||
if (useRandomName) {
|
if (useRandomName) {
|
||||||
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
|
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
|
||||||
@@ -50,8 +53,10 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
|
|||||||
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||||
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
||||||
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
||||||
inputFile.pipe(outputFile)
|
inputFileStream.pipe(outputFile)
|
||||||
resolve(destinationName)
|
inputFileStream.on('end', () => {
|
||||||
|
resolve(destinationName)
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return reject(e)
|
return reject(e)
|
||||||
}
|
}
|
||||||
@@ -69,6 +74,31 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Moves attachments from the old location ('/images') to the new one ('/attachments/noteKey)
|
||||||
|
* @param renderedHTML HTML of the current note
|
||||||
|
* @param storagePath Storage path of the current note
|
||||||
|
* @param noteKey Key of the current note
|
||||||
|
*/
|
||||||
|
function migrateAttachments (renderedHTML, storagePath, noteKey) {
|
||||||
|
if (sander.existsSync(path.join(storagePath, 'images'))) {
|
||||||
|
const attachments = getAttachmentsInContent(renderedHTML) || []
|
||||||
|
if (attachments !== []) {
|
||||||
|
createAttachmentDestinationFolder(storagePath, noteKey)
|
||||||
|
}
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
const attachmentBaseName = path.basename(attachment)
|
||||||
|
const possibleLegacyPath = path.join(storagePath, 'images', attachmentBaseName)
|
||||||
|
if (sander.existsSync(possibleLegacyPath)) {
|
||||||
|
const destinationPath = path.join(storagePath, DESTINATION_FOLDER, attachmentBaseName)
|
||||||
|
if (!sander.existsSync(destinationPath)) {
|
||||||
|
sander.copyFileSync(possibleLegacyPath).to(destinationPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Fixes the URLs embedded in the generated HTML so that they again refer actual local files.
|
* @description Fixes the URLs embedded in the generated HTML so that they again refer actual local files.
|
||||||
* @param {String} renderedHTML HTML in that the links should be fixed
|
* @param {String} renderedHTML HTML in that the links should be fixed
|
||||||
@@ -76,7 +106,7 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
|
|||||||
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
|
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
|
||||||
*/
|
*/
|
||||||
function fixLocalURLS (renderedHTML, storagePath) {
|
function fixLocalURLS (renderedHTML, storagePath) {
|
||||||
return renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
|
return renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,8 +177,9 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
|
|||||||
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
||||||
base64data += base64data.replace('+', ' ')
|
base64data += base64data.replace('+', ' ')
|
||||||
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
||||||
fs.writeFile(imagePath, binaryData, 'binary')
|
fs.writeFileSync(imagePath, binaryData, 'binary')
|
||||||
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
|
const imageReferencePath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, imageName)
|
||||||
|
const imageMd = generateAttachmentMarkdown(imageName, imageReferencePath, true)
|
||||||
codeEditor.insertAttachmentMd(imageMd)
|
codeEditor.insertAttachmentMd(imageMd)
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(blob)
|
reader.readAsDataURL(blob)
|
||||||
@@ -161,7 +192,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
|
|||||||
*/
|
*/
|
||||||
function getAttachmentsInContent (markdownContent) {
|
function getAttachmentsInContent (markdownContent) {
|
||||||
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
|
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
|
||||||
const regexp = new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + '([a-zA-Z0-9]|-)+' + escapeStringRegexp(path.sep) + '[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)?', 'g')
|
const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + '|/)' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + '|/)' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
|
||||||
return preparedInput.match(regexp)
|
return preparedInput.match(regexp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +211,39 @@ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Moves the attachments of the current note to the new location.
|
||||||
|
* Returns a modified version of the given content so that the links to the attachments point to the new note key.
|
||||||
|
* @param {String} oldPath Source of the note to be moved
|
||||||
|
* @param {String} newPath Destination of the note to be moved
|
||||||
|
* @param {String} noteKey Old note key
|
||||||
|
* @param {String} newNoteKey New note key
|
||||||
|
* @param {String} noteContent Content of the note to be moved
|
||||||
|
* @returns {String} Modified version of noteContent in which the paths of the attachments are fixed
|
||||||
|
*/
|
||||||
|
function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
|
||||||
|
const src = path.join(oldPath, DESTINATION_FOLDER, noteKey)
|
||||||
|
const dest = path.join(newPath, DESTINATION_FOLDER, newNoteKey)
|
||||||
|
if (fse.existsSync(src)) {
|
||||||
|
fse.moveSync(src, dest)
|
||||||
|
}
|
||||||
|
return replaceNoteKeyWithNewNoteKey(noteContent, noteKey, newNoteKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the given content so that in all attachment references the oldNoteKey is replaced by the new one
|
||||||
|
* @param noteContent content that should be modified
|
||||||
|
* @param oldNoteKey note key to be replaced
|
||||||
|
* @param newNoteKey note key serving as a replacement
|
||||||
|
* @returns {String} modified note content
|
||||||
|
*/
|
||||||
|
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
|
||||||
|
if (noteContent) {
|
||||||
|
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
|
||||||
|
}
|
||||||
|
return noteContent
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Deletes all :storage and noteKey references from the given input.
|
* @description Deletes all :storage and noteKey references from the given input.
|
||||||
* @param input Input in which the references should be deleted
|
* @param input Input in which the references should be deleted
|
||||||
@@ -187,7 +251,18 @@ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
|
|||||||
* @returns {String} Input without the references
|
* @returns {String} Input without the references
|
||||||
*/
|
*/
|
||||||
function removeStorageAndNoteReferences (input, noteKey) {
|
function removeStorageAndNoteReferences (input, noteKey) {
|
||||||
return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey, 'g'), DESTINATION_FOLDER)
|
return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + noteKey + ')?', 'g'), DESTINATION_FOLDER)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Deletes the attachment folder specified by the given storageKey and noteKey
|
||||||
|
* @param storageKey Key of the storage of the note to be deleted
|
||||||
|
* @param noteKey Key of the note to be deleted
|
||||||
|
*/
|
||||||
|
function deleteAttachmentFolder (storageKey, noteKey) {
|
||||||
|
const storagePath = findStorage.findStorage(storageKey)
|
||||||
|
const noteAttachmentPath = path.join(storagePath.path, DESTINATION_FOLDER, noteKey)
|
||||||
|
sander.rimrafSync(noteAttachmentPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,6 +272,9 @@ function removeStorageAndNoteReferences (input, noteKey) {
|
|||||||
* @param noteKey NoteKey of the current note. Is used to determine the belonging attachment folder.
|
* @param noteKey NoteKey of the current note. Is used to determine the belonging attachment folder.
|
||||||
*/
|
*/
|
||||||
function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey) {
|
function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey) {
|
||||||
|
if (storageKey == null || noteKey == null || markdownContent == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const targetStorage = findStorage.findStorage(storageKey)
|
const targetStorage = findStorage.findStorage(storageKey)
|
||||||
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||||
const attachmentsInNote = getAttachmentsInContent(markdownContent)
|
const attachmentsInNote = getAttachmentsInContent(markdownContent)
|
||||||
@@ -206,11 +284,10 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
|
|||||||
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
|
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync(attachmentFolder)) {
|
if (fs.existsSync(attachmentFolder)) {
|
||||||
fs.readdir(attachmentFolder, (err, files) => {
|
fs.readdir(attachmentFolder, (err, files) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Error reading directory '" + attachmentFolder + "'. Error:")
|
console.error('Error reading directory "' + attachmentFolder + '". Error:')
|
||||||
console.error(err)
|
console.error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -219,17 +296,109 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
|
|||||||
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
|
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
|
||||||
fs.unlink(absolutePathOfFile, (err) => {
|
fs.unlink(absolutePathOfFile, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Could not delete '%s'", absolutePathOfFile)
|
console.error('Could not delete "%s"', absolutePathOfFile)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.info("File '" + absolutePathOfFile + "' deleted because it was not included in the content of the note")
|
console.info('File "' + absolutePathOfFile + '" deleted because it was not included in the content of the note')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.info("Attachment folder ('" + attachmentFolder + "') did not exist..")
|
console.info('Attachment folder ("' + attachmentFolder + '") did not exist..')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clones the attachments of a given note.
|
||||||
|
* Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination.
|
||||||
|
* @param oldNote Note that is being cloned
|
||||||
|
* @param newNote Clone of the note
|
||||||
|
*/
|
||||||
|
function cloneAttachments (oldNote, newNote) {
|
||||||
|
if (newNote.type === 'MARKDOWN_NOTE') {
|
||||||
|
const oldStorage = findStorage.findStorage(oldNote.storage)
|
||||||
|
const newStorage = findStorage.findStorage(newNote.storage)
|
||||||
|
const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || []
|
||||||
|
|
||||||
|
const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key)
|
||||||
|
if (!sander.existsSync(destinationFolder)) {
|
||||||
|
sander.mkdirSync(destinationFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attachment of attachmentsPaths) {
|
||||||
|
const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment))
|
||||||
|
sander.copyFileSync(attachment).to(destination)
|
||||||
|
}
|
||||||
|
newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key)
|
||||||
|
} else {
|
||||||
|
console.debug('Cloning of the attachment was skipped since it only works for MARKDOWN_NOTEs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateFileNotFoundMarkdown () {
|
||||||
|
return '**' + i18n.__('⚠ 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! ⚠') + '**'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a given text is a link to an boostnote attachment
|
||||||
|
* @param text Text that might contain a attachment link
|
||||||
|
* @return {Boolean} Result of the test
|
||||||
|
*/
|
||||||
|
function isAttachmentLink (text) {
|
||||||
|
if (text) {
|
||||||
|
return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + escapeStringRegexp(path.sep) + '.*\\).*', 'gi')) != null
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Handles the paste of an attachment link. Copies the referenced attachment to the location belonging to the new note.
|
||||||
|
* Returns a modified version of the pasted text so that it matches the copied attachment (resp. the new location)
|
||||||
|
* @param storageKey StorageKey of the current note
|
||||||
|
* @param noteKey NoteKey of the currentNote
|
||||||
|
* @param linkText Text that was pasted
|
||||||
|
* @return {Promise<String>} Promise returning the modified text
|
||||||
|
*/
|
||||||
|
function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
|
||||||
|
if (storageKey != null && noteKey != null && linkText != null) {
|
||||||
|
const storagePath = findStorage.findStorage(storageKey).path
|
||||||
|
const attachments = getAttachmentsInContent(linkText) || []
|
||||||
|
const replaceInstructions = []
|
||||||
|
const copies = []
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
const absPathOfAttachment = attachment.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))
|
||||||
|
copies.push(
|
||||||
|
sander.exists(absPathOfAttachment)
|
||||||
|
.then((fileExists) => {
|
||||||
|
if (!fileExists) {
|
||||||
|
const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
|
||||||
|
replaceInstructions.push({regexp: fileNotFoundRegexp, replacement: this.generateFileNotFoundMarkdown()})
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
return this.copyAttachment(absPathOfAttachment, storageKey, noteKey)
|
||||||
|
.then((fileName) => {
|
||||||
|
const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
|
||||||
|
replaceInstructions.push({
|
||||||
|
regexp: replaceLinkRegExp,
|
||||||
|
replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')'
|
||||||
|
})
|
||||||
|
return Promise.resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return Promise.all(copies).then(() => {
|
||||||
|
let modifiedLinkText = linkText
|
||||||
|
for (const replaceInstruction of replaceInstructions) {
|
||||||
|
modifiedLinkText = modifiedLinkText.replace(replaceInstruction.regexp, replaceInstruction.replacement)
|
||||||
|
}
|
||||||
|
return modifiedLinkText
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log('One if the parameters was null -> Do nothing..')
|
||||||
|
return Promise.resolve(linkText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +411,14 @@ module.exports = {
|
|||||||
getAttachmentsInContent,
|
getAttachmentsInContent,
|
||||||
getAbsolutePathsOfAttachmentsInContent,
|
getAbsolutePathsOfAttachmentsInContent,
|
||||||
removeStorageAndNoteReferences,
|
removeStorageAndNoteReferences,
|
||||||
|
deleteAttachmentFolder,
|
||||||
deleteAttachmentsNotPresentInNote,
|
deleteAttachmentsNotPresentInNote,
|
||||||
|
moveAttachments,
|
||||||
|
cloneAttachments,
|
||||||
|
isAttachmentLink,
|
||||||
|
handleAttachmentLinkPaste,
|
||||||
|
generateFileNotFoundMarkdown,
|
||||||
|
migrateAttachments,
|
||||||
STORAGE_FOLDER_PLACEHOLDER,
|
STORAGE_FOLDER_PLACEHOLDER,
|
||||||
DESTINATION_FOLDER
|
DESTINATION_FOLDER
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
|
||||||
|
|
||||||
// TODO: ehhc: delete this
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Copy an image and return the path.
|
|
||||||
* @param {String} filePath
|
|
||||||
* @param {String} storageKey
|
|
||||||
* @param {Boolean} rename create new filename or leave the old one
|
|
||||||
* @return {Promise<any>} an image path
|
|
||||||
*/
|
|
||||||
function copyImage (filePath, storageKey, rename = true) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
const targetStorage = findStorage(storageKey)
|
|
||||||
|
|
||||||
const inputImage = fs.createReadStream(filePath)
|
|
||||||
const imageExt = path.extname(filePath)
|
|
||||||
const imageName = rename ? Math.random().toString(36).slice(-16) : path.basename(filePath, imageExt)
|
|
||||||
const basename = `${imageName}${imageExt}`
|
|
||||||
const imageDir = path.join(targetStorage.path, 'images')
|
|
||||||
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
|
|
||||||
const outputImage = fs.createWriteStream(path.join(imageDir, basename))
|
|
||||||
outputImage.on('error', reject)
|
|
||||||
inputImage.on('error', reject)
|
|
||||||
inputImage.on('end', () => {
|
|
||||||
resolve(basename)
|
|
||||||
})
|
|
||||||
inputImage.pipe(outputImage)
|
|
||||||
} catch (e) {
|
|
||||||
return reject(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = copyImage
|
|
||||||
26
browser/main/lib/dataApi/createSnippet.js
Normal file
26
browser/main/lib/dataApi/createSnippet.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
||||||
|
|
||||||
|
function createSnippet (snippetFile) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const newSnippet = {
|
||||||
|
id: crypto.randomBytes(16).toString('hex'),
|
||||||
|
name: 'Unnamed snippet',
|
||||||
|
prefix: [],
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
fetchSnippet(null, snippetFile).then((snippets) => {
|
||||||
|
snippets.push(newSnippet)
|
||||||
|
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
resolve(newSnippet)
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createSnippet
|
||||||
@@ -5,6 +5,7 @@ const resolveStorageNotes = require('./resolveStorageNotes')
|
|||||||
const CSON = require('@rokt33r/season')
|
const CSON = require('@rokt33r/season')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
const deleteSingleNote = require('./deleteNote')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} storageKey
|
* @param {String} storageKey
|
||||||
@@ -49,11 +50,7 @@ function deleteFolder (storageKey, folderKey) {
|
|||||||
|
|
||||||
const deleteAllNotes = targetNotes
|
const deleteAllNotes = targetNotes
|
||||||
.map(function deleteNote (note) {
|
.map(function deleteNote (note) {
|
||||||
const notePath = path.join(storage.path, 'notes', note.key + '.cson')
|
return deleteSingleNote(storageKey, note.key)
|
||||||
return sander.unlink(notePath)
|
|
||||||
.catch(function (err) {
|
|
||||||
console.warn('Failed to delete', notePath, err)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
return Promise.all(deleteAllNotes)
|
return Promise.all(deleteAllNotes)
|
||||||
.then(() => storage)
|
.then(() => storage)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const resolveStorageData = require('./resolveStorageData')
|
const resolveStorageData = require('./resolveStorageData')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
|
const attachmentManagement = require('./attachmentManagement')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
|
||||||
function deleteNote (storageKey, noteKey) {
|
function deleteNote (storageKey, noteKey) {
|
||||||
@@ -25,6 +26,10 @@ function deleteNote (storageKey, noteKey) {
|
|||||||
storageKey
|
storageKey
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(function deleteAttachments (storageInfo) {
|
||||||
|
attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey)
|
||||||
|
return storageInfo
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = deleteNote
|
module.exports = deleteNote
|
||||||
|
|||||||
17
browser/main/lib/dataApi/deleteSnippet.js
Normal file
17
browser/main/lib/dataApi/deleteSnippet.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
||||||
|
|
||||||
|
function deleteSnippet (snippet, snippetFile) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetchSnippet(null, snippetFile).then((snippets) => {
|
||||||
|
snippets = snippets.filter(currentSnippet => currentSnippet.id !== snippet.id)
|
||||||
|
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
resolve(snippet)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = deleteSnippet
|
||||||
20
browser/main/lib/dataApi/fetchSnippet.js
Normal file
20
browser/main/lib/dataApi/fetchSnippet.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
|
||||||
|
function fetchSnippet (id, snippetFile) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(snippetFile || consts.SNIPPET_FILE, 'utf8', (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
const snippets = JSON.parse(data)
|
||||||
|
if (id) {
|
||||||
|
const snippet = snippets.find(snippet => { return snippet.id === id })
|
||||||
|
resolve(snippet)
|
||||||
|
}
|
||||||
|
resolve(snippets)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = fetchSnippet
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
const dataApi = {
|
const dataApi = {
|
||||||
init: require('./init'),
|
init: require('./init'),
|
||||||
|
toggleStorage: require('./toggleStorage'),
|
||||||
addStorage: require('./addStorage'),
|
addStorage: require('./addStorage'),
|
||||||
renameStorage: require('./renameStorage'),
|
renameStorage: require('./renameStorage'),
|
||||||
removeStorage: require('./removeStorage'),
|
removeStorage: require('./removeStorage'),
|
||||||
@@ -13,6 +14,10 @@ const dataApi = {
|
|||||||
deleteNote: require('./deleteNote'),
|
deleteNote: require('./deleteNote'),
|
||||||
moveNote: require('./moveNote'),
|
moveNote: require('./moveNote'),
|
||||||
migrateFromV5Storage: require('./migrateFromV5Storage'),
|
migrateFromV5Storage: require('./migrateFromV5Storage'),
|
||||||
|
createSnippet: require('./createSnippet'),
|
||||||
|
deleteSnippet: require('./deleteSnippet'),
|
||||||
|
updateSnippet: require('./updateSnippet'),
|
||||||
|
fetchSnippet: require('./fetchSnippet'),
|
||||||
|
|
||||||
_migrateFromV6Storage: require('./migrateFromV6Storage'),
|
_migrateFromV6Storage: require('./migrateFromV6Storage'),
|
||||||
_resolveStorageData: require('./resolveStorageData'),
|
_resolveStorageData: require('./resolveStorageData'),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const CSON = require('@rokt33r/season')
|
|||||||
const keygen = require('browser/lib/keygen')
|
const keygen = require('browser/lib/keygen')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
const attachmentManagement = require('./attachmentManagement')
|
||||||
|
|
||||||
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
||||||
let oldStorage, newStorage
|
let oldStorage, newStorage
|
||||||
@@ -63,36 +64,20 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
|||||||
noteData.key = newNoteKey
|
noteData.key = newNoteKey
|
||||||
noteData.storage = newStorageKey
|
noteData.storage = newStorageKey
|
||||||
noteData.updatedAt = new Date()
|
noteData.updatedAt = new Date()
|
||||||
|
noteData.oldContent = noteData.content
|
||||||
|
|
||||||
return noteData
|
return noteData
|
||||||
})
|
})
|
||||||
.then(function moveImages (noteData) {
|
.then(function moveAttachments (noteData) {
|
||||||
if (oldStorage.path === newStorage.path) return noteData
|
if (oldStorage.path === newStorage.path) {
|
||||||
|
return noteData
|
||||||
const searchImagesRegex = /!\[.*\]\(:storage\/(.+)\)/gi
|
|
||||||
let match = searchImagesRegex.exec(noteData.content)
|
|
||||||
|
|
||||||
const moveTasks = []
|
|
||||||
while (match != null) {
|
|
||||||
const [, filename] = match
|
|
||||||
const oldPath = path.join(oldStorage.path, 'attachments', filename)
|
|
||||||
const newPath = path.join(newStorage.path, 'attachments', filename)
|
|
||||||
// TODO: ehhc: attachmentManagement
|
|
||||||
moveTasks.push(
|
|
||||||
sander.copyFile(oldPath).to(newPath)
|
|
||||||
.then(() => {
|
|
||||||
fs.unlinkSync(oldPath)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// find next occurence
|
|
||||||
match = searchImagesRegex.exec(noteData.content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(moveTasks).then(() => noteData)
|
noteData.content = attachmentManagement.moveAttachments(oldStorage.path, newStorage.path, noteKey, newNoteKey, noteData.content)
|
||||||
|
return noteData
|
||||||
})
|
})
|
||||||
.then(function writeAndReturn (noteData) {
|
.then(function writeAndReturn (noteData) {
|
||||||
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage']))
|
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage', 'oldContent']))
|
||||||
return noteData
|
return noteData
|
||||||
})
|
})
|
||||||
.then(function deleteOldNote (data) {
|
.then(function deleteOldNote (data) {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ function resolveStorageData (storageCache) {
|
|||||||
key: storageCache.key,
|
key: storageCache.key,
|
||||||
name: storageCache.name,
|
name: storageCache.name,
|
||||||
type: storageCache.type,
|
type: storageCache.type,
|
||||||
path: storageCache.path
|
path: storageCache.path,
|
||||||
|
isOpen: storageCache.isOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json')
|
const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json')
|
||||||
|
|||||||
28
browser/main/lib/dataApi/toggleStorage.js
Normal file
28
browser/main/lib/dataApi/toggleStorage.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const _ = require('lodash')
|
||||||
|
const resolveStorageData = require('./resolveStorageData')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} key
|
||||||
|
* @param {Boolean} isOpen
|
||||||
|
* @return {Object} Storage meta data
|
||||||
|
*/
|
||||||
|
function toggleStorage (key, isOpen) {
|
||||||
|
let cachedStorageList
|
||||||
|
try {
|
||||||
|
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
|
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
|
||||||
|
} catch (err) {
|
||||||
|
console.log('error got')
|
||||||
|
console.error(err)
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
const targetStorage = _.find(cachedStorageList, {key: key})
|
||||||
|
if (targetStorage == null) return Promise.reject('Storage')
|
||||||
|
|
||||||
|
targetStorage.isOpen = isOpen
|
||||||
|
localStorage.setItem('storages', JSON.stringify(cachedStorageList))
|
||||||
|
|
||||||
|
return resolveStorageData(targetStorage)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = toggleStorage
|
||||||
33
browser/main/lib/dataApi/updateSnippet.js
Normal file
33
browser/main/lib/dataApi/updateSnippet.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
|
||||||
|
function updateSnippet (snippet, snippetFile) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const snippets = JSON.parse(fs.readFileSync(snippetFile || consts.SNIPPET_FILE, 'utf-8'))
|
||||||
|
|
||||||
|
for (let i = 0; i < snippets.length; i++) {
|
||||||
|
const currentSnippet = snippets[i]
|
||||||
|
|
||||||
|
if (currentSnippet.id === snippet.id) {
|
||||||
|
if (
|
||||||
|
currentSnippet.name === snippet.name &&
|
||||||
|
currentSnippet.prefix === snippet.prefix &&
|
||||||
|
currentSnippet.content === snippet.content
|
||||||
|
) {
|
||||||
|
// if everything is the same then don't write to disk
|
||||||
|
resolve(snippets)
|
||||||
|
} else {
|
||||||
|
currentSnippet.name = snippet.name
|
||||||
|
currentSnippet.prefix = snippet.prefix
|
||||||
|
currentSnippet.content = snippet.content
|
||||||
|
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
resolve(snippets)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = updateSnippet
|
||||||
7
browser/main/lib/shortcut.js
Normal file
7
browser/main/lib/shortcut.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'toggleMode': () => {
|
||||||
|
ee.emit('topbar:togglemodebutton')
|
||||||
|
}
|
||||||
|
}
|
||||||
40
browser/main/lib/shortcutManager.js
Normal file
40
browser/main/lib/shortcutManager.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import Mousetrap from 'mousetrap'
|
||||||
|
import CM from 'browser/main/lib/ConfigManager'
|
||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import { isObjectEqual } from 'browser/lib/utils'
|
||||||
|
require('mousetrap-global-bind')
|
||||||
|
import functions from './shortcut'
|
||||||
|
|
||||||
|
let shortcuts = CM.get().hotkey
|
||||||
|
|
||||||
|
ee.on('config-renew', function () {
|
||||||
|
// only update if hotkey changed !
|
||||||
|
const newHotkey = CM.get().hotkey
|
||||||
|
if (!isObjectEqual(newHotkey, shortcuts)) {
|
||||||
|
updateShortcut(newHotkey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateShortcut (newHotkey) {
|
||||||
|
Mousetrap.reset()
|
||||||
|
shortcuts = newHotkey
|
||||||
|
applyShortcuts(newHotkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatShortcut (shortcut) {
|
||||||
|
return shortcut.toLowerCase().replace(/ /g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyShortcuts (shortcuts) {
|
||||||
|
for (const shortcut in shortcuts) {
|
||||||
|
const toggler = formatShortcut(shortcuts[shortcut])
|
||||||
|
// only bind if the function for that shortcut exists
|
||||||
|
if (functions[shortcut]) {
|
||||||
|
Mousetrap.bindGlobal(toggler, functions[shortcut])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyShortcuts(CM.get().hotkey)
|
||||||
|
|
||||||
|
module.exports = applyShortcuts
|
||||||
@@ -11,11 +11,12 @@ p
|
|||||||
font-size 16px
|
font-size 16px
|
||||||
|
|
||||||
.cf-link
|
.cf-link
|
||||||
width 250px
|
|
||||||
height 35px
|
height 35px
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
border none
|
border none
|
||||||
background-color alpha(#1EC38B, 90%)
|
background-color alpha(#1EC38B, 90%)
|
||||||
|
padding-left 20px
|
||||||
|
padding-right 20px
|
||||||
&:hover
|
&:hover
|
||||||
background-color #1EC38B
|
background-color #1EC38B
|
||||||
transition 0.2s
|
transition 0.2s
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ class HotkeyTab extends React.Component {
|
|||||||
handleHotkeyChange (e) {
|
handleHotkeyChange (e) {
|
||||||
const { config } = this.state
|
const { config } = this.state
|
||||||
config.hotkey = {
|
config.hotkey = {
|
||||||
toggleMain: this.refs.toggleMain.value
|
toggleMain: this.refs.toggleMain.value,
|
||||||
|
toggleMode: this.refs.toggleMode.value
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
config
|
config
|
||||||
@@ -115,6 +116,17 @@ class HotkeyTab extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Toggle editor mode')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input styleName='group-section-control-input'
|
||||||
|
onChange={(e) => this.handleHotkeyChange(e)}
|
||||||
|
ref='toggleMode'
|
||||||
|
value={config.hotkey.toggleMode}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div styleName='group-control'>
|
<div styleName='group-control'>
|
||||||
<button styleName='group-control-leftButton'
|
<button styleName='group-control-leftButton'
|
||||||
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
||||||
|
|||||||
90
browser/main/modals/PreferencesModal/SnippetEditor.js
Normal file
90
browser/main/modals/PreferencesModal/SnippetEditor.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
import React from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import styles from './SnippetTab.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
|
||||||
|
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||||
|
const buildCMRulers = (rulers, enableRulers) =>
|
||||||
|
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
|
||||||
|
|
||||||
|
class SnippetEditor extends React.Component {
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.props.onRef(this)
|
||||||
|
const { rulers, enableRulers } = this.props
|
||||||
|
this.cm = CodeMirror(this.refs.root, {
|
||||||
|
rulers: buildCMRulers(rulers, enableRulers),
|
||||||
|
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,
|
||||||
|
dragDrop: false,
|
||||||
|
foldGutter: true,
|
||||||
|
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
mode: 'null'
|
||||||
|
})
|
||||||
|
this.cm.setSize('100%', '100%')
|
||||||
|
let changeDelay = null
|
||||||
|
|
||||||
|
this.cm.on('change', () => {
|
||||||
|
this.snippet.content = this.cm.getValue()
|
||||||
|
|
||||||
|
clearTimeout(changeDelay)
|
||||||
|
changeDelay = setTimeout(() => {
|
||||||
|
this.saveSnippet()
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.props.onRef(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
onSnippetChanged (newSnippet) {
|
||||||
|
this.snippet = newSnippet
|
||||||
|
this.cm.setValue(this.snippet.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
onSnippetNameOrPrefixChanged (newSnippet) {
|
||||||
|
this.snippet.name = newSnippet.name
|
||||||
|
this.snippet.prefix = newSnippet.prefix.toString().replace(/\s/g, '').split(',')
|
||||||
|
this.saveSnippet()
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSnippet () {
|
||||||
|
dataApi.updateSnippet(this.snippet).catch((err) => { throw err })
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { fontSize } = this.props
|
||||||
|
let fontFamily = this.props.fontFamily
|
||||||
|
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||||
|
? [fontFamily].concat(defaultEditorFontFamily)
|
||||||
|
: defaultEditorFontFamily
|
||||||
|
return (
|
||||||
|
<div styleName='SnippetEditor' ref='root' tabIndex='-1' style={{
|
||||||
|
fontFamily: fontFamily.join(', '),
|
||||||
|
fontSize: fontSize
|
||||||
|
}} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SnippetEditor.defaultProps = {
|
||||||
|
readOnly: false,
|
||||||
|
theme: 'xcode',
|
||||||
|
keyMap: 'sublime',
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Monaco, Consolas',
|
||||||
|
indentSize: 4,
|
||||||
|
indentType: 'space'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SnippetEditor, styles)
|
||||||
95
browser/main/modals/PreferencesModal/SnippetList.js
Normal file
95
browser/main/modals/PreferencesModal/SnippetList.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styles from './SnippetTab.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
|
class SnippetList extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
snippets: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.reloadSnippetList()
|
||||||
|
eventEmitter.on('snippetList:reload', this.reloadSnippetList.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSnippetList () {
|
||||||
|
dataApi.fetchSnippet().then(snippets => {
|
||||||
|
this.setState({snippets})
|
||||||
|
this.props.onSnippetSelect(snippets[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSnippetContextMenu (snippet) {
|
||||||
|
context.popup([{
|
||||||
|
label: i18n.__('Delete snippet'),
|
||||||
|
click: () => this.deleteSnippet(snippet)
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSnippet (snippet) {
|
||||||
|
dataApi.deleteSnippet(snippet).then(() => {
|
||||||
|
this.reloadSnippetList()
|
||||||
|
this.props.onSnippetDeleted(snippet)
|
||||||
|
}).catch(err => { throw err })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSnippetClick (snippet) {
|
||||||
|
this.props.onSnippetSelect(snippet)
|
||||||
|
}
|
||||||
|
|
||||||
|
createSnippet () {
|
||||||
|
dataApi.createSnippet().then(() => {
|
||||||
|
this.reloadSnippetList()
|
||||||
|
// scroll to end of list when added new snippet
|
||||||
|
const snippetList = document.getElementById('snippets')
|
||||||
|
snippetList.scrollTop = snippetList.scrollHeight
|
||||||
|
}).catch(err => { throw err })
|
||||||
|
}
|
||||||
|
|
||||||
|
defineSnippetStyleName (snippet) {
|
||||||
|
const { currentSnippet } = this.props
|
||||||
|
if (currentSnippet == null) return
|
||||||
|
if (currentSnippet.id === snippet.id) {
|
||||||
|
return 'snippet-item-selected'
|
||||||
|
} else {
|
||||||
|
return 'snippet-item'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { snippets } = this.state
|
||||||
|
return (
|
||||||
|
<div styleName='snippet-list'>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<button styleName='group-control-button' onClick={() => this.createSnippet()}>
|
||||||
|
<i className='fa fa-plus' /> {i18n.__('New Snippet')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul id='snippets' styleName='snippets'>
|
||||||
|
{
|
||||||
|
snippets.map((snippet) => (
|
||||||
|
<li
|
||||||
|
styleName={this.defineSnippetStyleName(snippet)}
|
||||||
|
key={snippet.id}
|
||||||
|
onContextMenu={() => this.handleSnippetContextMenu(snippet)}
|
||||||
|
onClick={() => this.handleSnippetClick(snippet)}>
|
||||||
|
{snippet.name}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SnippetList, styles)
|
||||||
117
browser/main/modals/PreferencesModal/SnippetTab.js
Normal file
117
browser/main/modals/PreferencesModal/SnippetTab.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './SnippetTab.styl'
|
||||||
|
import SnippetEditor from './SnippetEditor'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import SnippetList from './SnippetList'
|
||||||
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
|
class SnippetTab extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
currentSnippet: null
|
||||||
|
}
|
||||||
|
this.changeDelay = null
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSnippetNameOrPrefixChange () {
|
||||||
|
clearTimeout(this.changeDelay)
|
||||||
|
this.changeDelay = setTimeout(() => {
|
||||||
|
// notify the snippet editor that the name or prefix of snippet has been changed
|
||||||
|
this.snippetEditor.onSnippetNameOrPrefixChanged(this.state.currentSnippet)
|
||||||
|
eventEmitter.emit('snippetList:reload')
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSnippetSelect (snippet) {
|
||||||
|
const { currentSnippet } = this.state
|
||||||
|
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
||||||
|
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
||||||
|
// notify the snippet editor to load the content of the new snippet
|
||||||
|
this.snippetEditor.onSnippetChanged(changedSnippet)
|
||||||
|
this.setState({currentSnippet: changedSnippet})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSnippetNameOrPrefixChanged (e, type) {
|
||||||
|
const newSnippet = Object.assign({}, this.state.currentSnippet)
|
||||||
|
if (type === 'name') {
|
||||||
|
newSnippet.name = e.target.value
|
||||||
|
} else {
|
||||||
|
newSnippet.prefix = e.target.value
|
||||||
|
}
|
||||||
|
this.setState({ currentSnippet: newSnippet })
|
||||||
|
this.handleSnippetNameOrPrefixChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteSnippet (snippet) {
|
||||||
|
// prevent old snippet still display when deleted
|
||||||
|
if (snippet.id === this.state.currentSnippet.id) {
|
||||||
|
this.setState({currentSnippet: null})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { config, storageKey } = this.props
|
||||||
|
const { currentSnippet } = this.state
|
||||||
|
|
||||||
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='header'>{i18n.__('Snippets')}</div>
|
||||||
|
<SnippetList
|
||||||
|
onSnippetSelect={this.handleSnippetSelect.bind(this)}
|
||||||
|
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
|
||||||
|
currentSnippet={currentSnippet} />
|
||||||
|
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={currentSnippet ? currentSnippet.name : ''}
|
||||||
|
onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'name') }}
|
||||||
|
type='text' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Snippet prefix')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={currentSnippet ? currentSnippet.prefix : ''}
|
||||||
|
onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'prefix') }}
|
||||||
|
type='text' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div styleName='snippet-editor-section'>
|
||||||
|
<SnippetEditor
|
||||||
|
storageKey={storageKey}
|
||||||
|
theme={config.editor.theme}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
|
fontFamily={config.editor.fontFamily}
|
||||||
|
fontSize={editorFontSize}
|
||||||
|
indentType={config.editor.indentType}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
|
enableRulers={config.editor.enableRulers}
|
||||||
|
rulers={config.editor.rulers}
|
||||||
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
|
onRef={ref => { this.snippetEditor = ref }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SnippetTab.PropTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SnippetTab, styles)
|
||||||
198
browser/main/modals/PreferencesModal/SnippetTab.styl
Normal file
198
browser/main/modals/PreferencesModal/SnippetTab.styl
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
@import('./Tab')
|
||||||
|
@import('./ConfigTab')
|
||||||
|
|
||||||
|
.root
|
||||||
|
padding 15px
|
||||||
|
white-space pre
|
||||||
|
line-height 1.4
|
||||||
|
color alpha($ui-text-color, 90%)
|
||||||
|
width 100%
|
||||||
|
font-size 14px
|
||||||
|
|
||||||
|
.group
|
||||||
|
margin-bottom 45px
|
||||||
|
|
||||||
|
.group-header
|
||||||
|
@extend .header
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.group-header2
|
||||||
|
font-size 20px
|
||||||
|
color $ui-text-color
|
||||||
|
margin-bottom 15px
|
||||||
|
margin-top 30px
|
||||||
|
|
||||||
|
.group-section
|
||||||
|
margin-bottom 20px
|
||||||
|
display flex
|
||||||
|
line-height 30px
|
||||||
|
|
||||||
|
.group-section-label
|
||||||
|
width 150px
|
||||||
|
text-align left
|
||||||
|
margin-right 10px
|
||||||
|
font-size 14px
|
||||||
|
|
||||||
|
.group-section-control
|
||||||
|
flex 1
|
||||||
|
margin-left 5px
|
||||||
|
|
||||||
|
.group-section-control select
|
||||||
|
outline none
|
||||||
|
border 1px solid $ui-borderColor
|
||||||
|
font-size 16px
|
||||||
|
height 30px
|
||||||
|
width 250px
|
||||||
|
margin-bottom 5px
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.group-section-control-input
|
||||||
|
height 30px
|
||||||
|
vertical-align middle
|
||||||
|
width 400px
|
||||||
|
font-size $tab--button-font-size
|
||||||
|
border solid 1px $border-color
|
||||||
|
border-radius 2px
|
||||||
|
padding 0 5px
|
||||||
|
outline none
|
||||||
|
&:disabled
|
||||||
|
background-color $ui-input--disabled-backgroundColor
|
||||||
|
|
||||||
|
.group-control-button
|
||||||
|
height 30px
|
||||||
|
border none
|
||||||
|
border-top-right-radius 2px
|
||||||
|
border-bottom-right-radius 2px
|
||||||
|
colorPrimaryButton()
|
||||||
|
vertical-align middle
|
||||||
|
padding 0 20px
|
||||||
|
|
||||||
|
.group-checkBoxSection
|
||||||
|
margin-bottom 15px
|
||||||
|
display flex
|
||||||
|
line-height 30px
|
||||||
|
padding-left 15px
|
||||||
|
|
||||||
|
.group-control
|
||||||
|
padding-top 10px
|
||||||
|
box-sizing border-box
|
||||||
|
height 40px
|
||||||
|
text-align right
|
||||||
|
:global
|
||||||
|
.alert
|
||||||
|
display inline-block
|
||||||
|
position absolute
|
||||||
|
top 60px
|
||||||
|
right 15px
|
||||||
|
font-size 14px
|
||||||
|
.success
|
||||||
|
color #1EC38B
|
||||||
|
.error
|
||||||
|
color red
|
||||||
|
.warning
|
||||||
|
color #FFA500
|
||||||
|
|
||||||
|
.snippet-list
|
||||||
|
width 30%
|
||||||
|
height calc(100% - 200px)
|
||||||
|
position absolute
|
||||||
|
|
||||||
|
.snippets
|
||||||
|
height calc(100% - 8px)
|
||||||
|
overflow scroll
|
||||||
|
background: #f5f5f5
|
||||||
|
|
||||||
|
.snippet-item
|
||||||
|
height 50px
|
||||||
|
font-size 15px
|
||||||
|
line-height 50px
|
||||||
|
padding 0 5%
|
||||||
|
cursor pointer
|
||||||
|
position relative
|
||||||
|
|
||||||
|
&::after
|
||||||
|
width 90%
|
||||||
|
height 1px
|
||||||
|
background rgba(0, 0, 0, 0.1)
|
||||||
|
position absolute
|
||||||
|
top 100%
|
||||||
|
left 5%
|
||||||
|
content ''
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background darken(#f5f5f5, 5)
|
||||||
|
|
||||||
|
.snippet-item-selected
|
||||||
|
@extend .snippet-list .snippet-item
|
||||||
|
background darken(#f5f5f5, 5)
|
||||||
|
|
||||||
|
.snippet-detail
|
||||||
|
width 70%
|
||||||
|
height calc(100% - 200px)
|
||||||
|
position absolute
|
||||||
|
left 33%
|
||||||
|
|
||||||
|
.SnippetEditor
|
||||||
|
position absolute
|
||||||
|
width 100%
|
||||||
|
height 90%
|
||||||
|
|
||||||
|
body[data-theme="default"], body[data-theme="white"]
|
||||||
|
.snippets
|
||||||
|
background $ui-backgroundColor
|
||||||
|
.snippet-item
|
||||||
|
color black
|
||||||
|
&::after
|
||||||
|
background $ui-borderColor
|
||||||
|
&:hover
|
||||||
|
background darken($ui-backgroundColor, 5)
|
||||||
|
.snippet-item-selected
|
||||||
|
background darken($ui-backgroundColor, 5)
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.snippets
|
||||||
|
background $ui-dark-backgroundColor
|
||||||
|
.snippet-item
|
||||||
|
color white
|
||||||
|
&::after
|
||||||
|
background $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background darken($ui-dark-backgroundColor, 5)
|
||||||
|
.snippet-item-selected
|
||||||
|
background darken($ui-dark-backgroundColor, 5)
|
||||||
|
.snippet-detail
|
||||||
|
color white
|
||||||
|
.group-control-button
|
||||||
|
colorDarkPrimaryButton()
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.snippets
|
||||||
|
background $ui-solarized-dark-backgroundColor
|
||||||
|
.snippet-item
|
||||||
|
color white
|
||||||
|
&::after
|
||||||
|
background $ui-solarized-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background darken($ui-solarized-dark-backgroundColor, 5)
|
||||||
|
.snippet-item-selected
|
||||||
|
background darken($ui-solarized-dark-backgroundColor, 5)
|
||||||
|
.snippet-detail
|
||||||
|
color white
|
||||||
|
.group-control-button
|
||||||
|
colorSolarizedDarkPrimaryButton()
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.snippets
|
||||||
|
background $ui-monokai-backgroundColor
|
||||||
|
.snippet-item
|
||||||
|
color White
|
||||||
|
&::after
|
||||||
|
background $ui-monokai-borderColor
|
||||||
|
&:hover
|
||||||
|
background darken($ui-monokai-backgroundColor, 5)
|
||||||
|
.snippet-item-selected
|
||||||
|
background darken($ui-monokai-backgroundColor, 5)
|
||||||
|
.snippet-detail
|
||||||
|
color white
|
||||||
|
.group-control-button
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
@@ -182,7 +182,7 @@ class StoragesTab extends React.Component {
|
|||||||
<div styleName='addStorage-body-section-path'>
|
<div styleName='addStorage-body-section-path'>
|
||||||
<input styleName='addStorage-body-section-path-input'
|
<input styleName='addStorage-body-section-path-input'
|
||||||
ref='addStoragePath'
|
ref='addStoragePath'
|
||||||
placeholder='Select Folder'
|
placeholder={i18n.__('Select Folder')}
|
||||||
value={this.state.newStorage.path}
|
value={this.state.newStorage.path}
|
||||||
onChange={(e) => this.handleAddStorageChange(e)}
|
onChange={(e) => this.handleAddStorageChange(e)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'codemirror-mode-elixir'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import { getLanguages } from 'browser/lib/Languages'
|
import { getLanguages } from 'browser/lib/Languages'
|
||||||
|
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -28,6 +29,8 @@ class UiTab extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
|
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
|
||||||
|
CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css')
|
||||||
|
this.customCSSCM.getCodeMirror().setSize('400px', '400px')
|
||||||
this.handleSettingDone = () => {
|
this.handleSettingDone = () => {
|
||||||
this.setState({UiAlert: {
|
this.setState({UiAlert: {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@@ -97,7 +100,11 @@ class UiTab extends React.Component {
|
|||||||
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
|
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
|
||||||
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
||||||
smartQuotes: this.refs.previewSmartQuotes.checked,
|
smartQuotes: this.refs.previewSmartQuotes.checked,
|
||||||
sanitize: this.refs.previewSanitize.value
|
breaks: this.refs.previewBreaks.checked,
|
||||||
|
smartArrows: this.refs.previewSmartArrows.checked,
|
||||||
|
sanitize: this.refs.previewSanitize.value,
|
||||||
|
allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
|
||||||
|
customCSS: this.customCSSCM.getCodeMirror().getValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +165,7 @@ class UiTab extends React.Component {
|
|||||||
const { config, codemirrorTheme } = this.state
|
const { config, codemirrorTheme } = this.state
|
||||||
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
|
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
|
||||||
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
|
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
|
||||||
|
const fontFamily = normalizeEditorFontFamily(config.editor.fontFamily)
|
||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root'>
|
||||||
<div styleName='group'>
|
<div styleName='group'>
|
||||||
@@ -233,7 +241,7 @@ class UiTab extends React.Component {
|
|||||||
disabled={OSX}
|
disabled={OSX}
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
/>
|
/>
|
||||||
Disable Direct Write(It will be applied after restarting)
|
{i18n.__('Disable Direct Write (It will be applied after restarting)')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
@@ -255,8 +263,16 @@ class UiTab extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
<div styleName='code-mirror'>
|
<div styleName='code-mirror' style={{fontFamily}}>
|
||||||
<ReactCodeMirror ref={e => (this.codeMirrorInstance = e)} value={codemirrorSampleCode} options={{ lineNumbers: true, readOnly: true, mode: 'javascript', theme: codemirrorTheme }} />
|
<ReactCodeMirror
|
||||||
|
ref={e => (this.codeMirrorInstance = e)}
|
||||||
|
value={codemirrorSampleCode}
|
||||||
|
options={{
|
||||||
|
lineNumbers: true,
|
||||||
|
readOnly: true,
|
||||||
|
mode: 'javascript',
|
||||||
|
theme: codemirrorTheme
|
||||||
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -473,7 +489,27 @@ class UiTab extends React.Component {
|
|||||||
ref='previewSmartQuotes'
|
ref='previewSmartQuotes'
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
/>
|
/>
|
||||||
Enable smart quotes
|
{i18n.__('Enable smart quotes')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.preview.breaks}
|
||||||
|
ref='previewBreaks'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Render newlines in Markdown paragraphs as <br>')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.preview.smartArrows}
|
||||||
|
ref='previewSmartArrows'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -558,6 +594,32 @@ class UiTab extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('Custom CSS')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={config.preview.allowCustomCSS}
|
||||||
|
ref='previewAllowCustomCSS'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Allow custom CSS for preview')}
|
||||||
|
<div style={{fontFamily}}>
|
||||||
|
<ReactCodeMirror
|
||||||
|
width='400px'
|
||||||
|
height='400px'
|
||||||
|
onChange={e => this.handleUIChange(e)}
|
||||||
|
ref={e => (this.customCSSCM = e)}
|
||||||
|
value={config.preview.customCSS}
|
||||||
|
options={{
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: 'css',
|
||||||
|
theme: codemirrorTheme
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-control'>
|
<div styleName='group-control'>
|
||||||
<button styleName='group-control-rightButton'
|
<button styleName='group-control-rightButton'
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import UiTab from './UiTab'
|
|||||||
import InfoTab from './InfoTab'
|
import InfoTab from './InfoTab'
|
||||||
import Crowdfunding from './Crowdfunding'
|
import Crowdfunding from './Crowdfunding'
|
||||||
import StoragesTab from './StoragesTab'
|
import StoragesTab from './StoragesTab'
|
||||||
|
import SnippetTab from './SnippetTab'
|
||||||
import Blog from './Blog'
|
import Blog from './Blog'
|
||||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
@@ -86,6 +87,14 @@ class Preferences extends React.Component {
|
|||||||
haveToSave={alert => this.setState({BlogAlert: alert})}
|
haveToSave={alert => this.setState({BlogAlert: alert})}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
case 'SNIPPET':
|
||||||
|
return (
|
||||||
|
<SnippetTab
|
||||||
|
dispatch={dispatch}
|
||||||
|
config={config}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'STORAGES':
|
case 'STORAGES':
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
@@ -123,7 +132,8 @@ class Preferences extends React.Component {
|
|||||||
{target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert},
|
{target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert},
|
||||||
{target: 'INFO', label: i18n.__('About')},
|
{target: 'INFO', label: i18n.__('About')},
|
||||||
{target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')},
|
{target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')},
|
||||||
{target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert}
|
{target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert},
|
||||||
|
{target: 'SNIPPET', label: i18n.__('Snippets')}
|
||||||
]
|
]
|
||||||
|
|
||||||
const navButtons = tabs.map((tab) => {
|
const navButtons = tabs.map((tab) => {
|
||||||
|
|||||||
@@ -38,29 +38,13 @@ function data (state = defaultDataMap(), action) {
|
|||||||
if (note.isTrashed) {
|
if (note.isTrashed) {
|
||||||
state.trashedSet.add(uniqueKey)
|
state.trashedSet.add(uniqueKey)
|
||||||
}
|
}
|
||||||
|
const storageNoteList = getOrInitItem(state.storageNoteMap, note.storage)
|
||||||
let storageNoteList = state.storageNoteMap.get(note.storage)
|
|
||||||
if (storageNoteList == null) {
|
|
||||||
storageNoteList = new Set(storageNoteList)
|
|
||||||
state.storageNoteMap.set(note.storage, storageNoteList)
|
|
||||||
}
|
|
||||||
storageNoteList.add(uniqueKey)
|
storageNoteList.add(uniqueKey)
|
||||||
|
|
||||||
let folderNoteSet = state.folderNoteMap.get(folderKey)
|
const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey)
|
||||||
if (folderNoteSet == null) {
|
|
||||||
folderNoteSet = new Set(folderNoteSet)
|
|
||||||
state.folderNoteMap.set(folderKey, folderNoteSet)
|
|
||||||
}
|
|
||||||
folderNoteSet.add(uniqueKey)
|
folderNoteSet.add(uniqueKey)
|
||||||
|
|
||||||
note.tags.forEach((tag) => {
|
assignToTags(note.tags, state, uniqueKey)
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList == null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
return state
|
return state
|
||||||
case 'UPDATE_NOTE':
|
case 'UPDATE_NOTE':
|
||||||
@@ -74,40 +58,18 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.noteMap = new Map(state.noteMap)
|
state.noteMap = new Map(state.noteMap)
|
||||||
state.noteMap.set(uniqueKey, note)
|
state.noteMap.set(uniqueKey, note)
|
||||||
|
|
||||||
if (oldNote == null || oldNote.isStarred !== note.isStarred) {
|
updateStarredChange(oldNote, note, state, uniqueKey)
|
||||||
state.starredSet = new Set(state.starredSet)
|
|
||||||
if (note.isStarred) {
|
|
||||||
state.starredSet.add(uniqueKey)
|
|
||||||
} else {
|
|
||||||
state.starredSet.delete(uniqueKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
||||||
state.trashedSet = new Set(state.trashedSet)
|
state.trashedSet = new Set(state.trashedSet)
|
||||||
if (note.isTrashed) {
|
if (note.isTrashed) {
|
||||||
state.trashedSet.add(uniqueKey)
|
state.trashedSet.add(uniqueKey)
|
||||||
state.starredSet.delete(uniqueKey)
|
state.starredSet.delete(uniqueKey)
|
||||||
|
removeFromTags(note.tags, state, uniqueKey)
|
||||||
note.tags.forEach(tag => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList != null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.delete(uniqueKey)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
state.trashedSet.delete(uniqueKey)
|
state.trashedSet.delete(uniqueKey)
|
||||||
|
|
||||||
note.tags.forEach(tag => {
|
assignToTags(note.tags, state, uniqueKey)
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList != null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (note.isStarred) {
|
if (note.isStarred) {
|
||||||
state.starredSet.add(uniqueKey)
|
state.starredSet.add(uniqueKey)
|
||||||
@@ -125,54 +87,12 @@ function data (state = defaultDataMap(), action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update foldermap if folder changed or post created
|
// Update foldermap if folder changed or post created
|
||||||
if (oldNote == null || oldNote.folder !== note.folder) {
|
updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
|
||||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
|
||||||
let folderNoteSet = state.folderNoteMap.get(folderKey)
|
|
||||||
folderNoteSet = new Set(folderNoteSet)
|
|
||||||
folderNoteSet.add(uniqueKey)
|
|
||||||
state.folderNoteMap.set(folderKey, folderNoteSet)
|
|
||||||
|
|
||||||
if (oldNote != null) {
|
|
||||||
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
|
||||||
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
|
|
||||||
oldFolderNoteList = new Set(oldFolderNoteList)
|
|
||||||
oldFolderNoteList.delete(uniqueKey)
|
|
||||||
state.folderNoteMap.set(oldFolderKey, oldFolderNoteList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldNote != null) {
|
if (oldNote != null) {
|
||||||
const discardedTags = _.difference(oldNote.tags, note.tags)
|
updateTagChanges(oldNote, note, state, uniqueKey)
|
||||||
const addedTags = _.difference(note.tags, oldNote.tags)
|
|
||||||
if (discardedTags.length + addedTags.length > 0) {
|
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
|
||||||
|
|
||||||
discardedTags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList != null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.delete(uniqueKey)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
addedTags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
assignToTags(note.tags, state, uniqueKey)
|
||||||
note.tags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList == null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
@@ -220,26 +140,10 @@ function data (state = defaultDataMap(), action) {
|
|||||||
originFolderList.delete(originKey)
|
originFolderList.delete(originKey)
|
||||||
state.folderNoteMap.set(originFolderKey, originFolderList)
|
state.folderNoteMap.set(originFolderKey, originFolderList)
|
||||||
|
|
||||||
// From tagMap
|
removeFromTags(originNote.tags, state, originKey)
|
||||||
if (originNote.tags.length > 0) {
|
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
|
||||||
originNote.tags.forEach((tag) => {
|
|
||||||
let noteSet = state.tagNoteMap.get(tag)
|
|
||||||
noteSet = new Set(noteSet)
|
|
||||||
noteSet.delete(originKey)
|
|
||||||
state.tagNoteMap.set(tag, noteSet)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldNote == null || oldNote.isStarred !== note.isStarred) {
|
updateStarredChange(oldNote, note, state, uniqueKey)
|
||||||
state.starredSet = new Set(state.starredSet)
|
|
||||||
if (note.isStarred) {
|
|
||||||
state.starredSet.add(uniqueKey)
|
|
||||||
} else {
|
|
||||||
state.starredSet.delete(uniqueKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
||||||
state.trashedSet = new Set(state.trashedSet)
|
state.trashedSet = new Set(state.trashedSet)
|
||||||
@@ -260,55 +164,13 @@ function data (state = defaultDataMap(), action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update foldermap if folder changed or post created
|
// Update foldermap if folder changed or post created
|
||||||
if (oldNote == null || oldNote.folder !== note.folder) {
|
updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
|
||||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
|
||||||
let folderNoteList = state.folderNoteMap.get(folderKey)
|
|
||||||
folderNoteList = new Set(folderNoteList)
|
|
||||||
folderNoteList.add(uniqueKey)
|
|
||||||
state.folderNoteMap.set(folderKey, folderNoteList)
|
|
||||||
|
|
||||||
if (oldNote != null) {
|
|
||||||
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
|
||||||
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
|
|
||||||
oldFolderNoteList = new Set(oldFolderNoteList)
|
|
||||||
oldFolderNoteList.delete(uniqueKey)
|
|
||||||
state.folderNoteMap.set(oldFolderKey, oldFolderNoteList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from old folder map
|
// Remove from old folder map
|
||||||
if (oldNote != null) {
|
if (oldNote != null) {
|
||||||
const discardedTags = _.difference(oldNote.tags, note.tags)
|
updateTagChanges(oldNote, note, state, uniqueKey)
|
||||||
const addedTags = _.difference(note.tags, oldNote.tags)
|
|
||||||
if (discardedTags.length + addedTags.length > 0) {
|
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
|
||||||
|
|
||||||
discardedTags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList != null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.delete(uniqueKey)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
addedTags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
assignToTags(note.tags, state, uniqueKey)
|
||||||
note.tags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList == null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
@@ -347,16 +209,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
folderSet.delete(uniqueKey)
|
folderSet.delete(uniqueKey)
|
||||||
state.folderNoteMap.set(folderKey, folderSet)
|
state.folderNoteMap.set(folderKey, folderSet)
|
||||||
|
|
||||||
// From tagMap
|
removeFromTags(targetNote.tags, state, uniqueKey)
|
||||||
if (targetNote.tags.length > 0) {
|
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
|
||||||
targetNote.tags.forEach((tag) => {
|
|
||||||
let noteSet = state.tagNoteMap.get(tag)
|
|
||||||
noteSet = new Set(noteSet)
|
|
||||||
noteSet.delete(uniqueKey)
|
|
||||||
state.tagNoteMap.set(tag, noteSet)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
state.noteMap = new Map(state.noteMap)
|
state.noteMap = new Map(state.noteMap)
|
||||||
state.noteMap.delete(uniqueKey)
|
state.noteMap.delete(uniqueKey)
|
||||||
@@ -420,9 +273,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
// Delete key from tag map
|
// Delete key from tag map
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||||
note.tags.forEach((tag) => {
|
note.tags.forEach((tag) => {
|
||||||
let tagNoteSet = state.tagNoteMap.get(tag)
|
const tagNoteSet = getOrInitItem(state.tagNoteMap, tag)
|
||||||
tagNoteSet = new Set(tagNoteSet)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteSet)
|
|
||||||
tagNoteSet.delete(noteKey)
|
tagNoteSet.delete(noteKey)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -449,11 +300,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.starredSet.add(uniqueKey)
|
state.starredSet.add(uniqueKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
let storageNoteList = state.storageNoteMap.get(note.storage)
|
const storageNoteList = getOrInitItem(state.tagNoteMap, note.storage)
|
||||||
if (storageNoteList == null) {
|
|
||||||
storageNoteList = new Set(storageNoteList)
|
|
||||||
state.storageNoteMap.set(note.storage, storageNoteList)
|
|
||||||
}
|
|
||||||
storageNoteList.add(uniqueKey)
|
storageNoteList.add(uniqueKey)
|
||||||
|
|
||||||
let folderNoteSet = state.folderNoteMap.get(folderKey)
|
let folderNoteSet = state.folderNoteMap.get(folderKey)
|
||||||
@@ -464,11 +311,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
folderNoteSet.add(uniqueKey)
|
folderNoteSet.add(uniqueKey)
|
||||||
|
|
||||||
note.tags.forEach((tag) => {
|
note.tags.forEach((tag) => {
|
||||||
let tagNoteSet = state.tagNoteMap.get(tag)
|
const tagNoteSet = getOrInitItem(state.tagNoteMap, tag)
|
||||||
if (tagNoteSet == null) {
|
|
||||||
tagNoteSet = new Set(tagNoteSet)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteSet)
|
|
||||||
}
|
|
||||||
tagNoteSet.add(uniqueKey)
|
tagNoteSet.add(uniqueKey)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -517,6 +360,12 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.storageMap = new Map(state.storageMap)
|
state.storageMap = new Map(state.storageMap)
|
||||||
state.storageMap.set(action.storage.key, action.storage)
|
state.storageMap.set(action.storage.key, action.storage)
|
||||||
return state
|
return state
|
||||||
|
case 'EXPAND_STORAGE':
|
||||||
|
state = Object.assign({}, state)
|
||||||
|
state.storageMap = new Map(state.storageMap)
|
||||||
|
action.storage.isOpen = action.isOpen
|
||||||
|
state.storageMap.set(action.storage.key, action.storage)
|
||||||
|
return state
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
@@ -559,6 +408,73 @@ function status (state = defaultStatus, action) {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateStarredChange (oldNote, note, state, uniqueKey) {
|
||||||
|
if (oldNote == null || oldNote.isStarred !== note.isStarred) {
|
||||||
|
state.starredSet = new Set(state.starredSet)
|
||||||
|
if (note.isStarred) {
|
||||||
|
state.starredSet.add(uniqueKey)
|
||||||
|
} else {
|
||||||
|
state.starredSet.delete(uniqueKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFolderChange (oldNote, note, state, folderKey, uniqueKey) {
|
||||||
|
if (oldNote == null || oldNote.folder !== note.folder) {
|
||||||
|
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||||
|
let folderNoteList = state.folderNoteMap.get(folderKey)
|
||||||
|
folderNoteList = new Set(folderNoteList)
|
||||||
|
folderNoteList.add(uniqueKey)
|
||||||
|
state.folderNoteMap.set(folderKey, folderNoteList)
|
||||||
|
|
||||||
|
if (oldNote != null) {
|
||||||
|
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
||||||
|
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
|
||||||
|
oldFolderNoteList = new Set(oldFolderNoteList)
|
||||||
|
oldFolderNoteList.delete(uniqueKey)
|
||||||
|
state.folderNoteMap.set(oldFolderKey, oldFolderNoteList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTagChanges (oldNote, note, state, uniqueKey) {
|
||||||
|
const discardedTags = _.difference(oldNote.tags, note.tags)
|
||||||
|
const addedTags = _.difference(note.tags, oldNote.tags)
|
||||||
|
if (discardedTags.length + addedTags.length > 0) {
|
||||||
|
removeFromTags(discardedTags, state, uniqueKey)
|
||||||
|
assignToTags(addedTags, state, uniqueKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignToTags (tags, state, uniqueKey) {
|
||||||
|
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||||
|
tags.forEach((tag) => {
|
||||||
|
const tagNoteList = getOrInitItem(state.tagNoteMap, tag)
|
||||||
|
tagNoteList.add(uniqueKey)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromTags (tags, state, uniqueKey) {
|
||||||
|
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||||
|
tags.forEach(tag => {
|
||||||
|
let tagNoteList = state.tagNoteMap.get(tag)
|
||||||
|
if (tagNoteList != null) {
|
||||||
|
tagNoteList = new Set(tagNoteList)
|
||||||
|
tagNoteList.delete(uniqueKey)
|
||||||
|
state.tagNoteMap.set(tag, tagNoteList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrInitItem (target, key) {
|
||||||
|
let results = target.get(key)
|
||||||
|
if (results == null) {
|
||||||
|
results = new Set()
|
||||||
|
target.set(key, results)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
const reducer = combineReducers({
|
const reducer = combineReducers({
|
||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ app.on('ready', function () {
|
|||||||
mainWindow.setMenu(menu)
|
mainWindow.setMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check update every hour
|
// Check update every day
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
checkUpdate()
|
checkUpdate()
|
||||||
}, 1000 * 60 * 60 * 24)
|
}, 1000 * 60 * 60 * 24)
|
||||||
@@ -106,7 +106,7 @@ app.on('ready', function () {
|
|||||||
checkUpdate()
|
checkUpdate()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 10000)
|
}, 10 * 1000)
|
||||||
ipcServer = require('./ipcServer')
|
ipcServer = require('./ipcServer')
|
||||||
ipcServer.server.start()
|
ipcServer.server.start()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -266,6 +266,13 @@ const view = {
|
|||||||
click () {
|
click () {
|
||||||
mainWindow.setFullScreen(!mainWindow.isFullScreen())
|
mainWindow.setFullScreen(!mainWindow.isFullScreen())
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'zoomin',
|
||||||
|
accelerator: macOS ? 'CommandOrControl+Plus' : 'Control+='
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'zoomout'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const mainWindow = new BrowserWindow({
|
|||||||
autoHideMenuBar: showMenu,
|
autoHideMenuBar: showMenu,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
zoomFactor: 1.0,
|
zoomFactor: 1.0,
|
||||||
blinkFeatures: 'OverlayScrollbars'
|
enableBlinkFeatures: 'OverlayScrollbars'
|
||||||
},
|
},
|
||||||
icon: path.resolve(__dirname, '../resources/app.png')
|
icon: path.resolve(__dirname, '../resources/app.png')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
|
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
|
||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
electron.webFrame.setVisualZoomLevelLimits(1, 1)
|
||||||
var scriptUrl = window._.find(electron.remote.process.argv, (a) => a === '--hot')
|
var scriptUrl = window._.find(electron.remote.process.argv, (a) => a === '--hot')
|
||||||
? 'http://localhost:8080/assets/main.js'
|
? 'http://localhost:8080/assets/main.js'
|
||||||
: '../compiled/main.js'
|
: '../compiled/main.js'
|
||||||
|
|||||||
@@ -150,5 +150,7 @@
|
|||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,5 +205,7 @@
|
|||||||
"Unnamed": "Unbenannt",
|
"Unnamed": "Unbenannt",
|
||||||
"Rename": "Umbenennen",
|
"Rename": "Umbenennen",
|
||||||
"Folder Name": "Ordnername",
|
"Folder Name": "Ordnername",
|
||||||
"No tags": "Keine Tags"
|
"No tags": "Keine 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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
"Preferences": "Preferences",
|
"Preferences": "Preferences",
|
||||||
"Make a note": "Make a note",
|
"Make a note": "Make a note",
|
||||||
"Ctrl": "Ctrl",
|
"Ctrl": "Ctrl",
|
||||||
"Ctrl(^)": "Ctrl",
|
"Ctrl(^)": "Ctrl(^)",
|
||||||
"to create a new note": "to create a new note",
|
"to create a new note": "to create a new note",
|
||||||
"Toggle Mode": "Toggle Mode",
|
"Toggle Mode": "Toggle Mode",
|
||||||
|
"Add tag...": "Add tag...",
|
||||||
"Trash": "Trash",
|
"Trash": "Trash",
|
||||||
"MODIFICATION DATE": "MODIFICATION DATE",
|
"MODIFICATION DATE": "MODIFICATION DATE",
|
||||||
"Words": "Words",
|
"Words": "Words",
|
||||||
@@ -20,9 +21,12 @@
|
|||||||
".html": ".html",
|
".html": ".html",
|
||||||
"Print": "Print",
|
"Print": "Print",
|
||||||
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
||||||
|
"Help": "Help",
|
||||||
|
"Hide Help": "Hide Help",
|
||||||
"Storages": "Storages",
|
"Storages": "Storages",
|
||||||
"Add Storage Location": "Add Storage Location",
|
"Add Storage Location": "Add Storage Location",
|
||||||
"Add Folder": "Add Folder",
|
"Add Folder": "Add Folder",
|
||||||
|
"Select Folder": "Select Folder",
|
||||||
"Open Storage folder": "Open Storage folder",
|
"Open Storage folder": "Open Storage folder",
|
||||||
"Unlink": "Unlink",
|
"Unlink": "Unlink",
|
||||||
"Edit": "Edit",
|
"Edit": "Edit",
|
||||||
@@ -34,6 +38,8 @@
|
|||||||
"Solarized Dark": "Solarized Dark",
|
"Solarized Dark": "Solarized Dark",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
|
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
|
||||||
|
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
|
||||||
|
"Show only related tags": "Show only related tags",
|
||||||
"Editor Theme": "Editor Theme",
|
"Editor Theme": "Editor Theme",
|
||||||
"Editor Font Size": "Editor Font Size",
|
"Editor Font Size": "Editor Font Size",
|
||||||
"Editor Font Family": "Editor Font Family",
|
"Editor Font Family": "Editor Font Family",
|
||||||
@@ -51,6 +57,7 @@
|
|||||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
|
"⚠️ 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",
|
"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",
|
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
|
||||||
|
"Enable smart quotes": "Enable smart quotes",
|
||||||
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
|
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
|
||||||
"Preview": "Preview",
|
"Preview": "Preview",
|
||||||
"Preview Font Size": "Preview Font Size",
|
"Preview Font Size": "Preview Font Size",
|
||||||
@@ -127,6 +134,7 @@
|
|||||||
"Storage": "Storage",
|
"Storage": "Storage",
|
||||||
"Hotkeys": "Hotkeys",
|
"Hotkeys": "Hotkeys",
|
||||||
"Show/Hide Boostnote": "Show/Hide Boostnote",
|
"Show/Hide Boostnote": "Show/Hide Boostnote",
|
||||||
|
"Toggle editor mode": "Toggle editor mode",
|
||||||
"Restore": "Restore",
|
"Restore": "Restore",
|
||||||
"Permanent Delete": "Permanent Delete",
|
"Permanent Delete": "Permanent Delete",
|
||||||
"Confirm note deletion": "Confirm note deletion",
|
"Confirm note deletion": "Confirm note deletion",
|
||||||
@@ -146,12 +154,26 @@
|
|||||||
"UserName": "UserName",
|
"UserName": "UserName",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Russian": "Russian",
|
"Russian": "Russian",
|
||||||
|
"Hungarian": "Hungarian",
|
||||||
"Command(⌘)": "Command(⌘)",
|
"Command(⌘)": "Command(⌘)",
|
||||||
|
"Add Storage": "Add Storage",
|
||||||
|
"Name": "Name",
|
||||||
|
"Type": "Type",
|
||||||
|
"File System": "File System",
|
||||||
|
"Setting up 3rd-party cloud storage integration:": "Setting up 3rd-party cloud storage integration:",
|
||||||
|
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
|
||||||
|
"Location": "Location",
|
||||||
|
"Add": "Add",
|
||||||
|
"Unlink Storage": "Unlink Storage",
|
||||||
|
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.",
|
||||||
"Editor Rulers": "Editor Rulers",
|
"Editor Rulers": "Editor Rulers",
|
||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
"Disable": "Disable",
|
"Disable": "Disable",
|
||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||||
|
"Render newlines in Markdown paragraphs as <br>": "Render newlines in Markdown paragraphs as <br>",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,5 +150,7 @@
|
|||||||
"Sanitization": "Saneamiento",
|
"Sanitization": "Saneamiento",
|
||||||
"Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)",
|
"Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)",
|
||||||
"Allow styles": "Permitir estilos",
|
"Allow styles": "Permitir estilos",
|
||||||
"Allow dangerous html tags": "Permitir etiquetas html peligrosas"
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,5 +153,7 @@
|
|||||||
"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! ⚠"
|
||||||
}
|
}
|
||||||
@@ -141,14 +141,16 @@
|
|||||||
"Portuguese": "Portugais",
|
"Portuguese": "Portugais",
|
||||||
"Spanish": "Espagnol",
|
"Spanish": "Espagnol",
|
||||||
"You have to save!": "Il faut sauvegarder !",
|
"You have to save!": "Il faut sauvegarder !",
|
||||||
"Russian": "Russian",
|
"Russian": "Russe",
|
||||||
"Command(⌘)": "Command(⌘)",
|
"Command(⌘)": "Command(⌘)",
|
||||||
"Editor Rulers": "Editor Rulers",
|
"Editor Rulers": "Règles dans l'éditeur",
|
||||||
"Enable": "Enable",
|
"Enable": "Activer",
|
||||||
"Disable": "Disable",
|
"Disable": "Désactiver",
|
||||||
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
|
"Allow preview to scroll past the last line": "Permettre de scroller après la dernière ligne dans l'aperçu",
|
||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "N'accepter que les tags html sécurisés (recommandé)",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Accepter les styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"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 ! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"Ctrl(^)": "Ctrl",
|
"Ctrl(^)": "Ctrl",
|
||||||
"to create a new note": "hogy létrehozz egy jegyzetet",
|
"to create a new note": "hogy létrehozz egy jegyzetet",
|
||||||
"Toggle Mode": "Mód Váltás",
|
"Toggle Mode": "Mód Váltás",
|
||||||
|
"Add tag...": "Tag hozzáadása...",
|
||||||
"Trash": "Lomtár",
|
"Trash": "Lomtár",
|
||||||
"MODIFICATION DATE": "MÓDOSÍTÁS DÁTUMA",
|
"MODIFICATION DATE": "MÓDOSÍTÁS DÁTUMA",
|
||||||
"Words": "Szó",
|
"Words": "Szó",
|
||||||
@@ -20,9 +21,12 @@
|
|||||||
".html": ".html",
|
".html": ".html",
|
||||||
"Print": "Nyomtatás",
|
"Print": "Nyomtatás",
|
||||||
"Your preferences for Boostnote": "Boostnote beállításaid",
|
"Your preferences for Boostnote": "Boostnote beállításaid",
|
||||||
|
"Help": "Súgó",
|
||||||
|
"Hide Help": "Súgó Elrejtése",
|
||||||
"Storages": "Tárolók",
|
"Storages": "Tárolók",
|
||||||
"Add Storage Location": "Tároló Hozzáadása",
|
"Add Storage Location": "Tároló Hozzáadása",
|
||||||
"Add Folder": "Könyvtár Hozzáadása",
|
"Add Folder": "Könyvtár Hozzáadása",
|
||||||
|
"Select Folder": "Könyvtár Kiválasztása",
|
||||||
"Open Storage folder": "Tároló Megnyitása",
|
"Open Storage folder": "Tároló Megnyitása",
|
||||||
"Unlink": "Tároló Leválasztása",
|
"Unlink": "Tároló Leválasztása",
|
||||||
"Edit": "Szerkesztés",
|
"Edit": "Szerkesztés",
|
||||||
@@ -34,6 +38,8 @@
|
|||||||
"Solarized Dark": "Solarized Dark",
|
"Solarized Dark": "Solarized Dark",
|
||||||
"Dark": "Sötét",
|
"Dark": "Sötét",
|
||||||
"Show a confirmation dialog when deleting notes": "Kérjen megerősítést a jegyzetek törlése előtt",
|
"Show a confirmation dialog when deleting notes": "Kérjen megerősítést a jegyzetek törlése előtt",
|
||||||
|
"Disable Direct Write (It will be applied after restarting)": "Jegyzet Azonnali Mentésének Tiltása (Újraindítás igényel)",
|
||||||
|
"Show only related tags": "Csak a kapcsolódó tag-ek megjelenítése",
|
||||||
"Editor Theme": "Szerkesztő Témája",
|
"Editor Theme": "Szerkesztő Témája",
|
||||||
"Editor Font Size": "Szerkesztő Betűmérete",
|
"Editor Font Size": "Szerkesztő Betűmérete",
|
||||||
"Editor Font Family": "Szerkesztő Betűtípusa",
|
"Editor Font Family": "Szerkesztő Betűtípusa",
|
||||||
@@ -51,6 +57,7 @@
|
|||||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Kérlek, indítsd újra a programot a kiosztás megváltoztatása után",
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Kérlek, indítsd újra a programot a kiosztás megváltoztatása után",
|
||||||
"Show line numbers in the editor": "Mutatassa a sorszámokat a szerkesztőben",
|
"Show line numbers in the editor": "Mutatassa a sorszámokat a szerkesztőben",
|
||||||
"Allow editor to scroll past the last line": "A szerkesztőben az utolsó sor alá is lehessen görgetni",
|
"Allow editor to scroll past the last line": "A szerkesztőben az utolsó sor alá is lehessen görgetni",
|
||||||
|
"Enable smart quotes": "Idézőjelek párjának automatikus beírása",
|
||||||
"Bring in web page title when pasting URL on editor": "Weboldal főcímének lekérdezése URL cím beillesztésekor",
|
"Bring in web page title when pasting URL on editor": "Weboldal főcímének lekérdezése URL cím beillesztésekor",
|
||||||
"Preview": "Megtekintés",
|
"Preview": "Megtekintés",
|
||||||
"Preview Font Size": "Megtekintés Betűmérete",
|
"Preview Font Size": "Megtekintés Betűmérete",
|
||||||
@@ -127,6 +134,7 @@
|
|||||||
"Storage": "Tároló",
|
"Storage": "Tároló",
|
||||||
"Hotkeys": "Gyorsbillentyűk",
|
"Hotkeys": "Gyorsbillentyűk",
|
||||||
"Show/Hide Boostnote": "Boostnote Megjelenítése/Elrejtése",
|
"Show/Hide Boostnote": "Boostnote Megjelenítése/Elrejtése",
|
||||||
|
"Toggle editor mode": "Szerkesztő mód váltása",
|
||||||
"Restore": "Visszaállítás",
|
"Restore": "Visszaállítás",
|
||||||
"Permanent Delete": "Végleges Törlés",
|
"Permanent Delete": "Végleges Törlés",
|
||||||
"Confirm note deletion": "Törlés megerősítése",
|
"Confirm note deletion": "Törlés megerősítése",
|
||||||
@@ -146,8 +154,8 @@
|
|||||||
"UserName": "FelhasznaloNev",
|
"UserName": "FelhasznaloNev",
|
||||||
"Password": "Jelszo",
|
"Password": "Jelszo",
|
||||||
"Russian": "Russian",
|
"Russian": "Russian",
|
||||||
"Command(⌘)": "Command(⌘)",
|
|
||||||
"Hungarian": "Hungarian",
|
"Hungarian": "Hungarian",
|
||||||
|
"Command(⌘)": "Command(⌘)",
|
||||||
"Add Storage": "Tároló hozzáadása",
|
"Add Storage": "Tároló hozzáadása",
|
||||||
"Name": "Név",
|
"Name": "Név",
|
||||||
"Type": "Típus",
|
"Type": "Típus",
|
||||||
@@ -156,6 +164,17 @@
|
|||||||
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
|
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
|
||||||
"Location": "Hely",
|
"Location": "Hely",
|
||||||
"Add": "Hozzáadás",
|
"Add": "Hozzáadás",
|
||||||
|
"Select Folder": "Könyvtár Kiválasztása",
|
||||||
"Unlink Storage": "Tároló Leválasztása",
|
"Unlink Storage": "Tároló Leválasztása",
|
||||||
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "A leválasztás eltávolítja ezt a tárolót a Boostnote-ból. Az adatok nem lesznek törölve, kérlek manuálisan töröld a könyvtárat a merevlemezről, ha szükséges."
|
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "A leválasztás eltávolítja ezt a tárolót a Boostnote-ból. Az adatok nem lesznek törölve, kérlek manuálisan töröld a könyvtárat a merevlemezről, ha szükséges.",
|
||||||
|
"Editor Rulers": "Szerkesztő Margók",
|
||||||
|
"Enable": "Engedélyezés",
|
||||||
|
"Disable": "Tiltás",
|
||||||
|
"Sanitization": "Tisztítás",
|
||||||
|
"Only allow secure html tags (recommended)": "Csak a biztonságos html tag-ek engedélyezése (ajánlott)",
|
||||||
|
"Render newlines in Markdown paragraphs as <br>": "Az újsor karaktert <br> soremelésként jelenítse meg a Markdown jegyzetekben",
|
||||||
|
"Allow styles": "Stílusok engedélyezése",
|
||||||
|
"Allow dangerous html tags": "Veszélyes html tag-ek engedélyezése",
|
||||||
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,5 +153,7 @@
|
|||||||
"Sanitization": "Bonifica",
|
"Sanitization": "Bonifica",
|
||||||
"Only allow secure html tags (recommended)": "Consenti solo tag HTML sicuri (raccomandato)",
|
"Only allow secure html tags (recommended)": "Consenti solo tag HTML sicuri (raccomandato)",
|
||||||
"Allow styles": "Consenti stili",
|
"Allow styles": "Consenti stili",
|
||||||
"Allow dangerous html tags": "Consenti tag HTML pericolosi"
|
"Allow dangerous html tags": "Consenti tag HTML pericolosi",
|
||||||
}"
|
"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! ⚠"
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"Ctrl(^)": "Ctrl",
|
"Ctrl(^)": "Ctrl",
|
||||||
"to create a new note": "ノートを新規に作成",
|
"to create a new note": "ノートを新規に作成",
|
||||||
"Toggle Mode": "モード切替",
|
"Toggle Mode": "モード切替",
|
||||||
|
"Add tag...": "タグを追加...",
|
||||||
"Trash": "ゴミ箱",
|
"Trash": "ゴミ箱",
|
||||||
"MODIFICATION DATE": "修正日",
|
"MODIFICATION DATE": "修正日",
|
||||||
"Words": "ワード",
|
"Words": "ワード",
|
||||||
@@ -20,9 +21,12 @@
|
|||||||
".html": ".html",
|
".html": ".html",
|
||||||
"Print": "印刷",
|
"Print": "印刷",
|
||||||
"Your preferences for Boostnote": "Boostnoteの個人設定",
|
"Your preferences for Boostnote": "Boostnoteの個人設定",
|
||||||
|
"Help": "ヘルプ",
|
||||||
|
"Hide Help": "ヘルプを隠す",
|
||||||
"Storages": "ストレージ",
|
"Storages": "ストレージ",
|
||||||
"Add Storage Location": "ストレージロケーションを追加",
|
"Add Storage Location": "ストレージロケーションを追加",
|
||||||
"Add Folder": "フォルダを追加",
|
"Add Folder": "フォルダを追加",
|
||||||
|
"Select Folder": "フォルダを選択",
|
||||||
"Open Storage folder": "ストレージフォルダを開く",
|
"Open Storage folder": "ストレージフォルダを開く",
|
||||||
"Unlink": "リンク解除",
|
"Unlink": "リンク解除",
|
||||||
"Edit": "編集",
|
"Edit": "編集",
|
||||||
@@ -34,6 +38,8 @@
|
|||||||
"Solarized Dark": "明灰",
|
"Solarized Dark": "明灰",
|
||||||
"Dark": "暗灰",
|
"Dark": "暗灰",
|
||||||
"Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する",
|
"Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する",
|
||||||
|
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
|
||||||
|
"Show only related tags": "関連するタグのみ表示する",
|
||||||
"Editor Theme": "エディタのテーマ",
|
"Editor Theme": "エディタのテーマ",
|
||||||
"Editor Font Size": "エディタのフォントサイズ",
|
"Editor Font Size": "エディタのフォントサイズ",
|
||||||
"Editor Font Family": "エディタのフォント",
|
"Editor Font Family": "エディタのフォント",
|
||||||
@@ -48,17 +54,18 @@
|
|||||||
"default": "デフォルト",
|
"default": "デフォルト",
|
||||||
"vim": "vim",
|
"vim": "vim",
|
||||||
"emacs": "emacs",
|
"emacs": "emacs",
|
||||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Plキーマップ変更後は Boostnote を再起動してください",
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ キーマップ変更後は Boostnote を再起動してください",
|
||||||
"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",
|
"Enable smart quotes": "スマートクォートを有効にする",
|
||||||
|
"Bring in web page title when pasting URL on editor": "URLを貼り付けた時にWebページのタイトルを取得する",
|
||||||
"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 Open Delimiter": "LaTeX 開始デリミタ(インライン)",
|
||||||
"LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
|
"LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
|
"LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
|
"LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
|
||||||
@@ -83,7 +90,7 @@
|
|||||||
"You can choose to enable or disable this option.": "このオプションは有効/無効を選択できます。",
|
"You can choose to enable or disable this option.": "このオプションは有効/無効を選択できます。",
|
||||||
"Enable analytics to help improve Boostnote": "Boostnote の機能向上のための解析機能を有効にする",
|
"Enable analytics to help improve Boostnote": "Boostnote の機能向上のための解析機能を有効にする",
|
||||||
"Crowdfunding": "クラウドファンディング",
|
"Crowdfunding": "クラウドファンディング",
|
||||||
"Dear everyone,": "Dear everyone,",
|
"Dear everyone,": "みなさまへ",
|
||||||
"Thank you for using Boostnote!": "Boostnote を利用いただき、ありがとうございます!",
|
"Thank you for using Boostnote!": "Boostnote を利用いただき、ありがとうございます!",
|
||||||
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote はおよそ 200 の国と地域において、開発者コミュニティを中心に利用されています。",
|
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote はおよそ 200 の国と地域において、開発者コミュニティを中心に利用されています。",
|
||||||
"To continue supporting this growth, and to satisfy community expectations,": "この成長を持続し、またコミュニティからの要望に答えるため、",
|
"To continue supporting this growth, and to satisfy community expectations,": "この成長を持続し、またコミュニティからの要望に答えるため、",
|
||||||
@@ -112,6 +119,7 @@
|
|||||||
"Updated": "更新日時",
|
"Updated": "更新日時",
|
||||||
"Created": "作成日時",
|
"Created": "作成日時",
|
||||||
"Alphabetically": "アルファベット順",
|
"Alphabetically": "アルファベット順",
|
||||||
|
"Counter": "数順",
|
||||||
"Default View": "デフォルトビュー",
|
"Default View": "デフォルトビュー",
|
||||||
"Compressed View": "圧縮ビュー",
|
"Compressed View": "圧縮ビュー",
|
||||||
"Search": "検索",
|
"Search": "検索",
|
||||||
@@ -126,6 +134,7 @@
|
|||||||
"Storage": "ストレージ",
|
"Storage": "ストレージ",
|
||||||
"Hotkeys": "ホットキー",
|
"Hotkeys": "ホットキー",
|
||||||
"Show/Hide Boostnote": "Boostnote の表示/非表示",
|
"Show/Hide Boostnote": "Boostnote の表示/非表示",
|
||||||
|
"Toggle editor mode": "エディタモードの切替",
|
||||||
"Restore": "リストア",
|
"Restore": "リストア",
|
||||||
"Permanent Delete": "永久に削除",
|
"Permanent Delete": "永久に削除",
|
||||||
"Confirm note deletion": "ノート削除確認",
|
"Confirm note deletion": "ノート削除確認",
|
||||||
@@ -142,13 +151,29 @@
|
|||||||
"Portuguese": "ポルトガル語",
|
"Portuguese": "ポルトガル語",
|
||||||
"Spanish": "スペイン語",
|
"Spanish": "スペイン語",
|
||||||
"You have to save!": "保存してください!",
|
"You have to save!": "保存してください!",
|
||||||
|
"UserName": "ユーザー名",
|
||||||
|
"Password": "パスワード",
|
||||||
"Russian": "ロシア語",
|
"Russian": "ロシア語",
|
||||||
|
"Hungarian": "ハンガリー語",
|
||||||
"Command(⌘)": "コマンド(⌘)",
|
"Command(⌘)": "コマンド(⌘)",
|
||||||
|
"Add Storage": "ストレージを追加",
|
||||||
|
"Name": "名前",
|
||||||
|
"Type": "種類",
|
||||||
|
"File System": "ファイルシステム",
|
||||||
|
"Setting up 3rd-party cloud storage integration:": "サードパーティのクラウドストレージとの統合を設定する:",
|
||||||
|
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
|
||||||
|
"Location": "ロケーション",
|
||||||
|
"Add": "追加",
|
||||||
|
"Unlink Storage": "ストレージのリンクを解除",
|
||||||
|
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "リンクの解除ではBoostnoteからリンクされたストレージを削除しますが、データは削除されません。データを削除する場合はご自身でハードドライブからフォルダを削除してください。",
|
||||||
"Editor Rulers": "罫線",
|
"Editor Rulers": "罫線",
|
||||||
"Enable": "有効",
|
"Enable": "有効",
|
||||||
"Disable": "無効",
|
"Disable": "無効",
|
||||||
"Sanitization": "サニタイズ",
|
"Sanitization": "サニタイズ",
|
||||||
"Only allow secure html tags (recommended)": "安全なHTMLタグのみ利用を許可する(推奨)",
|
"Only allow secure html tags (recommended)": "安全なHTMLタグのみ利用を許可する(推奨)",
|
||||||
|
"Render newlines in Markdown paragraphs as <br>": "Markdown 中の改行でプレビューも改行する",
|
||||||
"Allow styles": "スタイルを許可する",
|
"Allow styles": "スタイルを許可する",
|
||||||
"Allow dangerous html tags": "安全でないHTMLタグの利用を許可する"
|
"Allow dangerous html tags": "安全でないHTMLタグの利用を許可する",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "テキストの矢印を綺麗な記号に変換する ⚠ この設定はMarkdown内でのHTMLコメントに干渉します。",
|
||||||
|
"⚠ 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! ⚠": "⚠ このノートのストレージに存在しない添付ファイルへのリンクを貼り付けました。添付ファイルへのリンクの貼り付けは同一ストレージ内でのみサポートされています。代わりに添付ファイルをドラッグアンドドロップしてください! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@
|
|||||||
"You have to save!": "저장해주세요!",
|
"You have to save!": "저장해주세요!",
|
||||||
"Russian": "Russian",
|
"Russian": "Russian",
|
||||||
"Command(⌘)": "Command(⌘)",
|
"Command(⌘)": "Command(⌘)",
|
||||||
"Delete Folder": "폴더 삭게",
|
"Delete Folder": "폴더 삭제",
|
||||||
"This will delete all notes in the folder and can not be undone.": "폴더의 모든 노트를 지우게 되고, 되돌릴 수 없습니다.",
|
"This will delete all notes in the folder and can not be undone.": "폴더의 모든 노트를 지우게 되고, 되돌릴 수 없습니다.",
|
||||||
"UserName": "유저명",
|
"UserName": "유저명",
|
||||||
"Password": "패스워드",
|
"Password": "패스워드",
|
||||||
@@ -156,5 +156,7 @@
|
|||||||
"Sanitization": "허용 태그 범위",
|
"Sanitization": "허용 태그 범위",
|
||||||
"Only allow secure html tags (recommended)": "안전한 HTML 태그만 허용 (추천)",
|
"Only allow secure html tags (recommended)": "안전한 HTML 태그만 허용 (추천)",
|
||||||
"Allow styles": "style 태그, 속성까지 허용",
|
"Allow styles": "style 태그, 속성까지 허용",
|
||||||
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,5 +149,7 @@
|
|||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,5 +149,7 @@
|
|||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,5 +149,7 @@
|
|||||||
"Sanitization": "Sanitização",
|
"Sanitization": "Sanitização",
|
||||||
"Only allow secure html tags (recommended)": "Permitir apenas tags html seguras (recomendado)",
|
"Only allow secure html tags (recommended)": "Permitir apenas tags html seguras (recomendado)",
|
||||||
"Allow styles": "Permitir estilos",
|
"Allow styles": "Permitir estilos",
|
||||||
"Allow dangerous html tags": "Permitir tags html perigosas"
|
"Allow dangerous html tags": "Permitir tags html perigosas",
|
||||||
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,5 +149,7 @@
|
|||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,5 +146,7 @@
|
|||||||
"Russian": "Русский",
|
"Russian": "Русский",
|
||||||
"Editor Rulers": "Editor Rulers",
|
"Editor Rulers": "Editor Rulers",
|
||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
"Disable": "Disable"
|
"Disable": "Disable",
|
||||||
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,5 +148,7 @@
|
|||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
155
locales/tr.json
Normal file
155
locales/tr.json
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
{
|
||||||
|
"Notes": "Notlar",
|
||||||
|
"Tags": "Etiketler",
|
||||||
|
"Preferences": "Tercihler",
|
||||||
|
"Make a note": "Not Oluştur",
|
||||||
|
"Ctrl": "Ctrl",
|
||||||
|
"Ctrl(^)": "Ctrl",
|
||||||
|
"to create a new note": "yeni not oluşturmak için",
|
||||||
|
"Toggle Mode": "Mod Değiştir",
|
||||||
|
"Trash": "Çöp",
|
||||||
|
"MODIFICATION DATE": "DEĞİŞİKLİK TARİHİ",
|
||||||
|
"Words": "Kelimeler",
|
||||||
|
"Letters": "Harfler",
|
||||||
|
"STORAGE": "SAKLAMA ALANI",
|
||||||
|
"FOLDER": "DOSYA",
|
||||||
|
"CREATION DATE": "OLUŞTURULMA TARİHİ",
|
||||||
|
"NOTE LINK": "NOT BAĞLANTISI",
|
||||||
|
".md": ".md",
|
||||||
|
".txt": ".txt",
|
||||||
|
".html": ".html",
|
||||||
|
"Print": "Yazdır",
|
||||||
|
"Your preferences for Boostnote": "Boostnote tercihleriniz",
|
||||||
|
"Storages": "Saklama Alanları",
|
||||||
|
"Add Storage Location": "Saklama Yeri Ekle",
|
||||||
|
"Add Folder": "Dosya Ekle",
|
||||||
|
"Open Storage folder": "Saklama Alanı Dosyasını Aç",
|
||||||
|
"Unlink": "Bağlantıyı kaldır",
|
||||||
|
"Edit": "Düzenle",
|
||||||
|
"Delete": "Sil",
|
||||||
|
"Interface": "Arayüz",
|
||||||
|
"Interface Theme": "Arayüz Teması",
|
||||||
|
"Default": "Varsayılan",
|
||||||
|
"White": "Beyaz",
|
||||||
|
"Solarized Dark": "Solarize Karanlık",
|
||||||
|
"Dark": "Karanlık",
|
||||||
|
"Show a confirmation dialog when deleting notes": "Notlar silinirken onay ekranını göster",
|
||||||
|
"Editor Theme": "Editör Teması",
|
||||||
|
"Editor Font Size": "Editör Yazı Büyüklüğü",
|
||||||
|
"Editor Font Family": "Editör Yazı Ailesi",
|
||||||
|
"Editor Indent Style": "Editör Girinti Stili",
|
||||||
|
"Spaces": "Boşluklar",
|
||||||
|
"Tabs": "Tablar",
|
||||||
|
"Switch to Preview": "Önizlemeye Geç",
|
||||||
|
"When Editor Blurred": "Editörden çıkıldığında",
|
||||||
|
"When Editor Blurred, Edit On Double Click": "Editörden Çıkıldığında, Çift Tıklayarak Düzenle",
|
||||||
|
"On Right Click": "Sağ tıklandığında",
|
||||||
|
"Editor Keymap": "Editör Tuş Haritası",
|
||||||
|
"default": "varsayılan",
|
||||||
|
"vim": "vim",
|
||||||
|
"emacs": "emacs",
|
||||||
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Tuş haritası değişikliklerinden sonra lütfen Boostnote'u yeniden başlatın",
|
||||||
|
"Show line numbers in the editor": "Editörde satır numaralarını göster",
|
||||||
|
"Allow editor to scroll past the last line": "Editörün son satırı geçmesine izin ver",
|
||||||
|
"Bring in web page title when pasting URL on editor": "Editörde URL yapıştırırken web sayfasının başlığını getir",
|
||||||
|
"Preview": "Önizleme",
|
||||||
|
"Preview Font Size": "Yazı Büyüklüğünü Önizle",
|
||||||
|
"Preview Font Family": "Yazı Tipini Önizle",
|
||||||
|
"Code block Theme": "Kod bloğu Teması",
|
||||||
|
"Allow preview to scroll past the last line": "Önizlemenin son satırı geçmesine izin ver",
|
||||||
|
"Show line numbers for preview code blocks": "Kod bloklarının önizlemesinde satır numaralarını göster",
|
||||||
|
"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",
|
||||||
|
"Community": "Topluluk",
|
||||||
|
"Subscribe to Newsletter": "Bültene Kayıt Ol",
|
||||||
|
"GitHub": "GitHub",
|
||||||
|
"Blog": "Blog",
|
||||||
|
"Facebook Group": "Facebook Grubu",
|
||||||
|
"Twitter": "Twitter",
|
||||||
|
"About": "Hakkında",
|
||||||
|
"Boostnote": "Boostnote",
|
||||||
|
"An open source note-taking app made for programmers just like you.": "Tıpkı sizin gibi programcılar için yapılmış açık kaynak not alma uygulaması",
|
||||||
|
"Website": "Websitesi",
|
||||||
|
"Development": "Geliştirme",
|
||||||
|
" : Development configurations for Boostnote.": " : Boostnote için geliştirme ayarları.",
|
||||||
|
"Copyright (C) 2017 - 2018 BoostIO": "Her hakkı saklıdır. (C) 2017 - 2018 BoostIO",
|
||||||
|
"License: GPL v3": "Lisans: GPL v3",
|
||||||
|
"Analytics": "Analizler",
|
||||||
|
"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, uygulamanın geliştirilmesi amacıyla anonim veriler toplar. Notlarınızın içeriği gibi kişisel bilgiler kesinlikle toplanmaz.",
|
||||||
|
"You can see how it works on ": "Nasıl çalıştığını görebilirsiniz ",
|
||||||
|
"You can choose to enable or disable this option.": "Bu seçeneği etkinleştirmeyi veya devre dışı bırakmayı seçebilirsiniz.",
|
||||||
|
"Enable analytics to help improve Boostnote": "Boostnote'un geliştirilmesine katkıda bulunmak için analizleri etkinleştirin",
|
||||||
|
"Crowdfunding": "Kitle Fonlaması",
|
||||||
|
"Dear everyone,": "Sevgili herkes,",
|
||||||
|
"Thank you for using Boostnote!": "Boostnote'u kullandığınız için teşekkürler!",
|
||||||
|
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote, 200 farklı ülke ve bölgede, harika bir geliştirici topluluğu tarafından kullanılmaktadır.",
|
||||||
|
"To continue supporting this growth, and to satisfy community expectations,": "Bu büyümeyi desteklemeye devam etmek ve topluluk beklentilerini karşılamak için,",
|
||||||
|
"we would like to invest more time and resources in this project.": "bu projeye daha fazla zaman ve kaynak yatırmak istiyoruz.",
|
||||||
|
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Bu projeyi beğeniyor ve potansiyel görüyorsanız, OpenCollective üzerinden bizi destekleyerek katkıda bulunabilirsiniz!",
|
||||||
|
"Thanks,": "Teşekkürler,",
|
||||||
|
"Boostnote maintainers": "Boostnote'un bakımını yapanlar",
|
||||||
|
"Support via OpenCollective": "OpenCollective aracılığıyla destekle",
|
||||||
|
"Language": "Dil",
|
||||||
|
"English": "İngilizce",
|
||||||
|
"German": "Almanca",
|
||||||
|
"French": "Fransızca",
|
||||||
|
"Show \"Saved to Clipboard\" notification when copying": "Kopyalandığında \"Clipboard'a kopyalandı\" uyarısını göster",
|
||||||
|
"All Notes": "Tüm Notlar",
|
||||||
|
"Starred": "Yıldızlı",
|
||||||
|
"Are you sure to ": "Bu klasörü",
|
||||||
|
" delete": " silmek istediğinize",
|
||||||
|
"this folder?": " emin misiniz?",
|
||||||
|
"Confirm": "Onayla",
|
||||||
|
"Cancel": "İptal",
|
||||||
|
"Markdown Note": "Markdown Notu",
|
||||||
|
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Bu format metin dökümanları oluşturmak içindir. Listeler, kod blokları ve Latex blokları mevcuttur.",
|
||||||
|
"Snippet Note": "Parça Not",
|
||||||
|
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Bu format kod parçacıkları oluşturmak içindir. Çoklu kod parçaları tek bir not içinde gruplanabilir.",
|
||||||
|
"Tab to switch format": "Format değiştirmek için Tab tuşunu kullan",
|
||||||
|
"Updated": "Güncellendi",
|
||||||
|
"Created": "Oluşturuldu",
|
||||||
|
"Alphabetically": "Alfabetik Olarak",
|
||||||
|
"Default View": "Varsayılan Görünüm",
|
||||||
|
"Compressed View": "Sıkıştırılmış Görünüm",
|
||||||
|
"Search": "Ara",
|
||||||
|
"Blog Type": "Blog Tipi",
|
||||||
|
"Blog Address": "Blog Adresi",
|
||||||
|
"Save": "Kaydet",
|
||||||
|
"Auth": "Auth",
|
||||||
|
"Authentication Method": "Doğrulama Yöntemi",
|
||||||
|
"JWT": "JWT",
|
||||||
|
"USER": "KULLANICI",
|
||||||
|
"Token": "Token",
|
||||||
|
"Storage": "Saklama Alanı",
|
||||||
|
"Hotkeys": "Kısayol Tuşları",
|
||||||
|
"Show/Hide Boostnote": "Boostnote'u Göster/Gizle ",
|
||||||
|
"Restore": "Geri Yükle",
|
||||||
|
"Permanent Delete": "Kalıcı Olarak Sil",
|
||||||
|
"Confirm note deletion": "Not silmeyi onayla",
|
||||||
|
"This will permanently remove this note.": "Bu not kalıcı olarak silinecektir.",
|
||||||
|
"Successfully applied!": "Başarıyla Uygulandı!",
|
||||||
|
"Albanian": "Arnavutça",
|
||||||
|
"Chinese (zh-CN)": "Çince (zh-CN)",
|
||||||
|
"Chinese (zh-TW)": "Çince (zh-TW)",
|
||||||
|
"Danish": "Danca",
|
||||||
|
"Japanese": "Japonca",
|
||||||
|
"Korean": "Korean",
|
||||||
|
"Norwegian": "Norveççe",
|
||||||
|
"Polish": "Lehçe",
|
||||||
|
"Portuguese": "Portekizce",
|
||||||
|
"Spanish": "İspanyolca",
|
||||||
|
"You have to save!": "Kaydetmelisiniz!",
|
||||||
|
"UserName": "KullanıcıAdı",
|
||||||
|
"Password": "Şifre",
|
||||||
|
"Russian": "Rusça",
|
||||||
|
"Command(⌘)": "Command(⌘)",
|
||||||
|
"Editor Rulers": "Editör Cetvelleri",
|
||||||
|
"Enable": "Etkinleştir",
|
||||||
|
"Disable": "Etkisizleştir",
|
||||||
|
"Sanitization": "Temizleme",
|
||||||
|
"Only allow secure html tags (recommended)": "Sadece güvenli html etiketlerine izin ver (tavsiye edilen)",
|
||||||
|
"Allow styles": "Stillere izin ver",
|
||||||
|
"Allow dangerous html tags": "Tehlikeli html etiketlerine izin ver"
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"Storages": "本地存储",
|
"Storages": "本地存储",
|
||||||
"Add Storage Location": "添加一个本地存储位置",
|
"Add Storage Location": "添加一个本地存储位置",
|
||||||
"Add Folder": "新建文件夹",
|
"Add Folder": "新建文件夹",
|
||||||
"Open Storage folder": "打开本地存储位置",
|
"Open Storage folder": "打开本地存储文件夹",
|
||||||
"Unlink": "取消链接",
|
"Unlink": "取消链接",
|
||||||
"Edit": "编辑",
|
"Edit": "编辑",
|
||||||
"Delete": "删除",
|
"Delete": "删除",
|
||||||
@@ -34,10 +34,11 @@
|
|||||||
"Solarized Dark": "Solarized Dark",
|
"Solarized Dark": "Solarized Dark",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
"Show a confirmation dialog when deleting notes": "删除笔记的时候,显示确认框",
|
"Show a confirmation dialog when deleting notes": "删除笔记的时候,显示确认框",
|
||||||
|
"Editor": "编辑器",
|
||||||
"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",
|
"Tabs": "Tabs",
|
||||||
"Switch to Preview": "快速切换到预览界面",
|
"Switch to Preview": "快速切换到预览界面",
|
||||||
@@ -48,20 +49,21 @@
|
|||||||
"default": "默认",
|
"default": "默认",
|
||||||
"vim": "vim",
|
"vim": "vim",
|
||||||
"emacs": "emacs",
|
"emacs": "emacs",
|
||||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ 设置好快捷键后,记得重启boostnote",
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ 修改后,请重启 boostnote",
|
||||||
"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 单行开头分隔符",
|
"LaTeX Inline Open Delimiter": "LaTeX 单行开头分隔符",
|
||||||
"LaTeX Inline Close Delimiter": "LaTeX 单行结尾分隔符",
|
"LaTeX Inline Close Delimiter": "LaTeX 单行结尾分隔符",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX 多行开头分隔符",
|
"LaTeX Block Open Delimiter": "LaTeX 多行开头分隔符",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX 多行结尾分隔符",
|
"LaTeX Block Close Delimiter": "LaTeX 多行结尾分隔符",
|
||||||
|
"PlantUML Server": "PlantUML 服务器",
|
||||||
"Community": "社区",
|
"Community": "社区",
|
||||||
"Subscribe to Newsletter": "订阅邮件",
|
"Subscribe to Newsletter": "订阅邮件",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
@@ -73,24 +75,24 @@
|
|||||||
"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": "官网",
|
||||||
"Development": "开发",
|
"Development": "开发",
|
||||||
" : Development configurations for Boostnote.": " : Boostnote的开发配置",
|
" : Development configurations for Boostnote.": " : Boostnote 的开发配置",
|
||||||
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
|
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
|
||||||
"License: GPL v3": "License: GPL v3",
|
"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 收集匿名数据只为了提升软件使用体验,绝对不收集任何个人信息(包括笔记内容)",
|
"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 收集匿名数据只为了提升软件使用体验,绝对不收集任何个人信息(包括笔记内容)",
|
||||||
"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": "允许对数据进行分析,帮助我们改进Boostnote",
|
"Enable analytics to help improve Boostnote": "允许对数据进行分析,帮助我们改进 Boostnote",
|
||||||
"Crowdfunding": "众筹",
|
"Crowdfunding": "众筹",
|
||||||
"Dear everyone,": "亲爱的用户:",
|
"Dear everyone,": "亲爱的用户:",
|
||||||
"Thank you for using Boostnote!": "谢谢你使用Boostnote!",
|
"Thank you for using Boostnote!": "谢谢你使用 Boostnote!",
|
||||||
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "大约有200个不同的国家和地区的优秀开发者们都在使用Boostnote!",
|
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "大约有200个不同的国家和地区的优秀开发者们都在使用 Boostnote!",
|
||||||
"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!": "如果你喜欢这款软件并且看好它的潜力, 请在OpenCollective上支持我们!",
|
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "如果你喜欢这款软件并且看好它的潜力, 请在 OpenCollective 上支持我们!",
|
||||||
"Thanks,": "十分感谢!",
|
"Thanks,": "十分感谢!",
|
||||||
"Boostnote maintainers": "Boostnote的维护人员",
|
"Boostnote maintainers": "Boostnote 的维护人员",
|
||||||
"Support via OpenCollective": "在OpenCollective上支持我们",
|
"Support via OpenCollective": "在 OpenCollective 上支持我们",
|
||||||
"Language": "语言",
|
"Language": "语言",
|
||||||
"English": "English",
|
"English": "English",
|
||||||
"German": "German",
|
"German": "German",
|
||||||
@@ -103,14 +105,15 @@
|
|||||||
"this folder?": "这个文件夹?",
|
"this folder?": "这个文件夹?",
|
||||||
"Confirm": "确认",
|
"Confirm": "确认",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
"Markdown Note": "Markdown笔记",
|
"Markdown Note": "Markdown 笔记",
|
||||||
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "创建文档,清单,代码块甚至是Latex格式文档",
|
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "创建文档,清单,代码块甚至是 Latex 格式文档",
|
||||||
"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键切换格式",
|
"Tab to switch format": "使用 Tab 键切换格式",
|
||||||
"Updated": "更新时间",
|
"Updated": "更新时间",
|
||||||
"Created": "创建时间",
|
"Created": "创建时间",
|
||||||
"Alphabetically": "A~Z排序",
|
"Alphabetically": "A~Z 排序",
|
||||||
|
"Counter":"标签下文章数量排序",
|
||||||
"Default View": "默认视图",
|
"Default View": "默认视图",
|
||||||
"Compressed View": "列表视图",
|
"Compressed View": "列表视图",
|
||||||
"Search": "搜索",
|
"Search": "搜索",
|
||||||
@@ -146,7 +149,63 @@
|
|||||||
"Enable": "开启",
|
"Enable": "开启",
|
||||||
"Disable": "关闭",
|
"Disable": "关闭",
|
||||||
"Sanitization": "代码处理",
|
"Sanitization": "代码处理",
|
||||||
"Only allow secure html tags (recommended)": "只允许安全的html标签(推荐)",
|
"Only allow secure html tags (recommended)": "只允许安全的 html 标签(推荐)",
|
||||||
"Allow styles": "允许样式",
|
"Allow styles": "允许样式",
|
||||||
"Allow dangerous html tags": "允许危险的html标签"
|
"Allow dangerous html tags": "允许危险的 html 标签",
|
||||||
|
"Select filter mode": "选择过滤模式",
|
||||||
|
"Add tag...": "添加标签...",
|
||||||
|
"Star":"星标",
|
||||||
|
"Fullscreen": "全屏",
|
||||||
|
"Info":"详情",
|
||||||
|
"Remove pin": "取消置顶",
|
||||||
|
"Pin to Top": "置顶",
|
||||||
|
"Delete Note": "删除笔记",
|
||||||
|
"Clone Note": "复制笔记",
|
||||||
|
"Restore Note": "恢复笔记",
|
||||||
|
"Copy Note Link": "复制笔记链接",
|
||||||
|
"Publish Blog": "发布博客",
|
||||||
|
"Update Blog": "更新博客",
|
||||||
|
"Open Blog": "打开博客",
|
||||||
|
"Empty Trash": "清空废纸篓",
|
||||||
|
"Rename Folder": "重命名文件夹",
|
||||||
|
"Export Folder": "导出文件夹",
|
||||||
|
"Export as txt": "导出为 txt",
|
||||||
|
"Export as md": "导出为 md",
|
||||||
|
"Delete Folder": "删除文件夹",
|
||||||
|
"Select directory": "选择目录",
|
||||||
|
"Select a folder to export the files to": "选择一个导出目录",
|
||||||
|
"Description...": "描述...",
|
||||||
|
"Publish Failed": "发布失败",
|
||||||
|
"Check and update your blog setting and try again.": "检查并修改你的博客设置后重试。",
|
||||||
|
"Delete a snippet": "删除一个代码片段",
|
||||||
|
"This work cannot be undone.": "此操作无法撤销。",
|
||||||
|
"Help": "帮助",
|
||||||
|
"Hungarian": "匈牙利语",
|
||||||
|
"Hide Help": "隐藏帮助",
|
||||||
|
"wordpress": "Wordpress",
|
||||||
|
"Add Storage": "添加存储",
|
||||||
|
"Name": "名称",
|
||||||
|
"Type": "类型",
|
||||||
|
"File System": "文件",
|
||||||
|
"Setting up 3rd-party cloud storage integration:": "设置整合第三方云存储:",
|
||||||
|
"Cloud-Syncing-and-Backup": "云端同步和备份",
|
||||||
|
"Location": "路径",
|
||||||
|
"Select Folder": "选择文件夹",
|
||||||
|
"Add": "添加",
|
||||||
|
"Available Keys": "可用按键",
|
||||||
|
"Select Directory": "选择目录",
|
||||||
|
"copy": "副本",
|
||||||
|
"Create new folder": "创建新文件夹",
|
||||||
|
"Folder name": "文件夹名称",
|
||||||
|
"Create": "创建",
|
||||||
|
"Unlink Storage": "取消存储链接",
|
||||||
|
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "取消链接会移除存储和 Boostnote 的链接。文件将不会被删除,如果你需要的话可以手动删除此目录。",
|
||||||
|
"Empty note": "空笔记",
|
||||||
|
"Unnamed":"未命名",
|
||||||
|
"Rename": "重命名",
|
||||||
|
"Folder Name": "文件夹名称",
|
||||||
|
"No tags":"无标签",
|
||||||
|
"Render newlines in Markdown paragraphs as <br>":"在 Markdown 段落中使用 <br> 换行",
|
||||||
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
"Preferences": "偏好設定",
|
"Preferences": "偏好設定",
|
||||||
"Make a note": "做點筆記",
|
"Make a note": "做點筆記",
|
||||||
"Ctrl": "Ctrl",
|
"Ctrl": "Ctrl",
|
||||||
"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": "字數",
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
".html": ".html",
|
".html": ".html",
|
||||||
"Print": "列印",
|
"Print": "列印",
|
||||||
"Your preferences for Boostnote": "Boostnote 偏好設定",
|
"Your preferences for Boostnote": "Boostnote 偏好設定",
|
||||||
"Storages": "本機儲存空間",
|
"Storages": "儲存空間",
|
||||||
"Add Storage Location": "新增一個本機儲存位置",
|
"Add Storage Location": "新增儲存位置",
|
||||||
"Add Folder": "新增資料夾",
|
"Add Folder": "新增資料夾",
|
||||||
"Open Storage folder": "開啟儲存資料夾",
|
"Open Storage folder": "開啟儲存資料夾",
|
||||||
"Unlink": "解除連結",
|
"Unlink": "解除連結",
|
||||||
@@ -43,12 +43,12 @@
|
|||||||
"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": "編輯器 Keymap",
|
"Editor Keymap": "編輯器 Keymap",
|
||||||
"default": "預設",
|
"default": "預設",
|
||||||
"vim": "vim",
|
"vim": "vim",
|
||||||
"emacs": "emacs",
|
"emacs": "emacs",
|
||||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ 請重新開啟 Boostnote 以完成設定。",
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ 修改鍵盤配置請重新開啟 Boostnote ",
|
||||||
"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": "在編輯器貼上網址的時候,自動加上網頁標題",
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
"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 收集匿名資料單純只為了提升軟體使用體驗,絕對不收集任何個人資料(包括筆記內容)",
|
"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 收集匿名資料單純只為了提升軟體使用體驗,絕對不收集任何個人資料(包括筆記內容)",
|
||||||
"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": "允許數據分析以協助我們改進 Boostnote",
|
"Enable analytics to help improve Boostnote": "允許數據分析以協助我們改進 Boostnote",
|
||||||
"Crowdfunding": "群眾募資",
|
"Crowdfunding": "群眾募資",
|
||||||
"Dear everyone,": "親愛的用戶:",
|
"Dear everyone,": "親愛的用戶:",
|
||||||
@@ -104,9 +104,9 @@
|
|||||||
"Confirm": "確認",
|
"Confirm": "確認",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
"Markdown Note": "Markdown 筆記",
|
"Markdown Note": "Markdown 筆記",
|
||||||
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "建立文件、清單,也可以使用程式碼區塊甚至是 Latex 區塊。",
|
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "建立文件、清單,也可以使用程式碼區塊及 Latex 區塊。",
|
||||||
"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 鍵切換格式",
|
"Tab to switch format": "使用 Tab 鍵切換格式",
|
||||||
"Updated": "依更新時間排序",
|
"Updated": "依更新時間排序",
|
||||||
"Created": "依建立時間排序",
|
"Created": "依建立時間排序",
|
||||||
@@ -117,12 +117,12 @@
|
|||||||
"Blog Type": "部落格類型",
|
"Blog Type": "部落格類型",
|
||||||
"Blog Address": "部落格網址",
|
"Blog Address": "部落格網址",
|
||||||
"Save": "儲存",
|
"Save": "儲存",
|
||||||
"Auth": "Auth",
|
"Auth": "驗證",
|
||||||
"Authentication Method": "認證方法",
|
"Authentication Method": "認證方法",
|
||||||
"JWT": "JWT",
|
"JWT": "JWT",
|
||||||
"USER": "USER",
|
"USER": "USER",
|
||||||
"Token": "Token",
|
"Token": "Token",
|
||||||
"Storage": "本機儲存空間",
|
"Storage": "儲存空間",
|
||||||
"Hotkeys": "快捷鍵",
|
"Hotkeys": "快捷鍵",
|
||||||
"Show/Hide Boostnote": "顯示/隱藏 Boostnote",
|
"Show/Hide Boostnote": "顯示/隱藏 Boostnote",
|
||||||
"Restore": "還原",
|
"Restore": "還原",
|
||||||
@@ -144,9 +144,11 @@
|
|||||||
"Russian": "Russian",
|
"Russian": "Russian",
|
||||||
"Editor Rulers": "編輯器中顯示垂直尺規",
|
"Editor Rulers": "編輯器中顯示垂直尺規",
|
||||||
"Enable": "啟用",
|
"Enable": "啟用",
|
||||||
"Disable": "禁用",
|
"Disable": "停用",
|
||||||
"Sanitization": "過濾 HTML 程式碼",
|
"Sanitization": "過濾 HTML 程式碼",
|
||||||
"Only allow secure html tags (recommended)": "只允許安全的 HTML 標籤 (建議)",
|
"Only allow secure html tags (recommended)": "只允許安全的 HTML 標籤 (建議)",
|
||||||
"Allow styles": "允許樣式",
|
"Allow styles": "允許樣式",
|
||||||
"Allow dangerous html tags": "允許危險的 HTML 標籤"
|
"Allow dangerous html tags": "允許危險的 HTML 標籤",
|
||||||
|
"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! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
26
package.json
26
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.11.4",
|
"version": "0.11.7",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"description": "Boostnote",
|
"description": "Boostnote",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -12,12 +12,12 @@
|
|||||||
"compile": "grunt compile",
|
"compile": "grunt compile",
|
||||||
"test": "PWD=$(pwd) NODE_ENV=test ava --serial",
|
"test": "PWD=$(pwd) NODE_ENV=test ava --serial",
|
||||||
"jest": "jest",
|
"jest": "jest",
|
||||||
"fix": "npm run lint --fix",
|
"fix": "eslint . --fix",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\""
|
"dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\""
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"electron-version": "1.7.11"
|
"electron-version": "2.0.3"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -53,13 +53,16 @@
|
|||||||
"@rokt33r/season": "^5.3.0",
|
"@rokt33r/season": "^5.3.0",
|
||||||
"aws-sdk": "^2.48.0",
|
"aws-sdk": "^2.48.0",
|
||||||
"aws-sdk-mobile-analytics": "^0.9.2",
|
"aws-sdk-mobile-analytics": "^0.9.2",
|
||||||
"codemirror": "^5.37.0",
|
"codemirror": "^5.39.0",
|
||||||
"codemirror-mode-elixir": "^1.1.1",
|
"codemirror-mode-elixir": "^1.1.1",
|
||||||
"electron-config": "^0.2.1",
|
"electron-config": "^0.2.1",
|
||||||
"electron-gh-releases": "^2.0.2",
|
"electron-gh-releases": "^2.0.2",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"file-url": "^2.0.2",
|
||||||
"filenamify": "^2.0.0",
|
"filenamify": "^2.0.0",
|
||||||
"flowchart.js": "^1.6.5",
|
"flowchart.js": "^1.6.5",
|
||||||
"font-awesome": "^4.3.0",
|
"font-awesome": "^4.3.0",
|
||||||
|
"fs-extra": "^5.0.0",
|
||||||
"i18n-2": "^0.7.2",
|
"i18n-2": "^0.7.2",
|
||||||
"iconv-lite": "^0.4.19",
|
"iconv-lite": "^0.4.19",
|
||||||
"immutable": "^3.8.1",
|
"immutable": "^3.8.1",
|
||||||
@@ -68,21 +71,24 @@
|
|||||||
"lodash": "^4.11.1",
|
"lodash": "^4.11.1",
|
||||||
"lodash-move": "^1.1.1",
|
"lodash-move": "^1.1.1",
|
||||||
"markdown-it": "^6.0.1",
|
"markdown-it": "^6.0.1",
|
||||||
"markdown-it-checkbox": "^1.1.0",
|
"markdown-it-admonition": "https://github.com/johannbre/markdown-it-admonition.git",
|
||||||
"markdown-it-emoji": "^1.1.1",
|
"markdown-it-emoji": "^1.1.1",
|
||||||
"markdown-it-footnote": "^3.0.0",
|
"markdown-it-footnote": "^3.0.0",
|
||||||
"markdown-it-imsize": "^2.0.1",
|
"markdown-it-imsize": "^2.0.1",
|
||||||
"markdown-it-kbd": "^1.1.1",
|
"markdown-it-kbd": "^1.1.1",
|
||||||
"markdown-it-multimd-table": "^2.0.1",
|
"markdown-it-multimd-table": "^2.0.1",
|
||||||
"markdown-it-named-headers": "^0.0.4",
|
"markdown-it-named-headers": "^0.0.4",
|
||||||
"markdown-it-plantuml": "^0.3.0",
|
"markdown-it-plantuml": "^1.1.0",
|
||||||
"md5": "^2.0.0",
|
"markdown-it-smartarrows": "^1.0.1",
|
||||||
"mdurl": "^1.0.1",
|
"mdurl": "^1.0.1",
|
||||||
"moment": "^2.10.3",
|
"moment": "^2.10.3",
|
||||||
|
"mousetrap": "^1.6.1",
|
||||||
|
"mousetrap-global-bind": "^1.1.0",
|
||||||
"node-ipc": "^8.1.0",
|
"node-ipc": "^8.1.0",
|
||||||
"raphael": "^2.2.7",
|
"raphael": "^2.2.7",
|
||||||
"react": "^15.5.4",
|
"react": "^15.5.4",
|
||||||
"react-codemirror": "^0.3.0",
|
"react-codemirror": "^0.3.0",
|
||||||
|
"react-debounce-render": "^4.0.1",
|
||||||
"react-dom": "^15.0.2",
|
"react-dom": "^15.0.2",
|
||||||
"react-redux": "^4.4.5",
|
"react-redux": "^4.4.5",
|
||||||
"react-sortable-hoc": "^0.6.7",
|
"react-sortable-hoc": "^0.6.7",
|
||||||
@@ -90,8 +96,6 @@
|
|||||||
"sander": "^0.5.1",
|
"sander": "^0.5.1",
|
||||||
"sanitize-html": "^1.18.2",
|
"sanitize-html": "^1.18.2",
|
||||||
"striptags": "^2.2.1",
|
"striptags": "^2.2.1",
|
||||||
"superagent": "^1.2.0",
|
|
||||||
"superagent-promise": "^1.0.3",
|
|
||||||
"unique-slug": "2.0.0",
|
"unique-slug": "2.0.0",
|
||||||
"uuid": "^3.2.1"
|
"uuid": "^3.2.1"
|
||||||
},
|
},
|
||||||
@@ -113,12 +117,12 @@
|
|||||||
"css-loader": "^0.19.0",
|
"css-loader": "^0.19.0",
|
||||||
"devtron": "^1.1.0",
|
"devtron": "^1.1.0",
|
||||||
"dom-storage": "^2.0.2",
|
"dom-storage": "^2.0.2",
|
||||||
"electron": "1.7.11",
|
"electron": "2.0.3",
|
||||||
"electron-packager": "^6.0.0",
|
"electron-packager": "^6.0.0",
|
||||||
"eslint": "^3.13.1",
|
"eslint": "^3.13.1",
|
||||||
"eslint-config-standard": "^6.2.1",
|
"eslint-config-standard": "^6.2.1",
|
||||||
"eslint-config-standard-jsx": "^3.2.0",
|
"eslint-config-standard-jsx": "^3.2.0",
|
||||||
"eslint-plugin-react": "^7.2.0",
|
"eslint-plugin-react": "^7.8.2",
|
||||||
"eslint-plugin-standard": "^3.0.1",
|
"eslint-plugin-standard": "^3.0.1",
|
||||||
"faker": "^3.1.0",
|
"faker": "^3.1.0",
|
||||||
"grunt": "^0.4.5",
|
"grunt": "^0.4.5",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Boostnote is an open source project. It's an independent project with its ongoin
|
|||||||
## Community
|
## Community
|
||||||
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
||||||
- [Twitter](https://twitter.com/boostnoteapp)
|
- [Twitter](https://twitter.com/boostnoteapp)
|
||||||
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzUxODgwMTc2MDg3LTgwZjA2Zjg3NjFlMzczNTVjNGMzZTk0MmIyNmE3ZjEwYTNhMTA0Y2Y4NDNlNWU4YjZlNmJiNGZhNDViOTA1ZjM)
|
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzkxOTk4ODkyNzc0LThkNmMzY2VlZjVhYTNiYjE5YjQyZGVjNTJlYTY1OGMyZTFjNGU5YTUyYjUzOWZhYTU4OTVlNDYyNDFjYWMzNDM)
|
||||||
- [Blog](https://boostlog.io/tags/boostnote)
|
- [Blog](https://boostlog.io/tags/boostnote)
|
||||||
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
||||||
|
|
||||||
|
|||||||
BIN
resources/fonts/MaterialIcons-Regular.ttf
Executable file
BIN
resources/fonts/MaterialIcons-Regular.ttf
Executable file
Binary file not shown.
BIN
resources/fonts/MaterialIcons-Regular.woff
Executable file
BIN
resources/fonts/MaterialIcons-Regular.woff
Executable file
Binary file not shown.
BIN
resources/fonts/MaterialIcons-Regular.woff2
Executable file
BIN
resources/fonts/MaterialIcons-Regular.woff2
Executable file
Binary file not shown.
@@ -7,6 +7,9 @@ const findStorage = require('browser/lib/findStorage')
|
|||||||
jest.mock('unique-slug')
|
jest.mock('unique-slug')
|
||||||
const uniqueSlug = require('unique-slug')
|
const uniqueSlug = require('unique-slug')
|
||||||
const mdurl = require('mdurl')
|
const mdurl = require('mdurl')
|
||||||
|
const fse = require('fs-extra')
|
||||||
|
jest.mock('sander')
|
||||||
|
const sander = require('sander')
|
||||||
|
|
||||||
const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement')
|
const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement')
|
||||||
|
|
||||||
@@ -48,11 +51,13 @@ it('should test that copyAttachment works correctly assuming correct working of
|
|||||||
const noteKey = 'noteKey'
|
const noteKey = 'noteKey'
|
||||||
const dummyUniquePath = 'dummyPath'
|
const dummyUniquePath = 'dummyPath'
|
||||||
const dummyStorage = {path: 'dummyStoragePath'}
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
const dummyReadStream = {}
|
||||||
|
|
||||||
|
dummyReadStream.pipe = jest.fn()
|
||||||
|
dummyReadStream.on = jest.fn((event, callback) => { callback() })
|
||||||
fs.existsSync = jest.fn()
|
fs.existsSync = jest.fn()
|
||||||
fs.existsSync.mockReturnValue(true)
|
fs.existsSync.mockReturnValue(true)
|
||||||
fs.createReadStream = jest.fn()
|
fs.createReadStream = jest.fn(() => dummyReadStream)
|
||||||
fs.createReadStream.mockReturnValue({pipe: jest.fn()})
|
|
||||||
fs.createWriteStream = jest.fn()
|
fs.createWriteStream = jest.fn()
|
||||||
|
|
||||||
findStorage.findStorage = jest.fn()
|
findStorage.findStorage = jest.fn()
|
||||||
@@ -75,7 +80,11 @@ it('should test that copyAttachment creates a new folder if the attachment folde
|
|||||||
const noteKey = 'noteKey'
|
const noteKey = 'noteKey'
|
||||||
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER)
|
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER)
|
||||||
const attachmentFolderNoteKyPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
|
const attachmentFolderNoteKyPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
|
||||||
|
const dummyReadStream = {}
|
||||||
|
|
||||||
|
dummyReadStream.pipe = jest.fn()
|
||||||
|
dummyReadStream.on = jest.fn()
|
||||||
|
fs.createReadStream = jest.fn(() => dummyReadStream)
|
||||||
fs.existsSync = jest.fn()
|
fs.existsSync = jest.fn()
|
||||||
fs.existsSync.mockReturnValueOnce(true)
|
fs.existsSync.mockReturnValueOnce(true)
|
||||||
fs.existsSync.mockReturnValueOnce(false)
|
fs.existsSync.mockReturnValueOnce(false)
|
||||||
@@ -97,7 +106,11 @@ it('should test that copyAttachment creates a new folder if the attachment folde
|
|||||||
|
|
||||||
it('should test that copyAttachment don\'t uses a random file name if not intended ', function () {
|
it('should test that copyAttachment don\'t uses a random file name if not intended ', function () {
|
||||||
const dummyStorage = {path: 'dummyStoragePath'}
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
const dummyReadStream = {}
|
||||||
|
|
||||||
|
dummyReadStream.pipe = jest.fn()
|
||||||
|
dummyReadStream.on = jest.fn()
|
||||||
|
fs.createReadStream = jest.fn(() => dummyReadStream)
|
||||||
fs.existsSync = jest.fn()
|
fs.existsSync = jest.fn()
|
||||||
fs.existsSync.mockReturnValueOnce(true)
|
fs.existsSync.mockReturnValueOnce(true)
|
||||||
fs.existsSync.mockReturnValueOnce(false)
|
fs.existsSync.mockReturnValueOnce(false)
|
||||||
@@ -187,7 +200,7 @@ it('should test that getAttachmentsInContent finds all attachments', function ()
|
|||||||
' </body>\n' +
|
' </body>\n' +
|
||||||
'</html>'
|
'</html>'
|
||||||
const actual = systemUnderTest.getAttachmentsInContent(testInput)
|
const actual = systemUnderTest.getAttachmentsInContent(testInput)
|
||||||
const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg']
|
const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp.png', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx.pdf', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg']
|
||||||
expect(actual).toEqual(expect.arrayContaining(expected))
|
expect(actual).toEqual(expect.arrayContaining(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -212,8 +225,8 @@ it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute
|
|||||||
' </body>\n' +
|
' </body>\n' +
|
||||||
'</html>'
|
'</html>'
|
||||||
const actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath)
|
const actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath)
|
||||||
const expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp',
|
const expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp.png',
|
||||||
dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx',
|
dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx.pdf',
|
||||||
dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg']
|
dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg']
|
||||||
expect(actual).toEqual(expect.arrayContaining(expected))
|
expect(actual).toEqual(expect.arrayContaining(expected))
|
||||||
})
|
})
|
||||||
@@ -261,6 +274,19 @@ it('should remove the all ":storage" and noteKey references', function () {
|
|||||||
expect(actual).toEqual(expectedOutput)
|
expect(actual).toEqual(expectedOutput)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should delete the correct attachment folder if a note is deleted', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
sander.rimrafSync = jest.fn()
|
||||||
|
|
||||||
|
const expectedPathToBeDeleted = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
|
||||||
|
systemUnderTest.deleteAttachmentFolder(storageKey, noteKey)
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
|
||||||
|
expect(sander.rimrafSync).toHaveBeenCalledWith(expectedPathToBeDeleted)
|
||||||
|
})
|
||||||
|
|
||||||
it('should test that deleteAttachmentsNotPresentInNote deletes all unreferenced attachments ', function () {
|
it('should test that deleteAttachmentsNotPresentInNote deletes all unreferenced attachments ', function () {
|
||||||
const dummyStorage = {path: 'dummyStoragePath'}
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
const noteKey = 'noteKey'
|
const noteKey = 'noteKey'
|
||||||
@@ -312,3 +338,402 @@ it('should test that deleteAttachmentsNotPresentInNote does not delete reference
|
|||||||
}
|
}
|
||||||
expect(fsUnlinkCallArguments.includes(path.join(attachmentFolderPath, dummyFilesInFolder[0]))).toBe(false)
|
expect(fsUnlinkCallArguments.includes(path.join(attachmentFolderPath, dummyFilesInFolder[0]))).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should test that deleteAttachmentsNotPresentInNote does nothing if noteKey, storageKey or noteContent was null', function () {
|
||||||
|
const noteKey = null
|
||||||
|
const storageKey = null
|
||||||
|
const markdownContent = ''
|
||||||
|
|
||||||
|
findStorage.findStorage = jest.fn()
|
||||||
|
fs.existsSync = jest.fn()
|
||||||
|
fs.readdir = jest.fn()
|
||||||
|
fs.unlink = jest.fn()
|
||||||
|
|
||||||
|
systemUnderTest.deleteAttachmentsNotPresentInNote(markdownContent, storageKey, noteKey)
|
||||||
|
expect(fs.existsSync).not.toHaveBeenCalled()
|
||||||
|
expect(fs.readdir).not.toHaveBeenCalled()
|
||||||
|
expect(fs.unlink).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that deleteAttachmentsNotPresentInNote does nothing if noteKey, storageKey or noteContent was undefined', function () {
|
||||||
|
const noteKey = undefined
|
||||||
|
const storageKey = undefined
|
||||||
|
const markdownContent = ''
|
||||||
|
|
||||||
|
findStorage.findStorage = jest.fn()
|
||||||
|
fs.existsSync = jest.fn()
|
||||||
|
fs.readdir = jest.fn()
|
||||||
|
fs.unlink = jest.fn()
|
||||||
|
|
||||||
|
systemUnderTest.deleteAttachmentsNotPresentInNote(markdownContent, storageKey, noteKey)
|
||||||
|
expect(fs.existsSync).not.toHaveBeenCalled()
|
||||||
|
expect(fs.readdir).not.toHaveBeenCalled()
|
||||||
|
expect(fs.unlink).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that moveAttachments moves attachments only if the source folder existed', function () {
|
||||||
|
fse.existsSync = jest.fn(() => false)
|
||||||
|
fse.moveSync = jest.fn()
|
||||||
|
|
||||||
|
const oldPath = 'oldPath'
|
||||||
|
const newPath = 'newPath'
|
||||||
|
const oldNoteKey = 'oldNoteKey'
|
||||||
|
const newNoteKey = 'newNoteKey'
|
||||||
|
const content = ''
|
||||||
|
|
||||||
|
const expectedSource = path.join(oldPath, systemUnderTest.DESTINATION_FOLDER, oldNoteKey)
|
||||||
|
|
||||||
|
systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, content)
|
||||||
|
expect(fse.existsSync).toHaveBeenCalledWith(expectedSource)
|
||||||
|
expect(fse.moveSync).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that moveAttachments moves attachments to the right destination', function () {
|
||||||
|
fse.existsSync = jest.fn(() => true)
|
||||||
|
fse.moveSync = jest.fn()
|
||||||
|
|
||||||
|
const oldPath = 'oldPath'
|
||||||
|
const newPath = 'newPath'
|
||||||
|
const oldNoteKey = 'oldNoteKey'
|
||||||
|
const newNoteKey = 'newNoteKey'
|
||||||
|
const content = ''
|
||||||
|
|
||||||
|
const expectedSource = path.join(oldPath, systemUnderTest.DESTINATION_FOLDER, oldNoteKey)
|
||||||
|
const expectedDestination = path.join(newPath, systemUnderTest.DESTINATION_FOLDER, newNoteKey)
|
||||||
|
|
||||||
|
systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, content)
|
||||||
|
expect(fse.existsSync).toHaveBeenCalledWith(expectedSource)
|
||||||
|
expect(fse.moveSync).toHaveBeenCalledWith(expectedSource, expectedDestination)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that moveAttachments returns a correct modified content version', function () {
|
||||||
|
fse.existsSync = jest.fn()
|
||||||
|
fse.moveSync = jest.fn()
|
||||||
|
|
||||||
|
const oldPath = 'oldPath'
|
||||||
|
const newPath = 'newPath'
|
||||||
|
const oldNoteKey = 'oldNoteKey'
|
||||||
|
const newNoteKey = 'newNoteKey'
|
||||||
|
const testInput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNoteKey + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
const expectedOutput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
|
||||||
|
const actualContent = systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, testInput)
|
||||||
|
expect(actualContent).toBe(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that cloneAttachments modifies the content of the new note correctly', function () {
|
||||||
|
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'}
|
||||||
|
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'}
|
||||||
|
const testInput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
newNote.content = testInput
|
||||||
|
findStorage.findStorage = jest.fn()
|
||||||
|
findStorage.findStorage.mockReturnValue({path: 'dummyStoragePath'})
|
||||||
|
|
||||||
|
const expectedOutput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
systemUnderTest.cloneAttachments(oldNote, newNote)
|
||||||
|
|
||||||
|
expect(newNote.content).toBe(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that cloneAttachments finds all attachments and copies them to the new location', function () {
|
||||||
|
const storagePathOld = 'storagePathOld'
|
||||||
|
const storagePathNew = 'storagePathNew'
|
||||||
|
const dummyStorageOld = {path: storagePathOld}
|
||||||
|
const dummyStorageNew = {path: storagePathNew}
|
||||||
|
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'MARKDOWN_NOTE'}
|
||||||
|
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'MARKDOWN_NOTE'}
|
||||||
|
const testInput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
oldNote.content = testInput
|
||||||
|
newNote.content = testInput
|
||||||
|
|
||||||
|
const copyFileSyncResp = {to: jest.fn()}
|
||||||
|
sander.copyFileSync = jest.fn()
|
||||||
|
sander.copyFileSync.mockReturnValue(copyFileSyncResp)
|
||||||
|
findStorage.findStorage = jest.fn()
|
||||||
|
findStorage.findStorage.mockReturnValueOnce(dummyStorageOld)
|
||||||
|
findStorage.findStorage.mockReturnValue(dummyStorageNew)
|
||||||
|
|
||||||
|
const pathAttachmentOneFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'image.jpg')
|
||||||
|
const pathAttachmentOneTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'image.jpg')
|
||||||
|
|
||||||
|
const pathAttachmentTwoFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'pdf.pdf')
|
||||||
|
const pathAttachmentTwoTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'pdf.pdf')
|
||||||
|
|
||||||
|
systemUnderTest.cloneAttachments(oldNote, newNote)
|
||||||
|
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(oldNote.storage)
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(newNote.storage)
|
||||||
|
expect(sander.copyFileSync).toHaveBeenCalledTimes(2)
|
||||||
|
expect(copyFileSyncResp.to).toHaveBeenCalledTimes(2)
|
||||||
|
expect(sander.copyFileSync.mock.calls[0][0]).toBe(pathAttachmentOneFrom)
|
||||||
|
expect(copyFileSyncResp.to.mock.calls[0][0]).toBe(pathAttachmentOneTo)
|
||||||
|
expect(sander.copyFileSync.mock.calls[1][0]).toBe(pathAttachmentTwoFrom)
|
||||||
|
expect(copyFileSyncResp.to.mock.calls[1][0]).toBe(pathAttachmentTwoTo)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that cloneAttachments finds all attachments and copies them to the new location', function () {
|
||||||
|
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'SOMETHING_ELSE'}
|
||||||
|
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'SOMETHING_ELSE'}
|
||||||
|
const testInput = 'Test input'
|
||||||
|
oldNote.content = testInput
|
||||||
|
newNote.content = testInput
|
||||||
|
|
||||||
|
sander.copyFileSync = jest.fn()
|
||||||
|
findStorage.findStorage = jest.fn()
|
||||||
|
|
||||||
|
systemUnderTest.cloneAttachments(oldNote, newNote)
|
||||||
|
|
||||||
|
expect(findStorage.findStorage).not.toHaveBeenCalled()
|
||||||
|
expect(sander.copyFileSync).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that isAttachmentLink works correctly', function () {
|
||||||
|
expect(systemUnderTest.isAttachmentLink('text')).toBe(false)
|
||||||
|
expect(systemUnderTest.isAttachmentLink('text [linkText](link)')).toBe(false)
|
||||||
|
expect(systemUnderTest.isAttachmentLink('text ')).toBe(false)
|
||||||
|
expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf)')).toBe(true)
|
||||||
|
expect(systemUnderTest.isAttachmentLink('')).toBe(true)
|
||||||
|
expect(systemUnderTest.isAttachmentLink('text [ linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf)')).toBe(true)
|
||||||
|
expect(systemUnderTest.isAttachmentLink('text ')).toBe(true)
|
||||||
|
expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true)
|
||||||
|
expect(systemUnderTest.isAttachmentLink(' test')).toBe(true)
|
||||||
|
expect(systemUnderTest.isAttachmentLink('text [linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true)
|
||||||
|
expect(systemUnderTest.isAttachmentLink('text  test')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste copies the attachments to the new location', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const pasteText = 'text '
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
|
||||||
|
|
||||||
|
sander.exists = jest.fn(() => Promise.resolve(true))
|
||||||
|
systemUnderTest.copyAttachment = jest.fn(() => Promise.resolve('dummyNewFileName'))
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then(() => {
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
|
||||||
|
expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePath)
|
||||||
|
expect(systemUnderTest.copyAttachment).toHaveBeenCalledWith(expectedSourceFilePath, storageKey, newNoteKey)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste don\'t try to copy the file if it does not exist', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const pasteText = 'text '
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
|
||||||
|
|
||||||
|
sander.exists = jest.fn(() => Promise.resolve(false))
|
||||||
|
systemUnderTest.copyAttachment = jest.fn()
|
||||||
|
systemUnderTest.generateFileNotFoundMarkdown = jest.fn()
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then(() => {
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
|
||||||
|
expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePath)
|
||||||
|
expect(systemUnderTest.copyAttachment).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste copies multiple attachments if multiple were pasted', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const pasteText = 'text  ..' +
|
||||||
|
''
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const expectedSourceFilePathOne = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
|
||||||
|
const expectedSourceFilePathTwo = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'img.jpg')
|
||||||
|
|
||||||
|
sander.exists = jest.fn(() => Promise.resolve(true))
|
||||||
|
systemUnderTest.copyAttachment = jest.fn(() => Promise.resolve('dummyNewFileName'))
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then(() => {
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
|
||||||
|
expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePathOne)
|
||||||
|
expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePathTwo)
|
||||||
|
expect(systemUnderTest.copyAttachment).toHaveBeenCalledWith(expectedSourceFilePathOne, storageKey, newNoteKey)
|
||||||
|
expect(systemUnderTest.copyAttachment).toHaveBeenCalledWith(expectedSourceFilePathTwo, storageKey, newNoteKey)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste returns the correct modified paste text', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const dummyNewFileName = 'dummyNewFileName'
|
||||||
|
const pasteText = 'text '
|
||||||
|
const expectedText = 'text '
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
|
||||||
|
sander.exists = jest.fn(() => Promise.resolve(true))
|
||||||
|
systemUnderTest.copyAttachment = jest.fn(() => Promise.resolve(dummyNewFileName))
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then((returnedPastedText) => {
|
||||||
|
expect(returnedPastedText).toBe(expectedText)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste returns the correct modified paste text if multiple links are posted', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const dummyNewFileNameOne = 'dummyNewFileName'
|
||||||
|
const dummyNewFileNameTwo = 'dummyNewFileNameTwo'
|
||||||
|
const pasteText = 'text  ' +
|
||||||
|
''
|
||||||
|
const expectedText = 'text  ' +
|
||||||
|
''
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
|
||||||
|
sander.exists = jest.fn(() => Promise.resolve(true))
|
||||||
|
systemUnderTest.copyAttachment = jest.fn()
|
||||||
|
systemUnderTest.copyAttachment.mockReturnValueOnce(Promise.resolve(dummyNewFileNameOne))
|
||||||
|
systemUnderTest.copyAttachment.mockReturnValue(Promise.resolve(dummyNewFileNameTwo))
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then((returnedPastedText) => {
|
||||||
|
expect(returnedPastedText).toBe(expectedText)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste calls the copy method correct if multiple links are posted where one file was found and one was not', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const pasteText = 'text  ..' +
|
||||||
|
''
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const expectedSourceFilePathOne = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf')
|
||||||
|
const expectedSourceFilePathTwo = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'img.jpg')
|
||||||
|
|
||||||
|
sander.exists = jest.fn()
|
||||||
|
sander.exists.mockReturnValueOnce(Promise.resolve(false))
|
||||||
|
sander.exists.mockReturnValue(Promise.resolve(true))
|
||||||
|
systemUnderTest.copyAttachment = jest.fn(() => Promise.resolve('dummyNewFileName'))
|
||||||
|
systemUnderTest.generateFileNotFoundMarkdown = jest.fn()
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then(() => {
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
|
||||||
|
expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePathOne)
|
||||||
|
expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePathTwo)
|
||||||
|
expect(systemUnderTest.copyAttachment).toHaveBeenCalledTimes(1)
|
||||||
|
expect(systemUnderTest.copyAttachment).toHaveBeenCalledWith(expectedSourceFilePathTwo, storageKey, newNoteKey)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste returns the correct modified paste text if the file was not found', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const pasteText = 'text '
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const fileNotFoundMD = 'file not found'
|
||||||
|
const expectedPastText = 'text ' + fileNotFoundMD
|
||||||
|
|
||||||
|
systemUnderTest.generateFileNotFoundMarkdown = jest.fn(() => fileNotFoundMD)
|
||||||
|
sander.exists = jest.fn(() => Promise.resolve(false))
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then((returnedPastedText) => {
|
||||||
|
expect(returnedPastedText).toBe(expectedPastText)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste returns the correct modified paste text if multiple files were not found', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const pasteText = 'text  ' +
|
||||||
|
''
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const fileNotFoundMD = 'file not found'
|
||||||
|
const expectedPastText = 'text ' + fileNotFoundMD + ' ' + fileNotFoundMD
|
||||||
|
systemUnderTest.generateFileNotFoundMarkdown = jest.fn(() => fileNotFoundMD)
|
||||||
|
|
||||||
|
sander.exists = jest.fn(() => Promise.resolve(false))
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then((returnedPastedText) => {
|
||||||
|
expect(returnedPastedText).toBe(expectedPastText)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste returns the correct modified paste text if one file was found and one was not found', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const dummyFoundFileName = 'dummyFileName'
|
||||||
|
const fileNotFoundMD = 'file not found'
|
||||||
|
const pasteText = 'text  .. ' +
|
||||||
|
''
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const expectedPastText = 'text ' + fileNotFoundMD + ' .. '
|
||||||
|
|
||||||
|
sander.exists = jest.fn()
|
||||||
|
sander.exists.mockReturnValueOnce(Promise.resolve(false))
|
||||||
|
sander.exists.mockReturnValue(Promise.resolve(true))
|
||||||
|
systemUnderTest.copyAttachment = jest.fn(() => Promise.resolve(dummyFoundFileName))
|
||||||
|
systemUnderTest.generateFileNotFoundMarkdown = jest.fn(() => fileNotFoundMD)
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then((returnedPastedText) => {
|
||||||
|
expect(returnedPastedText).toBe(expectedPastText)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that handleAttachmentLinkPaste returns the correct modified paste text if one file was found and one was not found', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723'
|
||||||
|
const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723'
|
||||||
|
const dummyFoundFileName = 'dummyFileName'
|
||||||
|
const fileNotFoundMD = 'file not found'
|
||||||
|
const pasteText = 'text  .. ' +
|
||||||
|
''
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const expectedPastText = 'text  .. ' + fileNotFoundMD
|
||||||
|
|
||||||
|
sander.exists = jest.fn()
|
||||||
|
sander.exists.mockReturnValueOnce(Promise.resolve(true))
|
||||||
|
sander.exists.mockReturnValue(Promise.resolve(false))
|
||||||
|
systemUnderTest.copyAttachment = jest.fn(() => Promise.resolve(dummyFoundFileName))
|
||||||
|
systemUnderTest.generateFileNotFoundMarkdown = jest.fn(() => fileNotFoundMD)
|
||||||
|
|
||||||
|
return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText)
|
||||||
|
.then((returnedPastedText) => {
|
||||||
|
expect(returnedPastedText).toBe(expectedPastText)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
34
tests/dataApi/createSnippet-test.js
Normal file
34
tests/dataApi/createSnippet-test.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const createSnippet = require('browser/main/lib/dataApi/createSnippet')
|
||||||
|
const sander = require('sander')
|
||||||
|
const os = require('os')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const snippetFilePath = path.join(os.tmpdir(), 'test', 'create-snippet')
|
||||||
|
const snippetFile = path.join(snippetFilePath, 'snippets.json')
|
||||||
|
|
||||||
|
test.beforeEach((t) => {
|
||||||
|
sander.writeFileSync(snippetFile, '[]')
|
||||||
|
})
|
||||||
|
|
||||||
|
test.serial('Create a snippet', (t) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(function doTest () {
|
||||||
|
return Promise.all([
|
||||||
|
createSnippet(snippetFile)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.then(function assert (data) {
|
||||||
|
data = data[0]
|
||||||
|
const snippets = JSON.parse(sander.readFileSync(snippetFile))
|
||||||
|
const snippet = snippets.find(currentSnippet => currentSnippet.id === data.id)
|
||||||
|
t.not(snippet, undefined)
|
||||||
|
t.is(snippet.name, data.name)
|
||||||
|
t.deepEqual(snippet.prefix, data.prefix)
|
||||||
|
t.is(snippet.content, data.content)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.after.always(() => {
|
||||||
|
sander.rimrafSync(snippetFilePath)
|
||||||
|
})
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
const test = require('ava')
|
const test = require('ava')
|
||||||
const deleteFolder = require('browser/main/lib/dataApi/deleteFolder')
|
const deleteFolder = require('browser/main/lib/dataApi/deleteFolder')
|
||||||
|
const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement')
|
||||||
|
const createNote = require('browser/main/lib/dataApi/createNote')
|
||||||
|
const fs = require('fs')
|
||||||
|
const faker = require('faker')
|
||||||
|
|
||||||
global.document = require('jsdom').jsdom('<body></body>')
|
global.document = require('jsdom').jsdom('<body></body>')
|
||||||
global.window = document.defaultView
|
global.window = document.defaultView
|
||||||
@@ -24,8 +28,32 @@ test.beforeEach((t) => {
|
|||||||
test.serial('Delete a folder', (t) => {
|
test.serial('Delete a folder', (t) => {
|
||||||
const storageKey = t.context.storage.cache.key
|
const storageKey = t.context.storage.cache.key
|
||||||
const folderKey = t.context.storage.json.folders[0].key
|
const folderKey = t.context.storage.json.folders[0].key
|
||||||
|
let noteKey
|
||||||
|
|
||||||
|
const input1 = {
|
||||||
|
type: 'SNIPPET_NOTE',
|
||||||
|
description: faker.lorem.lines(),
|
||||||
|
snippets: [{
|
||||||
|
name: faker.system.fileName(),
|
||||||
|
mode: 'text',
|
||||||
|
content: faker.lorem.lines()
|
||||||
|
}],
|
||||||
|
tags: faker.lorem.words().split(' '),
|
||||||
|
folder: folderKey
|
||||||
|
}
|
||||||
|
input1.title = input1.description.split('\n').shift()
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
.then(function prepare () {
|
||||||
|
return createNote(storageKey, input1)
|
||||||
|
.then(function createAttachmentFolder (data) {
|
||||||
|
fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER))
|
||||||
|
fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.key))
|
||||||
|
noteKey = data.key
|
||||||
|
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
})
|
||||||
.then(function doTest () {
|
.then(function doTest () {
|
||||||
return deleteFolder(storageKey, folderKey)
|
return deleteFolder(storageKey, folderKey)
|
||||||
})
|
})
|
||||||
@@ -36,6 +64,9 @@ test.serial('Delete a folder', (t) => {
|
|||||||
t.true(_.find(jsonData.folders, {key: folderKey}) == null)
|
t.true(_.find(jsonData.folders, {key: folderKey}) == null)
|
||||||
const notePaths = sander.readdirSync(data.storage.path, 'notes')
|
const notePaths = sander.readdirSync(data.storage.path, 'notes')
|
||||||
t.is(notePaths.length, t.context.storage.notes.filter((note) => note.folder !== folderKey).length)
|
t.is(notePaths.length, t.context.storage.notes.filter((note) => note.folder !== folderKey).length)
|
||||||
|
|
||||||
|
const attachmentFolderPath = path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, noteKey)
|
||||||
|
t.false(fs.existsSync(attachmentFolderPath))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const sander = require('sander')
|
|||||||
const os = require('os')
|
const os = require('os')
|
||||||
const CSON = require('@rokt33r/season')
|
const CSON = require('@rokt33r/season')
|
||||||
const faker = require('faker')
|
const faker = require('faker')
|
||||||
|
const fs = require('fs')
|
||||||
|
const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement')
|
||||||
|
|
||||||
const storagePath = path.join(os.tmpdir(), 'test/delete-note')
|
const storagePath = path.join(os.tmpdir(), 'test/delete-note')
|
||||||
|
|
||||||
@@ -42,6 +44,11 @@ test.serial('Delete a note', (t) => {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(function doTest () {
|
.then(function doTest () {
|
||||||
return createNote(storageKey, input1)
|
return createNote(storageKey, input1)
|
||||||
|
.then(function createAttachmentFolder (data) {
|
||||||
|
fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER))
|
||||||
|
fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.key))
|
||||||
|
return data
|
||||||
|
})
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
return deleteNote(storageKey, data.key)
|
return deleteNote(storageKey, data.key)
|
||||||
})
|
})
|
||||||
@@ -52,8 +59,13 @@ test.serial('Delete a note', (t) => {
|
|||||||
t.fail('note cson must be deleted.')
|
t.fail('note cson must be deleted.')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
t.is(err.code, 'ENOENT')
|
t.is(err.code, 'ENOENT')
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(function assertAttachmentFolderDeleted (data) {
|
||||||
|
const attachmentFolderPath = path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.noteKey)
|
||||||
|
t.is(fs.existsSync(attachmentFolderPath), false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.after(function after () {
|
test.after(function after () {
|
||||||
|
|||||||
37
tests/dataApi/deleteSnippet-test.js
Normal file
37
tests/dataApi/deleteSnippet-test.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const deleteSnippet = require('browser/main/lib/dataApi/deleteSnippet')
|
||||||
|
const sander = require('sander')
|
||||||
|
const os = require('os')
|
||||||
|
const path = require('path')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
const snippetFilePath = path.join(os.tmpdir(), 'test', 'delete-snippet')
|
||||||
|
const snippetFile = path.join(snippetFilePath, 'snippets.json')
|
||||||
|
const newSnippet = {
|
||||||
|
id: crypto.randomBytes(16).toString('hex'),
|
||||||
|
name: 'Unnamed snippet',
|
||||||
|
prefix: [],
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeEach((t) => {
|
||||||
|
sander.writeFileSync(snippetFile, JSON.stringify([newSnippet]))
|
||||||
|
})
|
||||||
|
|
||||||
|
test.serial('Delete a snippet', (t) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(function doTest () {
|
||||||
|
return Promise.all([
|
||||||
|
deleteSnippet(newSnippet, snippetFile)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.then(function assert (data) {
|
||||||
|
data = data[0]
|
||||||
|
const snippets = JSON.parse(sander.readFileSync(snippetFile))
|
||||||
|
t.is(snippets.length, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.after.always(() => {
|
||||||
|
sander.rimrafSync(snippetFilePath)
|
||||||
|
})
|
||||||
@@ -13,6 +13,7 @@ const TestDummy = require('../fixtures/TestDummy')
|
|||||||
const os = require('os')
|
const os = require('os')
|
||||||
const faker = require('faker')
|
const faker = require('faker')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
const sander = require('sander')
|
||||||
|
|
||||||
const storagePath = path.join(os.tmpdir(), 'test/export-note')
|
const storagePath = path.join(os.tmpdir(), 'test/export-note')
|
||||||
|
|
||||||
@@ -60,3 +61,8 @@ test.serial('Export a folder', (t) => {
|
|||||||
t.false(fs.existsSync(filePath))
|
t.false(fs.existsSync(filePath))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test.after.always(function after () {
|
||||||
|
localStorage.clear()
|
||||||
|
sander.rimrafSync(storagePath)
|
||||||
|
})
|
||||||
|
|||||||
38
tests/dataApi/toggleStorage-test.js
Normal file
38
tests/dataApi/toggleStorage-test.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const toggleStorage = require('browser/main/lib/dataApi/toggleStorage')
|
||||||
|
|
||||||
|
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 _ = require('lodash')
|
||||||
|
const TestDummy = require('../fixtures/TestDummy')
|
||||||
|
const sander = require('sander')
|
||||||
|
const os = require('os')
|
||||||
|
|
||||||
|
const storagePath = path.join(os.tmpdir(), 'test/toggle-storage')
|
||||||
|
|
||||||
|
test.beforeEach((t) => {
|
||||||
|
t.context.storage = TestDummy.dummyStorage(storagePath)
|
||||||
|
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
||||||
|
})
|
||||||
|
|
||||||
|
test.serial('Toggle a storage location', (t) => {
|
||||||
|
const storageKey = t.context.storage.cache.key
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(function doTest () {
|
||||||
|
return toggleStorage(storageKey, true)
|
||||||
|
})
|
||||||
|
.then(function assert (data) {
|
||||||
|
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
|
t.true(_.find(cachedStorageList, {key: storageKey}).isOpen === true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.after(function after () {
|
||||||
|
localStorage.clear()
|
||||||
|
sander.rimrafSync(storagePath)
|
||||||
|
})
|
||||||
47
tests/dataApi/updateSnippet-test.js
Normal file
47
tests/dataApi/updateSnippet-test.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const updateSnippet = require('browser/main/lib/dataApi/updateSnippet')
|
||||||
|
const sander = require('sander')
|
||||||
|
const os = require('os')
|
||||||
|
const path = require('path')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
const snippetFilePath = path.join(os.tmpdir(), 'test', 'update-snippet')
|
||||||
|
const snippetFile = path.join(snippetFilePath, 'snippets.json')
|
||||||
|
const oldSnippet = {
|
||||||
|
id: crypto.randomBytes(16).toString('hex'),
|
||||||
|
name: 'Initial snippet',
|
||||||
|
prefix: [],
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSnippet = {
|
||||||
|
id: oldSnippet.id,
|
||||||
|
name: 'new name',
|
||||||
|
prefix: ['prefix'],
|
||||||
|
content: 'new content'
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeEach((t) => {
|
||||||
|
sander.writeFileSync(snippetFile, JSON.stringify([oldSnippet]))
|
||||||
|
})
|
||||||
|
|
||||||
|
test.serial('Update a snippet', (t) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(function doTest () {
|
||||||
|
return Promise.all([
|
||||||
|
updateSnippet(newSnippet, snippetFile)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.then(function assert () {
|
||||||
|
const snippets = JSON.parse(sander.readFileSync(snippetFile))
|
||||||
|
const snippet = snippets.find(currentSnippet => currentSnippet.id === newSnippet.id)
|
||||||
|
t.not(snippet, undefined)
|
||||||
|
t.is(snippet.name, newSnippet.name)
|
||||||
|
t.deepEqual(snippet.prefix, newSnippet.prefix)
|
||||||
|
t.is(snippet.content, newSnippet.content)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.after.always(() => {
|
||||||
|
sander.rimrafSync(snippetFilePath)
|
||||||
|
})
|
||||||
5
tests/fixtures/markdowns.js
vendored
5
tests/fixtures/markdowns.js
vendored
@@ -48,10 +48,13 @@ const checkboxes = `
|
|||||||
|
|
||||||
const smartQuotes = 'This is a "QUOTE".'
|
const smartQuotes = 'This is a "QUOTE".'
|
||||||
|
|
||||||
|
const breaks = 'This is the first line.\nThis is the second line.'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
basic,
|
basic,
|
||||||
codeblock,
|
codeblock,
|
||||||
katex,
|
katex,
|
||||||
checkboxes,
|
checkboxes,
|
||||||
smartQuotes
|
smartQuotes,
|
||||||
|
breaks
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,3 +34,12 @@ test('Markdown.render() should text with quotes correctly', t => {
|
|||||||
const renderedNonSmartQuotes = newmd.render(markdownFixtures.smartQuotes)
|
const renderedNonSmartQuotes = newmd.render(markdownFixtures.smartQuotes)
|
||||||
t.snapshot(renderedNonSmartQuotes)
|
t.snapshot(renderedNonSmartQuotes)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Markdown.render() should render line breaks correctly', t => {
|
||||||
|
const renderedBreaks = md.render(markdownFixtures.breaks)
|
||||||
|
t.snapshot(renderedBreaks)
|
||||||
|
|
||||||
|
const newmd = new Markdown({ breaks: false })
|
||||||
|
const renderedNonBreaks = newmd.render(markdownFixtures.breaks)
|
||||||
|
t.snapshot(renderedNonBreaks)
|
||||||
|
})
|
||||||
|
|||||||
16
tests/lib/normalize-editor-font-family-test.js
Normal file
16
tests/lib/normalize-editor-font-family-test.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Unit test for browser/lib/normalizeEditorFontFamily
|
||||||
|
*/
|
||||||
|
import test from 'ava'
|
||||||
|
import normalizeEditorFontFamily from '../../browser/lib/normalizeEditorFontFamily'
|
||||||
|
import consts from '../../browser/lib/consts'
|
||||||
|
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
||||||
|
|
||||||
|
test('normalizeEditorFontFamily() should return default font family (string[])', t => {
|
||||||
|
t.is(normalizeEditorFontFamily(), defaultEditorFontFamily.join(', '))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('normalizeEditorFontFamily(["hoge", "huga"]) should return default font family connected with arg.', t => {
|
||||||
|
const arg = 'font1, font2'
|
||||||
|
t.is(normalizeEditorFontFamily(arg), `${arg}, ${defaultEditorFontFamily.join(', ')}`)
|
||||||
|
})
|
||||||
@@ -4,6 +4,20 @@ The actual snapshot is saved in `markdown-test.js.snap`.
|
|||||||
|
|
||||||
Generated by [AVA](https://ava.li).
|
Generated by [AVA](https://ava.li).
|
||||||
|
|
||||||
|
## Markdown.render() should render line breaks correctly
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
`<p data-line="0">This is the first line.<br />␊
|
||||||
|
This is the second line.</p>␊
|
||||||
|
`
|
||||||
|
|
||||||
|
> Snapshot 2
|
||||||
|
|
||||||
|
`<p data-line="0">This is the first line.␊
|
||||||
|
This is the second line.</p>␊
|
||||||
|
`
|
||||||
|
|
||||||
## Markdown.render() should renders KaTeX correctly
|
## Markdown.render() should renders KaTeX correctly
|
||||||
|
|
||||||
> Snapshot 1
|
> Snapshot 1
|
||||||
|
|||||||
Binary file not shown.
@@ -28,18 +28,15 @@ var config = {
|
|||||||
externals: [
|
externals: [
|
||||||
'node-ipc',
|
'node-ipc',
|
||||||
'electron',
|
'electron',
|
||||||
'md5',
|
|
||||||
'superagent',
|
|
||||||
'superagent-promise',
|
|
||||||
'lodash',
|
'lodash',
|
||||||
'markdown-it',
|
'markdown-it',
|
||||||
'moment',
|
'moment',
|
||||||
'markdown-it-emoji',
|
'markdown-it-emoji',
|
||||||
'fs-jetpack',
|
'fs-jetpack',
|
||||||
'@rokt33r/markdown-it-math',
|
'@rokt33r/markdown-it-math',
|
||||||
'markdown-it-checkbox',
|
|
||||||
'markdown-it-kbd',
|
'markdown-it-kbd',
|
||||||
'markdown-it-plantuml',
|
'markdown-it-plantuml',
|
||||||
|
'markdown-it-admonition',
|
||||||
'devtron',
|
'devtron',
|
||||||
'@rokt33r/season',
|
'@rokt33r/season',
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user