From 8e5391b130f78adfc4a9380d1816f71f1dce0fdb Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Thu, 12 Jan 2023 08:49:30 +0200 Subject: [PATCH 1/7] feat: add capture as a new header flag --- src/actions/org.js | 6 ++-- .../components/CaptureTemplate/index.js | 19 ++++++++++ .../OrgFile/components/CaptureModal/index.js | 11 ++++-- src/components/OrgFile/index.js | 4 +-- src/lib/sample_capture_templates.js | 2 ++ src/reducers/capture.js | 1 + src/reducers/org.js | 36 +++++++++++++------ src/reducers/org.unit.test.js | 3 +- 8 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/actions/org.js b/src/actions/org.js index ebb1b7ee..d74748fc 100644 --- a/src/actions/org.js +++ b/src/actions/org.js @@ -483,14 +483,14 @@ export const updateTableCellValue = (cellId, newValue) => ({ dirtying: true, }); -export const insertCapture = (templateId, content, shouldPrepend) => (dispatch, getState) => { +export const insertCapture = (templateId, content, shouldPrepend, shouldCaptureAsNewHeader) => (dispatch, getState) => { dispatch(closePopup()); const template = getState() .capture.get('captureTemplates') .concat(sampleCaptureTemplates) .find((template) => template.get('id') === templateId); - dispatch({ type: 'INSERT_CAPTURE', template, content, shouldPrepend, dirtying: true }); + dispatch({ type: 'INSERT_CAPTURE', template, content, shouldPrepend, shouldCaptureAsNewHeader, dirtying: true }); }; export const clearPendingCapture = () => ({ @@ -551,7 +551,7 @@ export const insertPendingCapture = () => (dispatch, getState) => { )}${captureContent}${substitutedTemplate.substring(initialCursorIndex)}` : `${substitutedTemplate}${captureContent}`; - dispatch(insertCapture(template.get('id'), content, template.get('shouldPrepend'))); + dispatch(insertCapture(template.get('id'), content, template.get('shouldPrepend'), template.get('shouldCaptureAsNewHeader'))); dispatch(sync({ successMessage: 'Item captured' })); }; diff --git a/src/components/CaptureTemplatesEditor/components/CaptureTemplate/index.js b/src/components/CaptureTemplatesEditor/components/CaptureTemplate/index.js index 969ce4f0..73e45c4e 100644 --- a/src/components/CaptureTemplatesEditor/components/CaptureTemplate/index.js +++ b/src/components/CaptureTemplatesEditor/components/CaptureTemplate/index.js @@ -39,6 +39,9 @@ export default ({ const togglePrepend = () => onFieldPathUpdate(template.get('id'), ['shouldPrepend'], !template.get('shouldPrepend')); + const toggleCaptureAsNewHeader = () => + onFieldPathUpdate(template.get('id'), ['shouldCaptureAsNewHeader'], !template.get('shouldCaptureAsNewHeader')); + const handleAddNewOrgFileAvailability = () => { onAddNewTemplateOrgFileAvailability(template.get('id')); }; @@ -259,6 +262,21 @@ export default ({ ); + + const renderCaptureAsNewHeader = (template) => ( +
+
+
Capture as new header?
+ +
+ +
+ By default, new captured content is added as a new header. Disable this + setting to append content to an existing one (the last one in the path). +
+
+ ); + const renderTemplateField = (template) => (
@@ -369,6 +387,7 @@ export default ({ {renderFilePath(template)} {renderHeaderPaths(template)} {renderPrependField(template)} + {renderCaptureAsNewHeader(template)} {renderTemplateField(template)} {renderDeleteButton()}
diff --git a/src/components/OrgFile/components/CaptureModal/index.js b/src/components/OrgFile/components/CaptureModal/index.js index 0b7d987c..2edf2ff7 100644 --- a/src/components/OrgFile/components/CaptureModal/index.js +++ b/src/components/OrgFile/components/CaptureModal/index.js @@ -31,6 +31,7 @@ export default ({ template, onCapture, headers }) => { const [textareaValue, setTextareaValue] = useState(substitutedTemplate); const [shouldPrepend, setShouldPrepend] = useState(template.get('shouldPrepend')); + const [shouldCaptureAsNewHeader, setShouldCaptureAsNewHeader] = useState(template.get('shouldCaptureAsNewHeader')); /** INFO: Some versions of Mobile Safari do _not_ like it when the focus is set without an explicit user interaction. This is the case @@ -91,12 +92,14 @@ export default ({ template, onCapture, headers }) => { } }, [textarea, initialCursorIndex]); - const handleCaptureClick = () => onCapture(template.get('id'), textareaValue, shouldPrepend); + const handleCaptureClick = () => onCapture(template.get('id'), textareaValue, shouldPrepend, shouldCaptureAsNewHeader); const handleTextareaChange = (event) => setTextareaValue(event.target.value); const handlePrependSwitchToggle = () => setShouldPrepend(!shouldPrepend); + const handleCaptureAsNewHeaderSwitchToggle = () => setShouldCaptureAsNewHeader(!shouldCaptureAsNewHeader); + return ( <>
@@ -133,11 +136,15 @@ export default ({ template, onCapture, headers }) => { Prepend:
- +
+ Capture as new header: + +
+ {/* Add padding to move the above textarea above the fold. More documentation, see getMinHeight(). */} {isMobileSafari13 &&
} diff --git a/src/components/OrgFile/index.js b/src/components/OrgFile/index.js index 34d5cf1b..492e2fef 100644 --- a/src/components/OrgFile/index.js +++ b/src/components/OrgFile/index.js @@ -237,8 +237,8 @@ class OrgFile extends PureComponent { } } - handleCapture(templateId, content, shouldPrepend) { - this.props.org.insertCapture(templateId, content, shouldPrepend); + handleCapture(templateId, content, shouldPrepend, shouldCaptureAsNewHeader) { + this.props.org.insertCapture(templateId, content, shouldPrepend, shouldCaptureAsNewHeader); } handlePopupClose() { diff --git a/src/lib/sample_capture_templates.js b/src/lib/sample_capture_templates.js index a1e6e385..74b46d5e 100644 --- a/src/lib/sample_capture_templates.js +++ b/src/lib/sample_capture_templates.js @@ -12,6 +12,7 @@ export default fromJS([ letter: '', orgFilesWhereAvailable: [], shouldPrepend: false, + shouldCaptureAsNewHeader: true, template: '* TODO %?', isSample: true, }, @@ -24,6 +25,7 @@ export default fromJS([ letter: '', orgFilesWhereAvailable: [], shouldPrepend: true, + shouldCaptureAsNewHeader: true, template: '* You can insert timestamps too! %T %?', isSample: true, }, diff --git a/src/reducers/capture.js b/src/reducers/capture.js index 4497eea3..2ce9dd7e 100644 --- a/src/reducers/capture.js +++ b/src/reducers/capture.js @@ -23,6 +23,7 @@ const addNewEmptyCaptureTemplate = (state) => { orgFilesWhereAvailable: [''], headerPaths: [''], shouldPrepend: false, + shouldCaptureAsNewHeader: true, template: '', }) ) diff --git a/src/reducers/org.js b/src/reducers/org.js index c9d07f1d..49c3a452 100644 --- a/src/reducers/org.js +++ b/src/reducers/org.js @@ -841,7 +841,7 @@ const updateTableCellValue = (state, action) => { const insertCapture = (state, action) => { const headers = state.get('headers'); - const { template, content, shouldPrepend } = action; + const { template, content, shouldPrepend, shouldCaptureAsNewHeader } = action; const { newIndex, nestingLevel, parentHeader } = insertCapturePosition( template, @@ -853,21 +853,37 @@ const insertCapture = (state, action) => { return state; } - const newHeader = newHeaderFromText(content, state.get('todoKeywordSets')).set( - 'nestingLevel', - nestingLevel - ); + if (!shouldCaptureAsNewHeader) { + const header = getTargetHeader(template, headers); + const headerId = header.get('id') + const rawDescription = header.get('rawDescription'); + const newRawDescription = rawDescription ? + (shouldPrepend ? content + rawDescription : rawDescription + content) + : content; + return updateHeaderDescription (state, {headerId, newRawDescription}); + } else { + const newHeader = newHeaderFromText(content, state.get('todoKeywordSets')).set( + 'nestingLevel', + nestingLevel + ); - state = state.update('headers', (headers) => headers.insert(newIndex, newHeader)); - if (parentHeader !== undefined) { - // We inserted the new header under a parent rather than at the top or - // bottom of the file. - state = updateCookiesOfHeaderWithId(state, parentHeader.get('id')); + state = state.update('headers', (headers) => headers.insert(newIndex, newHeader)); + if (parentHeader !== undefined) { + // We inserted the new header under a parent rather than at the top or + // bottom of the file. + state = updateCookiesOfHeaderWithId(state, parentHeader.get('id')); + } } return state; }; +const getTargetHeader = (template, headers) => { + const headerPaths = template.get('headerPaths'); + const header = headerWithPath(headers, headerPaths); + return header !== null ? header : newHeaderFromText("", {nestingLevel: 1}); +} + const insertCapturePosition = (template, headers, shouldPrepend) => { const headerPaths = template.get('headerPaths'); if (headerPaths.size === 0) { diff --git a/src/reducers/org.unit.test.js b/src/reducers/org.unit.test.js index e92cdbd0..50994804 100644 --- a/src/reducers/org.unit.test.js +++ b/src/reducers/org.unit.test.js @@ -181,6 +181,7 @@ describe('org reducer', () => { file: '', orgFilesWhereAvailable: [], shouldPrepend: false, + shouldCaptureAsNewHeader: true, template: '* TODO %?', isSample: true, }; @@ -218,7 +219,7 @@ describe('org reducer', () => { expect(extractTitleAndNesting(headers.last())).toEqual(['A second nested header', 2]); } - function insertCapture(path, template, shouldPrepend) { + function insertCapture(path, template, shouldPrepend, shouldCaptureAsNewHeader) { // Check initially parsed file looks as expected let headers = store.getState().org.present.getIn(['files', path, 'headers']); expect(headers.size).toEqual(4); From b14143367fced308c6678d8d648866d93e55a838 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Mon, 16 Jan 2023 09:46:31 +0200 Subject: [PATCH 2/7] fix: handling of newlines --- src/reducers/org.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/reducers/org.js b/src/reducers/org.js index 49c3a452..73632c0b 100644 --- a/src/reducers/org.js +++ b/src/reducers/org.js @@ -854,12 +854,11 @@ const insertCapture = (state, action) => { } if (!shouldCaptureAsNewHeader) { - const header = getTargetHeader(template, headers); + const headerPaths = template.get('headerPaths'); + const header = findHeaderMatchingPaths(headers, headerPaths); const headerId = header.get('id') const rawDescription = header.get('rawDescription'); - const newRawDescription = rawDescription ? - (shouldPrepend ? content + rawDescription : rawDescription + content) - : content; + const newRawDescription = shouldPrepend ? prependContent(rawDescription, content) : appendContent(rawDescription, content); return updateHeaderDescription (state, {headerId, newRawDescription}); } else { const newHeader = newHeaderFromText(content, state.get('todoKeywordSets')).set( @@ -878,8 +877,23 @@ const insertCapture = (state, action) => { return state; }; -const getTargetHeader = (template, headers) => { - const headerPaths = template.get('headerPaths'); +const prependContent = (existing, content) => { + if (!existing || existing === '') { + return content; + } + existing = existing.replace(/^[\s\n]*/, ""); + return content + '\n' + existing; +} + +const appendContent = (existing, content) => { + if (!existing || existing === '') { + return content; + } + existing = existing.replace(/[\s\n]*$/, ""); + return existing + '\n' + content; +} + +const findHeaderMatchingPaths = (headers, headerPaths) => { const header = headerWithPath(headers, headerPaths); return header !== null ? header : newHeaderFromText("", {nestingLevel: 1}); } From e411a620b2ea5b44dc67c67df7ad4a4271b038f1 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Mon, 16 Jan 2023 09:57:10 +0200 Subject: [PATCH 3/7] fix: handling of existing templates --- src/actions/org.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/actions/org.js b/src/actions/org.js index d74748fc..1479af16 100644 --- a/src/actions/org.js +++ b/src/actions/org.js @@ -551,7 +551,8 @@ export const insertPendingCapture = () => (dispatch, getState) => { )}${captureContent}${substitutedTemplate.substring(initialCursorIndex)}` : `${substitutedTemplate}${captureContent}`; - dispatch(insertCapture(template.get('id'), content, template.get('shouldPrepend'), template.get('shouldCaptureAsNewHeader'))); + dispatch(insertCapture(template.get('id'), content, template.get('shouldPrepend'), + !template.has('shouldCaptureAsNewHeader') || template.get('shouldCaptureAsNewHeader'))); dispatch(sync({ successMessage: 'Item captured' })); }; From 54cb650da8d2a5af1a868c405ae64219a1ca8725 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Mon, 16 Jan 2023 11:01:40 +0200 Subject: [PATCH 4/7] fix: unit test --- src/reducers/org.unit.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/reducers/org.unit.test.js b/src/reducers/org.unit.test.js index 50994804..398052e9 100644 --- a/src/reducers/org.unit.test.js +++ b/src/reducers/org.unit.test.js @@ -225,7 +225,12 @@ describe('org reducer', () => { expect(headers.size).toEqual(4); expectOrigFirstHeader(headers); expectOrigLastHeader(headers); - const action = types.insertCapture(template.id, content, shouldPrepend); + const action = types.insertCapture( + template.id, + content, + shouldPrepend, + shouldCaptureAsNewHeader + ); store.dispatch(action); const newHeaders = store.getState().org.present.getIn(['files', path, 'headers']); expect(newHeaders.size).toEqual(5); From 3b99ea2926e03e1d904efc80b953b21dfc6cf97b Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Mon, 16 Jan 2023 12:03:37 +0200 Subject: [PATCH 5/7] chore: prettier eslint --- src/actions/org.js | 246 ++++++++++-------- .../components/CaptureTemplate/index.js | 16 +- .../OrgFile/components/CaptureModal/index.js | 15 +- .../OrgFile/components/Header/index.js | 9 +- .../components/TaskListView/index.js | 8 +- .../components/TimestampEditor/index.js | 12 +- src/lib/headline_filter.js | 175 ++++++------- src/lib/parse_org.js | 21 +- src/reducers/org.js | 148 ++++++----- 9 files changed, 346 insertions(+), 304 deletions(-) diff --git a/src/actions/org.js b/src/actions/org.js index 1479af16..eaee6fa7 100644 --- a/src/actions/org.js +++ b/src/actions/org.js @@ -110,119 +110,123 @@ export const sync = (options) => (dispatch, getState) => { // wrapping function `syncDebounced`. This will actually debounce // `doSync`, because the inner function `sync` will be created only // once. -const doSync = ({ - forceAction = null, - successMessage = 'Changes pushed', - shouldSuppressMessages = false, - path, -} = {}) => (dispatch, getState) => { - const client = getState().syncBackend.get('client'); - const currentPath = getState().org.present.get('path'); - path = path || currentPath; - if (!path || path.startsWith(STATIC_FILE_PREFIX)) { - return; - } +const doSync = + ({ + forceAction = null, + successMessage = 'Changes pushed', + shouldSuppressMessages = false, + path, + } = {}) => + (dispatch, getState) => { + const client = getState().syncBackend.get('client'); + const currentPath = getState().org.present.get('path'); + path = path || currentPath; + if (!path || path.startsWith(STATIC_FILE_PREFIX)) { + return; + } - // Calls do `doSync` are already debounced using a timer, but on big - // Org files or slow connections, it's still possible to have - // concurrent requests to `doSync` which has no merit. When - // `isLoading`, don't trigger another sync in parallel. Instead, - // call `syncDebounced` and return immediately. This will - // recursively enqueue the request to do a sync until the current - // sync is finished. Since it's a debounced call, enqueueing it - // recursively is efficient. - // That is, unless the user manually hits the 'sync' button - // (indicated by `forceAction === 'manual'`). Then, do what the user - // requests. - if (getState().base.get('isLoading').includes(path) && forceAction !== 'manual') { - // Since there is a quick succession of debounced requests to - // synchronize, the user likely is in a undo/redo workflow with - // potential new changes to the Org file in between. In such a - // situation, it is easy for the remote file to have a newer - // `lastModifiedAt` date than the `lastSyncAt` date. Hence, - // pushing is the right action - no need for the modal to ask the - // user for her request to pull/push or cancel. - dispatch(sync({ forceAction: 'push' })); - return; - } + // Calls do `doSync` are already debounced using a timer, but on big + // Org files or slow connections, it's still possible to have + // concurrent requests to `doSync` which has no merit. When + // `isLoading`, don't trigger another sync in parallel. Instead, + // call `syncDebounced` and return immediately. This will + // recursively enqueue the request to do a sync until the current + // sync is finished. Since it's a debounced call, enqueueing it + // recursively is efficient. + // That is, unless the user manually hits the 'sync' button + // (indicated by `forceAction === 'manual'`). Then, do what the user + // requests. + if (getState().base.get('isLoading').includes(path) && forceAction !== 'manual') { + // Since there is a quick succession of debounced requests to + // synchronize, the user likely is in a undo/redo workflow with + // potential new changes to the Org file in between. In such a + // situation, it is easy for the remote file to have a newer + // `lastModifiedAt` date than the `lastSyncAt` date. Hence, + // pushing is the right action - no need for the modal to ask the + // user for her request to pull/push or cancel. + dispatch(sync({ forceAction: 'push' })); + return; + } - if (!shouldSuppressMessages) { - dispatch(setLoadingMessage(`Syncing ...`)); - } - dispatch(setIsLoading(true, path)); - dispatch(setOrgFileErrorMessage(null)); - - client - .getFileContentsAndMetadata(path) - .then(({ contents, lastModifiedAt }) => { - const isDirty = getState().org.present.getIn(['files', path, 'isDirty']); - const lastServerModifiedAt = parseISO(lastModifiedAt); - const lastSyncAt = getState().org.present.getIn(['files', path, 'lastSyncAt']); - - if (isAfter(lastSyncAt, lastServerModifiedAt) || forceAction === 'push') { - if (isDirty) { - const contents = exportOrg({ - headers: getState().org.present.getIn(['files', path, 'headers']), - linesBeforeHeadings: getState().org.present.getIn([ - 'files', - path, - 'linesBeforeHeadings', - ]), - dontIndent: getState().base.get('shouldNotIndentOnExport'), - }); - client - .updateFile(path, contents) - .then(() => { - if (!shouldSuppressMessages) { - dispatch(setDisappearingLoadingMessage(successMessage, 2000)); - } else { - setTimeout(() => dispatch(hideLoadingMessage()), 2000); - } - dispatch(setIsLoading(false, path)); - dispatch(setDirty(false, path)); - dispatch(setLastSyncAt(addSeconds(new Date(), 5), path)); - }) - .catch((error) => { - const err = `There was an error pushing the file ${path}: ${error.toString()}`; - console.error(err); - dispatch(setDisappearingLoadingMessage(err, 5000)); - dispatch(hideLoadingMessage()); - dispatch(setIsLoading(false, path)); - // Re-enqueue the file to be synchronized again - dispatch(sync({ path })); + if (!shouldSuppressMessages) { + dispatch(setLoadingMessage(`Syncing ...`)); + } + dispatch(setIsLoading(true, path)); + dispatch(setOrgFileErrorMessage(null)); + + client + .getFileContentsAndMetadata(path) + .then(({ contents, lastModifiedAt }) => { + const isDirty = getState().org.present.getIn(['files', path, 'isDirty']); + const lastServerModifiedAt = parseISO(lastModifiedAt); + const lastSyncAt = getState().org.present.getIn(['files', path, 'lastSyncAt']); + + if (isAfter(lastSyncAt, lastServerModifiedAt) || forceAction === 'push') { + if (isDirty) { + const contents = exportOrg({ + headers: getState().org.present.getIn(['files', path, 'headers']), + linesBeforeHeadings: getState().org.present.getIn([ + 'files', + path, + 'linesBeforeHeadings', + ]), + dontIndent: getState().base.get('shouldNotIndentOnExport'), }); - } else { - if (!shouldSuppressMessages) { - dispatch(setDisappearingLoadingMessage('Nothing to sync', 2000)); + client + .updateFile(path, contents) + .then(() => { + if (!shouldSuppressMessages) { + dispatch(setDisappearingLoadingMessage(successMessage, 2000)); + } else { + setTimeout(() => dispatch(hideLoadingMessage()), 2000); + } + dispatch(setIsLoading(false, path)); + dispatch(setDirty(false, path)); + dispatch(setLastSyncAt(addSeconds(new Date(), 5), path)); + }) + .catch((error) => { + const err = `There was an error pushing the file ${path}: ${error.toString()}`; + console.error(err); + dispatch(setDisappearingLoadingMessage(err, 5000)); + dispatch(hideLoadingMessage()); + dispatch(setIsLoading(false, path)); + // Re-enqueue the file to be synchronized again + dispatch(sync({ path })); + }); } else { - setTimeout(() => dispatch(hideLoadingMessage()), 2000); + if (!shouldSuppressMessages) { + dispatch(setDisappearingLoadingMessage('Nothing to sync', 2000)); + } else { + setTimeout(() => dispatch(hideLoadingMessage()), 2000); + } + dispatch(setIsLoading(false, path)); } - dispatch(setIsLoading(false, path)); - } - } else { - if (isDirty && forceAction !== 'pull') { - dispatch(hideLoadingMessage()); - dispatch(setIsLoading(false, path)); - dispatch(activatePopup('sync-confirmation', { lastServerModifiedAt, lastSyncAt, path })); } else { - dispatch(parseFile(path, contents)); - dispatch(setDirty(false, path)); - dispatch(setLastSyncAt(addSeconds(new Date(), 5), path)); - if (!shouldSuppressMessages) { - dispatch(setDisappearingLoadingMessage(`Latest version pulled: ${path}`, 2000)); + if (isDirty && forceAction !== 'pull') { + dispatch(hideLoadingMessage()); + dispatch(setIsLoading(false, path)); + dispatch( + activatePopup('sync-confirmation', { lastServerModifiedAt, lastSyncAt, path }) + ); } else { - setTimeout(() => dispatch(hideLoadingMessage()), 2000); + dispatch(parseFile(path, contents)); + dispatch(setDirty(false, path)); + dispatch(setLastSyncAt(addSeconds(new Date(), 5), path)); + if (!shouldSuppressMessages) { + dispatch(setDisappearingLoadingMessage(`Latest version pulled: ${path}`, 2000)); + } else { + setTimeout(() => dispatch(hideLoadingMessage()), 2000); + } + dispatch(setIsLoading(false, path)); } - dispatch(setIsLoading(false, path)); } - } - }) - .catch(() => { - dispatch(hideLoadingMessage()); - dispatch(setIsLoading(false, path)); - dispatch(setOrgFileErrorMessage(`File ${path} not found`)); - }); -}; + }) + .catch(() => { + dispatch(hideLoadingMessage()); + dispatch(setIsLoading(false, path)); + dispatch(setOrgFileErrorMessage(`File ${path} not found`)); + }); + }; export const openHeader = (headerId) => ({ type: 'OPEN_HEADER', @@ -483,15 +487,23 @@ export const updateTableCellValue = (cellId, newValue) => ({ dirtying: true, }); -export const insertCapture = (templateId, content, shouldPrepend, shouldCaptureAsNewHeader) => (dispatch, getState) => { - dispatch(closePopup()); +export const insertCapture = + (templateId, content, shouldPrepend, shouldCaptureAsNewHeader) => (dispatch, getState) => { + dispatch(closePopup()); - const template = getState() - .capture.get('captureTemplates') - .concat(sampleCaptureTemplates) - .find((template) => template.get('id') === templateId); - dispatch({ type: 'INSERT_CAPTURE', template, content, shouldPrepend, shouldCaptureAsNewHeader, dirtying: true }); -}; + const template = getState() + .capture.get('captureTemplates') + .concat(sampleCaptureTemplates) + .find((template) => template.get('id') === templateId); + dispatch({ + type: 'INSERT_CAPTURE', + template, + content, + shouldPrepend, + shouldCaptureAsNewHeader, + dirtying: true, + }); + }; export const clearPendingCapture = () => ({ type: 'CLEAR_PENDING_CAPTURE', @@ -551,8 +563,14 @@ export const insertPendingCapture = () => (dispatch, getState) => { )}${captureContent}${substitutedTemplate.substring(initialCursorIndex)}` : `${substitutedTemplate}${captureContent}`; - dispatch(insertCapture(template.get('id'), content, template.get('shouldPrepend'), - !template.has('shouldCaptureAsNewHeader') || template.get('shouldCaptureAsNewHeader'))); + dispatch( + insertCapture( + template.get('id'), + content, + template.get('shouldPrepend'), + !template.has('shouldCaptureAsNewHeader') || template.get('shouldCaptureAsNewHeader') + ) + ); dispatch(sync({ successMessage: 'Item captured' })); }; diff --git a/src/components/CaptureTemplatesEditor/components/CaptureTemplate/index.js b/src/components/CaptureTemplatesEditor/components/CaptureTemplate/index.js index 73e45c4e..8a17f25f 100644 --- a/src/components/CaptureTemplatesEditor/components/CaptureTemplate/index.js +++ b/src/components/CaptureTemplatesEditor/components/CaptureTemplate/index.js @@ -40,7 +40,11 @@ export default ({ onFieldPathUpdate(template.get('id'), ['shouldPrepend'], !template.get('shouldPrepend')); const toggleCaptureAsNewHeader = () => - onFieldPathUpdate(template.get('id'), ['shouldCaptureAsNewHeader'], !template.get('shouldCaptureAsNewHeader')); + onFieldPathUpdate( + template.get('id'), + ['shouldCaptureAsNewHeader'], + !template.get('shouldCaptureAsNewHeader') + ); const handleAddNewOrgFileAvailability = () => { onAddNewTemplateOrgFileAvailability(template.get('id')); @@ -262,17 +266,19 @@ export default ({
); - const renderCaptureAsNewHeader = (template) => (
Capture as new header?
- +
- By default, new captured content is added as a new header. Disable this - setting to append content to an existing one (the last one in the path). + By default, new captured content is added as a new header. Disable this setting to append + content to an existing one (the last one in the path).
); diff --git a/src/components/OrgFile/components/CaptureModal/index.js b/src/components/OrgFile/components/CaptureModal/index.js index 2edf2ff7..a51f79d5 100644 --- a/src/components/OrgFile/components/CaptureModal/index.js +++ b/src/components/OrgFile/components/CaptureModal/index.js @@ -31,7 +31,9 @@ export default ({ template, onCapture, headers }) => { const [textareaValue, setTextareaValue] = useState(substitutedTemplate); const [shouldPrepend, setShouldPrepend] = useState(template.get('shouldPrepend')); - const [shouldCaptureAsNewHeader, setShouldCaptureAsNewHeader] = useState(template.get('shouldCaptureAsNewHeader')); + const [shouldCaptureAsNewHeader, setShouldCaptureAsNewHeader] = useState( + template.get('shouldCaptureAsNewHeader') + ); /** INFO: Some versions of Mobile Safari do _not_ like it when the focus is set without an explicit user interaction. This is the case @@ -92,13 +94,15 @@ export default ({ template, onCapture, headers }) => { } }, [textarea, initialCursorIndex]); - const handleCaptureClick = () => onCapture(template.get('id'), textareaValue, shouldPrepend, shouldCaptureAsNewHeader); + const handleCaptureClick = () => + onCapture(template.get('id'), textareaValue, shouldPrepend, shouldCaptureAsNewHeader); const handleTextareaChange = (event) => setTextareaValue(event.target.value); const handlePrependSwitchToggle = () => setShouldPrepend(!shouldPrepend); - const handleCaptureAsNewHeaderSwitchToggle = () => setShouldCaptureAsNewHeader(!shouldCaptureAsNewHeader); + const handleCaptureAsNewHeaderSwitchToggle = () => + setShouldCaptureAsNewHeader(!shouldCaptureAsNewHeader); return ( <> @@ -138,7 +142,10 @@ export default ({ template, onCapture, headers }) => {
Capture as new header: - +