From 762b4e88f888ce104584293c10ac5d9ea21ad6de Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 14 Nov 2022 17:16:59 +0000 Subject: [PATCH 01/13] All: Security: Fix XSS when a specially crafted string is passed to the renderer --- packages/app-cli/tests/md_to_html/sanitize_12.html | 1 + packages/app-cli/tests/md_to_html/sanitize_12.md | 3 +++ packages/renderer/MdToHtml.ts | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 packages/app-cli/tests/md_to_html/sanitize_12.html create mode 100644 packages/app-cli/tests/md_to_html/sanitize_12.md diff --git a/packages/app-cli/tests/md_to_html/sanitize_12.html b/packages/app-cli/tests/md_to_html/sanitize_12.html new file mode 100644 index 0000000000..c8a5e8c242 --- /dev/null +++ b/packages/app-cli/tests/md_to_html/sanitize_12.html @@ -0,0 +1 @@ +
ts
ts
diff --git a/packages/app-cli/tests/md_to_html/sanitize_12.md b/packages/app-cli/tests/md_to_html/sanitize_12.md new file mode 100644 index 0000000000..22f71bb68f --- /dev/null +++ b/packages/app-cli/tests/md_to_html/sanitize_12.md @@ -0,0 +1,3 @@ +```"> +ts +``` diff --git a/packages/renderer/MdToHtml.ts b/packages/renderer/MdToHtml.ts index 333c0e7cd3..1e57d69931 100644 --- a/packages/renderer/MdToHtml.ts +++ b/packages/renderer/MdToHtml.ts @@ -8,6 +8,8 @@ import { RenderResult, RenderResultPluginAsset } from './MarkupToHtml'; import { Options as NoteStyleOptions } from './noteStyle'; import hljs from './highlight'; +const Entities = require('html-entities').AllHtmlEntities; +const htmlentities = new Entities().encode; const MarkdownIt = require('markdown-it'); const md5 = require('md5'); @@ -482,7 +484,7 @@ export default class MdToHtml { // The strings includes the last \n that is part of the fence, // so we remove it because we need the exact code in the source block const trimmedStr = this.removeLastNewLine(str); - const sourceBlockHtml = `
${markdownIt.utils.escapeHtml(trimmedStr)}
`; + const sourceBlockHtml = `
${markdownIt.utils.escapeHtml(trimmedStr)}
`; if (this.shouldSkipHighlighting(trimmedStr, lang)) { outputCodeHtml = markdownIt.utils.escapeHtml(trimmedStr); From 8eef7c75e42085f0334256426c606a7b19decd4a Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 14 Nov 2022 17:22:06 +0000 Subject: [PATCH 02/13] Desktop release v2.9.13 --- packages/app-desktop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index d2df4c3d7e..e1691c1109 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -1,6 +1,6 @@ { "name": "@joplin/app-desktop", - "version": "2.9.12", + "version": "2.9.13", "description": "Joplin for Desktop", "main": "main.js", "private": true, From 275851091acb38a15c68634c24887da142558020 Mon Sep 17 00:00:00 2001 From: Kenichi Kobayashi Date: Tue, 15 Nov 2022 02:25:41 +0900 Subject: [PATCH 03/13] Desktop: Fixes #6416: Switching a note using Sidebar is slow and grayed out (#6430) --- .../app-desktop/gui/NoteEditor/NoteEditor.tsx | 9 +++++--- .../NoteEditor/utils/useEffectiveNoteId.ts | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.ts diff --git a/packages/app-desktop/gui/NoteEditor/NoteEditor.tsx b/packages/app-desktop/gui/NoteEditor/NoteEditor.tsx index 3e318af724..048fcff27f 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteEditor.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteEditor.tsx @@ -12,6 +12,7 @@ import useWindowCommandHandler from './utils/useWindowCommandHandler'; import useDropHandler from './utils/useDropHandler'; import useMarkupToHtml from './utils/useMarkupToHtml'; import useFormNote, { OnLoadEvent } from './utils/useFormNote'; +import useEffectiveNoteId from './utils/useEffectiveNoteId'; import useFolder from './utils/useFolder'; import styles_ from './styles'; import { NoteEditorProps, FormNote, ScrollOptions, ScrollOptionTypes, OnChangeEvent, NoteBodyEditorProps, AllAssetsOptions } from './utils/types'; @@ -66,9 +67,11 @@ function NoteEditor(props: NoteEditorProps) { setTitleHasBeenManuallyChanged(false); }, []); + const effectiveNoteId = useEffectiveNoteId(props); + const { formNote, setFormNote, isNewNote, resourceInfos } = useFormNote({ syncStarted: props.syncStarted, - noteId: props.noteId, + noteId: effectiveNoteId, isProvisional: props.isProvisional, titleInputRef: titleInputRef, editorRef: editorRef, @@ -192,7 +195,7 @@ function NoteEditor(props: NoteEditorProps) { setScrollWhenReady({ type: props.selectedNoteHash ? ScrollOptionTypes.Hash : ScrollOptionTypes.Percent, - value: props.selectedNoteHash ? props.selectedNoteHash : props.lastEditorScrollPercents[props.noteId] || 0, + value: props.selectedNoteHash ? props.selectedNoteHash : props.lastEditorScrollPercents[formNote.id] || 0, }); void ResourceEditWatcher.instance().stopWatchingAll(); @@ -549,7 +552,7 @@ function NoteEditor(props: NoteEditorProps) { } } - if (formNote.encryption_applied || !formNote.id || !props.noteId) { + if (formNote.encryption_applied || !formNote.id || !effectiveNoteId) { return renderNoNotes(styles.root); } diff --git a/packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.ts b/packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.ts new file mode 100644 index 0000000000..3852d9d485 --- /dev/null +++ b/packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.ts @@ -0,0 +1,21 @@ +import { useEffect, useRef } from 'react'; +import { NoteEditorProps } from './types'; + +export default function useEffectiveNoteId(props: NoteEditorProps) { + // When a notebook is changed without any selected note, + // no note is selected for a moment, and then a new note gets selected. + // In this short transient period, the last displayed note id should temporarily + // be used to prevent NoteEditor's body from being unmounted. + // See https://github.com/laurent22/joplin/issues/6416 + // and https://github.com/laurent22/joplin/pull/6430 for details. + + const lastDisplayedNoteId = useRef(null); + const whenNoteIdIsTransientlyAbsent = !props.noteId && props.notes.length > 0; + const effectiveNoteId = whenNoteIdIsTransientlyAbsent ? lastDisplayedNoteId.current : props.noteId; + + useEffect(() => { + if (props.noteId) lastDisplayedNoteId.current = props.noteId; + }, [props.noteId]); + + return effectiveNoteId; +} From f008f080f16ab1a5cb8e6c6900b72c888a553876 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 14 Nov 2022 17:26:54 +0000 Subject: [PATCH 04/13] Desktop release v2.9.14 --- .../NoteEditor/utils/useEffectiveNoteId.js | 20 ++ packages/app-desktop/gui/PromptDialog.js | 219 ++++++++++++++++++ packages/app-desktop/package.json | 2 +- 3 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js create mode 100644 packages/app-desktop/gui/PromptDialog.js diff --git a/packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js b/packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js new file mode 100644 index 0000000000..f11fdb2a59 --- /dev/null +++ b/packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js @@ -0,0 +1,20 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const react_1 = require('react'); +function useEffectiveNoteId(props) { + // When a notebook is changed without any selected note, + // no note is selected for a moment, and then a new note gets selected. + // In this short transient period, the last displayed note id should temporarily + // be used to prevent NoteEditor's body from being unmounted. + // See https://github.com/laurent22/joplin/issues/6416 + // and https://github.com/laurent22/joplin/pull/6430 for details. + const lastDisplayedNoteId = react_1.useRef(null); + const whenNoteIdIsTransientlyAbsent = !props.noteId && props.notes.length > 0; + const effectiveNoteId = whenNoteIdIsTransientlyAbsent ? lastDisplayedNoteId.current : props.noteId; + react_1.useEffect(() => { + if (props.noteId) { lastDisplayedNoteId.current = props.noteId; } + }, [props.noteId]); + return effectiveNoteId; +} +exports.default = useEffectiveNoteId; +// # sourceMappingURL=useEffectiveNoteId.js.map diff --git a/packages/app-desktop/gui/PromptDialog.js b/packages/app-desktop/gui/PromptDialog.js new file mode 100644 index 0000000000..037ca4103c --- /dev/null +++ b/packages/app-desktop/gui/PromptDialog.js @@ -0,0 +1,219 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const React = require('react'); +const locale_1 = require('@joplin/lib/locale'); +const theme_1 = require('@joplin/lib/theme'); +const time_1 = require('@joplin/lib/time'); +const Datetime = require('react-datetime').default; +const creatable_1 = require('react-select/creatable'); +const react_select_1 = require('react-select'); +const animated_1 = require('react-select/animated'); +class PromptDialog extends React.Component { + constructor(props) { + super(props); + this.answerInput_ = React.createRef(); + } + UNSAFE_componentWillMount() { + this.setState({ + visible: false, + answer: this.props.defaultValue ? this.props.defaultValue : '', + }); + this.focusInput_ = true; + } + UNSAFE_componentWillReceiveProps(newProps) { + if ('visible' in newProps && newProps.visible !== this.props.visible) { + this.setState({ visible: newProps.visible }); + if (newProps.visible) { this.focusInput_ = true; } + } + if ('defaultValue' in newProps && newProps.defaultValue !== this.props.defaultValue) { + this.setState({ answer: newProps.defaultValue }); + } + } + componentDidUpdate() { + if (this.focusInput_ && this.answerInput_.current) { this.answerInput_.current.focus(); } + this.focusInput_ = false; + } + styles(themeId, width, height, visible) { + const styleKey = `${themeId}_${width}_${height}_${visible}`; + if (styleKey === this.styleKey_) { return this.styles_; } + const theme = theme_1.themeStyle(themeId); + this.styleKey_ = styleKey; + this.styles_ = {}; + const paddingTop = 20; + this.styles_.modalLayer = { + zIndex: 9999, + position: 'absolute', + top: 0, + left: 0, + width: width, + height: height, + backgroundColor: 'rgba(0,0,0,0.6)', + display: visible ? 'flex' : 'none', + alignItems: 'flex-start', + justifyContent: 'center', + paddingTop: `${paddingTop}px`, + }; + this.styles_.promptDialog = { + backgroundColor: theme.backgroundColor, + padding: 16, + display: 'inline-block', + maxWidth: width * 0.5, + boxShadow: '6px 6px 20px rgba(0,0,0,0.5)', + }; + this.styles_.button = { + minWidth: theme.buttonMinWidth, + minHeight: theme.buttonMinHeight, + marginLeft: 5, + color: theme.color, + backgroundColor: theme.backgroundColor, + border: '1px solid', + borderColor: theme.dividerColor, + }; + this.styles_.label = { + marginRight: 5, + fontSize: theme.fontSize, + color: theme.color, + fontFamily: theme.fontFamily, + verticalAlign: 'middle', + }; + this.styles_.input = { + width: 0.5 * width, + maxWidth: 400, + color: theme.color, + backgroundColor: theme.backgroundColor, + border: '1px solid', + borderColor: theme.dividerColor, + }; + this.styles_.select = { + control: (provided) => Object.assign(provided, { + minWidth: width * 0.2, + maxWidth: width * 0.5, + fontFamily: theme.fontFamily, + }), + input: (provided) => Object.assign(provided, { + minWidth: '20px', + color: theme.color, + }), + menu: (provided) => Object.assign(provided, { + color: theme.color, + fontFamily: theme.fontFamily, + backgroundColor: theme.backgroundColor, + }), + option: (provided, state) => Object.assign(provided, { + color: theme.color, + fontFamily: theme.fontFamily, + paddingLeft: `${10 + (state.data.indentDepth || 0) * 20}px`, + }), + multiValueLabel: (provided) => Object.assign(provided, { + fontFamily: theme.fontFamily, + }), + multiValueRemove: (provided) => Object.assign(provided, { + color: theme.color, + }), + }; + this.styles_.selectTheme = (tagTheme) => Object.assign(tagTheme, { + borderRadius: 2, + colors: Object.assign(tagTheme.colors, { + primary: theme.raisedBackgroundColor, + primary25: theme.raisedBackgroundColor, + neutral0: theme.backgroundColor, + neutral5: theme.backgroundColor, + neutral10: theme.raisedBackgroundColor, + neutral20: theme.raisedBackgroundColor, + neutral30: theme.raisedBackgroundColor, + neutral40: theme.color, + neutral50: theme.color, + neutral60: theme.color, + neutral70: theme.color, + neutral80: theme.color, + neutral90: theme.color, + danger: theme.backgroundColor, + dangerLight: theme.colorError2, + }), + }); + this.styles_.desc = Object.assign({}, theme.textStyle, { + marginTop: 10, + }); + return this.styles_; + } + render() { + const style = this.props.style; + const theme = theme_1.themeStyle(this.props.themeId); + const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel']; + const styles = this.styles(this.props.themeId, style.width, style.height, this.state.visible); + const onClose = (accept, buttonType = null) => { + if (this.props.onClose) { + let outputAnswer = this.state.answer; + if (this.props.inputType === 'datetime') { + // outputAnswer = anythingToDate(outputAnswer); + outputAnswer = time_1.default.anythingToDateTime(outputAnswer); + } + this.props.onClose(accept ? outputAnswer : null, buttonType); + } + this.setState({ visible: false, answer: '' }); + }; + const onChange = (event) => { + this.setState({ answer: event.target.value }); + }; + // const anythingToDate = (o) => { + // if (o && o.toDate) return o.toDate(); + // if (!o) return null; + // let m = moment(o, time.dateTimeFormat()); + // if (m.isValid()) return m.toDate(); + // m = moment(o, time.dateFormat()); + // return m.isValid() ? m.toDate() : null; + // } + const onDateTimeChange = (momentObject) => { + this.setState({ answer: momentObject }); + }; + const onSelectChange = (newValue) => { + this.setState({ answer: newValue }); + this.focusInput_ = true; + }; + const onKeyDown = (event) => { + if (event.key === 'Enter') { + if (this.props.inputType !== 'tags' && this.props.inputType !== 'dropdown') { + onClose(true); + } else if (this.answerInput_.current && !this.answerInput_.current.state.menuIsOpen) { + // The menu will be open if the user is selecting a new item + onClose(true); + } + } else if (event.key === 'Escape') { + onClose(false); + } + }; + const descComp = this.props.description ? React.createElement('div', { style: styles.desc }, this.props.description) : null; + let inputComp = null; + if (this.props.inputType === 'datetime') { + inputComp = React.createElement(Datetime, { className: 'datetime-picker', value: this.state.answer, inputProps: { style: styles.input }, dateFormat: time_1.default.dateFormat(), timeFormat: time_1.default.timeFormat(), onChange: (momentObject) => onDateTimeChange(momentObject) }); + } else if (this.props.inputType === 'tags') { + inputComp = React.createElement(creatable_1.default, { className: 'tag-selector', styles: styles.select, theme: styles.selectTheme, ref: this.answerInput_, value: this.state.answer, placeholder: '', components: animated_1.default(), isMulti: true, isClearable: false, backspaceRemovesValue: true, options: this.props.autocomplete, onChange: onSelectChange, onKeyDown: (event) => onKeyDown(event) }); + } else if (this.props.inputType === 'dropdown') { + inputComp = React.createElement(react_select_1.default, { className: 'item-selector', styles: styles.select, theme: styles.selectTheme, ref: this.answerInput_, components: animated_1.default(), value: this.props.answer, defaultValue: this.props.defaultValue, isClearable: false, options: this.props.autocomplete, onChange: onSelectChange, onKeyDown: (event) => onKeyDown(event) }); + } else { + inputComp = React.createElement('input', { style: styles.input, ref: this.answerInput_, value: this.state.answer, type: 'text', onChange: event => onChange(event), onKeyDown: event => onKeyDown(event) }); + } + const buttonComps = []; + if (buttonTypes.indexOf('create') >= 0) { + buttonComps.push(React.createElement('button', { key: 'create', disabled: !this.state.answer, style: styles.button, onClick: () => onClose(true, 'create') }, locale_1._('Create'))); + } + if (buttonTypes.indexOf('ok') >= 0) { + buttonComps.push(React.createElement('button', { key: 'ok', disabled: !this.state.answer, style: styles.button, onClick: () => onClose(true, 'ok') }, locale_1._('OK'))); + } + if (buttonTypes.indexOf('cancel') >= 0) { + buttonComps.push(React.createElement('button', { key: 'cancel', style: styles.button, onClick: () => onClose(false, 'cancel') }, locale_1._('Cancel'))); + } + if (buttonTypes.indexOf('clear') >= 0) { + buttonComps.push(React.createElement('button', { key: 'clear', style: styles.button, onClick: () => onClose(false, 'clear') }, locale_1._('Clear'))); + } + return (React.createElement('div', { className: 'modal-layer', style: styles.modalLayer }, + React.createElement('div', { className: 'modal-dialog', style: styles.promptDialog }, + React.createElement('label', { style: styles.label }, this.props.label ? this.props.label : ''), + React.createElement('div', { style: { display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor } }, + inputComp, + descComp), + React.createElement('div', { style: { textAlign: 'right', marginTop: 10 } }, buttonComps)))); + } +} +exports.default = PromptDialog; +// # sourceMappingURL=PromptDialog.js.map diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index e1691c1109..d3c816691e 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -1,6 +1,6 @@ { "name": "@joplin/app-desktop", - "version": "2.9.13", + "version": "2.9.14", "description": "Joplin for Desktop", "main": "main.js", "private": true, From 7364e82c21e56ec647a2871037fb482ec0c62e26 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 14 Nov 2022 17:27:48 +0000 Subject: [PATCH 05/13] Revert "Desktop release v2.9.14" This reverts commit f008f080f16ab1a5cb8e6c6900b72c888a553876. --- .../NoteEditor/utils/useEffectiveNoteId.js | 20 -- packages/app-desktop/gui/PromptDialog.js | 219 ------------------ packages/app-desktop/package.json | 2 +- 3 files changed, 1 insertion(+), 240 deletions(-) delete mode 100644 packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js delete mode 100644 packages/app-desktop/gui/PromptDialog.js diff --git a/packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js b/packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js deleted file mode 100644 index f11fdb2a59..0000000000 --- a/packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); -const react_1 = require('react'); -function useEffectiveNoteId(props) { - // When a notebook is changed without any selected note, - // no note is selected for a moment, and then a new note gets selected. - // In this short transient period, the last displayed note id should temporarily - // be used to prevent NoteEditor's body from being unmounted. - // See https://github.com/laurent22/joplin/issues/6416 - // and https://github.com/laurent22/joplin/pull/6430 for details. - const lastDisplayedNoteId = react_1.useRef(null); - const whenNoteIdIsTransientlyAbsent = !props.noteId && props.notes.length > 0; - const effectiveNoteId = whenNoteIdIsTransientlyAbsent ? lastDisplayedNoteId.current : props.noteId; - react_1.useEffect(() => { - if (props.noteId) { lastDisplayedNoteId.current = props.noteId; } - }, [props.noteId]); - return effectiveNoteId; -} -exports.default = useEffectiveNoteId; -// # sourceMappingURL=useEffectiveNoteId.js.map diff --git a/packages/app-desktop/gui/PromptDialog.js b/packages/app-desktop/gui/PromptDialog.js deleted file mode 100644 index 037ca4103c..0000000000 --- a/packages/app-desktop/gui/PromptDialog.js +++ /dev/null @@ -1,219 +0,0 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); -const React = require('react'); -const locale_1 = require('@joplin/lib/locale'); -const theme_1 = require('@joplin/lib/theme'); -const time_1 = require('@joplin/lib/time'); -const Datetime = require('react-datetime').default; -const creatable_1 = require('react-select/creatable'); -const react_select_1 = require('react-select'); -const animated_1 = require('react-select/animated'); -class PromptDialog extends React.Component { - constructor(props) { - super(props); - this.answerInput_ = React.createRef(); - } - UNSAFE_componentWillMount() { - this.setState({ - visible: false, - answer: this.props.defaultValue ? this.props.defaultValue : '', - }); - this.focusInput_ = true; - } - UNSAFE_componentWillReceiveProps(newProps) { - if ('visible' in newProps && newProps.visible !== this.props.visible) { - this.setState({ visible: newProps.visible }); - if (newProps.visible) { this.focusInput_ = true; } - } - if ('defaultValue' in newProps && newProps.defaultValue !== this.props.defaultValue) { - this.setState({ answer: newProps.defaultValue }); - } - } - componentDidUpdate() { - if (this.focusInput_ && this.answerInput_.current) { this.answerInput_.current.focus(); } - this.focusInput_ = false; - } - styles(themeId, width, height, visible) { - const styleKey = `${themeId}_${width}_${height}_${visible}`; - if (styleKey === this.styleKey_) { return this.styles_; } - const theme = theme_1.themeStyle(themeId); - this.styleKey_ = styleKey; - this.styles_ = {}; - const paddingTop = 20; - this.styles_.modalLayer = { - zIndex: 9999, - position: 'absolute', - top: 0, - left: 0, - width: width, - height: height, - backgroundColor: 'rgba(0,0,0,0.6)', - display: visible ? 'flex' : 'none', - alignItems: 'flex-start', - justifyContent: 'center', - paddingTop: `${paddingTop}px`, - }; - this.styles_.promptDialog = { - backgroundColor: theme.backgroundColor, - padding: 16, - display: 'inline-block', - maxWidth: width * 0.5, - boxShadow: '6px 6px 20px rgba(0,0,0,0.5)', - }; - this.styles_.button = { - minWidth: theme.buttonMinWidth, - minHeight: theme.buttonMinHeight, - marginLeft: 5, - color: theme.color, - backgroundColor: theme.backgroundColor, - border: '1px solid', - borderColor: theme.dividerColor, - }; - this.styles_.label = { - marginRight: 5, - fontSize: theme.fontSize, - color: theme.color, - fontFamily: theme.fontFamily, - verticalAlign: 'middle', - }; - this.styles_.input = { - width: 0.5 * width, - maxWidth: 400, - color: theme.color, - backgroundColor: theme.backgroundColor, - border: '1px solid', - borderColor: theme.dividerColor, - }; - this.styles_.select = { - control: (provided) => Object.assign(provided, { - minWidth: width * 0.2, - maxWidth: width * 0.5, - fontFamily: theme.fontFamily, - }), - input: (provided) => Object.assign(provided, { - minWidth: '20px', - color: theme.color, - }), - menu: (provided) => Object.assign(provided, { - color: theme.color, - fontFamily: theme.fontFamily, - backgroundColor: theme.backgroundColor, - }), - option: (provided, state) => Object.assign(provided, { - color: theme.color, - fontFamily: theme.fontFamily, - paddingLeft: `${10 + (state.data.indentDepth || 0) * 20}px`, - }), - multiValueLabel: (provided) => Object.assign(provided, { - fontFamily: theme.fontFamily, - }), - multiValueRemove: (provided) => Object.assign(provided, { - color: theme.color, - }), - }; - this.styles_.selectTheme = (tagTheme) => Object.assign(tagTheme, { - borderRadius: 2, - colors: Object.assign(tagTheme.colors, { - primary: theme.raisedBackgroundColor, - primary25: theme.raisedBackgroundColor, - neutral0: theme.backgroundColor, - neutral5: theme.backgroundColor, - neutral10: theme.raisedBackgroundColor, - neutral20: theme.raisedBackgroundColor, - neutral30: theme.raisedBackgroundColor, - neutral40: theme.color, - neutral50: theme.color, - neutral60: theme.color, - neutral70: theme.color, - neutral80: theme.color, - neutral90: theme.color, - danger: theme.backgroundColor, - dangerLight: theme.colorError2, - }), - }); - this.styles_.desc = Object.assign({}, theme.textStyle, { - marginTop: 10, - }); - return this.styles_; - } - render() { - const style = this.props.style; - const theme = theme_1.themeStyle(this.props.themeId); - const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel']; - const styles = this.styles(this.props.themeId, style.width, style.height, this.state.visible); - const onClose = (accept, buttonType = null) => { - if (this.props.onClose) { - let outputAnswer = this.state.answer; - if (this.props.inputType === 'datetime') { - // outputAnswer = anythingToDate(outputAnswer); - outputAnswer = time_1.default.anythingToDateTime(outputAnswer); - } - this.props.onClose(accept ? outputAnswer : null, buttonType); - } - this.setState({ visible: false, answer: '' }); - }; - const onChange = (event) => { - this.setState({ answer: event.target.value }); - }; - // const anythingToDate = (o) => { - // if (o && o.toDate) return o.toDate(); - // if (!o) return null; - // let m = moment(o, time.dateTimeFormat()); - // if (m.isValid()) return m.toDate(); - // m = moment(o, time.dateFormat()); - // return m.isValid() ? m.toDate() : null; - // } - const onDateTimeChange = (momentObject) => { - this.setState({ answer: momentObject }); - }; - const onSelectChange = (newValue) => { - this.setState({ answer: newValue }); - this.focusInput_ = true; - }; - const onKeyDown = (event) => { - if (event.key === 'Enter') { - if (this.props.inputType !== 'tags' && this.props.inputType !== 'dropdown') { - onClose(true); - } else if (this.answerInput_.current && !this.answerInput_.current.state.menuIsOpen) { - // The menu will be open if the user is selecting a new item - onClose(true); - } - } else if (event.key === 'Escape') { - onClose(false); - } - }; - const descComp = this.props.description ? React.createElement('div', { style: styles.desc }, this.props.description) : null; - let inputComp = null; - if (this.props.inputType === 'datetime') { - inputComp = React.createElement(Datetime, { className: 'datetime-picker', value: this.state.answer, inputProps: { style: styles.input }, dateFormat: time_1.default.dateFormat(), timeFormat: time_1.default.timeFormat(), onChange: (momentObject) => onDateTimeChange(momentObject) }); - } else if (this.props.inputType === 'tags') { - inputComp = React.createElement(creatable_1.default, { className: 'tag-selector', styles: styles.select, theme: styles.selectTheme, ref: this.answerInput_, value: this.state.answer, placeholder: '', components: animated_1.default(), isMulti: true, isClearable: false, backspaceRemovesValue: true, options: this.props.autocomplete, onChange: onSelectChange, onKeyDown: (event) => onKeyDown(event) }); - } else if (this.props.inputType === 'dropdown') { - inputComp = React.createElement(react_select_1.default, { className: 'item-selector', styles: styles.select, theme: styles.selectTheme, ref: this.answerInput_, components: animated_1.default(), value: this.props.answer, defaultValue: this.props.defaultValue, isClearable: false, options: this.props.autocomplete, onChange: onSelectChange, onKeyDown: (event) => onKeyDown(event) }); - } else { - inputComp = React.createElement('input', { style: styles.input, ref: this.answerInput_, value: this.state.answer, type: 'text', onChange: event => onChange(event), onKeyDown: event => onKeyDown(event) }); - } - const buttonComps = []; - if (buttonTypes.indexOf('create') >= 0) { - buttonComps.push(React.createElement('button', { key: 'create', disabled: !this.state.answer, style: styles.button, onClick: () => onClose(true, 'create') }, locale_1._('Create'))); - } - if (buttonTypes.indexOf('ok') >= 0) { - buttonComps.push(React.createElement('button', { key: 'ok', disabled: !this.state.answer, style: styles.button, onClick: () => onClose(true, 'ok') }, locale_1._('OK'))); - } - if (buttonTypes.indexOf('cancel') >= 0) { - buttonComps.push(React.createElement('button', { key: 'cancel', style: styles.button, onClick: () => onClose(false, 'cancel') }, locale_1._('Cancel'))); - } - if (buttonTypes.indexOf('clear') >= 0) { - buttonComps.push(React.createElement('button', { key: 'clear', style: styles.button, onClick: () => onClose(false, 'clear') }, locale_1._('Clear'))); - } - return (React.createElement('div', { className: 'modal-layer', style: styles.modalLayer }, - React.createElement('div', { className: 'modal-dialog', style: styles.promptDialog }, - React.createElement('label', { style: styles.label }, this.props.label ? this.props.label : ''), - React.createElement('div', { style: { display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor } }, - inputComp, - descComp), - React.createElement('div', { style: { textAlign: 'right', marginTop: 10 } }, buttonComps)))); - } -} -exports.default = PromptDialog; -// # sourceMappingURL=PromptDialog.js.map diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index d3c816691e..e1691c1109 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -1,6 +1,6 @@ { "name": "@joplin/app-desktop", - "version": "2.9.14", + "version": "2.9.13", "description": "Joplin for Desktop", "main": "main.js", "private": true, From 018591d1fa9fd6c4613bffca6330eb8d9441712c Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 14 Nov 2022 17:28:16 +0000 Subject: [PATCH 06/13] Ignored --- .eslintignore | 3 +++ .gitignore | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.eslintignore b/.eslintignore index 91c39553c7..7e8a5695f3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -552,6 +552,9 @@ packages/app-desktop/gui/NoteEditor/utils/types.js.map packages/app-desktop/gui/NoteEditor/utils/useDropHandler.d.ts packages/app-desktop/gui/NoteEditor/utils/useDropHandler.js packages/app-desktop/gui/NoteEditor/utils/useDropHandler.js.map +packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.d.ts +packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js +packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js.map packages/app-desktop/gui/NoteEditor/utils/useFolder.d.ts packages/app-desktop/gui/NoteEditor/utils/useFolder.js packages/app-desktop/gui/NoteEditor/utils/useFolder.js.map diff --git a/.gitignore b/.gitignore index e5bb6c40a7..84cc9b1332 100644 --- a/.gitignore +++ b/.gitignore @@ -540,6 +540,9 @@ packages/app-desktop/gui/NoteEditor/utils/types.js.map packages/app-desktop/gui/NoteEditor/utils/useDropHandler.d.ts packages/app-desktop/gui/NoteEditor/utils/useDropHandler.js packages/app-desktop/gui/NoteEditor/utils/useDropHandler.js.map +packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.d.ts +packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js +packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js.map packages/app-desktop/gui/NoteEditor/utils/useFolder.d.ts packages/app-desktop/gui/NoteEditor/utils/useFolder.js packages/app-desktop/gui/NoteEditor/utils/useFolder.js.map From 4f4f18bc0e98c707d045189deec71011701a8474 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 14 Nov 2022 17:29:02 +0000 Subject: [PATCH 07/13] Desktop release v2.9.15 --- packages/app-desktop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index e1691c1109..b28a826a13 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -1,6 +1,6 @@ { "name": "@joplin/app-desktop", - "version": "2.9.13", + "version": "2.9.15", "description": "Joplin for Desktop", "main": "main.js", "private": true, From fe8e5adbee862711d47c6733742010131d6db5ae Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Sun, 6 Nov 2022 14:37:26 +0000 Subject: [PATCH 08/13] Tools: Trying to fix CI - macOS translation kit not needed --- .github/scripts/run_ci.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/scripts/run_ci.sh b/.github/scripts/run_ci.sh index d2b7461f26..55c8b6489e 100755 --- a/.github/scripts/run_ci.sh +++ b/.github/scripts/run_ci.sh @@ -130,12 +130,11 @@ fi # ============================================================================= # Check that we didn't lose any string due to gettext not being able to parse # newly modified or added scripts. This is convenient to quickly view on GitHub -# what commit may have broken translation building. We run this on macOS because -# we need the latest version of gettext (and stable Ubuntu doesn't have it). +# what commit may have broken translation building. # ============================================================================= if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then - if [ "$IS_MACOS" == "1" ]; then + if [ "$IS_LINUX" == "1" ]; then echo "Step: Checking for lost translation strings..." xgettext --version From 03a111eebb9b7715aa942a8e7a34cdbbd4e4f1f7 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 14 Nov 2022 22:35:41 +0000 Subject: [PATCH 09/13] Desktop release v2.9.16 --- packages/app-desktop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index b28a826a13..eb240616a8 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -1,6 +1,6 @@ { "name": "@joplin/app-desktop", - "version": "2.9.15", + "version": "2.9.16", "description": "Joplin for Desktop", "main": "main.js", "private": true, From 6e0c3c076da6a0b8dc6d492f19b96e823bdba0ec Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Tue, 15 Nov 2022 07:34:55 +0000 Subject: [PATCH 10/13] Tools: Trying to fix CI - macOS translation kit not needed --- .github/workflows/github-actions-main.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/github-actions-main.yml b/.github/workflows/github-actions-main.yml index d6ed7d99ef..f6a63cefae 100644 --- a/.github/workflows/github-actions-main.yml +++ b/.github/workflows/github-actions-main.yml @@ -21,13 +21,6 @@ jobs: sudo apt-get install -y libsecret-1-dev sudo apt-get install -y translate-toolkit - - name: Install macOS dependencies - if: runner.os == 'macOS' - run: | - brew update - brew install gettext - brew install translate-toolkit - - name: Install Docker Engine # if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v') if: runner.os == 'Linux' From a84a8e7710980f2d3c4f0bfabf8fb87876f2536a Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Tue, 15 Nov 2022 07:35:09 +0000 Subject: [PATCH 11/13] Desktop release v2.9.17 --- packages/app-desktop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index eb240616a8..61e3462b7a 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -1,6 +1,6 @@ { "name": "@joplin/app-desktop", - "version": "2.9.16", + "version": "2.9.17", "description": "Joplin for Desktop", "main": "main.js", "private": true, From 8f9895eaf94d4e2994d22e6a291439fbea589653 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Sun, 4 Dec 2022 18:03:36 +0000 Subject: [PATCH 12/13] iOS 12.9.1 --- .../ios/Joplin.xcodeproj/project.pbxproj | 16 ++++---- readme/changelog_ios.md | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj index 47e3c90894..991b3cfe2b 100644 --- a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj +++ b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj @@ -492,13 +492,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = A9BXAFS6CT; ENABLE_BITCODE = NO; INFOPLIST_FILE = Joplin/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 12.9.0; + MARKETING_VERSION = 12.9.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -521,12 +521,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEVELOPMENT_TEAM = A9BXAFS6CT; INFOPLIST_FILE = Joplin/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 12.9.0; + MARKETING_VERSION = 12.9.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -667,14 +667,14 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = A9BXAFS6CT; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 12.9.0; + MARKETING_VERSION = 12.9.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension; @@ -698,14 +698,14 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 82; + CURRENT_PROJECT_VERSION = 83; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = A9BXAFS6CT; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 12.9.0; + MARKETING_VERSION = 12.9.1; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/readme/changelog_ios.md b/readme/changelog_ios.md index e6e3e38b6d..85d84a20d7 100644 --- a/readme/changelog_ios.md +++ b/readme/changelog_ios.md @@ -1,5 +1,42 @@ # Joplin iOS app changelog +## [ios-v12.9.1](https://github.com/laurent22/joplin/releases/tag/ios-v12.9.1) - 2022-12-04T18:03:02Z + +- New: Add Markdown toolbar (#6753 by Henry Heino) +- New: Add alt text/roles to some buttons to improve accessibility (#6616 by Henry Heino) +- New: Add keyboard-activatable markdown commands (e.g. bold, italicize) (#6707 by Henry Heino) +- New: Add long-press tooltips (#6758 by Henry Heino) +- New: Add note bar (#6772 by Tolulope Malomo) +- Improved: Convert empty bolded regions to bold-italic regions in beta editor (#6807) (#6808 by Henry Heino) +- Improved: Ctrl+F search support in beta editor (#6587 by Henry Heino) +- Improved: Disable multi-highlighting to fix context menu (9b348fd) +- Improved: Display icon for all notebooks if at least one notebook has an icon (ec97dd8) +- Improved: Enable long-press menu (#6738 by Henry Heino) +- Improved: Improve syntax highlighting on mobile beta editor (#6684 by Henry Heino) +- Improved: Increase the attachment size limit to 200MB (#6848 by Self Not Found) +- Improved: Removes whitespace above navigation component (#6597 by [@tom](https://github.com/tom)) +- Improved: Respect system accessibility font size in rendered markdown (#6686) (#6685 by Henry Heino) +- Improved: Setting to disable spellcheck in beta editor (#6780 by Henry Heino) +- Improved: Show client ID in log (#6897 by Self Not Found) +- Improved: Supports attaching multiple files to a note at once (#6831 by Self Not Found) +- Improved: Translation: Update zh_TW (#6727 by Kevin Hsu) +- Improved: Update Mermaid 8.13.9 to 9.1.7 (#6849 by Helmut K. C. Tessarek) +- Fixed: Add button to reduce space below markdown toolbar (#6823) (#6805 by Henry Heino) +- Fixed: Do not encrypt non-owned note if it was not shared encrypted (#6645) +- Fixed: Fix checklist continuation in beta editor (#6577) (#6576 by Henry Heino) +- Fixed: Fix default font in beta editor (#6760) (#6759 by Henry Heino) +- Fixed: Fix multiple webview instances (#6841 by Henry Heino) +- Fixed: Fix occasional overscroll when opening the keyboard (#6700) (#6636 by Henry Heino) +- Fixed: Fix resources sync when proxy is set (#6817) (#6688 by Self Not Found) +- Fixed: Fix side menu width on wide screen devices (#6662 by Tolulope Malomo) +- Fixed: Fixed crash when trying to move note to notebook (#6898) +- Fixed: Fixed notebook icon spacing (633c9ac) +- Fixed: Fixed notebook icons alignment (ea6b7ca) +- Fixed: Note links with HTML notation did not work (#6515) +- Fixed: Scroll selection into view in beta editor when window resizes (#6610) (#5949 by Henry Heino) +- Fixed: Support non-ASCII characters in OneDrive (#6916) (#6838 by Self Not Found) +- Security: Fix XSS when a specially crafted string is passed to the renderer (762b4e8) + ## [ios-v12.8.1](https://github.com/laurent22/joplin/releases/tag/ios-v12.8.1) - 2022-06-06T10:56:27Z - Improved: Automatically start sync after setting the sync parameters (ff066ba) From d73657fa0d6b3c7dac89cfb9def75de0cb055d14 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Sun, 4 Dec 2022 18:50:51 +0000 Subject: [PATCH 13/13] Chore: Fix iOS build --- packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj index 991b3cfe2b..07fe73319c 100644 --- a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj +++ b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj @@ -327,7 +327,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export NODE_BINARY=/usr/local/opt/node@12/bin/node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; + shellScript = "export NODE_BINARY=/usr/local/bin/node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; 027E2AA6B101F8CFCA582EC1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase;