diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 7ae1ee51..c5fc8f69 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -3,10 +3,7 @@ import React from 'react' import _ from 'lodash' import CodeMirror from 'codemirror' import 'codemirror-mode-elixir' -import path from 'path' -import copyImage from 'browser/main/lib/dataApi/copyImage' -import { findStorage } from 'browser/lib/findStorage' -import fs from 'fs' +import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' import eventEmitter from 'browser/main/lib/eventEmitter' import iconv from 'iconv-lite' import crypto from 'crypto' @@ -354,23 +351,13 @@ export default class CodeEditor extends React.Component { this.editor.setCursor(cursor) } - handleDropImage (e) { - e.preventDefault() - const ValidImageTypes = ['image/gif', 'image/jpeg', 'image/png'] - - const file = e.dataTransfer.files[0] - const filePath = file.path - const filename = path.basename(filePath) - const fileType = file['type'] - - copyImage(filePath, this.props.storageKey).then((imagePath) => { - var showPreview = ValidImageTypes.indexOf(fileType) > 0 - const imageMd = `${showPreview ? '!' : ''}[${filename}](${path.join('/:storage', imagePath)})` - this.insertImageMd(imageMd) - }) + handleDropImage (dropEvent) { + dropEvent.preventDefault() + const {storageKey, noteKey} = this.props + attachmentManagement.handleAttachmentDrop(this, storageKey, noteKey, dropEvent) } - insertImageMd (imageMd) { + insertAttachmentMd (imageMd) { this.editor.replaceSelection(imageMd) } @@ -396,24 +383,8 @@ export default class CodeEditor extends React.Component { return prevChar === '](' && nextChar === ')' } if (dataTransferItem.type.match('image')) { - const blob = dataTransferItem.getAsFile() - const reader = new FileReader() - let base64data - - reader.readAsDataURL(blob) - reader.onloadend = () => { - base64data = reader.result.replace(/^data:image\/png;base64,/, '') - base64data += base64data.replace('+', ' ') - const binaryData = new Buffer(base64data, 'base64').toString('binary') - const imageName = Math.random().toString(36).slice(-16) - const storagePath = findStorage(this.props.storageKey).path - const imageDir = path.join(storagePath, 'images') - if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir) - const imagePath = path.join(imageDir, `${imageName}.png`) - fs.writeFile(imagePath, binaryData, 'binary') - const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})` - this.insertImageMd(imageMd) - } + const {storageKey, noteKey} = this.props + attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem) } else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { this.handlePasteUrl(e, editor, pastedTxt) } diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 83509184..313c6f90 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -5,7 +5,7 @@ import styles from './MarkdownEditor.styl' import CodeEditor from 'browser/components/CodeEditor' import MarkdownPreview from 'browser/components/MarkdownPreview' import eventEmitter from 'browser/main/lib/eventEmitter' -import {findStorage} from 'browser/lib/findStorage' +import { findStorage } from 'browser/lib/findStorage' class MarkdownEditor extends React.Component { constructor (props) { @@ -223,7 +223,7 @@ class MarkdownEditor extends React.Component { } render () { - const { className, value, config, storageKey } = this.props + const {className, value, config, storageKey, noteKey} = this.props let editorFontSize = parseInt(config.editor.fontSize, 10) if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 @@ -263,6 +263,7 @@ class MarkdownEditor extends React.Component { displayLineNumbers={config.editor.displayLineNumbers} scrollPastEnd={config.editor.scrollPastEnd} storageKey={storageKey} + noteKey={noteKey} fetchUrlTitle={config.editor.fetchUrlTitle} onChange={(e) => this.handleChange(e)} onBlur={(e) => this.handleBlur(e)} diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 6e6bb9ec..aa920975 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -13,9 +13,11 @@ import htmlTextHelper from 'browser/lib/htmlTextHelper' import copy from 'copy-to-clipboard' import mdurl from 'mdurl' import exportNote from 'browser/main/lib/dataApi/exportNote' -import {escapeHtmlCharacters} from 'browser/lib/utils' +import { escapeHtmlCharacters } from 'browser/lib/utils' const { remote } = require('electron') +const attachmentManagement = require('../main/lib/dataApi/attachmentManagement') + const { app } = remote const path = require('path') const dialog = remote.dialog @@ -391,13 +393,11 @@ export default class MarkdownPreview extends React.Component { value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock)) }) } - this.refs.root.contentWindow.document.body.innerHTML = this.markdown.render(value) + let renderedHTML = this.markdown.render(value) + this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath) _.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { this.fixDecodedURI(el) - el.href = this.markdown.normalizeLinkText(el.href) - if (!/\/:storage/.test(el.href)) return - el.href = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.href)))}` el.addEventListener('click', this.anchorClickHandler) }) @@ -409,12 +409,6 @@ export default class MarkdownPreview extends React.Component { el.addEventListener('click', this.linkClickHandler) }) - _.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => { - el.src = this.markdown.normalizeLinkText(el.src) - if (!/\/:storage/.test(el.src)) return - el.src = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}` - }) - codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme) ? codeBlockTheme : 'default' @@ -523,6 +517,15 @@ export default class MarkdownPreview extends React.Component { } handlelinkClick (e) { + e.preventDefault() + e.stopPropagation() + + const href = e.target.href + if (href.match(/^http/i)) { + shell.openExternal(href) + return + } + const noteHash = e.target.href.split('/').pop() // this will match the new uuid v4 hash and the old hash // e.g. diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index c30f50da..d82d4da3 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -88,7 +88,7 @@ class MarkdownSplitEditor extends React.Component { } render () { - const { config, value, storageKey } = this.props + const {config, value, storageKey, noteKey} = this.props const storage = findStorage(storageKey) let editorFontSize = parseInt(config.editor.fontSize, 10) if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 @@ -115,6 +115,7 @@ class MarkdownSplitEditor extends React.Component { scrollPastEnd={config.editor.scrollPastEnd} fetchUrlTitle={config.editor.fetchUrlTitle} storageKey={storageKey} + noteKey={noteKey} onChange={this.handleOnChange.bind(this)} onScroll={this.handleScroll.bind(this)} /> diff --git a/browser/components/SideNavFilter.js b/browser/components/SideNavFilter.js index 06cfc19a..3a259ce7 100644 --- a/browser/components/SideNavFilter.js +++ b/browser/components/SideNavFilter.js @@ -51,7 +51,7 @@ const SideNavFilter = ({ diff --git a/browser/components/TagListItem.js b/browser/components/TagListItem.js index ebef7df4..6cd50c9c 100644 --- a/browser/components/TagListItem.js +++ b/browser/components/TagListItem.js @@ -9,16 +9,26 @@ import CSSModules from 'browser/lib/CSSModules' /** * @param {string} name * @param {Function} handleClickTagListItem +* @param {Function} handleClickNarrowToTag * @param {bool} isActive +* @param {bool} isRelated */ -const TagListItem = ({name, handleClickTagListItem, isActive, count}) => ( - +const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => ( +
+ {isRelated + ? + :
+ } + +
) TagListItem.propTypes = { diff --git a/browser/components/TagListItem.styl b/browser/components/TagListItem.styl index b35b30cf..555520b0 100644 --- a/browser/components/TagListItem.styl +++ b/browser/components/TagListItem.styl @@ -1,5 +1,9 @@ +.tagList-itemContainer + display flex + .tagList-item display flex + flex 1 width 100% height 26px background-color transparent @@ -20,9 +24,16 @@ color $ui-button-default-color background-color $ui-button-default--active-backgroundColor +.tagList-itemNarrow + composes tagList-item + flex none + width 20px + padding 0 4px + .tagList-item-active background-color $ui-button-default--active-backgroundColor display flex + flex 1 width 100% height 26px padding 0 @@ -36,10 +47,16 @@ background-color alpha($ui-button-default--active-backgroundColor, 60%) transition 0.2s +.tagList-itemNarrow-active + composes tagList-item-active + flex none + width 20px + padding 0 4px + .tagList-item-name display block flex 1 - padding 0 15px + padding 0 8px 0 4px height 26px line-height 26px border-width 0 0 0 2px @@ -49,7 +66,10 @@ text-overflow ellipsis .tagList-item-count - padding 0 3px + float right + line-height 26px + padding-right 15px + font-size 13px body[data-theme="white"] .tagList-item diff --git a/browser/lib/Languages.js b/browser/lib/Languages.js new file mode 100644 index 00000000..09a1614e --- /dev/null +++ b/browser/lib/Languages.js @@ -0,0 +1,75 @@ +const languages = [ + { + name: 'Albanian', + locale: 'sq' + }, + { + name: 'Chinese (zh-CN)', + locale: 'zh-CN' + }, + { + name: 'Chinese (zh-TW)', + locale: 'zh-TW' + }, + { + name: 'Danish', + locale: 'da' + }, + { + name: 'English', + locale: 'en' + }, + { + name: 'French', + locale: 'fr' + }, + { + name: 'German', + locale: 'de' + }, + { + name: 'Hungarian', + locale: 'hu' + }, + { + name: 'Japanese', + locale: 'ja' + }, + { + name: 'Korean', + locale: 'ko' + }, + { + name: 'Norwegian', + locale: 'no' + }, + { + name: 'Polish', + locale: 'pl' + }, + { + name: 'Portuguese', + locale: 'pt' + }, + { + name: 'Russian', + locale: 'ru' + }, + { + name: 'Spanish', + locale: 'es-ES' + } +] + +module.exports = { + getLocales () { + return languages.reduce(function (localeList, locale) { + localeList.push(locale.locale) + return localeList + }, []) + }, + getLanguages () { + return languages + } +} + diff --git a/browser/lib/i18n.js b/browser/lib/i18n.js index f7bd96e9..e12e2aa6 100644 --- a/browser/lib/i18n.js +++ b/browser/lib/i18n.js @@ -1,11 +1,12 @@ const path = require('path') const { remote } = require('electron') const { app } = remote +const { getLocales } = require('./Languages.js') // load package for localization const i18n = new (require('i18n-2'))({ // setup some locales - other locales default to the first locale - locales: [ 'da', 'de', 'en', 'es-ES', 'fr', 'hu', 'ja', 'ko', 'pl', 'pt-BR', 'pt-PT', 'ru', 'sq', 'zh-CN', 'zh-TW' ], + locales: getLocales(), extension: '.json', directory: process.env.NODE_ENV === 'production' ? path.join(app.getAppPath(), './locales') diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 6f1f2f00..1ef488a7 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -5,7 +5,7 @@ import math from '@rokt33r/markdown-it-math' import _ from 'lodash' import ConfigManager from 'browser/main/lib/ConfigManager' import katex from 'katex' -import {lastFindInArray} from './utils' +import { lastFindInArray } from './utils' function createGutter (str, firstLineNumber) { if (Number.isNaN(firstLineNumber)) firstLineNumber = 1 @@ -234,10 +234,6 @@ class Markdown { if (!_.isString(content)) content = '' return this.md.render(content) } - - normalizeLinkText (linkText) { - return this.md.normalizeLinkText(linkText) - } } export default Markdown diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 05883c0e..72f832e3 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -289,6 +289,7 @@ class MarkdownNoteDetail extends React.Component { config={config} value={note.content} storageKey={note.storage} + noteKey={note.key} onChange={this.handleUpdateContent.bind(this)} ignorePreviewPointerEvents={ignorePreviewPointerEvents} /> @@ -298,6 +299,7 @@ class MarkdownNoteDetail extends React.Component { config={config} value={note.content} storageKey={note.storage} + noteKey={note.key} onChange={this.handleUpdateContent.bind(this)} ignorePreviewPointerEvents={ignorePreviewPointerEvents} /> diff --git a/browser/main/Main.js b/browser/main/Main.js index 14a56225..9f1c06e7 100644 --- a/browser/main/Main.js +++ b/browser/main/Main.js @@ -15,6 +15,7 @@ import eventEmitter from 'browser/main/lib/eventEmitter' import { hashHistory } from 'react-router' import store from 'browser/main/store' import i18n from 'browser/lib/i18n' +import { getLocales } from 'browser/lib/Languages' const path = require('path') const electron = require('electron') const { remote } = electron @@ -152,24 +153,7 @@ class Main extends React.Component { document.body.setAttribute('data-theme', 'default') } - const supportedLanguages = [ - 'sq', - 'zh-CN', - 'zh-TW', - 'da', - 'fr', - 'de', - 'hu', - 'ja', - 'ko', - 'no', - 'pl', - 'pt', - 'ru', - 'es-ES' - ] - - if (supportedLanguages.indexOf(config.ui.language) !== -1) { + if (getLocales().indexOf(config.ui.language) !== -1) { i18n.setLocale(config.ui.language) } else { i18n.setLocale('en') diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index ae018c9e..e8c09f65 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -282,8 +282,8 @@ class NoteList extends React.Component { ee.emit('detail:focus') } - // F or S key - if (e.keyCode === 70 || e.keyCode === 83) { + // L or S key + if (e.keyCode === 76 || e.keyCode === 83) { e.preventDefault() ee.emit('top:focus-search') } @@ -343,11 +343,10 @@ class NoteList extends React.Component { } if (location.pathname.match(/\/tags/)) { + const listOfTags = params.tagname.split(' ') return data.noteMap.map(note => { return note - }).filter(note => { - return note.tags.includes(params.tagname) - }) + }).filter(note => listOfTags.every(tag => note.tags.includes(tag))) } return this.getContextNotes() @@ -916,7 +915,7 @@ class NoteList extends React.Component { if (note.isTrashed !== true || location.pathname === '/trashed') return true }) - moment.locale('en', { + moment.updateLocale('en', { relativeTime: { future: 'in %s', past: '%s ago', diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index f8a65013..6b53478e 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -145,20 +145,27 @@ class SideNav extends React.Component { tagListComponent () { const { data, location, config } = this.props + const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap) let tagList = _.sortBy(data.tagNoteMap.map( - (tag, name) => ({name, size: tag.size})), - ['name'] - ) + (tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) }) + ), ['name']) if (config.sortTagsBy === 'COUNTER') { tagList = _.sortBy(tagList, item => (0 - item.size)) } + if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) { + tagList = tagList.filter( + tag => tag.related + ) + } return ( tagList.map(tag => { return ( @@ -167,10 +174,30 @@ class SideNav extends React.Component { ) } + getRelatedTags (activeTags, noteMap) { + if (activeTags.length === 0) { + return new Set() + } + const relatedNotes = noteMap.map( + note => ({key: note.key, tags: note.tags}) + ).filter( + note => activeTags.every(tag => note.tags.includes(tag)) + ) + let relatedTags = new Set() + relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag))) + return relatedTags + } + getTagActive (path, tag) { + return this.getActiveTags(path).includes(tag) + } + + getActiveTags (path) { const pathSegments = path.split('/') - const pathTag = pathSegments[pathSegments.length - 1] - return pathTag === tag + const tags = pathSegments[pathSegments.length - 1] + return (tags === 'alltags') + ? [] + : tags.split(' ') } handleClickTagListItem (name) { @@ -192,6 +219,19 @@ class SideNav extends React.Component { }) } + handleClickNarrowToTag (tag) { + const { router } = this.context + const { location } = this.props + let listOfTags = this.getActiveTags(location.pathname) + const indexOfTag = listOfTags.indexOf(tag) + if (indexOfTag > -1) { + listOfTags.splice(indexOfTag, 1) + } else { + listOfTags.push(tag) + } + router.push(`/tags/${listOfTags.join(' ')}`) + } + emptyTrash (entries) { const { dispatch } = this.props const deletionPromises = entries.map((note) => { diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js new file mode 100644 index 00000000..6d4d7406 --- /dev/null +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -0,0 +1,164 @@ +const uniqueSlug = require('unique-slug') +const fs = require('fs') +const path = require('path') +const findStorage = require('browser/lib/findStorage') +const mdurl = require('mdurl') + +const STORAGE_FOLDER_PLACEHOLDER = ':storage' +const DESTINATION_FOLDER = 'attachments' + +/** + * @description + * Copies a copy of an attachment to the storage folder specified by the given key and return the generated attachment name. + * Renames the file to match a unique file name. + * + * @param {String} sourceFilePath The source path of the attachment to be copied + * @param {String} storageKey Storage key of the destination storage + * @param {String} noteKey Key of the current note. Will be used as subfolder in :storage + * @param {boolean} useRandomName determines whether a random filename for the new file is used. If false the source file name is used + * @return {Promise} name (inclusive extension) of the generated file + */ +function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = true) { + return new Promise((resolve, reject) => { + if (!sourceFilePath) { + reject('sourceFilePath has to be given') + } + + if (!storageKey) { + reject('storageKey has to be given') + } + + if (!noteKey) { + reject('noteKey has to be given') + } + + try { + if (!fs.existsSync(sourceFilePath)) { + reject('source file does not exist') + } + + const targetStorage = findStorage.findStorage(storageKey) + + const inputFile = fs.createReadStream(sourceFilePath) + let destinationName + if (useRandomName) { + destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}` + } else { + destinationName = path.basename(sourceFilePath) + } + const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) + createAttachmentDestinationFolder(targetStorage.path, noteKey) + const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName)) + inputFile.pipe(outputFile) + resolve(destinationName) + } catch (e) { + return reject(e) + } + }) +} + +function createAttachmentDestinationFolder (destinationStoragePath, noteKey) { + let destinationDir = path.join(destinationStoragePath, DESTINATION_FOLDER) + if (!fs.existsSync(destinationDir)) { + fs.mkdirSync(destinationDir) + } + destinationDir = path.join(destinationStoragePath, DESTINATION_FOLDER, noteKey) + if (!fs.existsSync(destinationDir)) { + fs.mkdirSync(destinationDir) + } +} + +/** + * @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} storagePath Path of the current storage + * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths. + */ +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)) +} + +/** + * @description Generates the markdown code for a given attachment + * @param {String} fileName Name of the attachment + * @param {String} path Path of the attachment + * @param {Boolean} showPreview Indicator whether the generated markdown should show a preview of the image. Note that at the moment only previews for images are supported + * @returns {String} Generated markdown code + */ +function generateAttachmentMarkdown (fileName, path, showPreview) { + return `${showPreview ? '!' : ''}[${fileName}](${path})` +} + +/** + * @description Handles the drop-event of a file. Includes the necessary markdown code and copies the file to the corresponding storage folder. + * The method calls {CodeEditor#insertAttachmentMd()} to include the generated markdown at the needed place! + * @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code + * @param {String} storageKey Key of the current storage + * @param {String} noteKey Key of the current note + * @param {Event} dropEvent DropEvent + */ +function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) { + const file = dropEvent.dataTransfer.files[0] + const filePath = file.path + const originalFileName = path.basename(filePath) + const fileType = file['type'] + + copyAttachment(filePath, storageKey, noteKey).then((fileName) => { + let showPreview = fileType.startsWith('image') + let imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview) + codeEditor.insertAttachmentMd(imageMd) + }) +} + +/** + * @description Creates a new file in the storage folder belonging to the current note and inserts the correct markdown code + * @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code + * @param {String} storageKey Key of the current storage + * @param {String} noteKey Key of the current note + * @param {DataTransferItem} dataTransferItem Part of the past-event + */ +function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) { + if (!codeEditor) { + throw new Error('codeEditor has to be given') + } + if (!storageKey) { + throw new Error('storageKey has to be given') + } + + if (!noteKey) { + throw new Error('noteKey has to be given') + } + if (!dataTransferItem) { + throw new Error('dataTransferItem has to be given') + } + + const blob = dataTransferItem.getAsFile() + const reader = new FileReader() + let base64data + const targetStorage = findStorage.findStorage(storageKey) + const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) + createAttachmentDestinationFolder(targetStorage.path, noteKey) + + let imageName = `${uniqueSlug()}.png` + const imagePath = path.join(destinationDir, imageName) + + reader.onloadend = function () { + base64data = reader.result.replace(/^data:image\/png;base64,/, '') + base64data += base64data.replace('+', ' ') + const binaryData = new Buffer(base64data, 'base64').toString('binary') + fs.writeFile(imagePath, binaryData, 'binary') + let imageMd = generateAttachmentMarkdown(imageName, imagePath, true) + codeEditor.insertAttachmentMd(imageMd) + } + reader.readAsDataURL(blob) +} + +module.exports = { + copyAttachment, + fixLocalURLS, + generateAttachmentMarkdown, + handleAttachmentDrop, + handlePastImageEvent, + STORAGE_FOLDER_PLACEHOLDER, + DESTINATION_FOLDER +} diff --git a/browser/main/lib/dataApi/copyImage.js b/browser/main/lib/dataApi/copyImage.js index 6a79b8b7..24053bdd 100644 --- a/browser/main/lib/dataApi/copyImage.js +++ b/browser/main/lib/dataApi/copyImage.js @@ -2,6 +2,8 @@ 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 @@ -21,8 +23,12 @@ function copyImage (filePath, storageKey, rename = true) { 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) - resolve(basename) } catch (e) { return reject(e) } diff --git a/browser/main/lib/dataApi/exportNote.js b/browser/main/lib/dataApi/exportNote.js index 313bb85b..71f7d017 100755 --- a/browser/main/lib/dataApi/exportNote.js +++ b/browser/main/lib/dataApi/exportNote.js @@ -1,11 +1,12 @@ import copyFile from 'browser/main/lib/dataApi/copyFile' -import {findStorage} from 'browser/lib/findStorage' +import { findStorage } from 'browser/lib/findStorage' import filenamify from 'filenamify' const fs = require('fs') const path = require('path') const LOCAL_STORED_REGEX = /!\[(.*?)]\(\s*?\/:storage\/(.*\.\S*?)\)/gi +// TODO: ehhc: check this -> attachmentManagement const IMAGES_FOLDER_NAME = 'images' /** diff --git a/browser/main/lib/dataApi/moveNote.js b/browser/main/lib/dataApi/moveNote.js index 928d331b..cffb5c53 100644 --- a/browser/main/lib/dataApi/moveNote.js +++ b/browser/main/lib/dataApi/moveNote.js @@ -68,6 +68,8 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) { return noteData }) .then(function moveImages (noteData) { + if (oldStorage.path === newStorage.path) return noteData + const searchImagesRegex = /!\[.*?]\(\s*?\/:storage\/(.*\.\S*?)\)/gi let match = searchImagesRegex.exec(noteData.content) @@ -75,6 +77,7 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) { while (match != null) { const [, filename] = match const oldPath = path.join(oldStorage.path, 'images', filename) + // TODO: ehhc: attachmentManagement moveTasks.push( copyImage(oldPath, noteData.storage, false) .then(() => { diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index a092129a..21338731 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -10,6 +10,7 @@ import CodeMirror from 'codemirror' import 'codemirror-mode-elixir' import _ from 'lodash' import i18n from 'browser/lib/i18n' +import { getLanguages } from 'browser/lib/Languages' const OSX = global.process.platform === 'darwin' @@ -65,6 +66,7 @@ class UiTab extends React.Component { language: this.refs.uiLanguage.value, showCopyNotification: this.refs.showCopyNotification.checked, confirmDeletion: this.refs.confirmDeletion.checked, + showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked, disableDirectWrite: this.refs.uiD2w != null ? this.refs.uiD2w.checked : false @@ -182,22 +184,9 @@ class UiTab extends React.Component { onChange={(e) => this.handleUIChange(e)} ref='uiLanguage' > - - - - - - - - - - - - - - - - + { + getLanguages().map((language) => ) + }
@@ -222,6 +211,16 @@ class UiTab extends React.Component { {i18n.__('Show a confirmation dialog when deleting notes')} +
+ +
{ global.process.platform === 'win32' ?
diff --git a/lib/main-menu.js b/lib/main-menu.js index ddf5ee54..e1e2a83a 100644 --- a/lib/main-menu.js +++ b/lib/main-menu.js @@ -252,10 +252,20 @@ const view = { }, { label: 'Focus Search', - accelerator: 'Control+S', + accelerator: 'CommandOrControl+Shift+L', click () { mainWindow.webContents.send('top:focus-search') } + }, + { + type: 'separator' + }, + { + label: 'Toggle Full Screen', + accelerator: macOS ? 'Command+Control+F' : 'F11', + click () { + mainWindow.setFullScreen(!mainWindow.isFullScreen()) + } } ] } @@ -265,7 +275,7 @@ let editorFocused // Define extra shortcut keys mainWindow.webContents.on('before-input-event', (event, input) => { // Synonyms for Search (Find) - if (input.control && input.key === 'f' && input.type === 'keyDown') { + if (input.control && input.key === 'l' && input.type === 'keyDown') { if (!editorFocused) { mainWindow.webContents.send('top:focus-search') event.preventDefault() @@ -285,11 +295,6 @@ const window = { accelerator: 'Command+M', selector: 'performMiniaturize:' }, - { - label: 'Toggle Full Screen', - accelerator: 'Command+Control+F', - selector: 'toggleFullScreen:' - }, { label: 'Close', accelerator: 'Command+W', diff --git a/locales/es-ES.json b/locales/es-ES.json index ea20641b..d533579c 100644 --- a/locales/es-ES.json +++ b/locales/es-ES.json @@ -87,7 +87,7 @@ "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote es utilizado en alrededor de 200 países y regiones diferentes por una increíble comunidad de desarrolladores.", "To continue supporting this growth, and to satisfy community expectations,": "Para continuar apoyando este crecimiento y satisfacer las expectativas de la comunidad,", "we would like to invest more time and resources in this project.": "nos gustaría invertir más tiempo y recursos en este proyecto.", - "If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Si te gusta este proyecto y ves potencial en él, ¡puedes ayudar apoyándonos en OpenCollective!", + "If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Si te gusta este proyecto y ves su potencial, ¡puedes ayudar apoyándonos en OpenCollective!", "Thanks,": "Gracias,", "Boostnote maintainers": "Equipo de Boostnote", "Support via OpenCollective": "Contribuir vía OpenCollective", @@ -149,5 +149,5 @@ "Sanitization": "Saneamiento", "Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)", "Allow styles": "Permitir estilos", - "Allow dangerous html tags": "Permitir etiques html peligrosas" + "Allow dangerous html tags": "Permitir etiquetas html peligrosas" } diff --git a/locales/fa.json b/locales/fa.json new file mode 100644 index 00000000..e0aa1978 --- /dev/null +++ b/locales/fa.json @@ -0,0 +1,156 @@ +{ + "Notes": "یادداشت ها", + "Tags": "تگ ها", + "Preferences": "تنظیمات", + "Make a note": "یک یادداشت بنویس", + "Ctrl": "Ctrl", + "Ctrl(^)": "Ctrl", + "to create a new note": "برای ساخت یک یادداشت", + "Toggle Mode": "تغییر حالت نمایش", + "Trash": "سطل آشغال", + "MODIFICATION DATE": "تاریخ تغییر", + "Words": "کلمات", + "Letters": "حروف", + "STORAGE": "ذخیره سازی", + "FOLDER": "پوشه", + "CREATION DATE": "تاریخ ایجاد", + "NOTE LINK": "لینک یادداشت", + ".md": ".md", + ".txt": ".txt", + ".html": ".html", + "Print": "پرینت", + "Your preferences for Boostnote": "تنظیمات شما برای boostnote", + "Storages": "ذخیره سازی", + "Add Storage Location": "افزودن محل ذخیره سازی", + "Add Folder": "ساخت پوشه", + "Open Storage folder": "بازکردن پوشه ذخیره سازی", + "Unlink": "حذف لینک", + "Edit": "ویرایش", + "Delete": "حذف", + "Interface": "رابط کاربری", + "Interface Theme": "تم رابط کاربری", + "Default": "پیش فرض", + "White": "روشن", + "Solarized Dark": "سولارایز", + "Dark": "تاریک", + "Show a confirmation dialog when deleting notes": "هنگام حذف یادداشت ها یک پیام تایید نمایش بده.", + "Editor Theme": "تم ویرایشگر", + "Editor Font Size": "اندازه فونت ویرایشگر", + "Editor Font Family": "فونت ویرایشگر", + "Editor Indent Style": "حالت فاصله گذاری ویرایشگر", + "Spaces": "Spaces", + "Tabs": "Tabs", + "Switch to Preview": "دیدن پیش نمایش", + "When Editor Blurred": "وقتی ویرایشگر از حالت ویرایش خارج شد ", + "When Editor Blurred, Edit On Double Click": "وقتی ویرایشگر از حالت ویرایش خارج شد و با دبل کلیک ویرایش کنید.", + "On Right Click": "راست کلیک", + "Editor Keymap": "ویرایشگر Keymap", + "default": "پیش فرض", + "vim": "vim", + "emacs": "emacs", + "⚠️ Please restart boostnote after you change the keymap": "⚠️ برنامه را دوباره راه اندازی کنید keymap لطفا بعد از تغییر", + "Show line numbers in the editor": "شماره خطوط در ویرایشگر را نمایش بده.", + "Allow editor to scroll past the last line": "اجازه بده ویرایشگر بعد از آخرین خط اسکرول کند.", + "Bring in web page title when pasting URL on editor": "هنگامی که آدرس اینترنتی در ویرایشگر اضافه شد عنوان آنرا نمایش بده", + "Preview": "پیش نمایش", + "Preview Font Size": "اندازه فونتِ پیش نمایش", + "Preview Font Family": " فونتِ پیش نمایش", + "Code block Theme": "تم بخش کد", + "Allow preview to scroll past the last line": "اجازه بده پیش نمایش بعد از آخرین خط اسکرول کند.", + "Show line numbers for preview code blocks": "شماره خطوط در پیش نمایش را نمایش بده.", + "LaTeX Inline Open Delimiter": "جداکننده آغازین لاتکس خطی", + "LaTeX Inline Close Delimiter": "جداکننده پایانی لاتکس خطی", + "LaTeX Block Open Delimiter": "جداکننده آغازین بلوک لاتکس ", + "LaTeX Block Close Delimiter": "جداکننده آغازین بلوک لاتکس ", + "Community": "کامینیتی", + "Subscribe to Newsletter": "اشتراک در خبرنامه", + "GitHub": "گیت هاب", + "Blog": "بلاگ", + "Facebook Group": "گروه فیسبوک", + "Twitter": "توییتر", + "About": "درباره", + "Boostnote": "Boostnote", + "An open source note-taking app made for programmers just like you.": "یک دفترچه یادداشت متن باز ساخته شده برای برنامه نویسانی مثل تو.", + "Website": "وبسایت", + "Development": "توسعه", + " : Development configurations for Boostnote.": " : پیکربندی توسعه برای Boostnote.", + "Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", + "License: GPL v3": "لایسنس: GPL v3", + "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.": "Bosstnote اطلاعات ناشناس را برای بهبود عملکرد برنامه جمع آوری می‌کند.اطلاعات شخصی شما مثل محتوای یادداشت ها هرگز برای هیچ هدفی جمع آوری نمی‌شوند", + "You can see how it works on ": "میتوانید ببینید چگونه کار میکند. ", + "You can choose to enable or disable this option.": "میتوانید این گزینه را فعال یا غیرفعال کنید.", + "Enable analytics to help improve Boostnote":".تجزیه تحلیل داده ها را برای کمک به بهبود برنامه فعال کن", + "Crowdfunding": "جمع سپاری (سرمایه گذاری جمعی )", + "Dear everyone,": "عزیزان,", + "Thank you for using Boostnote!": "از شما بخاطر استفاده از boostnote ممنونیم!", + "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "در ۲۰۰ کشور مختلف دنیا مورد توسط جمعی از برنامه نویسان بی نظیر مورد استفاده قرار میگیرد. Boostnote", + "To continue supporting this growth, and to satisfy community expectations,": "برای حمایت از این رشد ، و برآورده شدن انتظارات کامینیتی,", + "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!": "اگر این پروژه را دوست دارید و پتانسیلی در آن می‌بینید، میتوانید مارا در اوپن‌ کالکتیو حمایت کنید.", + "Thanks,": "با تشکر,", + "Boostnote maintainers": "Boostnote نگهدارندگان", + "Support via OpenCollective": "حمایت کنید OpenCollective از طریق", + "Language": "زبان", + "English": "انگلیسی", + "German": "آلمانی", + "French": "فرانسوی", + "Show \"Saved to Clipboard\" notification when copying": "نمایش \"ذخیره در کلیپ‌بورد\" اطلاع رسانی هنگام کپی کردن", + "All Notes": "همه یادداشت ها", + "Starred": "ستاره دار", + "Are you sure to ": " مطمئن هستید که", + " delete": "حذف ", + "this folder?": "این پوشه ؟", + "Confirm": "تایید", + "Cancel": "انصراف", + "Markdown Note": "Markdown یادداشتِ", + "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "این قالب برای ساخت سند های متنی است. چک لیست ها و تکه کد ها و بلاک های لاتکس قابل استفاده اند.", + "Snippet Note": "Snippet یادداشتِ", + "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "این قالب برای ساخت تکه کد هاست. چند تکه کد میتوانند تبدیل به یک یادداشت شوند.", + "Tab to switch format": "را بزنید Tab برای تغییر فرمت", + "Updated": "بروزرسانی شد", + "Created": "ایجاد شد", + "Alphabetically": "بر اساس حروف الفبا", + "Counter": "شمارشگر", + "Default View": "نمایش پیش‌فرض", + "Compressed View": "نمایش فشرده", + "Search": "جستجو", + "Blog Type": "نوع وبلاگ", + "Blog Address": "آدرس وبلاگ", + "Save": "ذخیره", + "Auth": "هویت", + "Authentication Method": "متد احراز هویت", + "JWT": "JWT", + "USER": "کاربر", + "Token": "توکن", + "Storage": "ذخیره سازی", + "Hotkeys": "کلید های میانبر", + "Show/Hide Boostnote": "Boostnote نمایش/پنهان کردن", + "Restore": "بازگرداندن به حالت اول", + "Permanent Delete": "حذف بدون بازگشت", + "Confirm note deletion": ".حذف یادداشت را تایید کنید", + "This will permanently remove this note.": ".این کار یادداشت را بطور دائمی حذف خواهد کرد", + "Successfully applied!": "!با موفقیت اجرا شد", + "Albanian": "آلبانی", + "Chinese (zh-CN)": "چینی (zh-CN)", + "Chinese (zh-TW)": "چینی (zh-TW)", + "Danish": "دانمارکی", + "Japanese": "ژاپنی", + "Korean": "کره ای", + "Norwegian": "نروژی", + "Polish": "لهستانی", + "Portuguese": "پرتغالی", + "Spanish": "اسپانیایی", + "You have to save!": "!باید ذخیره کنید", + "UserName": "نام کاربری", + "Password": "رمز عبور", + "Russian": "روسی", + "Command(⌘)": "Command(⌘)", + "Editor Rulers": "Editor Rulers", + "Enable": "فعال", + "Disable": "غیرفعال", + "Sanitization": "پاکسازی کردن", + "Only allow secure html tags (recommended)": "(فقط تگ های امن اچ تی ام ال مجاز اند.(پیشنهاد میشود", + "Allow styles": "حالت های مجاز", + "Allow dangerous html tags": "تگ های خطرناک اچ‌ تی ام ال مجاز اند" +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json new file mode 100644 index 00000000..bfb09691 --- /dev/null +++ b/locales/it.json @@ -0,0 +1,156 @@ +{ + "Notes": "Note", + "Tags": "Tags", + "Preferences": "Preferenze", + "Make a note": "Crea una nota", + "Ctrl": "Ctrl", + "Ctrl(^)": "Ctrl", + "to create a new note": "per creare una nuova nota", + "Toggle Mode": "Cambia Modalità", + "Trash": "Cestino", + "MODIFICATION DATE": "DATA DI MODIFICA", + "Words": "Parole", + "Letters": "Lettere", + "STORAGE": "POSIZIONE", + "FOLDER": "CARTELLA", + "CREATION DATE": "DATA DI CREAZIONE", + "NOTE LINK": "LINK NOTA", + ".md": ".md", + ".txt": ".txt", + ".html": ".html", + "Print": "Stampa", + "Your preferences for Boostnote": "Le tue preferenze per Boostnote", + "Storages": "Posizioni", + "Add Storage Location": "Aggiungi posizione", + "Add Folder": "Aggiungi cartella", + "Open Storage folder": "Apri cartella di memoria", + "Unlink": "Scollega", + "Edit": "Modifica", + "Delete": "Elimina", + "Interface": "Interfaccia", + "Interface Theme": "Tema interfaccia", + "Default": "Default", + "White": "White", + "Solarized Dark": "Solarized Dark", + "Dark": "Dark", + "Show a confirmation dialog when deleting notes": "Mostra finestra di conferma quando elimini delle note", + "Editor Theme": "Tema dell'Editor", + "Editor Font Size": "Dimensione font dell'editor", + "Editor Font Family": "Famiglia del font dell'editor", + "Editor Indent Style": "Stile di indentazione dell'editor", + "Spaces": "Spazi", + "Tabs": "Tabs", + "Switch to Preview": "Passa all'anteprima", + "When Editor Blurred": "Quando l'editor è sfocato", + "When Editor Blurred, Edit On Double Click": "Quando l'Editor è sfocato, Modifica facendo doppio click", + "On Right Click": "Cliccando con il tasto destro", + "Editor Keymap": "keymapping dell'editor", + "default": "default", + "vim": "vim", + "emacs": "emacs", + "⚠️ Please restart boostnote after you change the keymap": "⚠️ Riavvia Boostnote dopo aver cambiato il keymapping", + "Show line numbers in the editor": "Mostra numero di linea nell'editor", + "Allow editor to scroll past the last line": "Consenti scrolling oltre l'ultima linea nell'editor", + "Bring in web page title when pasting URL on editor": "Mostra il titolo della pagina web quando incolli un URL nell'editor", + "Preview": "Anteprima", + "Preview Font Size": "Dimensione font nell'anteprima", + "Preview Font Family": "Famiglia del font dell'anteprima", + "Code block Theme": "Tema blocco di codice", + "Allow preview to scroll past the last line": "Consenti scrolling oltre l'ultima linea", + "Show line numbers for preview code blocks": "Mostra numero di linea per i blocchi di codice nell'Anteprima", + "LaTeX Inline Open Delimiter": "Delimitatore inline per apertura LaTex", + "LaTeX Inline Close Delimiter": "Delimitatore inline per chiusura LaTex", + "LaTeX Block Open Delimiter": "Delimitatore apertura LaTex", + "LaTeX Block Close Delimiter": "Delimitatore chiusura LaTex", + "Community": "Community", + "Subscribe to Newsletter": "Iscriviti alla Newsletter", + "GitHub": "GitHub", + "Blog": "Blog", + "Facebook Group": "Gruppo Facebook", + "Twitter": "Twitter", + "About": "About", + "Boostnote": "Boostnote", + "An open source note-taking app made for programmers just like you.": "Un'app open-source per prendere appunti, fatta per sviluppatori come te.", + "Website": "Sito Web", + "Development": "Sviluppo", + " : Development configurations for Boostnote.": " : Configurazioni di sviluppo per Boostnote.", + "Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", + "License: GPL v3": "Licenza: GPL v3", + "Analytics": "Statistiche", + "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 raccoglie dati anonimi al solo scopo di migliorare l'applicazione, e non raccoglie nessuna informazione personale rigurado il contenuto delle note.", + "You can see how it works on ": "Ypuoi vedere come su ", + "You can choose to enable or disable this option.": "Puoi scegliere se attivare o disattivare questa opzione.", + "Enable analytics to help improve Boostnote": "Attiva raccolta dati per aiutare a migliorare Boostnote", + "Crowdfunding": "Crowdfunding", + "Dear everyone,": "Cari utenti,", + "Thank you for using Boostnote!": "Grazie per stare utilizzando Boostnote!", + "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote è usato in circa 200 Paesi da una fantastica community di sviluppatori.", + "To continue supporting this growth, and to satisfy community expectations,": "Per continuare a supportarne la crescita, e per soddisfare le aspettative della comunità,", + "we would like to invest more time and resources in this project.": "ci piacerebbe investire più tempo e risorse in questo progetto.", + "If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Se ti piace questo progetto e ci vedi del potenziale, puoi aiutarci dandodci supporto su OpenCollective!", + "Thanks,": "Grazie,", + "Boostnote maintainers": "I mantainers di Boostnote", + "Support via OpenCollective": "Supporta su OpenCollective", + "Language": "Lingua", + "English": "Inglese", + "German": "Tedesco", + "French": "Francese", + "Show \"Saved to Clipboard\" notification when copying": "Mostra la notifica \"Salvato negli Appunti\" quando copi:", + "All Notes": "Tutte le note", + "Starred": "Contribuite", + "Are you sure to ": "Sei sicuro di ", + " delete": " eliminare", + "this folder?": "questa cartella?", + "Confirm": "Conferma", + "Cancel": "Cancella", + "Markdown Note": "Nota in Markdown", + "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Questo formato è per creare documenti di testo. Sono disponibili checklist, blocchi di codice and blocchi in Latex", + "Snippet Note": "Nota Snippet", + "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Questo formato è per creare snippets. Più snippet possono essere raccolti in un'unica nota.", + "Tab to switch format": "Premi Tab per cambiare formato", + "Updated": "Aggiornato", + "Created": "Creato", + "Alphabetically": "Ordine alfabetico", + "Counter": "Contatore", + "Default View": "Visione di default", + "Compressed View": "Visione compressa", + "Search": "Cerca", + "Blog Type": "Tipo di blog", + "Blog Address": "Indirizzo del blog", + "Save": "Salva", + "Auth": "Autorizzazione", + "Authentication Method": "Metodo di autenticazione", + "JWT": "JWT", + "USER": "USER", + "Token": "Token", + "Storage": "Storage", + "Hotkeys": "Hotkeys", + "Show/Hide Boostnote": "Mostra/Nascondi Boostnote", + "Restore": "Ripristina", + "Permanent Delete": "Elimina permanentemente", + "Confirm note deletion": "Conferma eliiminazione della nota", + "This will permanently remove this note.": "Questo eliminerà permanentemente questa nota.", + "Successfully applied!": "Applicato con successo!", + "Albanian": "Albanese", + "Chinese (zh-CN)": "Cinese (zh-CN)", + "Chinese (zh-TW)": "Cinese (zh-TW)", + "Danish": "Danese", + "Japanese": "Giapponese", + "Korean": "Koreano", + "Norwegian": "Novergese", + "Polish": "Polacco", + "Portuguese": "Portoghese", + "Spanish": "Spagnolo", + "You have to save!": "Devi salvare!", + "UserName": "UserName", + "Password": "Password", + "Russian": "Russo", + "Command(⌘)": "Comando(⌘)", + "Editor Rulers": "Regole dell'editor", + "Enable": "Abilita", + "Disable": "Disabilia", + "Sanitization": "Bonifica", + "Only allow secure html tags (recommended)": "Consenti solo tag HTML sicuri (raccomandato)", + "Allow styles": "Consenti stili", + "Allow dangerous html tags": "Consenti tag HTML pericolosi" +}" diff --git a/locales/ja.json b/locales/ja.json index 51f975d9..19609ffa 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -1,153 +1,153 @@ { - "Notes": "Notes", - "Tags": "Tags", - "Preferences": "Preferences", - "Make a note": "Make a note", + "Notes": "ノート", + "Tags": "タグ", + "Preferences": "設定", + "Make a note": "ノート作成", "Ctrl": "Ctrl", "Ctrl(^)": "Ctrl", - "to create a new note": "to create a new note", - "Toggle Mode": "Toggle Mode", - "Trash": "Trash", - "MODIFICATION DATE": "MODIFICATION DATE", - "Words": "Words", - "Letters": "Letters", - "STORAGE": "STORAGE", - "FOLDER": "FOLDER", - "CREATION DATE": "CREATION DATE", - "NOTE LINK": "NOTE LINK", + "to create a new note": "ノートを新規に作成", + "Toggle Mode": "モード切替", + "Trash": "ゴミ箱", + "MODIFICATION DATE": "修正日", + "Words": "ワード", + "Letters": "文字", + "STORAGE": "ストレージ", + "FOLDER": "フォルダ", + "CREATION DATE": "作成日", + "NOTE LINK": "リンク", ".md": ".md", ".txt": ".txt", ".html": ".html", - "Print": "Print", - "Your preferences for Boostnote": "Your preferences for Boostnote", - "Storages": "Storages", - "Add Storage Location": "Add Storage Location", - "Add Folder": "Add Folder", - "Open Storage folder": "Open Storage folder", - "Unlink": "Unlink", - "Edit": "Edit", - "Delete": "Delete", - "Interface": "Interface", - "Interface Theme": "Interface Theme", - "Default": "Default", - "White": "White", - "Solarized Dark": "Solarized Dark", - "Dark": "Dark", - "Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes", - "Editor Theme": "Editor Theme", - "Editor Font Size": "Editor Font Size", - "Editor Font Family": "Editor Font Family", - "Editor Indent Style": "Editor Indent Style", - "Spaces": "Spaces", - "Tabs": "Tabs", - "Switch to Preview": "Switch to Preview", - "When Editor Blurred": "When Editor Blurred", - "When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click", - "On Right Click": "On Right Click", - "Editor Keymap": "Editor Keymap", - "default": "default", + "Print": "印刷", + "Your preferences for Boostnote": "Boostnoteの個人設定", + "Storages": "ストレージ", + "Add Storage Location": "ストレージロケーションを追加", + "Add Folder": "フォルダを追加", + "Open Storage folder": "ストレージフォルダを開く", + "Unlink": "リンク解除", + "Edit": "編集", + "Delete": "削除", + "Interface": "インターフェース", + "Interface Theme": "インターフェーステーマ", + "Default": "デフォルト", + "White": "白", + "Solarized Dark": "明灰", + "Dark": "暗灰", + "Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する", + "Editor Theme": "エディタのテーマ", + "Editor Font Size": "エディタのフォントサイズ", + "Editor Font Family": "エディタのフォント", + "Editor Indent Style": "エディタのインデント方法", + "Spaces": "スペース", + "Tabs": "タブ", + "Switch to Preview": "プレビューへ移動", + "When Editor Blurred": "エディタがフォーカスを失った時", + "When Editor Blurred, Edit On Double Click": "エディタがフォーカスを失った時、ダブルクリックで編集", + "On Right Click": "右クリック", + "Editor Keymap": "エディタのキーマップ", + "default": "デフォルト", "vim": "vim", "emacs": "emacs", - "⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap", - "Show line numbers in the editor": "Show line numbers in the editor", - "Allow editor to scroll past the last line": "Allow editor to scroll past the last line", + "⚠️ Please restart boostnote after you change the keymap": "⚠️ Plキーマップ変更後は Boostnote を再起動してください", + "Show line numbers in the editor": "エディタ内に行番号を表示", + "Allow editor to scroll past the last line": "エディタが最終行以降にスクロールできるようにする", "Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor", - "Preview": "Preview", - "Preview Font Size": "Preview Font Size", - "Preview Font Family": "Preview Font Family", - "Code block Theme": "Code block Theme", - "Allow preview to scroll past the last line": "Allow preview to scroll past the last line", - "Show line numbers for preview code blocks": "Show line numbers for preview code blocks", - "LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter", - "LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter", - "LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter", - "LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter", - "Community": "Community", - "Subscribe to Newsletter": "Subscribe to Newsletter", + "Preview": "プレビュー", + "Preview Font Size": "プレビュー時フォントサイズ", + "Preview Font Family": "プレビュー時フォント", + "Code block Theme": "コードブロックのテーマ", + "Allow preview to scroll past the last line": "プレビュー時に最終行以降にスクロールできるようにする", + "Show line numbers for preview code blocks": "プレビュー時のコードブロック内に行番号を表示する", + "LaTeX Inline Open Delimiter": "LaTeX 開始デリミタ(インライン)Inline Open Delimiter", + "LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)", + "LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)", + "LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)", + "Community": "コミュニティ", + "Subscribe to Newsletter": "ニュースレターを購読する", "GitHub": "GitHub", - "Blog": "Blog", - "Facebook Group": "Facebook Group", + "Blog": "ブログ", + "Facebook Group": "Facebook グループ", "Twitter": "Twitter", - "About": "About", + "About": "について", "Boostnote": "Boostnote", - "An open source note-taking app made for programmers just like you.": "An open source note-taking app made for programmers just like you.", - "Website": "Website", - "Development": "Development", - " : Development configurations for Boostnote.": " : Development configurations for Boostnote.", + "An open source note-taking app made for programmers just like you.": "あなたのようなプログラマー向けのオープンソースメモ書きアプリケーション", + "Website": "ウェブサイト", + "Development": "開発", + " : Development configurations for Boostnote.": " : Boostnote の開発構成", "Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", - "License: GPL v3": "License: GPL v3", - "Analytics": "Analytics", - "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.", - "You can see how it works on ": "You can see how it works on ", - "You can choose to enable or disable this option.": "You can choose to enable or disable this option.", - "Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote", - "Crowdfunding": "Crowdfunding", + "License: GPL v3": "ライセンス: GPL v3", + "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 はアプリケーションの機能向上だけを目的に匿名データを収集します。ノートの内容を含めた個人の情報は一切収集しません。", + "You can see how it works on ": "どのように動くかをこちらで確認できます ", + "You can choose to enable or disable this option.": "このオプションは有効/無効を選択できます。", + "Enable analytics to help improve Boostnote": "Boostnote の機能向上のための解析機能を有効にする", + "Crowdfunding": "クラウドファンディング", "Dear everyone,": "Dear everyone,", - "Thank you for using Boostnote!": "Thank you for using Boostnote!", - "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.", - "To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,", - "we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.", - "If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!", - "Thanks,": "Thanks,", - "Boostnote maintainers": "Boostnote maintainers", - "Support via OpenCollective": "Support via OpenCollective", - "Language": "Language", - "English": "English", - "German": "German", - "French": "French", - "Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying", - "All Notes": "All Notes", - "Starred": "Starred", - "Are you sure to ": "Are you sure to ", - " delete": " delete", - "this folder?": "this folder?", - "Confirm": "Confirm", - "Cancel": "Cancel", - "Markdown Note": "Markdown Note", - "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.", - "Snippet Note": "Snippet Note", - "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "This format is for creating code snippets. Multiple snippets can be grouped into a single note.", - "Tab to switch format": "Tab to switch format", - "Updated": "Updated", - "Created": "Created", - "Alphabetically": "Alphabetically", - "Default View": "Default View", - "Compressed View": "Compressed View", - "Search": "Search", - "Blog Type": "Blog Type", - "Blog Address": "Blog Address", - "Save": "Save", - "Auth": "Auth", - "Authentication Method": "Authentication Method", + "Thank you for using Boostnote!": "Boostnote を利用いただき、ありがとうございます!", + "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,": "この成長を持続し、またコミュニティからの要望に答えるため、", + "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 を通じて支援いただくことができます!", + "Thanks,": "ありがとうございます。", + "Boostnote maintainers": "Boostnote メンテナンスチーム", + "Support via OpenCollective": "OpenCollective を通じて支援します", + "Language": "言語", + "English": "英語", + "German": "ドイツ語", + "French": "フランス語", + "Show \"Saved to Clipboard\" notification when copying": "クリップボードコピー時に \"クリップボードに保存\" 通知を表示する", + "All Notes": "すべてのノート", + "Starred": "スター付き", + "Are you sure to ": "本当に ", + " delete": "このフォルダを", + "this folder?": "削除しますか?", + "Confirm": "確認", + "Cancel": "キャンセル", + "Markdown Note": "マークダウン", + "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "このフォーマットはテキスト文書を作成することを目的としています。チェックリストや比較的長いコード、LaTeX にも向いています。", + "Snippet Note": "スニペット", + "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "このフォーマットは短いコードスニペットを作成することを目的としています。複数のコードスニペットを1つのグループにまとめて1つのノートとして扱うことも可能です。", + "Tab to switch format": "フォーマット切り替えタブ", + "Updated": "更新日時", + "Created": "作成日時", + "Alphabetically": "アルファベット順", + "Default View": "デフォルトビュー", + "Compressed View": "圧縮ビュー", + "Search": "検索", + "Blog Type": "ブログの種類", + "Blog Address": "ブログのアドレス", + "Save": "保存", + "Auth": "認証", + "Authentication Method": "認証方法", "JWT": "JWT", - "USER": "USER", - "Token": "Token", - "Storage": "Storage", - "Hotkeys": "Hotkeys", - "Show/Hide Boostnote": "Show/Hide Boostnote", - "Restore": "Restore", - "Permanent Delete": "Permanent Delete", - "Confirm note deletion": "Confirm note deletion", - "This will permanently remove this note.": "This will permanently remove this note.", - "Successfully applied!": "Successfully applied!", - "Albanian": "Albanian", - "Chinese (zh-CN)": "Chinese (zh-CN)", - "Chinese (zh-TW)": "Chinese (zh-TW)", - "Danish": "Danish", - "Japanese": "Japanese", - "Korean": "Korean", - "Norwegian": "Norwegian", - "Polish": "Polish", - "Portuguese": "Portuguese", - "Spanish": "Spanish", - "You have to save!": "You have to save!", - "Russian": "Russian", - "Command(⌘)": "Command(⌘)", - "Editor Rulers": "Editor Rulers", - "Enable": "Enable", - "Disable": "Disable", - "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" + "USER": "ユーザー", + "Token": "トークン", + "Storage": "ストレージ", + "Hotkeys": "ホットキー", + "Show/Hide Boostnote": "Boostnote の表示/非表示", + "Restore": "リストア", + "Permanent Delete": "永久に削除", + "Confirm note deletion": "ノート削除確認", + "This will permanently remove this note.": "本当にこのノートを削除します。", + "Successfully applied!": "成功しました!", + "Albanian": "アルバニア語", + "Chinese (zh-CN)": "簡体字中国語 (zh-CN)", + "Chinese (zh-TW)": "繁体字中国語 (zh-TW)", + "Danish": "デンマーク語", + "Japanese": "日本語", + "Korean": "韓国語", + "Norwegian": "ノルウェー語", + "Polish": "ポーランド語", + "Portuguese": "ポルトガル語", + "Spanish": "スペイン語", + "You have to save!": "保存してください!", + "Russian": "ロシア語", + "Command(⌘)": "コマンド(⌘)", + "Editor Rulers": "罫線", + "Enable": "有効", + "Disable": "無効", + "Sanitization": "サニタイズ", + "Only allow secure html tags (recommended)": "安全なHTMLタグのみ利用を許可する(推奨)", + "Allow styles": "スタイルを許可する", + "Allow dangerous html tags": "安全でないHTMLタグの利用を許可する" } diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 06f40608..bc60cfcc 100755 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -1,135 +1,135 @@ { "Notes": "筆記", "Tags": "標籤", - "Preferences": "首選項", - "Make a note": "新建筆記", + "Preferences": "偏好設定", + "Make a note": "做點筆記", "Ctrl": "Ctrl", "Ctrl(^)": "Ctrl", - "to create a new note": "新建筆記", + "to create a new note": "新增筆記", "Toggle Mode": "切換模式", "Trash": "廢紙簍", - "MODIFICATION DATE": "更改時間", - "Words": "單詞", + "MODIFICATION DATE": "修改時間", + "Words": "單字", "Letters": "字數", - "STORAGE": "本地儲存", + "STORAGE": "本機儲存空間", "FOLDER": "資料夾", - "CREATION DATE": "創建時間", - "NOTE LINK": "筆記鏈接", + "CREATION DATE": "建立時間", + "NOTE LINK": "筆記連結", ".md": ".md", ".txt": ".txt", ".html": ".html", "Print": "列印", - "Your preferences for Boostnote": "個性設置", - "Storages": "本地儲存", - "Add Storage Location": "添加一個本地儲存位置", - "Add Folder": "新建資料夾", - "Open Storage folder": "打開一個本地儲存位置", - "Unlink": "取消鏈接", + "Your preferences for Boostnote": "Boostnote 偏好設定", + "Storages": "本機儲存空間", + "Add Storage Location": "新增一個本機儲存位置", + "Add Folder": "新增資料夾", + "Open Storage folder": "開啟儲存資料夾", + "Unlink": "解除連結", "Edit": "編輯", "Delete": "刪除", "Interface": "界面", "Interface Theme": "主題", - "Default": "默認", + "Default": "預設", "White": "White", "Solarized Dark": "Solarized Dark", "Dark": "Dark", - "Show a confirmation dialog when deleting notes": "刪除筆記的時候,顯示確認框", + "Show a confirmation dialog when deleting notes": "刪除筆記的時候,顯示確認對話框", "Editor Theme": "編輯器主題", "Editor Font Size": "編輯器字型大小", "Editor Font Family": "編輯器字體", - "Editor Indent Style": "縮進風格", + "Editor Indent Style": "縮排風格", "Spaces": "空格", "Tabs": "Tabs", - "Switch to Preview": "快速切換到預覽界面", - "When Editor Blurred": "當編輯器失去焦點的時候,切換到預覽界面", - "When Editor Blurred, Edit On Double Click": "當編輯器失去焦點的時候預覽,雙擊切換到編輯界面", - "On Right Click": "右鍵點擊切換兩個界面", + "Switch to Preview": "切回預覽頁面的時機", + "When Editor Blurred": "當編輯器失去焦點時", + "When Editor Blurred, Edit On Double Click": "當編輯器失去焦點時,雙擊切換到編輯畫面", + "On Right Click": "點擊右鍵切換兩個頁面", "Editor Keymap": "編輯器 Keymap", - "default": "默認", + "default": "預設", "vim": "vim", "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": "在編輯器中顯示行號", - "Allow editor to scroll past the last line": "允許編輯器滾動到最後一行", - "Bring in web page title when pasting URL on editor": "粘貼網頁鏈接的時候,顯示為網頁標題", - "Preview": "預覽器", - "Preview Font Size": "預覽器字型大小", - "Preview Font Family": "預覽器字體", - "Code block Theme": "代碼塊主題", - "Allow preview to scroll past the last line": "允許預覽器滾動到最後一行", - "Show line numbers for preview code blocks": "在預覽器中顯示行號", - "LaTeX Inline Open Delimiter": "LaTeX 單行開頭分隔符", - "LaTeX Inline Close Delimiter": "LaTeX 單行結尾分隔符", - "LaTeX Block Open Delimiter": "LaTeX 多行開頭分隔符", - "LaTeX Block Close Delimiter": "LaTeX 多行結尾分隔符", - "Community": "社區", + "Allow editor to scroll past the last line": "允許編輯器捲軸捲動超過最後一行", + "Bring in web page title when pasting URL on editor": "在編輯器貼上網址的時候,自動加上網頁標題", + "Preview": "預覽頁面", + "Preview Font Size": "預覽頁面字型大小", + "Preview Font Family": "預覽頁面字體", + "Code block Theme": "程式碼區塊主題", + "Allow preview to scroll past the last line": "允許預覽頁面捲軸捲動超過最後一行", + "Show line numbers for preview code blocks": "在預覽頁面的程式碼區塊中顯示行號", + "LaTeX Inline Open Delimiter": "LaTeX 單行開頭符號", + "LaTeX Inline Close Delimiter": "LaTeX 單行結尾符號", + "LaTeX Block Open Delimiter": "LaTeX 多行開頭符號", + "LaTeX Block Close Delimiter": "LaTeX 多行結尾符號", + "Community": "社群", "Subscribe to Newsletter": "訂閱郵件", "GitHub": "GitHub", "Blog": "部落格", - "Facebook Group": "Facebook Group", + "Facebook Group": "Facebook 社團", "Twitter": "Twitter", "About": "關於", "Boostnote": "Boostnote", - "An open source note-taking app made for programmers just like you.": "一款專門為程式員朋友量身打造的開源筆記", + "An open source note-taking app made for programmers just like you.": "一款專門為程式設計師朋友量身打造的開源筆記軟體", "Website": "官網", "Development": "開發", - " : Development configurations for Boostnote.": " : Boostnote的開發配置", + " : Development configurations for Boostnote.": " : Boostnote 的開發組態", "Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "License: GPL v3": "License: GPL v3", "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 收集匿名數據只為了提升軟體使用體驗,絕對不收集任何個人信息(包括筆記內容)", - "You can see how it works on ": "你可以看看它的源碼是如何運作的 ", - "You can choose to enable or disable this option.": "你可以選擇開啟或不開啟這個功能", - "Enable analytics to help improve Boostnote": "允許對數據進行分析,幫助我們改進Boostnote", - "Crowdfunding": "眾籌", + "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 choose to enable or disable this option.": "你可以選擇啟用或禁用這項功能", + "Enable analytics to help improve Boostnote": "允許數據分析以協助我們改進 Boostnote", + "Crowdfunding": "群眾募資", "Dear everyone,": "親愛的用戶:", - "Thank you for using Boostnote!": "謝謝你使用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,": "為了繼續支持這種發展,和滿足社區的期待,", + "Thank you for using Boostnote!": "謝謝你使用 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,": "為了繼續支持這種發展,和滿足社群的期待,", "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,": "十分感謝!", - "Boostnote maintainers": "Boostnote的維護人員", - "Support via OpenCollective": "在OpenCollective上支持我們", + "Boostnote maintainers": "Boostnote 的維護人員", + "Support via OpenCollective": "在 OpenCollective 上支持我們", "Language": "語言", "English": "English", "German": "German", "French": "French", - "Show \"Saved to Clipboard\" notification when copying": "複製的時候,顯示 \"已複製\" 提示", + "Show \"Saved to Clipboard\" notification when copying": "複製的時候,顯示 \"已複製到剪貼簿\" 的通知", "All Notes": "所有筆記", - "Starred": "星標收藏", + "Starred": "星號收藏", "Are you sure to ": "你確定要 ", " delete": " 刪除", "this folder?": "這個資料夾嗎?", "Confirm": "確認", "Cancel": "取消", - "Markdown Note": "Markdown筆記", - "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "創建文檔,清單,代碼塊甚至是Latex格式文檔", - "Snippet Note": "代碼筆記", - "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "創建代碼片段,支持多種語法代碼片段", - "Tab to switch format": "使用Tab鍵切換格式", - "Updated": "更新時間", - "Created": "創建時間", - "Alphabetically": "A~Z排序", - "Default View": "默認視圖", - "Compressed View": "列表視圖", - "Search": "搜索", + "Markdown Note": "Markdown 筆記", + "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "建立文件、清單,也可以使用程式碼區塊甚至是 Latex 區塊。", + "Snippet Note": "程式碼片段筆記", + "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "建立程式碼區塊片段。數個程式碼區塊可以合在同一個筆記裡。", + "Tab to switch format": "使用 Tab 鍵切換格式", + "Updated": "依更新時間排序", + "Created": "依建立時間排序", + "Alphabetically": "依字母排序", + "Default View": "預設顯示", + "Compressed View": "緊密顯示", + "Search": "搜尋", "Blog Type": "部落格類型", - "Blog Address": "部落格地址", - "Save": "保存", + "Blog Address": "部落格網址", + "Save": "儲存", "Auth": "Auth", "Authentication Method": "認證方法", "JWT": "JWT", "USER": "USER", "Token": "Token", - "Storage": "本地儲存", + "Storage": "本機儲存空間", "Hotkeys": "快捷鍵", "Show/Hide Boostnote": "顯示/隱藏 Boostnote", - "Restore": "恢復", + "Restore": "還原", "Permanent Delete": "永久刪除", "Confirm note deletion": "確認刪除筆記", - "This will permanently remove this note.": "永久地刪除這條筆記", - "Successfully applied!": "設置成功", + "This will permanently remove this note.": "這將會永久地刪除這條筆記", + "Successfully applied!": "設定成功", "Albanian": "Albanian", "Chinese (zh-CN)": "简体中文", "Chinese (zh-TW)": "繁體中文", @@ -142,11 +142,11 @@ "Spanish": "Spanish", "You have to save!": "你必須儲存一下!", "Russian": "Russian", - "Editor Rulers": "Editor Rulers", - "Enable": "開啟", - "Disable": "關閉", - "Sanitization": "代碼處理", - "Only allow secure html tags (recommended)": "只允許安全的html標籤(推薦)", + "Editor Rulers": "編輯器中顯示垂直尺規", + "Enable": "啟用", + "Disable": "禁用", + "Sanitization": "過濾 HTML 程式碼", + "Only allow secure html tags (recommended)": "只允許安全的 HTML 標籤 (建議)", "Allow styles": "允許樣式", - "Allow dangerous html tags": "允許危險的html標籤" + "Allow dangerous html tags": "允許危險的 HTML 標籤" } diff --git a/package.json b/package.json index 038d4c00..82c90896 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "striptags": "^2.2.1", "superagent": "^1.2.0", "superagent-promise": "^1.0.3", + "unique-slug": "2.0.0", "uuid": "^3.2.1" }, "devDependencies": { diff --git a/readme.md b/readme.md index b5d02729..928bedc9 100644 --- a/readme.md +++ b/readme.md @@ -25,7 +25,7 @@ Boostnote is an open source project. It's an independent project with its ongoin ## Community - [Facebook Group](https://www.facebook.com/groups/boostnote/) - [Twitter](https://twitter.com/boostnoteapp) -- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzI3NTIxMTQzMTQzLTUyYWZmZWM1YzcwYzQ5OWQ5YzA3Y2M2NzUzNmIwNzYzMjg5NmQyOGJlNzcyZDJhMGY0ZDc0ZjdlZDFhMDdiMWE) +- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzUxODgwMTc2MDg3LTgwZjA2Zjg3NjFlMzczNTVjNGMzZTk0MmIyNmE3ZjEwYTNhMTA0Y2Y4NDNlNWU4YjZlNmJiNGZhNDViOTA1ZjM) - [Blog](https://boostlog.io/tags/boostnote) - [Reddit](https://www.reddit.com/r/Boostnote/) diff --git a/resources/app.png b/resources/app.png index ac817bc6..ee1b3c93 100755 Binary files a/resources/app.png and b/resources/app.png differ diff --git a/resources/boostnote-install.png b/resources/boostnote-install.png index 6bb31b4d..980e672d 100755 Binary files a/resources/boostnote-install.png and b/resources/boostnote-install.png differ diff --git a/resources/boostnote-install@2x.png b/resources/boostnote-install@2x.png index 6b4dd5fb..e45e137f 100755 Binary files a/resources/boostnote-install@2x.png and b/resources/boostnote-install@2x.png differ diff --git a/resources/dmg.png b/resources/dmg.png index 49196123..3d2cd438 100755 Binary files a/resources/dmg.png and b/resources/dmg.png differ diff --git a/resources/finder.png b/resources/finder.png index 4caf9249..7adb292a 100644 Binary files a/resources/finder.png and b/resources/finder.png differ diff --git a/resources/repository/top.png b/resources/repository/top.png index ac8dc699..29dfc033 100644 Binary files a/resources/repository/top.png and b/resources/repository/top.png differ diff --git a/resources/tray-icon-dark.png b/resources/tray-icon-dark.png index 4aa26bfd..20b47832 100644 Binary files a/resources/tray-icon-dark.png and b/resources/tray-icon-dark.png differ diff --git a/resources/tray-icon-dark@2x.png b/resources/tray-icon-dark@2x.png index 5e58d5ef..7d43788d 100644 Binary files a/resources/tray-icon-dark@2x.png and b/resources/tray-icon-dark@2x.png differ diff --git a/resources/tray-icon-default.png b/resources/tray-icon-default.png index 08725fb7..942b0220 100644 Binary files a/resources/tray-icon-default.png and b/resources/tray-icon-default.png differ diff --git a/resources/tray-icon-default@2x.png b/resources/tray-icon-default@2x.png index c2a7e17e..9cf088d9 100644 Binary files a/resources/tray-icon-default@2x.png and b/resources/tray-icon-default@2x.png differ diff --git a/resources/tray-icon.png b/resources/tray-icon.png index 08725fb7..942b0220 100644 Binary files a/resources/tray-icon.png and b/resources/tray-icon.png differ diff --git a/resources/tray-icon@2x.png b/resources/tray-icon@2x.png index c2a7e17e..9cf088d9 100644 Binary files a/resources/tray-icon@2x.png and b/resources/tray-icon@2x.png differ diff --git a/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap b/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap index 0805edcd..0521ca0e 100644 --- a/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap +++ b/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap @@ -1,19 +1,26 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TagListItem renders correctly 1`] = ` - + +
`; diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js new file mode 100644 index 00000000..c90e1961 --- /dev/null +++ b/tests/dataApi/attachmentManagement.test.js @@ -0,0 +1,168 @@ +'use strict' + +jest.mock('fs') +const fs = require('fs') +const path = require('path') +const findStorage = require('browser/lib/findStorage') +jest.mock('unique-slug') +const uniqueSlug = require('unique-slug') +const mdurl = require('mdurl') + +const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement') + +it('should test that copyAttachment should throw an error if sourcePath or storageKey or noteKey are undefined', function () { + systemUnderTest.copyAttachment(undefined, 'storageKey').then(() => {}, error => { + expect(error).toBe('sourceFilePath has to be given') + }) + systemUnderTest.copyAttachment(null, 'storageKey', 'noteKey').then(() => {}, error => { + expect(error).toBe('sourceFilePath has to be given') + }) + systemUnderTest.copyAttachment('source', undefined, 'noteKey').then(() => {}, error => { + expect(error).toBe('storageKey has to be given') + }) + systemUnderTest.copyAttachment('source', null, 'noteKey').then(() => {}, error => { + expect(error).toBe('storageKey has to be given') + }) + systemUnderTest.copyAttachment('source', 'storageKey', null).then(() => {}, error => { + expect(error).toBe('noteKey has to be given') + }) + systemUnderTest.copyAttachment('source', 'storageKey', undefined).then(() => {}, error => { + expect(error).toBe('noteKey has to be given') + }) +}) + +it('should test that copyAttachment should throw an error if sourcePath dosen\'t exists', function () { + fs.existsSync = jest.fn() + fs.existsSync.mockReturnValue(false) + + systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then(() => {}, error => { + expect(error).toBe('source file does not exist') + expect(fs.existsSync).toHaveBeenCalledWith('path') + }) +}) + +it('should test that copyAttachment works correctly assuming correct working of fs', function () { + const dummyExtension = '.ext' + const sourcePath = 'path' + dummyExtension + const storageKey = 'storageKey' + const noteKey = 'noteKey' + const dummyUniquePath = 'dummyPath' + const dummyStorage = {path: 'dummyStoragePath'} + + fs.existsSync = jest.fn() + fs.existsSync.mockReturnValue(true) + fs.createReadStream = jest.fn() + fs.createReadStream.mockReturnValue({pipe: jest.fn()}) + fs.createWriteStream = jest.fn() + + findStorage.findStorage = jest.fn() + findStorage.findStorage.mockReturnValue(dummyStorage) + uniqueSlug.mockReturnValue(dummyUniquePath) + + systemUnderTest.copyAttachment(sourcePath, storageKey, noteKey).then( + function (newFileName) { + expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey) + expect(fs.createReadStream).toHaveBeenCalledWith(sourcePath) + expect(fs.existsSync).toHaveBeenCalledWith(sourcePath) + expect(fs.createReadStream().pipe).toHaveBeenCalled() + expect(fs.createWriteStream).toHaveBeenCalledWith(path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey, dummyUniquePath + dummyExtension)) + expect(newFileName).toBe(dummyUniquePath + dummyExtension) + }) +}) + +it('should test that copyAttachment creates a new folder if the attachment folder doesn\'t exist', function () { + const dummyStorage = {path: 'dummyStoragePath'} + const noteKey = 'noteKey' + const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER) + const attachmentFolderNoteKyPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey) + + fs.existsSync = jest.fn() + fs.existsSync.mockReturnValueOnce(true) + fs.existsSync.mockReturnValueOnce(false) + fs.existsSync.mockReturnValueOnce(false) + fs.mkdirSync = jest.fn() + + findStorage.findStorage = jest.fn() + findStorage.findStorage.mockReturnValue(dummyStorage) + uniqueSlug.mockReturnValue('dummyPath') + + systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then( + function () { + expect(fs.existsSync).toHaveBeenCalledWith(attachmentFolderPath) + expect(fs.mkdirSync).toHaveBeenCalledWith(attachmentFolderPath) + expect(fs.existsSync).toHaveBeenLastCalledWith(attachmentFolderNoteKyPath) + expect(fs.mkdirSync).toHaveBeenLastCalledWith(attachmentFolderNoteKyPath) + }) +}) + +it('should test that copyAttachment don\'t uses a random file name if not intended ', function () { + const dummyStorage = {path: 'dummyStoragePath'} + + fs.existsSync = jest.fn() + fs.existsSync.mockReturnValueOnce(true) + fs.existsSync.mockReturnValueOnce(false) + fs.mkdirSync = jest.fn() + + findStorage.findStorage = jest.fn() + findStorage.findStorage.mockReturnValue(dummyStorage) + uniqueSlug.mockReturnValue('dummyPath') + + systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey', false).then( + function (newFileName) { + expect(newFileName).toBe('path') + }) +}) + +it('should replace the all ":storage" path with the actual storage path', function () { + const storageFolder = systemUnderTest.DESTINATION_FOLDER + const testInput = + '\n' + + ' \n' + + ' //header\n' + + ' \n' + + ' \n' + + '

Headline

\n' + + '

\n' + + ' dummyImage.png\n' + + '

\n' + + '

\n' + + ' dummyPDF.pdf\n' + + '

\n' + + '

\n' + + ' dummyImage2.jpg\n' + + '

\n' + + ' \n' + + '' + const storagePath = '<>' + const expectedOutput = + '\n' + + ' \n' + + ' //header\n' + + ' \n' + + ' \n' + + '

Headline

\n' + + '

\n' + + ' dummyImage.png\n' + + '

\n' + + '

\n' + + ' dummyPDF.pdf\n' + + '

\n' + + '

\n' + + ' dummyImage2.jpg\n' + + '

\n' + + ' \n' + + '' + const actual = systemUnderTest.fixLocalURLS(testInput, storagePath) + expect(actual).toEqual(expectedOutput) +}) + +it('should test that generateAttachmentMarkdown works correct both with previews and without', function () { + const fileName = 'fileName' + const path = 'path' + let expected = `![${fileName}](${path})` + let actual = systemUnderTest.generateAttachmentMarkdown(fileName, path, true) + expect(actual).toEqual(expected) + expected = `[${fileName}](${path})` + actual = systemUnderTest.generateAttachmentMarkdown(fileName, path, false) + expect(actual).toEqual(expected) +}) diff --git a/yarn.lock b/yarn.lock index 62b6600a..a0e4d96f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8714,6 +8714,12 @@ uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" +unique-slug@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab" + dependencies: + imurmurhash "^0.1.4" + unique-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"