1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

Merge branch 'master' into Issue_1900

This commit is contained in:
ehhc
2018-05-15 20:26:10 +02:00
committed by GitHub
29 changed files with 213 additions and 81 deletions

View File

@@ -4,6 +4,7 @@ import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
@@ -15,21 +16,6 @@ const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', '
const buildCMRulers = (rulers, enableRulers) =>
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
function pass (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}
export default class CodeEditor extends React.Component {
constructor (props) {
super(props)
@@ -50,6 +36,9 @@ export default class CodeEditor extends React.Component {
el = el.parentNode
}
this.props.onBlur != null && this.props.onBlur(e)
const {storageKey, noteKey} = this.props
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
}
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
this.loadStyleHandler = (e) => {
@@ -229,7 +218,7 @@ export default class CodeEditor extends React.Component {
}
setMode (mode) {
let syntax = CodeMirror.findModeByName(pass(mode))
let syntax = CodeMirror.findModeByName(convertModeName(mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
this.editor.setOption('mode', syntax.mime)

View File

@@ -10,6 +10,7 @@ import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
@@ -122,7 +123,6 @@ if (!OSX) {
defaultFontFamily.unshift('meiryo')
}
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
export default class MarkdownPreview extends React.Component {
constructor (props) {
super(props)
@@ -430,7 +430,7 @@ export default class MarkdownPreview extends React.Component {
: 'default'
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
let syntax = CodeMirror.findModeByName(el.className)
let syntax = CodeMirror.findModeByName(convertModeName(el.className))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
CodeMirror.requireMode(syntax.mode, () => {
const content = htmlTextHelper.decodeEntities(el.innerHTML)

View File

@@ -104,6 +104,7 @@ body[data-theme="dark"]
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -117,6 +118,7 @@ body[data-theme="dark"]
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -165,9 +167,10 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color $ui-solarized-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -178,9 +181,10 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
&:active
transition 0.15s
background-color $ui-solarized-dark-button--active-backgroundColor
color $ui-solarized-dark-text-color
// background-color $ui-solarized-dark-button--active-backgroundColor
color $ui-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -192,11 +196,13 @@ body[data-theme="solarized-dark"]
.item-simple--active
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-button--active-backgroundColor
background-color $ui-solarized-dark-tag-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
color $ui-dark-text-color
.item-simple-bottom-time
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
@@ -207,11 +213,14 @@ body[data-theme="solarized-dark"]
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4
.item-simple-title
color $ui-dark-text-color
border-bottom $ui-dark-borderColor
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4
body[data-theme="monokai"]
.root
@@ -223,13 +232,14 @@ body[data-theme="monokai"]
background-color $ui-monokai-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color alpha($ui-monokai-button-backgroundColor, 60%)
color $ui-monokai-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-monokai-text-color
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#fff, 20%)
@@ -239,6 +249,7 @@ body[data-theme="monokai"]
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -254,6 +265,7 @@ body[data-theme="monokai"]
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
color $ui-monokai-text-color
@@ -265,8 +277,11 @@ body[data-theme="monokai"]
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4
.item-simple-title
color $ui-dark-text-color
border-bottom $ui-dark-borderColor
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4

View File

@@ -55,10 +55,10 @@ class SnippetTab extends React.Component {
this.handleRename()
break
case 27:
this.setState({
name: this.props.snippet.name,
this.setState((prevState, props) => ({
name: props.snippet.name,
isRenaming: false
})
}))
break
}
}

View File

@@ -0,0 +1,14 @@
export default function convertModeName (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}

View File

@@ -145,11 +145,13 @@ class Markdown {
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
)
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
return `${serverAddress}/${zippedCode}`
}
})

View File

@@ -58,7 +58,7 @@ class MarkdownNoteDetail extends React.Component {
}
componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (this.saveQueue != null) this.saveNow()
this.setState({
note: Object.assign({}, nextProps.note)

View File

@@ -18,6 +18,7 @@ import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle'
import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
@@ -29,21 +30,6 @@ import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
function pass (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}
const electron = require('electron')
const { remote } = electron
const { Menu, MenuItem, dialog } = remote
@@ -82,7 +68,7 @@ class SnippetNoteDetail extends React.Component {
}
componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (this.saveQueue != null) this.saveNow()
const nextNote = Object.assign({
description: ''
@@ -382,11 +368,11 @@ class SnippetNoteDetail extends React.Component {
name: mode
})
}
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
}
@@ -395,11 +381,11 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].mode = name
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
@@ -413,10 +399,10 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
}
@@ -611,17 +597,17 @@ class SnippetNoteDetail extends React.Component {
}
jumpNextTab () {
this.setState({
snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
}, () => {
this.setState(state => ({
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
}), () => {
this.focusEditor()
})
}
jumpPrevTab () {
this.setState({
snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
}, () => {
this.setState(state => ({
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
}), () => {
this.focusEditor()
})
}
@@ -677,7 +663,7 @@ class SnippetNoteDetail extends React.Component {
const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
return <div styleName='tabView'

View File

@@ -53,6 +53,7 @@ export const DEFAULT_CONFIG = {
latexInlineClose: '$',
latexBlockOpen: '$$',
latexBlockClose: '$$',
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
scrollPastEnd: false,
smartQuotes: true,
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'

View File

@@ -158,7 +158,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
/**
* @description Returns all attachment paths of the given markdown
* @param {String} markdownContent content in which the attachment paths should be found
* @returns {String[]} Array of the relativ paths (starting with :storage) of the attachments of the given markdown
* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
*/
function getAttachmentsInContent (markdownContent) {
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
@@ -202,6 +202,49 @@ function deleteAttachmentFolder (storageKey, noteKey) {
sander.rimrafSync(noteAttachmentPath)
}
/**
* @description Deletes all attachments stored in the attachment folder of the give not that are not referenced in the markdownContent
* @param markdownContent Content of the note. All unreferenced notes will be deleted
* @param storageKey StorageKey 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) {
const targetStorage = findStorage.findStorage(storageKey)
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
const attachmentsInNote = getAttachmentsInContent(markdownContent)
const attachmentsInNoteOnlyFileNames = []
if (attachmentsInNote) {
for (let i = 0; i < attachmentsInNote.length; i++) {
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
}
}
if (fs.existsSync(attachmentFolder)) {
fs.readdir(attachmentFolder, (err, files) => {
if (err) {
console.error("Error reading directory '" + attachmentFolder + "'. Error:")
console.error(err)
return
}
files.forEach(file => {
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
fs.unlink(absolutePathOfFile, (err) => {
if (err) {
console.error("Could not delete '%s'", absolutePathOfFile)
console.error(err)
return
}
console.info("File '" + absolutePathOfFile + "' deleted because it was not included in the content of the note")
})
}
})
})
} else {
console.info("Attachment folder ('" + attachmentFolder + "') did not exist..")
}
}
module.exports = {
copyAttachment,
fixLocalURLS,
@@ -212,6 +255,7 @@ module.exports = {
getAbsolutePathsOfAttachmentsInContent,
removeStorageAndNoteReferences,
deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote,
STORAGE_FOLDER_PLACEHOLDER,
DESTINATION_FOLDER
}

View File

@@ -6,7 +6,6 @@ const CSON = require('@rokt33r/season')
const keygen = require('browser/lib/keygen')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
const copyImage = require('./copyImage')
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
let oldStorage, newStorage
@@ -70,16 +69,17 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
.then(function moveImages (noteData) {
if (oldStorage.path === newStorage.path) return noteData
const searchImagesRegex = /!\[.*?]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
const searchImagesRegex = /!\[.*\]\(:storage\/(.+)\)/gi
let match = searchImagesRegex.exec(noteData.content)
const moveTasks = []
while (match != null) {
const [, filename] = match
const oldPath = path.join(oldStorage.path, 'images', filename)
const oldPath = path.join(oldStorage.path, 'attachments', filename)
const newPath = path.join(newStorage.path, 'attachments', filename)
// TODO: ehhc: attachmentManagement
moveTasks.push(
copyImage(oldPath, noteData.storage, false)
sander.copyFile(oldPath).to(newPath)
.then(() => {
fs.unlinkSync(oldPath)
})

View File

@@ -94,6 +94,7 @@ class UiTab extends React.Component {
latexInlineClose: this.refs.previewLatexInlineClose.value,
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
latexBlockClose: this.refs.previewLatexBlockClose.value,
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
smartQuotes: this.refs.previewSmartQuotes.checked,
sanitize: this.refs.previewSanitize.value
@@ -544,6 +545,19 @@ class UiTab extends React.Component {
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('PlantUML Server')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
ref='previewPlantUMLServerAddress'
value={config.preview.plantUMLServerAddress}
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-control'>
<button styleName='group-control-rightButton'

View File

@@ -256,7 +256,7 @@ function data (state = defaultDataMap(), action) {
let noteSet = state.storageNoteMap.get(note.storage)
noteSet = new Set(noteSet)
noteSet.add(uniqueKey)
state.folderNoteMap.set(folderKey, noteSet)
state.storageNoteMap.set(folderKey, noteSet)
}
// Update foldermap if folder changed or post created