diff --git a/jgclark.Filer/README.md b/jgclark.Filer/README.md index aa2299b83..c7d656cb1 100644 --- a/jgclark.Filer/README.md +++ b/jgclark.Filer/README.md @@ -14,6 +14,9 @@ It pops up the command bar to choose the note you want to move it to, followed b If you want to stop date backlinks being added change the line at the top of the plugin'\ `fileItems.js` file to: `const pref_addDateBacklink = false;` ## History + +### v0.4.0 15.6.2021 (@dwertheimer) +- add /nns (moved from NoteHelpers) ### v0.3.3, 11.6.2021 - remove restriction to move to just project notes - update code to work with today's API fixes diff --git a/jgclark.Filer/plugin.json b/jgclark.Filer/plugin.json index f6148a21b..27f89db84 100644 --- a/jgclark.Filer/plugin.json +++ b/jgclark.Filer/plugin.json @@ -5,7 +5,7 @@ "plugin.description": "Help file (move) the current paragraph, selected paragraphs, or heading and its section, to different notes. Please see website for more details.", "plugin.icon": "", "plugin.author": "jgclark", "plugin.url": "https://github.com/NotePlan/plugins/tree/main/jgclark.Filer", - "plugin.version": "0.3.3", + "plugin.version": "0.4.0", "plugin.dependencies": [], "plugin.script": "script.js", "plugin.isRemote": "false", @@ -19,6 +19,11 @@ "name": "mp", "description": "move (file) paragraphs to different notes", "jsFunction": "fileParas" + }, + { + "name": "nns", + "description": "New note from selection (and leave link to it in its place)", + "jsFunction": "newNoteFromSelection" } ], "plugin.preferences": [ diff --git a/jgclark.Filer/script.js b/jgclark.Filer/script.js index a736a7528..9a2f6769e 100644 --- a/jgclark.Filer/script.js +++ b/jgclark.Filer/script.js @@ -250,9 +250,150 @@ var exports = (function (exports) { console.log(` Remove original para # ${i}`); note.removeParagraphAtIndex(i); } - } // globalThis.fileParas = fileParas + } + + //------------------------------------------------------------------ + + async function showMessage(message, confirmTitle = 'OK') { + return await CommandBar.showOptions([confirmTitle], message); + } // Show feedback Yes/No Question via Command Bar (@dwertheimer) + + async function showMessageYesNo(message, choicesArray = ['Yes', 'No']) { + const answer = await CommandBar.showOptions(choicesArray, message); + return choicesArray[answer.index]; + } + async function noteOpener(fullPath, desc, useProjNoteByFilename = true) { + console.log(`\tAbout to open filename: "${fullPath}" (${desc}) using ${useProjNoteByFilename ? 'projectNoteByFilename' : 'noteByFilename'}`); + const newNote = (await useProjNoteByFilename) ? DataStore.projectNoteByFilename(fullPath) : DataStore.noteByFilename(fullPath, 'Notes'); + + if (newNote) { + console.log(`\t\tOpened ${fullPath} (${desc} version) `); + } else { + console.log(`\t\tDidn't work! ${useProjNoteByFilename ? 'projectNoteByFilename' : 'noteByFilename'} returned ${newNote}`); + } + + return newNote; + } // Find a unique note title/filename so backlinks can work properly (@dwertheimer) + // Keep adding numbers to the end of a filename (if already taken) until it works + + function getUniqueNoteTitle(title) { + let i = 0, + res = [], + newTitle = title; + + while (++i === 1 || res.length > 0) { + newTitle = i === 1 ? title : `${title} ${i}`; + res = DataStore.projectNoteByTitle(newTitle, true, false); + } + + return newTitle; + } + async function chooseFolder(msg) { + let currentFolder; + const folders = DataStore.folders; // excludes Trash and Archive + + if (folders.length > 0) { + const re = await CommandBar.showOptions(folders, msg); + currentFolder = folders[re.index]; + } else { + // no Folders so go to root + currentFolder = '/'; + } + + console.log(`\tcurrentFolder=${currentFolder}`); + return currentFolder; + } + + //------------------------------------------------------------------ + async function newNoteFromSelection() { + const version = `0.4.0`; + console.log(`Running v${version}`); + const { + selectedLinesText, + selectedText, + selectedParagraphs + } = Editor; + console.log(`\nnewNoteFromSelection (running v${version}) ${selectedParagraphs.length} selected:`); + + if (selectedLinesText.length && selectedText !== '') { + // Get title for this note + console.log(`\t1st Para Type = ${selectedParagraphs[0].type} = "${selectedParagraphs[0].content}"`); + const isTextContent = ['title', 'text', 'empty'].indexOf(selectedParagraphs[0].type) >= 0; + const strippedFirstLine = selectedParagraphs[0].content; + let title = await CommandBar.showInput('Title of new note ([enter] to use text below)', strippedFirstLine); // If user just hit [enter], then use the first line as suggested + + if (!title) { + title = strippedFirstLine; + + if (isTextContent) { + selectedLinesText.shift(); + } + } + + const movedText = selectedLinesText.join('\n'); + const uniqueTitle = getUniqueNoteTitle(title); + + if (title !== uniqueTitle) { + await showMessage(`Title exists. Using "${uniqueTitle}" instead`); + title = uniqueTitle; + } + + const currentFolder = await chooseFolder('Select folder to add note in:'); + + if (title) { + // Create new note in the specific folder + const origFile = Editor.note.title || Editor.note.filename; // Calendar notes have no title + // const origFileType = Editor.note.type //either "Notes" or "Calendar" + + console.log(`\torigFile:${origFile}`); + const filename = (await DataStore.newNote(title, currentFolder)) ?? ''; + console.log(`\tnewNote returned Filename:${filename}`); + const fullPath = `${currentFolder !== '/' ? `${currentFolder}/` : ''}${filename}`; // This question needs to be here after newNote and before noteOpener + // to force a cache refresh after newNote. This API bug will eventually be fixed. + + const iblq = await CommandBar.showOptions(['Yes', 'No'], 'Insert link to new file where selection was?'); + const newNote = await noteOpener(fullPath, 'no leading slash'); + + if (newNote) { + console.log(`\tnewNote=${newNote}\n\t${newNote.title}`); + console.log(`\tcontent=${newNote.content}`); + const insertBackLink = iblq.index === 0; + + if (Editor.replaceSelectionWithText) { + // for compatibility, make sure the function exists + if (insertBackLink) { + Editor.replaceSelectionWithText(`[[${title}]]`); + } else { + Editor.replaceSelectionWithText(``); + } + } + + newNote.appendParagraph(movedText, 'empty'); + + if (insertBackLink) { + newNote.appendParagraph(`^^^ Moved from [[${origFile}]]:`, 'text'); + } + + if ((await showMessageYesNo('New Note created. Open it now?')) === 'Yes') { + await Editor.openNoteByFilename(fullPath); + } + } else { + console.log(`\tCould not open file: "${fullPath}"`); + showMessage(`\tCould not open file ${fullPath}`); + } + } else { + console.log('\tError: undefined or empty title'); + } + } else { + showMessage('No text was selected. Nothing to do.', "OK, I'll try again!"); + } + + console.log('\nnewNoteFromSelection (finished)'); + } + globalThis.newNoteFromSelection = newNoteFromSelection; exports.fileParas = fileParas; + exports.newNoteFromSelection = newNoteFromSelection; Object.defineProperty(exports, '__esModule', { value: true }); diff --git a/jgclark.Filer/src/fileItems.js b/jgclark.Filer/src/fileItems.js index 12b11b56f..da18fbdfc 100644 --- a/jgclark.Filer/src/fileItems.js +++ b/jgclark.Filer/src/fileItems.js @@ -1,11 +1,14 @@ // @flow // ----------------------------------------------------------------------------- -// Plugin to help move selected pargraphs to other notes +// Plugin to help move selected paragraphs to other notes // Jonathan Clark // v0.3.3, 11.6.2021 // ----------------------------------------------------------------------------- -import { rangeToString, displayTitle } from '../../np.statistics/src/statsHelpers' +import { + rangeToString, + displayTitle, +} from '../../np.statistics/src/statsHelpers' // Preference that needs to get added when there is a proper config system const pref_addDateBacklink = true @@ -14,18 +17,18 @@ const pref_addDateBacklink = true // Helper Functions // Return list of all notes, sorted by changed date (newest to oldest) -function allNotesSortedByChanged() { +function allNotesSortedByChanged():Array { const projectNotes = DataStore.projectNotes.slice() const calendarNotes = DataStore.calendarNotes.slice() const allNotes = projectNotes.concat(calendarNotes) const allNotesSortedByDate = allNotes.sort( - (first, second) => - second.changedDate - first.changedDate) // most recent first + (first, second) => second.changedDate - first.changedDate, + ) // most recent first return allNotesSortedByDate } // Convert paragraph(s) to single raw text string -function parasToText(paras) { +function parasToText(paras:Array):string { // console.log('parasToText: starting with ' + paras.length + ' paragraphs') let text = '' for (let i = 0; i < paras.length; i++) { @@ -114,7 +117,7 @@ export async function fileParas() { } parasToMove.push(p) } - console.log(` Found ${parasToMove.length-1} indented paras`) + console.log(` Found ${parasToMove.length - 1} indented paras`) } } @@ -210,5 +213,3 @@ export async function fileParas() { note.removeParagraphAtIndex(i) } } - -// globalThis.fileParas = fileParas diff --git a/jgclark.Filer/src/helpers.js b/jgclark.Filer/src/helpers.js new file mode 100644 index 000000000..cd081352b --- /dev/null +++ b/jgclark.Filer/src/helpers.js @@ -0,0 +1,93 @@ +// @flow + +//------------------------------------------------------------------ +// Helper functions +//------------------------------------------------------------------ +export function printNote(note) { + if (note == null) { + console.log('Note not found!') + return + } + + if (note.type === 'Notes') { + console.log( + `title: ${note.title ?? ''}\n\tfilename: ${ + note.filename ?? '' + }\n\thashtags: ${note.hashtags?.join(',') ?? ''}\n\tmentions: ${ + note.mentions?.join(',') ?? '' + }\n\tcreated: ${String(note.createdDate) ?? ''}\n\tchanged: ${ + String(note.changedDate) ?? '' + }`, + ) + } else { + console.log( + `date: ${String(note.createdDate) ?? ''}\n\tfilename: ${ + note.filename ?? '' + }\n\thashtags: ${note.hashtags?.join(',') ?? ''}\n\tmentions: ${ + note.mentions?.join(',') ?? '' + }`, + ) + } +} + +// Show feedback message using Command Bar (@dwertheimer) +export async function showMessage(message:string, confirmTitle:string = 'OK') { + return await CommandBar.showOptions([confirmTitle], message) +} + +// Show feedback Yes/No Question via Command Bar (@dwertheimer) +export async function showMessageYesNo(message:string, choicesArray:Array = ['Yes', 'No']):Promise { + const answer = await CommandBar.showOptions(choicesArray, message) + return choicesArray[answer.index] +} + +export async function noteOpener( + fullPath: string, + desc: string, + useProjNoteByFilename: boolean = true): Promise { + console.log( + `\tAbout to open filename: "${fullPath}" (${desc}) using ${ + useProjNoteByFilename ? 'projectNoteByFilename' : 'noteByFilename' + }`, + ) + const newNote = (await useProjNoteByFilename) + ? DataStore.projectNoteByFilename(fullPath) + : DataStore.noteByFilename(fullPath, 'Notes') + if (newNote) { + console.log(`\t\tOpened ${fullPath} (${desc} version) `) + } else { + console.log( + `\t\tDidn't work! ${ + useProjNoteByFilename ? 'projectNoteByFilename' : 'noteByFilename' + } returned ${newNote}`, + ) + } + return newNote +} + +// Find a unique note title/filename so backlinks can work properly (@dwertheimer) +// Keep adding numbers to the end of a filename (if already taken) until it works +export function getUniqueNoteTitle(title:string):string { + let i = 0, + res = [], + newTitle = title + while (++i === 1 || res.length > 0) { + newTitle = i === 1 ? title : `${title} ${i}` + res = DataStore.projectNoteByTitle(newTitle, true, false) + } + return newTitle +} + +export async function chooseFolder(msg:string):Promise { + let currentFolder + const folders = DataStore.folders // excludes Trash and Archive + if (folders.length > 0) { + const re = await CommandBar.showOptions(folders, msg) + currentFolder = folders[re.index] + } else { + // no Folders so go to root + currentFolder = '/' + } + console.log(`\tcurrentFolder=${currentFolder}`) + return currentFolder +} diff --git a/jgclark.Filer/src/index.js b/jgclark.Filer/src/index.js index 4c011e630..5154c8358 100644 --- a/jgclark.Filer/src/index.js +++ b/jgclark.Filer/src/index.js @@ -3,7 +3,8 @@ // ----------------------------------------------------------------------------- // Plugin to help move selected pargraphs to other notes // Jonathan Clark -// v0.3.3, 11.6.2021 +// v0.4, 15.6.2021 // ----------------------------------------------------------------------------- export { fileParas } from './fileItems' +export { newNoteFromSelection } from './newNoteFromSelection' diff --git a/jgclark.Filer/src/newNoteFromSelection.js b/jgclark.Filer/src/newNoteFromSelection.js new file mode 100644 index 000000000..698a37fa4 --- /dev/null +++ b/jgclark.Filer/src/newNoteFromSelection.js @@ -0,0 +1,106 @@ +// @flow +//------------------------------------------------------------------ +// @dwertheimer based on @jgclark's newNote +// Create new note from currently selected text +// and (optionally) leave backlink to it where selection was +import { + showMessage, + showMessageYesNo, + noteOpener, + getUniqueNoteTitle, + chooseFolder, +} from './helpers' + +export async function newNoteFromSelection() { + const version = `0.4.0` + console.log(`Running v${version}`) + const { selectedLinesText, selectedText, selectedParagraphs } = Editor + console.log( + `\nnewNoteFromSelection (running v${version}) ${selectedParagraphs.length} selected:`, + ) + + if (selectedLinesText.length && selectedText !== '') { + // Get title for this note + console.log( + `\t1st Para Type = ${selectedParagraphs[0].type} = "${selectedParagraphs[0].content}"`, + ) + + const isTextContent = + ['title', 'text', 'empty'].indexOf(selectedParagraphs[0].type) >= 0 + const strippedFirstLine = selectedParagraphs[0].content + let title = await CommandBar.showInput( + 'Title of new note ([enter] to use text below)', + strippedFirstLine, + ) + // If user just hit [enter], then use the first line as suggested + if (!title) { + title = strippedFirstLine + if (isTextContent) { + selectedLinesText.shift() + } + } + const movedText = selectedLinesText.join('\n') + const uniqueTitle = getUniqueNoteTitle(title) + if (title !== uniqueTitle) { + await showMessage(`Title exists. Using "${uniqueTitle}" instead`) + title = uniqueTitle + } + const currentFolder = await chooseFolder('Select folder to add note in:') + + if (title) { + // Create new note in the specific folder + const origFile = Editor.note.title || Editor.note.filename // Calendar notes have no title + // const origFileType = Editor.note.type //either "Notes" or "Calendar" + console.log(`\torigFile:${origFile}`) + const filename = (await DataStore.newNote(title, currentFolder)) ?? '' + console.log(`\tnewNote returned Filename:${filename}`) + + const fullPath = `${ + currentFolder !== '/' ? `${currentFolder}/` : '' + }${filename}` + + // This question needs to be here after newNote and before noteOpener + // to force a cache refresh after newNote. This API bug will eventually be fixed. + const iblq = await CommandBar.showOptions( + ['Yes', 'No'], + 'Insert link to new file where selection was?', + ) + + const newNote = await noteOpener(fullPath, 'no leading slash') + + if (newNote) { + console.log(`\tnewNote=${newNote}\n\t${newNote.title}`) + console.log(`\tcontent=${newNote.content}`) + + const insertBackLink = iblq.index === 0 + if (Editor.replaceSelectionWithText) { + // for compatibility, make sure the function exists + if (insertBackLink) { + Editor.replaceSelectionWithText(`[[${title}]]`) + } else { + Editor.replaceSelectionWithText(``) + } + } + + newNote.appendParagraph(movedText, 'empty') + if (insertBackLink) { + newNote.appendParagraph(`^^^ Moved from [[${origFile}]]:`, 'text') + } + if ( + (await showMessageYesNo('New Note created. Open it now?')) === 'Yes' + ) { + await Editor.openNoteByFilename(fullPath) + } + } else { + console.log(`\tCould not open file: "${fullPath}"`) + showMessage(`\tCould not open file ${fullPath}`) + } + } else { + console.log('\tError: undefined or empty title') + } + } else { + showMessage('No text was selected. Nothing to do.', "OK, I'll try again!") + } + console.log('\nnewNoteFromSelection (finished)') +} +globalThis.newNoteFromSelection = newNoteFromSelection diff --git a/jgclark.NoteHelpers/README.md b/jgclark.NoteHelpers/README.md index af9058f72..054a0529d 100644 --- a/jgclark.NoteHelpers/README.md +++ b/jgclark.NoteHelpers/README.md @@ -3,7 +3,6 @@ This plugin provides commands to help work with NotePlan notes: - `/mn`: which moves a note to a different folder the user selects - `/jh`: jumps the cursor to the heading of the current note that the user selects - `/jd`: simply jumps the cursor to the `## Done` section of the current note (if it exists) -- `/nns`: create a new note from selection (and leave link to it in its place) ## Configuration Before the configuration mechanism is available, you need to update the `jgclark.noteHelpers\noteHelpers.js` file in the plugin's folder directly. Update the following lines at the top of the file accordingly: @@ -20,6 +19,9 @@ Templates should normally end with a linefeed character (`\n`). ## History +### v0.9.3, 15.6.2021 (@dwertheimer) +- change: moved **/nns** (which was temporarily here) to Filer and cleaned up here + ### v0.9.0, 12.6.2021 - [add]: **/nns** command to add a new note from selection (and leave link to it in its place) (@dwertheimer) diff --git a/jgclark.NoteHelpers/noteHelpers.js b/jgclark.NoteHelpers/noteHelpers.js index 22e651528..35491663a 100644 --- a/jgclark.NoteHelpers/noteHelpers.js +++ b/jgclark.NoteHelpers/noteHelpers.js @@ -2,8 +2,7 @@ //-------------------------------------------------------------------------------------------------------------------- // Note Helpers plugin for NotePlan // Jonathan Clark & Eduard Metzger -// /nns by @dwertheimer -// v0.9.0, 1.6.2021 +// v0.9.3, 1.6.2021 //-------------------------------------------------------------------------------------------------------------------- // Globals @@ -60,23 +59,7 @@ async function selectFolder() { } globalThis.selectFolder = selectFolder -// Show feedback message using Command Bar (@dwertheimer) -async function showMessage(message, confirmTitle = 'OK') { - return await CommandBar.showOptions([confirmTitle], message) -} - -// Find a unique note title/filename so backlinks can work properly (@dwertheimer) -function getUniqueNoteTitle(title) { - let i = 0, - res = [] - while (++i === 1 || res.length > 0) { - newtitle = i === 1 ? title : `${title} ${i}` - res = DataStore.projectNoteByTitle(newtitle,true,false) - } - return newtitle -} - -//------------------------------------------------------------------ +//----------------------------------------------------------------- // Command from Eduard to move a note to a different folder function moveNote(selectedFolder) { const { title, filename } = Editor @@ -158,7 +141,7 @@ globalThis.jumpToDone = jumpToDone //------------------------------------------------------------------ // Set the title of a note from YAML, rather than the first line. -// NOTE: not currently working because of lack of API support yet (as of release 628) +// NOTE: not currently working because of lack of API support yet (as of release 636) // TODO: add following back into plugin.json to active this again: // { // "name": "Set title from YAML", @@ -188,83 +171,8 @@ function setTitleFromYAML() { } console.log(`\tnew title = ${newTitle}`) if (newTitle !== '') { - note.title = newTitle // TODO: setter not available not yet available (last checked on release 628) + note.title = newTitle // TODO: when setter available } printNote(Editor.note) } globalThis.setTitleFromYAML = setTitleFromYAML - -//------------------------------------------------------------------ -// @dwertheimer based on @jgclark's newNote -// Create new note from currently selected text -// and (optionally) leave backlink to it where selection was -async function newNoteFromSelection() { - const { selectedLinesText, selectedText } = Editor - console.log('\nnewNoteFromSelection (running):') - let currentFolder = '' - - if (selectedLinesText.length && selectedText !== '') { - // Get title for this note - const stripHashes = /^\s*(#)* *(.*)/ - const firstLineArray = stripHashes.exec(selectedLinesText[0]) - const strippedFirstLine = - firstLineArray.length === 3 ? firstLineArray[2] : '' - let title = await CommandBar.showInput( - 'Title of new note ([enter] to use text below)', - strippedFirstLine, - ) - // If user just hit [enter], then use the first line as suggested - if (!title) { - title = strippedFirstLine - selectedLinesText.shift() - } - const movedText = selectedLinesText.join('\n') - const uniqueTitle = getUniqueNoteTitle(title) - if (title !== uniqueTitle) { - await showMessage(`Title exists. Using "${uniqueTitle}" instead`) - title = uniqueTitle - } - const folders = DataStore.folders // excludes Trash and Archive - if (folders.length > 0) { - const re = await CommandBar.showOptions( - folders, - 'Select folder to add note in:', - ) - currentFolder = folders[re.index] - } else { - // no Folders so go to root - currentFolder = '/' - } - - if (title) { - // Create new note in the specific folder - const origFile = Editor.note.title || Editor.note.filename // Calendar notes have no title - // const origFileType = Editor.note.type //either "Notes" or "Calendar" - - const filename = DataStore.newNote(title, currentFolder) ?? '' - const iblq = await CommandBar.showOptions( - ['Yes', 'No'], - 'Insert link to new file where selection was?', - ) - const insertBackLink = iblq.index === 0 - if (Editor.replaceSelectionWithText) { - // for compatibility, make sure the function exists - if (insertBackLink) { - Editor.replaceSelectionWithText(`[[${title}]]`) - } else { - Editor.replaceSelectionWithText(``) - } - } - await Editor.openNoteByFilename(filename) - if (insertBackLink) - Editor.note.appendParagraph(`From [[${origFile}]]:`, 'empty') - Editor.note.appendParagraph(movedText, 'empty') - } else { - console.log('\tError: undefined or empty title') - } - } else { - showMessage('No text was selected. Nothing to do.', "OK, I'll try again!") - } - console.log('\nnewNoteFromSelection (finished)') -} -globalThis.newNoteFromSelection = newNoteFromSelection diff --git a/jgclark.NoteHelpers/plugin.json b/jgclark.NoteHelpers/plugin.json index 478c762fe..3bf6521c3 100644 --- a/jgclark.NoteHelpers/plugin.json +++ b/jgclark.NoteHelpers/plugin.json @@ -6,7 +6,7 @@ "plugin.icon": "", "plugin.author": "Jonathan Clark & Eduard Metzger", "plugin.url": "https://github.com/NotePlan/plugins/tree/main/jgclark.NoteHelpers/", - "plugin.version": "0.9.0", + "plugin.version": "0.9.3", "plugin.changelog": "See plugin README.md", "plugin.dependencies": [], "plugin.script": "noteHelpers.js", @@ -26,11 +26,6 @@ "name": "jd", "description": "Jump to the ## Done section. (Currently only works in main window.)", "jsFunction": "jumpToDone" - }, - { - "name": "nns", - "description": "New note from selection (and leave link to it in its place)", - "jsFunction": "newNoteFromSelection" } ], "plugin.preferences": [