From 73fbf49ba4cb3603b7f381ec9e3626b5efa0537d Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 21 Aug 2018 00:19:26 +0200 Subject: [PATCH 01/25] - show tags of note in alphabetical order - enable live count of notes --- browser/components/NoteItem.js | 16 ++++-- browser/components/TagListItem.js | 2 +- browser/main/Detail/MarkdownNoteDetail.js | 3 +- browser/main/Detail/SnippetNoteDetail.js | 1 + browser/main/Detail/TagSelect.js | 4 +- browser/main/NoteList/index.js | 1 + browser/main/SideNav/index.js | 18 +++++- browser/main/modals/PreferencesModal/UiTab.js | 55 +++++++++++++++---- locales/en.json | 4 +- locales/fr.json | 4 +- 10 files changed, 81 insertions(+), 27 deletions(-) diff --git a/browser/components/NoteItem.js b/browser/components/NoteItem.js index 5073dc73..b0731eca 100644 --- a/browser/components/NoteItem.js +++ b/browser/components/NoteItem.js @@ -24,16 +24,19 @@ const TagElement = ({ tagName }) => ( /** * @description Tag element list component. * @param {Array|null} tags + * @param {boolean} showTagsAlphabetically * @return {React.Component} */ -const TagElementList = tags => { +const TagElementList = (tags, showTagsAlphabetically) => { if (!isArray(tags)) { return [] } - const tagElements = tags.map(tag => TagElement({ tagName: tag })) - - return tagElements + if (showTagsAlphabetically) { + return _.sortBy(tags).map(tag => TagElement({ tagName: tag })) + } else { + return tags.map(tag => TagElement({ tagName: tag })) + } } /** @@ -55,7 +58,8 @@ const NoteItem = ({ pathname, storageName, folderName, - viewType + viewType, + showTagsAlphabetically }) => (
{note.tags.length > 0 - ? TagElementList(note.tags) + ? TagElementList(note.tags, showTagsAlphabetically) : handleClickTagListItem(name)}> {`# ${name}`} - {count} + {count !== 0 ? count : ''}
diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 82073162..705736e9 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -312,7 +312,7 @@ class MarkdownNoteDetail extends React.Component { } render () { - const { data, location } = this.props + const { data, location, config } = this.props const { note, editorType } = this.state const storageKey = note.storage const folderKey = note.folder @@ -363,6 +363,7 @@ class MarkdownNoteDetail extends React.Component { diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 652d1f53..3633ec85 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -744,6 +744,7 @@ class SnippetNoteDetail extends React.Component { this.handleChange(e)} />
diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index e251dd42..e51d5673 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -119,10 +119,10 @@ class TagSelect extends React.Component { } render () { - const { value, className } = this.props + const { value, className, showTagsAlphabetically } = this.props const tagList = _.isArray(value) - ? value.map((tag) => { + ? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => { return ( ) } diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index c4fa417b..d1292f68 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -19,6 +19,10 @@ import {SortableContainer} from 'react-sortable-hoc' import i18n from 'browser/lib/i18n' import context from 'browser/lib/context' +function findOne(haystack, arr) { + return arr.some(v => haystack.indexOf(v) >= 0) +} + class SideNav extends React.Component { // TODO: should not use electron stuff v0.7 @@ -144,12 +148,20 @@ class SideNav extends React.Component { tagListComponent () { const { data, location, config } = this.props - const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap) + const activeTags = this.getActiveTags(location.pathname) + const relatedTags = this.getRelatedTags(activeTags, data.noteMap) let tagList = _.sortBy(data.tagNoteMap.map( (tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) }) - ), ['name']).filter( + ).filter( tag => tag.size > 0 - ) + ), ['name']) + if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) { + const notesTags = data.noteMap.map(note => note.tags) + tagList = tagList.map(tag => { + tag.size = notesTags.filter(tags => tags.includes(tag.name) && findOne(tags, activeTags)).length + return tag + }) + } if (config.sortTagsBy === 'COUNTER') { tagList = _.sortBy(tagList, item => (0 - item.size)) } diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index aa3568e7..40269190 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -70,6 +70,8 @@ class UiTab extends React.Component { showCopyNotification: this.refs.showCopyNotification.checked, confirmDeletion: this.refs.confirmDeletion.checked, showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked, + showTagsAlphabetically: this.refs.showTagsAlphabetically.checked, + enableLiveNoteCounts: this.refs.enableLiveNoteCounts.checked, disableDirectWrite: this.refs.uiD2w != null ? this.refs.uiD2w.checked : false @@ -172,7 +174,9 @@ class UiTab extends React.Component {
{i18n.__('Interface')}
- {i18n.__('Interface Theme')} +
+ {i18n.__('Interface Theme')} +
this.handleUIChange(e)} @@ -221,16 +227,6 @@ class UiTab extends React.Component { {i18n.__('Show a confirmation dialog when deleting notes')}
-
- -
{ global.process.platform === 'win32' ?
@@ -246,6 +242,41 @@ class UiTab extends React.Component {
: null } +
Tags
+ +
+ +
+ +
+ +
+ +
+ +
+
Editor
diff --git a/locales/en.json b/locales/en.json index a9767492..d01a6175 100644 --- a/locales/en.json +++ b/locales/en.json @@ -175,5 +175,7 @@ "Allow styles": "Allow styles", "Allow dangerous html tags": "Allow dangerous html tags", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", - "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠" + "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", + "Show tags of a note in alphabetical order": "Show tags of a note in alphabetical order", + "Enable live count of notes": "Enable live count of notes" } diff --git a/locales/fr.json b/locales/fr.json index 8b880aa6..33a7c503 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -152,5 +152,7 @@ "Allow styles": "Accepter les styles", "Allow dangerous html tags": "Accepter les tags html dangereux", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.", - "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠" + "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠", + "Show tags of a note in alphabetical order": "Afficher les tags d'une note par ordre alphabétique", + "Enable live count of notes": "Activer le comptage live des notes" } From 7cde30d3522a4c23d6995ccb68393a12ab6c85e1 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 21 Aug 2018 00:24:03 +0200 Subject: [PATCH 02/25] fix lint errors --- browser/main/SideNav/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index d1292f68..0dd34989 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -19,8 +19,8 @@ import {SortableContainer} from 'react-sortable-hoc' import i18n from 'browser/lib/i18n' import context from 'browser/lib/context' -function findOne(haystack, arr) { - return arr.some(v => haystack.indexOf(v) >= 0) +function findOne (haystack, arr) { + return arr.some(v => haystack.indexOf(v) >= 0) } class SideNav extends React.Component { From 3bdc88cecb6ed117312e2860e3c84afc5b803d69 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 25 Aug 2018 23:14:05 +0200 Subject: [PATCH 03/25] fixing sanitization of inline html like () #1992 --- browser/lib/markdown-it-sanitize-html.js | 86 +++++++++++++++++++++++- browser/lib/markdown.js | 6 +- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/browser/lib/markdown-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js index 05e5e7be..ea27bfa0 100644 --- a/browser/lib/markdown-it-sanitize-html.js +++ b/browser/lib/markdown-it-sanitize-html.js @@ -2,6 +2,7 @@ import sanitizeHtml from 'sanitize-html' import { escapeHtmlCharacters } from './utils' +import url from 'url' module.exports = function sanitizePlugin (md, options) { options = options || {} @@ -25,7 +26,7 @@ module.exports = function sanitizePlugin (md, options) { const inlineTokens = state.tokens[tokenIdx].children for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) { if (inlineTokens[childIdx].type === 'html_inline') { - inlineTokens[childIdx].content = sanitizeHtml( + inlineTokens[childIdx].content = sanitizeInline( inlineTokens[childIdx].content, options ) @@ -35,3 +36,86 @@ module.exports = function sanitizePlugin (md, options) { } }) } + +const tag_regex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:="(?:[^\"]+)\")?)*)\s*>|<\/([A-Z][A-Z0-9]*)\s*>/i +const attributes_regex = /([A-Z][A-Z0-9]*)(="[^\"]+\")?/ig + +function sanitizeInline(html, options) { + let match = tag_regex.exec(html) + if (!match) { + return '' + } + + const { allowedTags, allowedAttributes, allowedIframeHostnames, selfClosing, allowedSchemesAppliedToAttributes } = options + + if (match[1] !== null) { + // opening tag + const tag = match[1].toLowerCase() + if (allowedTags.indexOf(tag) === -1) { + return '' + } + + const attributes = match[2] + + let attrs = '' + let name + let value + + while ((match = attributes_regex.exec(attributes))) { + name = match[1].toLowerCase() + value = match[2] + + if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) { + if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) { + if (naughtyHRef(value) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value))) { + continue + } + } + + attrs += ` ${name}${value}` + } + } + + if (selfClosing.indexOf(tag)) { + return '<' + tag + attrs + ' />' + } else { + return '<' + tag + attrs + '>' + } + } else { + // closing tag + if (allowedTags.indexOf(match[3].toLowerCase()) !== -1) { + return html + } else { + return '' + } + } +} + +function naughtyHRef(name, href, options) { + href = href.replace(/[\x00-\x20]+/g, '') + href = href.replace(/<\!\-\-.*?\-\-\>/g, '') + + const matches = href.match(/^([a-zA-Z]+)\:/) + if (!matches) { + if (href.match(/^[\/\\]{2}/)) { + return !options.allowProtocolRelative + } + + // No scheme + return false + } + + const scheme = matches[1].toLowerCase() + + return options.allowedSchemes.indexOf(scheme) === -1 +} + +function naughtyIFrame(src) { + try { + const parsed = url.parse(src, false, true) + + return allowedIframeHostnames.index(parsed.hostname) === -1 + } catch (e) { + return true + } +} \ No newline at end of file diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 49fd2f86..90b1ccea 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -105,7 +105,11 @@ class Markdown { 'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'], 'input': ['type', 'id', 'checked'] }, - allowedIframeHostnames: ['www.youtube.com'] + allowedIframeHostnames: ['www.youtube.com'], + selfClosing: [ 'img', 'br', 'hr', 'input' ], + allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ], + allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ], + allowProtocolRelative: true }) } From fabc975b2025c12402312862c26abfb12bcc00b4 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 25 Aug 2018 23:36:43 +0200 Subject: [PATCH 04/25] - fix lint errors - correctly parse self-closed tag - fix naughty functions --- browser/lib/markdown-it-sanitize-html.js | 46 ++++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/browser/lib/markdown-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js index ea27bfa0..9bdd3034 100644 --- a/browser/lib/markdown-it-sanitize-html.js +++ b/browser/lib/markdown-it-sanitize-html.js @@ -37,45 +37,45 @@ module.exports = function sanitizePlugin (md, options) { }) } -const tag_regex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:="(?:[^\"]+)\")?)*)\s*>|<\/([A-Z][A-Z0-9]*)\s*>/i -const attributes_regex = /([A-Z][A-Z0-9]*)(="[^\"]+\")?/ig +const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:="(?:[^\"]+)\")?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i +const attributesRegex = /([A-Z][A-Z0-9]*)(="[^\"]+\")?/ig -function sanitizeInline(html, options) { - let match = tag_regex.exec(html) +function sanitizeInline (html, options) { + let match = tagRegex.exec(html) if (!match) { return '' } - - const { allowedTags, allowedAttributes, allowedIframeHostnames, selfClosing, allowedSchemesAppliedToAttributes } = options - - if (match[1] !== null) { + + const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options + + if (match[1] !== undefined) { // opening tag const tag = match[1].toLowerCase() if (allowedTags.indexOf(tag) === -1) { return '' } - + const attributes = match[2] - + let attrs = '' let name let value - - while ((match = attributes_regex.exec(attributes))) { + + while ((match = attributesRegex.exec(attributes))) { name = match[1].toLowerCase() value = match[2] - + if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) { if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) { - if (naughtyHRef(value) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value))) { + if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) { continue } } - + attrs += ` ${name}${value}` } } - + if (selfClosing.indexOf(tag)) { return '<' + tag + attrs + ' />' } else { @@ -91,10 +91,10 @@ function sanitizeInline(html, options) { } } -function naughtyHRef(name, href, options) { - href = href.replace(/[\x00-\x20]+/g, '') +function naughtyHRef (href, options) { + // href = href.replace(/[\x00-\x20]+/g, '') href = href.replace(/<\!\-\-.*?\-\-\>/g, '') - + const matches = href.match(/^([a-zA-Z]+)\:/) if (!matches) { if (href.match(/^[\/\\]{2}/)) { @@ -110,12 +110,12 @@ function naughtyHRef(name, href, options) { return options.allowedSchemes.indexOf(scheme) === -1 } -function naughtyIFrame(src) { +function naughtyIFrame (src, options) { try { const parsed = url.parse(src, false, true) - - return allowedIframeHostnames.index(parsed.hostname) === -1 + + return options.allowedIframeHostnames.index(parsed.hostname) === -1 } catch (e) { return true } -} \ No newline at end of file +} From 2a838ebb0b46bad08f54f46256396fac1c52c02a Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sun, 26 Aug 2018 00:14:29 +0200 Subject: [PATCH 05/25] fixing single quoted attributes --- browser/lib/markdown-it-sanitize-html.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/browser/lib/markdown-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js index 9bdd3034..ce6c5e29 100644 --- a/browser/lib/markdown-it-sanitize-html.js +++ b/browser/lib/markdown-it-sanitize-html.js @@ -37,8 +37,8 @@ module.exports = function sanitizePlugin (md, options) { }) } -const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:="(?:[^\"]+)\")?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i -const attributesRegex = /([A-Z][A-Z0-9]*)(="[^\"]+\")?/ig +const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i +const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig function sanitizeInline (html, options) { let match = tagRegex.exec(html) @@ -63,7 +63,7 @@ function sanitizeInline (html, options) { while ((match = attributesRegex.exec(attributes))) { name = match[1].toLowerCase() - value = match[2] + value = match[3] if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) { if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) { @@ -72,7 +72,10 @@ function sanitizeInline (html, options) { } } - attrs += ` ${name}${value}` + attrs += ` ${name}` + if (match[2]) { + attrs += `="${value}"` + } } } @@ -83,7 +86,7 @@ function sanitizeInline (html, options) { } } else { // closing tag - if (allowedTags.indexOf(match[3].toLowerCase()) !== -1) { + if (allowedTags.indexOf(match[4].toLowerCase()) !== -1) { return html } else { return '' From 5006aaae38d409aab09d0b9b1bd73e19be09959b Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 28 Aug 2018 01:44:33 +0200 Subject: [PATCH 06/25] fix live note counts when multiple tags are selected --- browser/main/SideNav/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index 0dd34989..65f76691 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -19,8 +19,8 @@ import {SortableContainer} from 'react-sortable-hoc' import i18n from 'browser/lib/i18n' import context from 'browser/lib/context' -function findOne (haystack, arr) { - return arr.some(v => haystack.indexOf(v) >= 0) +function matchActiveTags (tags, activeTags) { + return _.every(activeTags, v => tags.indexOf(v) >= 0) } class SideNav extends React.Component { @@ -158,7 +158,7 @@ class SideNav extends React.Component { if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) { const notesTags = data.noteMap.map(note => note.tags) tagList = tagList.map(tag => { - tag.size = notesTags.filter(tags => tags.includes(tag.name) && findOne(tags, activeTags)).length + tag.size = notesTags.filter(tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)).length return tag }) } From 8b4a9dd3259b6935e9fc2c6b346ca62d0ee02cdb Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Wed, 29 Aug 2018 19:28:09 +0200 Subject: [PATCH 07/25] add `saveTagsAlphabetically` option --- browser/main/Detail/MarkdownNoteDetail.js | 1 + browser/main/Detail/SnippetNoteDetail.js | 1 + browser/main/Detail/TagSelect.js | 10 ++++++++-- browser/main/modals/PreferencesModal/UiTab.js | 12 ++++++++++++ locales/en.json | 1 + locales/fr.json | 1 + 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 705736e9..1e30d28b 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -363,6 +363,7 @@ class MarkdownNoteDetail extends React.Component { diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 3633ec85..4a54e7c4 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -744,6 +744,7 @@ class SnippetNoteDetail extends React.Component { this.handleChange(e)} /> diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index e51d5673..9bfbabbe 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -82,8 +82,14 @@ class TagSelect extends React.Component { value = _.isArray(value) ? value.slice() : [] - value.push(newTag) - value = _.uniq(value) + + if (!_.includes(value, newTag)) { + value.push(newTag) + } + + if (this.props.saveTagsAlphabetically) { + value = _.sortBy(value) + } this.setState({ newTag: '' diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 40269190..a53281bc 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -71,6 +71,7 @@ class UiTab extends React.Component { confirmDeletion: this.refs.confirmDeletion.checked, showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked, showTagsAlphabetically: this.refs.showTagsAlphabetically.checked, + saveTagsAlphabetically: this.refs.saveTagsAlphabetically.checked, enableLiveNoteCounts: this.refs.enableLiveNoteCounts.checked, disableDirectWrite: this.refs.uiD2w != null ? this.refs.uiD2w.checked @@ -244,6 +245,17 @@ class UiTab extends React.Component { }
Tags
+
+ +
+
- +