diff --git a/helpers/paragraph.js b/helpers/paragraph.js index 0a8a80d17..310b3cca3 100644 --- a/helpers/paragraph.js +++ b/helpers/paragraph.js @@ -123,6 +123,18 @@ export function rangeToString(r: TRange): string { return `range: ${r.start}-${r.end}` } +/** + * Pretty print range information + * Note: This is a copy of what's in general.js to avoid circular dependency. + * @author @EduardMe + */ +export function contentRangeToString(content: string, r: TRange): string { + if (r == null) { + return 'Range is undefined!' + } + return `${content.slice(r.start, r.end + 1)} [${r.start}-${r.end}]` +} + /** * Return title of note useful for display, including for * - daily calendar notes (the YYYYMMDD) diff --git a/np.Tidy/CHANGELOG.md b/np.Tidy/CHANGELOG.md index f3bbe790c..b111cbab9 100644 --- a/np.Tidy/CHANGELOG.md +++ b/np.Tidy/CHANGELOG.md @@ -1,8 +1,12 @@ # 🧹 Tidy Up Changelog See Plugin [README](https://github.com/NotePlan/plugins/blob/main/np.Tidy/README.md) for full details on the available commands and use from callbacks and templates. +## [0.9.2] - 2023-11-??? +- Updates the list of command aliases to suit changes in NotePlan 3.9.9. +- **/list conflicted notes** now includes the machine name in the note title it creates (available from NotePlan 3.9.9). + ## [0.9.1] - 2023-09-15 -- /List stubs now ignores its own output note when finding stubs +- /List stubs now ignores its own output note when finding stubs. ## [0.9.0] - 2023-08-27 - new **/List stubs** command that creates/updates a note that lists all your notes that have note links (wikilinks) that lead nowhere. diff --git a/np.Tidy/README.md b/np.Tidy/README.md index 7d92fc618..1a5ab3297 100644 --- a/np.Tidy/README.md +++ b/np.Tidy/README.md @@ -3,19 +3,21 @@ This plugin provides commands to help tidy up your notes: - **/File root-level notes** (alias "frnl"): For each root-level note, asks which folder you'd like it moved to. (There's a setting for ones to permanently ignore.) -- **/List conflicted notes**: creates/updates a note that lists all your notes on your current device with file-level conflicts, along with summary details about them. -- **/List duplicate notes**: creates/updates a note that lists all your notes with identical titles, along with summary details about those potential duplicates: +- **/List conflicted notes** (alias "conflicts"): creates/updates a note that lists all your notes on your current device with file-level conflicts, along with summary details about them. It gives options to delete one or other of the conflicted versions. Note: _conflicted notes can appear on each device you run NotePlan on, and the conflicted copies do not sync. Therefore you should consider running this on each of your devices._ + ![](conflicted-notes@2x.png) +- **/List duplicate notes** (alias "dupes"): creates/updates a note that lists all your notes with identical titles, along with summary details about those potential duplicates. It gives options to delete one or other of the conflicted versions: ![](duplicate-note-display@2x.png) - **/List stubs**: creates a note that lists all your notes that have wikilinks that lead nowhere. -- **/Remove blank notes**: deletes any completely blank notes, or just with a starting '#' character. +- **/Move top-level tasks in Editor to heading** (alias "mtth"): Move tasks orphaned at top of active note (prior to any heading) to under a specified heading. Note: this command does not work inside a template. See details below. +- **/Remove blank notes** (alias: "rbn"): deletes any completely blank notes, or just with a starting '#' character. - **/Remove orphaned blockIDs** (alias "rob"): Remove blockIDs from lines that had been sync'd, but have become 'orphans' as the other copies of the blockID have since been deleted. -- **/Remove section from recent notes** (alias "rsfrn"): Remove a given section (heading + its content block) from recently-changed notes. Can be used with parameters from Template or x-callback. - - **/Remove content under heading in all notes** (alias "rcuh"). Use wisely, as this is dangerous! (original function by @dwertheimer) +- **/Remove section from recent notes** (alias "rsrn"): Remove a given section (heading + its content block) from recently-changed notes. Can be used with parameters from Template or x-callback. + - **/Remove section from all notes** (alias "rsan"). Remove a given section (heading + its content block) from _all notes_. Use wisely, as this is dangerous! (original function by @dwertheimer) - **/Remove time parts from @done() dates** (alias "rtp"): Remove time parts of @done(date time) from recently-updated notes. Can be used with parameters from Template or Callback. - **/Remove @done() markers** (alias "rdm"): Remove @done(...) markers from recently-updated notes, optionally just from completed checklist items. -- **/Remove >today tags from completed todos** (alias "removeToday" or "rmt"): Removes the ">today" tag still attached to completed/cancelled tasks that means they keep showing up in Today's references every day forever. Does not touch open tasks. +- **/Remove >today tags from completed todos** (alias "rmt"): Removes the ">today" tag still attached to completed/cancelled tasks that means they keep showing up in Today's references every day forever. Does not touch open tasks. - **/Remove triggers from recent calendar notes** (alias "rtcn"): Remove one or more triggers from recently changed calendar notes (in the past). -- **/Move top-level tasks in Editor to heading** (alias "mtth"): Move tasks orphaned at top of active note (prior to any heading) to under a specified heading. NOTE: this command does not work inside a template. See details below. +- **/Log notes changed in interval** (alias "lncii"): Write a list of notes changed in the last interval of days to the plugin console log. It will default to the 'Default Recent Time Interval' setting unless passed as a parameter. Most can be used with parameters from a Template, or via an x-callback call. @@ -23,8 +25,11 @@ There's also the **/Tidy Up** (alias "tua"), which runs as many of the other com (If these commands are useful to you, you'll probably find the [Note Helpers plugin](https://github.com/NotePlan/plugins/blob/main/jgclark.NoteHelpers/) helpful too. It's rather arbitrary which commands live in which plugin.) -## Using from Templates -If these commands are valuable to you, then you probably want to be running them regularly. NotePlan doesn't (yet) allow fully automatic running of commands, but you can get close by including the commands in your Daily Note Template that you run each day (e.g. via the separate /dayStart command from my [Daily Journal plugin](https://github.com/NotePlan/plugins/blob/main/jgclark.DailyJournal/README.md)). +## Automating Tidy Up +If these commands are valuable to you, then you probably want to be running them regularly. NotePlan doesn't yet allow fully automatic running of commands, but you can get close by either including the commands in a frequently-used Template, or from a third-party utility that can invoke x-callback commands. Each are described below. + +### Using from Templates +You can include Tidy Up commands in your Daily Note Template that you run each day (e.g. via the separate /dayStart command from my [Daily Journal plugin](https://github.com/NotePlan/plugins/blob/main/jgclark.DailyJournal/README.md)). To call all the checked commands in settings inside your template: @@ -42,10 +47,10 @@ For example, this will remove sections with the heading 'Habit Progress' from no **Tip:** as these are complicated and fiddly to create, **I suggest you use @dwertheimer's excellent [Link Creator plugin](https://github.com/NotePlan/plugins/blob/main/np.CallbackURLs/README.md) command "/Get X-Callback-URL"** which makes it much simpler. -### Running **/Move top-level tasks in Editor to heading** in a template +#### Running **/Move top-level tasks in Editor to heading** in a template This command rewrites the current document in the Editor, moving tasks from the top to underneath a specified heading. It cannot run like the other commands by itself or as part of TidyUp in a template, because the template processor is rewriting the document in parallel. You will get duplicate headings. There is a way to include this in your daily note, however. If you include some code like the following in your daily note template, it will run the command and include the output in the flow of writing the template, and so the document will not be getting written twice in parallel. -``` +```markdown ## Tasks * <% const tasks = await DataStore.invokePluginCommandByName("Tidy: Move top-level tasks in Editor to heading","np.Tidy",["Tasks",true,true]); -%> @@ -62,9 +67,7 @@ NOTE: (thx @phenix): The order is important because the task header needs to be > **NOTE:** If you also run the `Tidy Up` command in your template, you should uncheck this command in the TidyUp settings. - - -## Using from x-callback calls +### Using from x-callback calls It's possible to call most of these commands from [outside NotePlan using the **x-callback mechanism**](https://help.noteplan.co/article/49-x-callback-url-scheme#runplugin). The URL calls all take the same form: `noteplan://x-callback-url/runPlugin?pluginID=np.Tidy&command=&arg0=` @@ -92,12 +95,13 @@ The available parameters are: | Remove time parts from @done() dates | runSilently | | Remove >today tags from completed todos | runSilently | | Move top-level tasks in Editor to heading | Heading name to place the tasks under | runSilently | - **Tip:** as these are complicated and fiddly to create, **I strongly suggest you use @dwertheimer's excellent [Link Creator plugin]() command "/Get X-Callback-URL"** which makes it vastly easier. ## Configuration -Click the gear button on the '🧹 Tidy Up' line in the Plugin Preferences panel, and fill in the settings accordingly. Defaults and descriptions are given for each one. +On macOS, click the gear button on the '🧹 Tidy Up' line in the Plugin Preferences panel, and fill in the settings accordingly. Defaults and descriptions are given for each one. + +On iOS/iPadOS, use the "/Update plugin settings" command for this plugin, which will guide you through the options in turn. ## Thanks @dwertheimer wrote one of the functions used in this plugin, and helped beta test much of the plugin. @@ -107,7 +111,7 @@ If you find an issue with this plugin, or would like to suggest new features for If you would like to support my late-night work extending NotePlan through writing these plugins, you can through: -[Buy Me A Coffee](https://www.buymeacoffee.com/revjgc) +[Buy Me A Coffee](https://www.buymeacoffee.com/revjgc) Thanks! diff --git a/np.Tidy/conflicted-notes@2x.png b/np.Tidy/conflicted-notes@2x.png new file mode 100644 index 000000000..8fbbb0a08 Binary files /dev/null and b/np.Tidy/conflicted-notes@2x.png differ diff --git a/np.Tidy/plugin.json b/np.Tidy/plugin.json index 6cd948e39..37162e121 100644 --- a/np.Tidy/plugin.json +++ b/np.Tidy/plugin.json @@ -6,8 +6,8 @@ "plugin.name": "🧹 Tidy Up", "plugin.author": "jgclark", "plugin.description": "Tidy up and delete various things in your NotePlan notes", - "plugin.version": "0.9.1", - "plugin.lastUpdateInfo": "v0.9.1: exclude the list-of-stubs note from itself.\nv0.9.0: new '/list stubs' command.\nv0.8.1: fixes.\nv0.8.0: new command: Move top-level tasks in Editor to heading\nv0.7.0: new command: /Remove >today tags from completed todos\nv0.6.0: new commands 'Remove empty notes' 'List conflicted notes' + display improvements.\nv0.5.0: new 'list duplicate notes' command.", + "plugin.version": "0.9.2", + "plugin.lastUpdateInfo": "v0.9.2: update command aliases.\nv0.9.1: exclude the list-of-stubs note from itself.\nv0.9.0: new '/list stubs' command.\nv0.8.1: fixes.\nv0.8.0: new command: Move top-level tasks in Editor to heading\nv0.7.0: new command: /Remove >today tags from completed todos\nv0.6.0: new commands 'Remove empty notes' 'List conflicted notes' + display improvements.\nv0.5.0: new 'list duplicate notes' command.", "plugin.dependencies": [], "plugin.script": "script.js", "plugin.url": "https://github.com/NotePlan/plugins/blob/main/np.Tidy/README.md", @@ -18,8 +18,8 @@ "description": "Run as many of the other commands in this plugin as you have configured.", "jsFunction": "tidyUpAll", "alias": [ - "tidy", - "tua" + "tua", + "tidy" ] }, { @@ -27,9 +27,8 @@ "description": "For each root-level note, asks which folder you'd like it moved to. (There's a setting for ones to ignore.)", "jsFunction": "fileRootNotes", "alias": [ - "tidy", - "file", - "frln" + "frln", + "tidy" ] }, { @@ -50,21 +49,29 @@ }, { "name": "List stubs", - "alias": [ - "link", - "wiki", - "wikilink" - ], "description": "Creates/updates a note that lists all your notes that have note links (wikilinks) that lead nowhere", "jsFunction": "listStubs" }, + { + "name": "Move top-level tasks in Editor to heading", + "description": "Tasks at top of active note (prior to any heading) will be placed under a specified heading", + "jsFunction": "moveTopLevelTasksInEditor", + "alias": [ + "mtth" + ], + "arguments": [ + "Heading name to place the tasks under (will be created if doesn't exist). If you are running this command in a template, put any non-blank text in the field below.", + "Run silently (e.g. in a template). Default is false.", + "Return the content of the tasks text, rather than inserting under a heading (e.g. for inserting in a tempate)" + ] + }, { "name": "Remove @done() markers", "description": "Remove @done() markers from recently-updated notes. Can be used with parameters from Template or Callback.", "jsFunction": "removeDoneMarkers", "alias": [ - "tidy", - "rdm" + "rdm", + "tidy" ], "arguments": [ "Parameters" @@ -75,8 +82,8 @@ "description": "Remove notes that are completely empty, or just have a `#` character", "jsFunction": "removeBlankNotes", "alias": [ - "tidy", - "rbn" + "rbn", + "tidy" ], "arguments": [ "Parameters" @@ -87,8 +94,8 @@ "description": "Remove blockIDs from lines that had been sync'd, but are 'orphans' as the other copies of the blockID have since been deleted.", "jsFunction": "removeOrphanedBlockIDs", "alias": [ - "tidy", - "rob" + "rob", + "tidy" ], "arguments": [ "Parameters" @@ -99,8 +106,8 @@ "description": "Remove time parts of @done(date time) from recently-updated notes. Can be used with parameters from Template or Callback.", "jsFunction": "removeDoneTimeParts", "alias": [ - "tidy", - "rtp" + "rtp", + "tidy" ], "arguments": [ "Parameters" @@ -111,8 +118,8 @@ "description": "Remove a given section (both the heading and its content) from recently-changed notes.\nCan be used with parameters from Template or x-callback.", "jsFunction": "removeSectionFromRecentNotes", "alias": [ - "tidy", - "rsfn" + "rsrn", + "tidy" ], "arguments": [ "Parameters" @@ -123,46 +130,34 @@ "description": "Remove a given section (both the heading and its content) from all notes.\nCan be used with parameters from Template or x-callback.", "jsFunction": "removeSectionFromAllNotes", "alias": [ - "tidy", - "rcuh" + "rsan", + "tidy" ], "arguments": [ "Parameters" ] }, { - "name": "Remove triggers from recent calendar notes", - "description": "Remove one or more triggers from recent (but past) calendar notes.\nCan be used with parameters from Template or x-callback.", - "jsFunction": "removeTriggersFromRecentCalendarNotes", + "name": "Remove >today tags from completed todos", + "description": "Remove Completed todos that have a >today tag. Can be used with parameters from Template or Callback.", + "jsFunction": "removeTodayTagsFromCompletedTodos", "alias": [ - "tidy", - "rtcn" - ], - "arguments": [ - "Parameters" + "rmt", + "removeToday" ] }, { - "name": "Log notes changed in interval (Tidy)", - "description": "Write a list of Log notes changed in the last interval of days to the plugin log. It will default to the 'Default Recent Time Interval' setting unless passed as a parameter.", - "jsFunction": "logNotesChangedInInterval", + "name": "Remove triggers from recent calendar notes", + "description": "Remove one or more triggers from recent (but past) calendar notes.\nCan be used with parameters from Template or x-callback.", + "jsFunction": "removeTriggersFromRecentCalendarNotes", "alias": [ - "tidy", - "lncii" + "rtcn", + "tidy" ], "arguments": [ "Parameters" ] }, - { - "name": "Remove >today tags from completed todos", - "description": "Remove Completed todos that have a >today tag. Can be used with parameters from Template or Callback.", - "jsFunction": "removeTodayTagsFromCompletedTodos", - "alias": [ - "removeToday", - "rmt" - ] - }, { "name": "resolveConflictWithCurrentVersion", "hidden": true, @@ -176,23 +171,21 @@ "jsFunction": "resolveConflictWithOtherVersion" }, { - "name": "Tidy: update plugin settings", - "description": "Settings interface (even for iOS)", - "jsFunction": "updateSettings" - }, - { - "name": "Tidy: Move top-level tasks in Editor to heading", - "description": "Tasks at top of active note (prior to any heading) will be placed under a specified heading", - "jsFunction": "moveTopLevelTasksInEditor", + "name": "Log notes changed in interval", + "description": "Write a list of notes changed in the last interval of days to the plugin console log. It will default to the 'Default Recent Time Interval' setting unless passed as a parameter.", + "jsFunction": "logNotesChangedInInterval", "alias": [ - "mtth", - "mtlth" + "lncii", + "tidy" ], "arguments": [ - "Heading name to place the tasks under (will be created if doesn't exist). If you are running this command in a template, put any non-blank text in the field below.", - "Run silently (e.g. in a template). Default is false.", - "Return the content of the tasks text, rather than inserting under a heading (e.g. for inserting in a tempate)" + "Parameters" ] + }, + { + "name": "Update plugin settings", + "description": "Settings interface (even for iOS)", + "jsFunction": "updateSettings" } ], "plugin.commands_disabled": [ diff --git a/np.Tidy/src/conflicts.js b/np.Tidy/src/conflicts.js index 31343a5eb..704f49dd8 100644 --- a/np.Tidy/src/conflicts.js +++ b/np.Tidy/src/conflicts.js @@ -1,7 +1,7 @@ // @flow //----------------------------------------------------------------------------- // Jonathan Clark -// Last updated 23.6.2023 for v0.6.0 by @jgclark +// Last updated 7.11.2023 for v0.9.2 by @jgclark //----------------------------------------------------------------------------- import pluginJson from '../plugin.json' @@ -24,6 +24,7 @@ import { } from '@helpers/general' import { getProjectNotesInFolder } from '@helpers/note' import { noteOpenInEditor } from '@helpers/NPWindows' +import { contentRangeToString } from '@helpers/paragraph' import { showMessage } from "@helpers/userInput" const pluginID = 'np.Tidy' @@ -66,11 +67,10 @@ async function getConflictedNotes(foldersToExclude: Array = []): Promise // Get all conflicts const conflictedNotes = notes.filter(n => (n.conflictedVersion != null)) - // const dupeTitles = conflictedNotes.map(n => displayTitle(n)) // Log details of each dupe for (const cn of conflictedNotes) { - logDebug('getConflictedNotes', `- ${displayTitle(cn)}`) + // logDebug('getConflictedNotes', `- ${displayTitle(cn)}`) const cv = cn.conflictedVersion if (cv) { // clo(cv, 'conflictedVersion = ') @@ -84,7 +84,7 @@ async function getConflictedNotes(foldersToExclude: Array = []): Promise logError('getConflictedNotes', `- ${displayTitle(cn)} appears to have no conflictedVersion`) } } - clo(outputArray, '->') + // clo(outputArray, '->') return outputArray } catch (err) { @@ -99,13 +99,14 @@ async function getConflictedNotes(foldersToExclude: Array = []): Promise */ export async function listConflicts(params: string = ''): Promise { try { - logDebug(pluginJson, `listConflicts: Starting with params '${params}'`) + const machineName = NotePlan.environment.machineName ?? NotePlan.environment.platform + logDebug(pluginJson, `listConflicts: Starting with params '${params}' on ${machineName}`) let config = await getSettings() const outputFilename = config.conflictNoteFilename ?? 'Conflicted Notes.md' // Decide whether to run silently const runSilently: boolean = await getTagParamsFromString(params ?? '', 'runSilently', false) - logDebug('removeDoneMarkers', `runSilently = ${String(runSilently)}`) + logDebug('listConflicts', `runSilently = ${String(runSilently)}`) CommandBar.showLoading(true, `Finding notes with conflicts`) await CommandBar.onAsyncThread() @@ -123,7 +124,7 @@ export async function listConflicts(params: string = ''): Promise { // remove old conflicted note list (if it exists) const res = DataStore.moveNote(outputFilename, '@Trash') if (res) { - logDebug('getConflictedNotes', `Moved existing conflicted note list '${outputFilename}' to @Trash.`) + logDebug('listConflicts', `Moved existing conflicted note list '${outputFilename}' to @Trash.`) } return } else { @@ -134,24 +135,19 @@ export async function listConflicts(params: string = ''): Promise { const outputArray = [] // Start with an x-callback link under the title to allow this to be refreshed easily - outputArray.push(`# Conflicted notes on ${NotePlan.environment.platform}`) + outputArray.push(`# Conflicted notes on ${machineName}`) const xCallbackRefreshButton = createPrettyRunPluginLink('🔄 Click to refresh', 'np.Tidy', 'List conflicted notes', []) - const summaryLine = `Found ${conflictedNotes.length} conflicts on ${NotePlan.environment.platform} at ${nowLocaleShortDateTime()}. ${xCallbackRefreshButton}` + const summaryLine = `Found ${conflictedNotes.length} conflicts on ${machineName} at ${nowLocaleShortDateTime()}. ${xCallbackRefreshButton}` outputArray.push(summaryLine) for (const cn of conflictedNotes) { - // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-call] - logDebug('getConflictedNotes', `- ${displayTitle(cn)}, ${cn.filename}`) const titleToDisplay = (cn.note.title !== '') ? displayTitle(cn.note) : '(note with no title)' + logDebug(pluginJson, `- ${cn.filename}`) + const thisFolder = cn.filename.includes('/') ? getFolderFromFilename(cn.filename) : '(root)' const mainContent = cn.note.content ?? '' - - // Write out all details for this main note - logDebug(pluginJson, `- ${titleToDisplay} / ${cn.filename}`) // Make some button links for main note const openMe = createOpenOrDeleteNoteCallbackUrl(cn.filename, 'filename', '', 'splitView', false) - // const deleteMe = createOpenOrDeleteNoteCallbackUrl(cn.filename, 'filename', '', 'splitView', true) outputArray.push(`${thisFolder}/**${titleToDisplay}**`) outputArray.push(`- Main note (${cn.filename}): ${String(cn.note.paragraphs?.length ?? 0)} lines, ${String(cn.content?.length ?? 0)} bytes (created ${relativeDateFromDate(cn.note.createdDate)}, updated ${relativeDateFromDate(cn.note.changedDate)}) [open note](${openMe})`) @@ -160,12 +156,19 @@ export async function listConflicts(params: string = ''): Promise { const cvContent = cn.note.conflictedVersion.content ?? '' outputArray.push(`- Conflicted version note: ${String(cvContent.split('\n').length)} lines, ${String(cvContent.length ?? 0)} bytes`) + // Calculate amount of difference between them const greaterSize = Math.max(cn.note.content?.length ?? 0, cvContent?.length ?? 0) const allDiffRanges = NotePlan.stringDiff(cvContent, mainContent) const totalDiffBytes = allDiffRanges.reduce((a, b) => a + Math.abs(b.length), 0) if (totalDiffBytes > 0) { const percentDiff = percentWithTerm(totalDiffBytes, greaterSize, 'chars') outputArray.push(`- ${percentDiff} difference between them (from ${String(allDiffRanges.length)} areas)`) + // Write allDiffRanges to debug log + logDebug('listConflicts', 'Here are the areas of difference:') + for (const thisDiffRange of allDiffRanges) { + logDebug('', contentRangeToString(cn.content, thisDiffRange)) + } + } else { outputArray.push(`- oddly, the conflicted version appears to be identical`) } @@ -187,15 +190,17 @@ export async function listConflicts(params: string = ''): Promise { } } catch (err) { - logError('listDuplicates', JSP(err)) + logError('listConflicts', JSP(err)) } } /** * Command to be called by x-callback to run the API function of the same name, on the given note filename */ -export function resolveConflictWithCurrentVersion(filename: string): void { +export async function resolveConflictWithCurrentVersion(filename: string): Promise { try { + // Attempt to get spinner to appear, to show that something is happening. + CommandBar.showLoading(true, 'Deleting other note version') logDebug(pluginJson, `resolveConflictWithCurrentVersion() starting for file '${filename}'`) if (NotePlan.environment.buildVersion < 1053) { logWarn(pluginJson, `resolveConflictWithOtherVersion() can't be run until NP v3.9.3`) @@ -207,17 +212,20 @@ export function resolveConflictWithCurrentVersion(filename: string): void { return } theNote.resolveConflictWithCurrentVersion() + CommandBar.showLoading(false) } catch (err) { logError(pluginJson, JSP(err)) + CommandBar.showLoading(false) } } /** * Command to be called by x-callback to run the API function of the same name, on the given note filename */ -export function resolveConflictWithOtherVersion(filename: string): void { +export async function resolveConflictWithOtherVersion(filename: string): Promise { try { + CommandBar.showLoading(true, 'Deleting main note version') logDebug(pluginJson, `resolveConflictWithOtherVersion() starting for file '${filename}'`) if (NotePlan.environment.buildVersion < 1053) { logWarn(pluginJson, `resolveConflictWithOtherVersion() can't be run until NP v3.9.3`) @@ -229,8 +237,10 @@ export function resolveConflictWithOtherVersion(filename: string): void { return } theNote.resolveConflictWithOtherVersion() + CommandBar.showLoading(false) } catch (err) { logError(pluginJson, JSP(err)) + CommandBar.showLoading(false) } }