diff --git a/CHANGELOG.md b/CHANGELOG.md index 9747d8329..50bc706cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ All NotePlan plugins follow `semver` versioning. For details, please refer to [s See Plugin [README](https://github.com/NotePlan/plugins/blob/main/codedungeon.Toolbox/README.md) for details on available commands and use case. +## [3.1.0] - 2022-02-19 (@mikeerickson) +- fixed issue with release script +- refactored release validation in CLI `npc plugin:release` +- add guard to make sure releasing from plugins directory + ## [3.0.2] - 2022-02-17 (@mikeerickson) - restored `docs` command diff --git a/cd.FormattedDateTime/CHANGELOG.md b/cd.FormattedDateTime/CHANGELOG.md new file mode 100644 index 000000000..99d485ba3 --- /dev/null +++ b/cd.FormattedDateTime/CHANGELOG.md @@ -0,0 +1,23 @@ +# cd.FormattedDateTime Changelog + +## About cd.FormattedDateTime Plugin + +Example Plugin to demonstrate how to integrate np.Templating Plugin into a standard NotePlan Plugin + +See Plugin [README](https://github.com/NotePlan/plugins/blob/main/cd.FormattedDateTime/README.md) for details on available commands and use case. + +## [0.0.1] - 2022-02-22 (mikeerickson) + +### Added +Initial Release + +## Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Plugin Versioning Uses Semver + +All NotePlan plugins follow `semver` versioning. For details, please refer to [semver website](https://semver.org/) diff --git a/cd.FormattedDateTime/FormattedDateTime.md b/cd.FormattedDateTime/FormattedDateTime.md new file mode 100644 index 000000000..b1f1150fc --- /dev/null +++ b/cd.FormattedDateTime/FormattedDateTime.md @@ -0,0 +1,4 @@ +# FormattedDateTime +***** +#### Demonstration using extended method +<%= myFormattedDateTime({format: '%A, %B %d, %Y'}) %> diff --git a/cd.FormattedDateTime/README.md b/cd.FormattedDateTime/README.md new file mode 100644 index 000000000..7ff095d1f --- /dev/null +++ b/cd.FormattedDateTime/README.md @@ -0,0 +1,19 @@ +# FormattedDateTime Noteplan Plugin +Example NotePlan Plugin demonstration how to integrate np.Templating + +## License + +Copyright © 2022 Mike Erickson +Released under the MIT license + +## Credits + +**FormattedDateTime for NotePlan** written by **Mike Erickson** + +E-Mail: [codedungeon@gmail.com](mailto:codedungeon@gmail.com) + +Support: [https://github.com/NotePlan/plugins/issues](https://github.com/NotePlan/plugins/issues) + +Twitter: [@codedungeon](http://twitter.com/codedungeon) + +Website: [codedungeon.io](http://codedungeon.io) diff --git a/cd.FormattedDateTime/plugin.json b/cd.FormattedDateTime/plugin.json new file mode 100644 index 000000000..548b41cf2 --- /dev/null +++ b/cd.FormattedDateTime/plugin.json @@ -0,0 +1,38 @@ +{ + "macOS.minVersion": "10.13.0", + "noteplan.minAppVersion": "3.4.1", + "plugin.id": "cd.FormattedDateTime", + "plugin.name": "๐Ÿงฉ FormattedDateTime", + "plugin.version": "0.0.1", + "plugin.description": "Sample Templating Plugin", + "plugin.author": "codedungeon", + "plugin.dependencies": [], + "plugin.script": "script.js", + "plugin.url": "https://github.com/NotePlan/plugins/blob/main/cd.FormattedDateTime/README.md", + "plugin.commands": [ + { + "name": "xx:formattedDateTime", + "description": "Display Formatted Date Time", + "jsFunction": "templateFormattedDateTime", + "alias": [] + } + ], + "plugin.settings": [ + { + "type": "heading", + "title": "FormattedDateTime Settings" + }, + { + "key": "version", + "type": "hidden", + "description": "FormattedDateTime Settings Version" + }, + { + "key": "format", + "title": "Format", + "type": "string", + "description": "Date Time Format", + "default": "%A, %B %d, %Y" + } + ] +} diff --git a/cd.FormattedDateTime/src/index.js b/cd.FormattedDateTime/src/index.js new file mode 100644 index 000000000..b5320fe88 --- /dev/null +++ b/cd.FormattedDateTime/src/index.js @@ -0,0 +1,51 @@ +// @flow +// NOTE: Strip out all the comments if you want to just read the code (hopefully pretty self explanatory) + +import pluginJson from '../plugin.json' + +// import np.Templating Library +import NPTemplating from 'NPTemplating' + +import { formattedDateTimeTemplate } from '@plugins/dwertheimer.DateAutomations/src/dateFunctions' +// import { getSetting } from '@helpers/NPconfiguration' + +import { updateSettingData } from '@helpers/NPconfiguration' +import { log, logError } from '@helpers/dev' + +export async function templateFormattedDateTime(): Promise { + try { + // NOTE: this is only here to initialize settings since the plugin was never installed + await updateSettingData(pluginJson) + + const templateObj = { + methods: { + myFormattedDateTime: async (params: any = {}) => { + // this is only looking in current plugin settings file, more generic method coming in getSetting + const defaultFormat: string = DataStore.settings.format + + // build format string passed to `formattedDateTimeTemplate` + const format: string = params && params.hasOwnProperty('format') ? params.format : defaultFormat + + // NOTE: np.Templating actually passes an as it is defined in template, unlike existing templating passes things as a string + // create string version of format block which `formattedDateTimeTemplate` expects + const formatString: string = `{format: ${format}}` + + // call existing `formattedDateTimeTemplate` from `./dwertheimer.DateAutomations/src/dateFunctions` + return formattedDateTimeTemplate(formatString) + }, + }, + } + + // Assumes a template with name `FormattedDateTime` exists + // see `FormattedDateTime.md` in plugin root for sample + const result: string = (await NPTemplating.renderTemplate('FormattedDateTime', templateObj)) || '' + + Editor.insertTextAtCursor(result) + } catch (error) { + logError(pluginJson, `templateFormattedDateTime :: ${error}`) + } +} + +export async function onUpdateOrInstall(): Promise { + updateSettingData(pluginJson) +} diff --git a/helpers/NPCalendar.js b/helpers/NPCalendar.js index 3492f05e3..a9650b746 100644 --- a/helpers/NPCalendar.js +++ b/helpers/NPCalendar.js @@ -18,9 +18,16 @@ import { keepTodayPortionOnly } from './calendar' import { getDateFromUnhyphenatedDateString, getISODateStringFromCalendarFilename, + type HourMinObj, printDateRange, + removeDateTagsAndToday, todaysDateISOString, } from './dateTime' +import { + addMinutes, + differenceInMinutes, +} from 'date-fns' +import { clo } from './dev' import { displayTitle } from './general' import { findEndOfActivePartOfNote } from './paragraph' import { @@ -39,7 +46,6 @@ import { } from './timeblocks' import { showMessage, showMessageYesNo } from './userInput' -import type { HourMinObj } from './dateTime' export type EventsConfig = { eventsHeading: string, @@ -53,6 +59,7 @@ export type EventsConfig = { processedTagName?: string /* if not set, uses RE_EVENT_ID */, removeTimeBlocksWhenProcessed?: boolean, calendarToWriteTo?: string, + defaultEventDuration: number } // ---------------------------------------------------------------------------- @@ -141,16 +148,30 @@ export async function writeTimeBlocksToCalendar(config: EventsConfig, note: TNot } timeBlockString = `${datePart} ${timeBlockString}` // NB: parseDateText returns an array, so we'll use the first one as most likely - const timeblockDateRange = Calendar.parseDateText(timeBlockString)[0] + let timeblockDateRange = Calendar.parseDateText(timeBlockString)[0] if (timeblockDateRange) { // We have a valid timeblock, so let's make the event etc. - // First strip out time + date (if present) from the timeblock line, + + // First see if this is a zero-length event, which happens when no end time + // was specified. If we have a defaultEventDuration then use it. + if (differenceInMinutes(timeblockDateRange.start, timeblockDateRange.end) === 0 && config.defaultEventDuration > 0) + { + const newEndDate = addMinutes(timeblockDateRange.end, config.defaultEventDuration) + timeblockDateRange = { start: timeblockDateRange.start, end: newEndDate} + } + + // Strip out time + date (if present) from the timeblock line, // as we don't want those to go into the calendar event itself (=restOfTask). // But also keep a version with date (if present) as we don't want to lose that from the task itself. - let restOfTaskWithoutTimeBlock = thisPara.content.replace(origTimeBlockString, '').trim() // take off timeblock - let restOfTaskWithoutDateTime = restOfTaskWithoutTimeBlock.replace(`>${datePart}`, '').trim() // take off >date (if present) - restOfTaskWithoutDateTime = restOfTaskWithoutDateTime.replace(datePart, '').trim() // take off date (if present) + let restOfTaskWithoutTimeBlock = thisPara.content + .replace(origTimeBlockString, '') + .replace(/\s{2,}/g, ' ') + .trimEnd() // take off timeblock + let restOfTaskWithoutDateTime = + removeDateTagsAndToday(restOfTaskWithoutTimeBlock) + .replace(timeBlockString, '') + .replace(/\s{2,}/g, ' ') console.log(`\tWill process time block '${timeBlockString}' for '${restOfTaskWithoutDateTime}'`) // Do we want to add this particular event? @@ -171,7 +192,7 @@ export async function writeTimeBlocksToCalendar(config: EventsConfig, note: TNot // Remove time block string (if wanted) let thisParaContent = thisPara.content - console.log(`\tstarting with thisPara.content: '${thisParaContent}'`) + // console.log(`\tstarting with thisPara.content: '${thisParaContent}'`) if (config.removeTimeBlocksWhenProcessed) { thisParaContent = restOfTaskWithoutTimeBlock } diff --git a/helpers/NPConfiguration.js b/helpers/NPConfiguration.js index d888b6f9c..5abb7d277 100644 --- a/helpers/NPConfiguration.js +++ b/helpers/NPConfiguration.js @@ -45,9 +45,7 @@ const log = (msg: string = ''): void => { * @return return this as structured data, in the format specified by the first line of the codeblock (should be `javascript`) */ export async function getConfiguration(configSection: string = ''): Promise { - const configFile = DataStore.projectNotes - .filter((n) => n.filename?.startsWith(STATIC_TEMPLATE_FOLDER)) - .find((n) => !!n.title?.startsWith('_configuration')) + const configFile = DataStore.projectNotes.filter((n) => n.filename?.startsWith(STATIC_TEMPLATE_FOLDER)).find((n) => !!n.title?.startsWith('_configuration')) const content: ?string = configFile?.content if (content == null) { @@ -98,11 +96,7 @@ export async function initConfiguration(pluginJsonData: any): Promise { * @param {any} pluginJsonData - plugin.json data for which plugin is being migrated * @return {number} migration result (-1 migration section not found, 1 success, 0 no migration necessary) */ -export async function migrateConfiguration( - configSection: string, - pluginJsonData: any, - silentMode?: boolean = false, -): Promise { +export async function migrateConfiguration(configSection: string, pluginJsonData: any, silentMode?: boolean = false): Promise { // migrationResult // will be 1 if _configuration was migrated to plugin settings // will be 0 if no migration necessary @@ -205,15 +199,15 @@ export function updateSettingData(pluginJsonData: any): number { return updateResult } -export function getSetting( - pluginName?: string = '', - key: string = '', - defaultValue?: { [string]: mixed }, -): { [string]: mixed } | null { - return null +export function getSetting(pluginName?: string = '', key: string = '', defaultValue?: { [string]: mixed }): any | null { + // this method is not working as I have to figure out a way to get the path to data directory + return 'INCOMPLETE' + + const pluginSettingsData = DataStore.loadJSON(`../${pluginName}/settings.json`) + return pluginSettingsData?.[key] ? pluginSettingsData[key] : defaultValue } -export function getSettings(pluginName?: string = '', defaultValue?: { [string]: mixed }): { [string]: mixed } | null { +export function getSettings(pluginName?: string = '', defaultValue?: { [string]: mixed }): any | null { return null } diff --git a/helpers/dateTime.js b/helpers/dateTime.js index 4872eb5c5..fd4900e03 100644 --- a/helpers/dateTime.js +++ b/helpers/dateTime.js @@ -149,7 +149,7 @@ export function removeDateTags(content: string): string { export function removeDateTagsAndToday(tag: string): string { return removeDateTags(tag) .replace(/>today/, '') - .replace(/ {2,}/gm, ' ') + .replace(/\s{2,}/g, ' ') .trimEnd() } diff --git a/helpers/dev.js b/helpers/dev.js index a973d5edd..9e2ce9af9 100644 --- a/helpers/dev.js +++ b/helpers/dev.js @@ -125,7 +125,11 @@ export function log(pluginInfo: any, message: any = '', type: string = 'LOG'): s msg = `${dt().padEnd(19)} | ${type.padEnd(5)} | ${pluginId} v${pluginVersion} :: ${_message(message)}` } else { msgType = arguments.length === 2 ? message : type - msg = `${dt().padEnd(19)} | ${msgType.padEnd(5)} | INVALID_PLUGIN_INFO :: ${_message(pluginInfo)}` + if (message.length > 0) { + msg = `${dt().padEnd(19)} | ${msgType.padEnd(5)} | ${pluginInfo} :: ${_message(message)}` + } else { + msg = `${dt().padEnd(19)} | ${msgType.padEnd(5)} | INVALID_PLUGIN_INFO :: ${_message(pluginInfo)}` + } } console.log(msg) diff --git a/jgclark.DailyJournal/plugin.json b/jgclark.DailyJournal/plugin.json index 8a4a98f4f..89907ea5e 100644 --- a/jgclark.DailyJournal/plugin.json +++ b/jgclark.DailyJournal/plugin.json @@ -1,5 +1,5 @@ { - "noteplan.min_version": "3.3.2", + "noteplan.minAppVersion": "3.3.2", "macOS.minVersion": "10.13.0", "plugin.id": "jgclark.DailyJournal", "plugin.name": "โ˜€๏ธ Daily Journal", diff --git a/jgclark.EventHelpers/CHANGELOG.md b/jgclark.EventHelpers/CHANGELOG.md index 3233e0041..2ba68bb2b 100644 --- a/jgclark.EventHelpers/CHANGELOG.md +++ b/jgclark.EventHelpers/CHANGELOG.md @@ -3,6 +3,10 @@ See [website README for more details](https://github.com/NotePlan/plugins/tree/m +## [0.11.5] - 2022-02-20 +### Added +- new `defaultEventDuration` (in minutes) which is used if the time block doesn't have an end time, to create it. Otherwise the event will be 0 minutes long. + ## [0.11.4] - 2022-02-07 ### Fixed - fix to allow `matchingEvent` calls to be run from Templates, after change to new built-in Settings screen diff --git a/jgclark.EventHelpers/README.md b/jgclark.EventHelpers/README.md index 64632ec43..0de3567a0 100644 --- a/jgclark.EventHelpers/README.md +++ b/jgclark.EventHelpers/README.md @@ -4,11 +4,12 @@ This plugin provides commands to help work with Calendars and Events: - `/insert day's events as list`: insert list of this day's calendar events at cursor - `/list day's events to log`: write list of this day's calendar events to console log - `/insert matching events`: adds this day's calendar events matching certain patterns at cursor -- `/time blocks to calendar`: takes [NotePlan-defined time blocks](https://help.noteplan.co/article/52-part-2-tasks-events-and-reminders#timeblocking) and converts to full Calendar events, in your current default calendar, as set by iCal. +- `/time blocks to calendar`: takes [NotePlan-defined time blocks](https://help.noteplan.co/article/52-part-2-tasks-events-and-reminders#timeblocking) and converts to full Calendar events, in your current default calendar, as set by iCal. (If the end time is not given, then the 'defaultEventDuration' setting is used to give it an end time.) - `/process date offsets`: finds date offset patterns and turns them into due dates, based on date at start of section. (See [Templates for Dates](#template-for-dates) below for full details.) The first four of these have a number of [options described below](#configuration). See [Theme customisation](#theme-customisation) below for more on how to customise display of time blocks and events. + ## Date Offsets This is best understood with a quick example: @@ -70,6 +71,7 @@ This uses JSON5 format: ensure there are commas at the end of all that lines tha - **calendarSet**: optional ["array","of calendar","names"] to filter by when showing list of events. If empty or missing, no filtering will be done. - **addMatchingEvents**: for `/add matching events` is a set of pairs of strings. The first string is what is matched for in an event's title. If it does match the second string is used as the format for how to insert the event details at the cursor. This uses the same `*|TITLE|*`, `*|START|*` (time), `*|END|*` (time), `*|NOTES|*` and `*|URL|*` format items below ... NB: At this point the 'location' field is unfortunately _not_ available through the API. - **calendarNameMappings**: optional - add mappings for your calendar names to appear in the output - e.g. from "Thomas" to "Me" with "Thomas;Me". +- **defaultEventDuration**: Event duration (in minutes) to use when making an event from a time block, if an end time is not given. ### Using Event Lists from a Template If you use Templates, this command can be called when a Template is inserted (including in the `/day start` command which applies your `Daily Note Template` file). To do this insert `{{events()}}` wherever you wish it to appear in the Template. By default it gives a simple markdown list of event title and start time. To **customise the list display**, you can add a `'template:"..."'` parameter to the `{{events()}}` command that sets how to present the list, and a separate parameter for items with no start/end times (`'allday_template:"..."`). diff --git a/jgclark.EventHelpers/__tests__/eventsToNotes.test.js b/jgclark.EventHelpers/__tests__/eventsToNotes.test.js index c1bdec85a..d7a0d8a00 100644 --- a/jgclark.EventHelpers/__tests__/eventsToNotes.test.js +++ b/jgclark.EventHelpers/__tests__/eventsToNotes.test.js @@ -1,27 +1,29 @@ /* global describe, expect, test, toEqual */ import { sortByCalendarNameAndStartTime } from '../src/eventsToNotes' +import { clo } from '../../helpers/dev' describe('eventsToNotes.js tests', () => { describe('sortByCalendarNameAndStartTime()', () => { - let sortedMap: { cal: string, start: string, text: string }[] = [] - sortedMap.push({cal:'calA',start:'10:00', text:'event string 2'}) - sortedMap.push({cal:'calA',start:'23:00', text:'event string 5'}) - sortedMap.push({cal:'calB',start:'09:00', text:'event string 1'}) - sortedMap.push({cal:'calB',start:'12:00', text:'event string 4'}) - sortedMap.push({cal:'calC',start:'11:00', text:'event string 3'}) - let mapForSorting: { cal: string, start: string, text: string }[] = [] mapForSorting.push({cal:'calB',start:'09:00', text:'event string 1'}) mapForSorting.push({cal:'calA',start:'10:00', text:'event string 2'}) mapForSorting.push({cal:'calC',start:'11:00', text:'event string 3'}) mapForSorting.push({cal:'calB',start:'12:00', text:'event string 4'}) mapForSorting.push({cal:'calA',start:'23:00', text:'event string 5'}) - mapForSorting.push({cal:'calC',start:'11:00', text:'event string 6'}) + mapForSorting.push({ cal: 'calC', start: '11:00', text: 'event string 6' }) + let sortedMap: { cal: string, start: string, text: string }[] = [] + sortedMap.push({cal:'calA',start:'10:00', text:'event string 2'}) + sortedMap.push({cal:'calA',start:'23:00', text:'event string 5'}) + sortedMap.push({cal:'calB',start:'09:00', text:'event string 1'}) + sortedMap.push({cal:'calB',start:'12:00', text:'event string 4'}) + sortedMap.push({cal:'calC',start:'11:00', text:'event string 3'}) + sortedMap.push({ cal: 'calC', start: '11:00', text: 'event string 6' }) + test('should sort by calendar name then start time test 1', () => { const result = mapForSorting.sort(sortByCalendarNameAndStartTime()) - console.log(result) + // clo(result) expect(result).toEqual(sortedMap) }) }) diff --git a/jgclark.EventHelpers/plugin.json b/jgclark.EventHelpers/plugin.json index e9109bcc3..b7764bfa1 100644 --- a/jgclark.EventHelpers/plugin.json +++ b/jgclark.EventHelpers/plugin.json @@ -1,5 +1,5 @@ { - "noteplan.min_version": "3.3.2", + "noteplan.minAppVersion": "3.3.2", "macOS.minVersion": "10.13.0", "plugin.id": "jgclark.EventHelpers", "plugin.name": "๐Ÿ•“ Event Helpers", @@ -7,7 +7,7 @@ "plugin.icon": "", "plugin.author": "jgclark", "plugin.url": "https://github.com/NotePlan/plugins/tree/main/jgclark.EventHelpers", - "plugin.version": "0.11.4", + "plugin.version": "0.11.5", "plugin.dependencies": [], "plugin.script": "script.js", "plugin.isRemote": "false", @@ -36,6 +36,11 @@ "name": "process date offsets", "description": "finds date offset patterns and turns them into due dates, based on date at start of section", "jsFunction": "processDateOffsets" + }, + { + "name": "events:upgrade", + "description": "upgrade events config", + "jsFunction": "onUpdateOrInstall" } ], "plugin.settings": [ @@ -132,6 +137,14 @@ "type": "json", "default": "{\n\t\"hour\": \"2-digit\", \n\t\"minute\": \"2-digit\", \n\t\"hour12\": false\n}", "required": false + }, + { + "key": "defaultEventDuration", + "title": "Default event duration", + "description": "Event duration (in minutes) to use when making an event from a time block, if an end time is not given.", + "type": "number", + "default": 60, + "required": true } ] } \ No newline at end of file diff --git a/jgclark.EventHelpers/src/config.js b/jgclark.EventHelpers/src/config.js index b74c9cc0c..465373b91 100644 --- a/jgclark.EventHelpers/src/config.js +++ b/jgclark.EventHelpers/src/config.js @@ -1,7 +1,7 @@ // @flow // ---------------------------------------------------------------------------- // Sort configuration for commands in the Event Helpers plugin. -// Last updated 7.2.2022 for v0.11.4, by @jgclark +// Last updated 19.2.2022 for v0.11.5, by @jgclark // @jgclark // ---------------------------------------------------------------------------- import { @@ -46,24 +46,6 @@ export async function getEventsSettings(): Promise { clo(config, `\t${configKey} settings from V2:`) return config - // console.log(`\tInfo: couldn't find 'events' settings in _configuration note. Will use defaults.`) - // return { - // eventsHeading: '### Events today', - // addMatchingEvents: { - // "meeting": "### *|TITLE|* (*|START|*)\\n*|NOTES|*", - // "webinar": "### *|TITLE|* (*|START|*) *|URL|*", - // "holiday": "*|TITLE|* *|NOTES|*", - // }, - // locale: 'en-US', - // timeOptions: { hour: '2-digit', minute: '2-digit', hour12: false }, - // calendarSet: [], - // calendarNameMappings: [], - // processedTagName: '#event_created', - // removeTimeBlocksWhenProcessed: true, - // addEventID: false, - // confirmEventCreation: false, - // calendarToWriteTo: '', - // } } else { // Read settings from _configuration, or if missing set a default // Don't mind if no config section is found @@ -89,6 +71,7 @@ export async function getEventsSettings(): Promise { addEventID: castBooleanFromMixed(v1Config, 'addEventID'), confirmEventCreation: castBooleanFromMixed(v1Config, 'confirmEventCreation'), calendarToWriteTo: castStringFromMixed(v1Config, 'calendarToWriteTo'), + defaultEventDuration: 60 // not available to set through ConfigV1 } // $FlowFixMe clo(config, `\t${configKey} settings from V1:`) diff --git a/jgclark.EventHelpers/src/eventsToNotes.js b/jgclark.EventHelpers/src/eventsToNotes.js index 7abf556a2..c30611223 100644 --- a/jgclark.EventHelpers/src/eventsToNotes.js +++ b/jgclark.EventHelpers/src/eventsToNotes.js @@ -1,7 +1,7 @@ // @flow // ---------------------------------------------------------------------------- // Command to bring calendar events into notes -// Last updated 28.1.2022 for v0.11.0, by @jgclark +// Last updated 19.2.2022 for v0.11.5, by @jgclark // @jgclark, with additions by @dwertheimer, @weyert, @m1well // ---------------------------------------------------------------------------- @@ -80,7 +80,7 @@ export async function listDaysEvents(paramString: string = ''): Promise ? mapForSorting.map((element) => element.text).join('\n') : outputArray.join('\n') - output.replace(/\\s{2,}/g, ' ') // If this array is empty -> empty string + output.replace(/\s{2,}/gm, ' ') // If this array is empty -> empty string console.log(output) return output } @@ -237,10 +237,10 @@ const calendarNameWithMapping = (name: string, mappings: Array): string } /** - * @private + * Sorter for CalendarItems * @author @m1well */ -const sortByCalendarNameAndStartTime = (): Function => { +export const sortByCalendarNameAndStartTime = (): Function => { return (b, a) => { if (a.cal !== b.cal) { if (a.cal > b.cal) { diff --git a/jgclark.Filer/plugin.json b/jgclark.Filer/plugin.json index 49804c308..06d249dad 100644 --- a/jgclark.Filer/plugin.json +++ b/jgclark.Filer/plugin.json @@ -1,5 +1,5 @@ { - "noteplan.min_version": "3.0.23", + "noteplan.minAppVersion": "3.0.23", "macOS.minVersion": "10.13.0", "plugin.id": "jgclark.Filer", "plugin.name": "๐Ÿ“ฆ Filer", diff --git a/jgclark.NoteHelpers/plugin.json b/jgclark.NoteHelpers/plugin.json index 99f24dd42..b767529aa 100644 --- a/jgclark.NoteHelpers/plugin.json +++ b/jgclark.NoteHelpers/plugin.json @@ -1,5 +1,5 @@ { - "noteplan.min_version": "3.0.23", + "noteplan.minAppVersion": "3.0.23", "macOS.minVersion": "10.13.0", "plugin.id": "jgclark.NoteHelpers", "plugin.name": "๐Ÿ“™ Note Helpers", diff --git a/jgclark.QuickCapture/plugin.json b/jgclark.QuickCapture/plugin.json index ad5d93be9..eddbd2eea 100644 --- a/jgclark.QuickCapture/plugin.json +++ b/jgclark.QuickCapture/plugin.json @@ -1,5 +1,5 @@ { - "noteplan.min_version": "3.3.2", + "noteplan.minAppVersion": "3.3.2", "macOS.minVersion": "10.13.0", "plugin.id": "jgclark.QuickCapture", "plugin.name": "โšก๏ธ Quick Capture", diff --git a/jgclark.RepeatExtensions/CHANGELOG.md b/jgclark.RepeatExtensions/CHANGELOG.md index 62c4210d7..f472a59c8 100644 --- a/jgclark.RepeatExtensions/CHANGELOG.md +++ b/jgclark.RepeatExtensions/CHANGELOG.md @@ -1,7 +1,9 @@ # What's changed in ๐Ÿ” Repeat Extensions plugin? -## [unreleased] -- just moving code around +## [0.3.1] - 2022-02-20 +### Changed +- can now be called by the command `/generate repeats`, and `/rpt` is now an alias for this +- code refactoring ## [0.3.0] - 2021-10-24 ### Added diff --git a/jgclark.RepeatExtensions/README.md b/jgclark.RepeatExtensions/README.md index e6825ec35..d7f7be469 100644 --- a/jgclark.RepeatExtensions/README.md +++ b/jgclark.RepeatExtensions/README.md @@ -15,16 +15,24 @@ is completed, and then `/rpt` run, the task then becomes: ``` and the task will show up again 2 weeks later. -**To run it on the _currently open note_, type `/rpt` in the command bar**. There is no automatic way to trigger plugins in NotePlan at the moment, so it needs to be run each time one of these `@repeat(interval)` tasks is completed. - -When run on a _project note_, it creates the new repeated task straight after the completed task. -When run on a _daily note_, it creates the new repeated task on the date of the new repeat. - -Notes: - -- Valid intervals are specified as `[+][0-9][bdwmqy]`. This allows for `b`usiness days, `d`ays, `w`eeks, `m`onths, `q`uarters or `y`ears. ('Business' days ignore weekends, but doesn't ignore public holidays, as they're different for each country.) -- When _interval_ is of the form `+2w` it will duplicate the task for 2 weeks after the date the _task was completed_. -- When _interval_ is of the form `2w` it will duplicate the task for 2 weeks after the date the _task was last due_. This is found from a `>yyyy-mm-dd` scheduled date. If this can't be determined, then it defaults to the first option. +## Running it +It runs on the _currently open note_, by typing `/generate repeats` (or its alias `/rpt`) in the command bar. +- When run on a _project note_, it creates the new repeated task straight after the completed task. +- When run on a _daily note_, it creates the new repeated task on the date of the new repeat. + +There is no automatic way to trigger plugins in NotePlan at the moment, so it needs to be run each time one of these `@repeat(interval)` tasks is completed. + +## Specifiying the Intervals +The time intervals have two parts: number and then a character. The **character** is one of: +- `b`: business days (ignore weekends, but doesn't ignore public holidays, as they're different for each country.) +- `d`: days +- `w`: weeks +- `m`: months +- `q`: quarters +- `y`: years. + +When the **number** starts with a **+** (e.g. `+1m`) it will duplicate the task for 1 month after the date the _task was completed_. +When the number doesn't start with a + (e.g. `1m`) it will duplicate the task for 1 month after the date the _task was last due_. This is found from a `>yyyy-mm-dd` scheduled date. If this can't be determined, then it defaults to the first option. ## Configuration For this feature to work, you need to have the '**Append Completion Date**' NotePlan setting turned on in the Preferences (and not to mind the time portion of the `@done(...)` tag being removed, as a sign that the line has been processed). diff --git a/jgclark.RepeatExtensions/plugin.json b/jgclark.RepeatExtensions/plugin.json index 3b31a04a8..218442cdc 100644 --- a/jgclark.RepeatExtensions/plugin.json +++ b/jgclark.RepeatExtensions/plugin.json @@ -1,19 +1,20 @@ { - "noteplan.min_version": "3.0.23", + "noteplan.minAppVersion": "3.0.23", "macOS.minVersion": "10.13.0", "plugin.id": "jgclark.RepeatExtensions", "plugin.name": "๐Ÿ” @repeat Extensions", - "plugin.description": "Commands to extend the built-in @repeat() mechanism. See website for more details.", + "plugin.description": "Commands to extend the built-in @repeat() mechanism. See info for more details.", "plugin.icon": "", "plugin.author": "Jonathan Clark", "plugin.url": "https://github.com/NotePlan/plugins/tree/main/jgclark.RepeatExtensions/", - "plugin.version": "0.3.0", + "plugin.version": "0.3.1", "plugin.dependencies": [], "plugin.script": "script.js", "plugin.isRemote": "false", "plugin.commands": [ { - "name": "rpt", + "name": "generate repeats", + "alias": ["rpt"], "description": "Generate new @repeat() tasks from completed ones", "jsFunction": "repeats" } diff --git a/jgclark.RepeatExtensions/src/index.js b/jgclark.RepeatExtensions/src/index.js index 638af792a..010c40fa5 100644 --- a/jgclark.RepeatExtensions/src/index.js +++ b/jgclark.RepeatExtensions/src/index.js @@ -1,9 +1,11 @@ // @flow - -//-------------------------------------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // Repeat Extensions plugin for NotePlan // Jonathan Clark -// v0.2.2, 11.6.2021 -//-------------------------------------------------------------------------------------------------------------------- +// Last updated 20.2.2022 for v0.3.1 +//----------------------------------------------------------------------------- export { repeats } from './repeatExtensions' + +// allow changes in plugin.json to trigger recompilation +import pluginJson from '../plugin.json' diff --git a/jgclark.RepeatExtensions/src/repeatExtensions.js b/jgclark.RepeatExtensions/src/repeatExtensions.js index a94727039..7c43a5c5e 100644 --- a/jgclark.RepeatExtensions/src/repeatExtensions.js +++ b/jgclark.RepeatExtensions/src/repeatExtensions.js @@ -1,10 +1,11 @@ // @flow -//-------------------------------------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // Repeat Extensions plugin for NotePlan // Jonathan Clark -// last updated v0.3.0+, 16.12.2021 -//-------------------------------------------------------------------------------------------------------------------- +// last updated 2022-02-20 for v0.3.1 +//----------------------------------------------------------------------------- +import pluginJson from "../plugin.json" import { RE_DATE, // find dates of form YYYY-MM-DD RE_DATE_INTERVAL, @@ -16,12 +17,13 @@ import { } from '../../helpers/NPdateTime' import { showMessage } from '../../helpers/userInput' import { findEndOfActivePartOfNote } from '../../helpers/paragraph' +import { log, logWarn, logError } from "@helpers/dev" //------------------------------------------------------------------ /** - * Process any completed(or cancelled) tasks with my extended @repeat(..) tags, - * and also remove the HH: MM portion of any @done(...) tasks. - * + * Process any completed (or cancelled) tasks with my extended @repeat(..) tags, + * and also remove the HH:MM portion of any @done(...) tasks. + * @author @jgclark */ export async function repeats(): Promise { // When interval is of the form '+2w' it will duplicate the task for 2 weeks @@ -45,14 +47,12 @@ export async function repeats(): Promise { // Get current note details const { paragraphs, title, note, type } = Editor - if (paragraphs === null) { + if (note === null || paragraphs === null) { // No note open, or no paragraphs (perhaps empty note), so don't do anything. - console.log('repeat: warning: No note open, or empty note.') + logError(pluginJson, 'No note open, or empty note.') return } let lineCount = paragraphs.length - // $FlowIgnore[incompatible-type] - console.log(`\nrepeats: from note '${title}'`) // check if the last paragraph is undefined, and if so delete it from our copy if (paragraphs[lineCount] === null) { @@ -80,31 +80,31 @@ export async function repeats(): Promise { // find lines with datetime to shorten, and capture date part of it // i.e. @done(YYYY-MM-DD HH:MM[AM|PM]) - // console.log(` [${n}] ${line}`) + // logWarn(pluginJson, ` [${n}] ${line}`) if (p.content.match(RE_DONE_DATE_TIME)) { // get completed date and time reReturnArray = line.match(RE_DONE_DATE_CAPTURE) ?? [] completedDate = reReturnArray[1] completedTime = reReturnArray[2] - console.log(` Found completed repeat ${completedDate} /${completedTime} in line ${n}`) + log(pluginJson, `Found completed repeat ${completedDate} ${completedTime} in line ${n}`) // remove time string from completed date-time updatedLine = line.replace(completedTime, '') // couldn't get a regex to work here p.content = updatedLine // Send the update to the Editor Editor.updateParagraph(p) - // console.log(` updated Paragraph ${p.lineIndex}`) + // log(pluginJson, ` updated Paragraph ${p.lineIndex}`) // Test if this is one of my special extended repeats if (updatedLine.match(RE_EXTENDED_REPEAT)) { repeatCount++ let newRepeatDate = '' - let outline = '' + let outputLine = '' // get repeat to apply reReturnArray = updatedLine.match(RE_EXTENDED_REPEAT_CAPTURE) // $FlowIgnore[incompatible-use] let dateIntervalString = (reReturnArray.length > 0) ? reReturnArray[1] : '' - console.log(`\tFound EXTENDED @repeat syntax: '${dateIntervalString}'`) + log(pluginJson, ` Found EXTENDED @repeat syntax: '${dateIntervalString}'`) if (dateIntervalString[0].startsWith('+')) { // New repeat date = completed date + interval @@ -113,65 +113,65 @@ export async function repeats(): Promise { dateIntervalString.length, ) newRepeatDate = calcOffsetDateStr(completedDate, dateIntervalString) - console.log(`\tAdding from completed date --> ${newRepeatDate}`) + log(pluginJson, ` Adding from completed date --> ${newRepeatDate}`) // Remove any >date updatedLine = updatedLine.replace(/\s+>\d{4}-[01]\d{1}-\d{2}/, '') // i.e. RE_DUE_DATE, but can't get regex to work with variables like this - // console.log(`\tupdatedLine: ${updatedLine}`) + // logWarn(pluginJson, `\tupdatedLine: ${updatedLine}`) } else { // New repeat date = due date + interval // look for the due date(>YYYY-MM-DD) let dueDate = '' const resArray = updatedLine.match(RE_DUE_DATE_CAPTURE) ?? [] - // console.log(resArray.length) + // log(pluginJson, resArray.length) if (resArray[1] != null) { - console.log(`\tmatch => ${resArray[1]}`) + logWarn(pluginJson, ` match => ${resArray[1]}`) dueDate = resArray[1] // need to remove the old due date updatedLine = updatedLine.replace(`>${dueDate}`, '') - // console.log(updatedLine); + // log(pluginJson, updatedLine); } else { // but if there is no due date then treat that as today dueDate = completedDate - // console.log(`\tno match => use completed date ${dueDate}`) + // logWarn(pluginJson, `\tno match => use completed date ${dueDate}`) } newRepeatDate = calcOffsetDateStr(dueDate, dateIntervalString) - console.log(`\tAdding from due date --> ${newRepeatDate}`) + log(pluginJson, ` Adding from due date --> ${newRepeatDate}`) } - outline = updatedLine.replace(/@done\(.*\)/, '').trim() + outputLine = updatedLine.replace(/@done\(.*\)/, '').trim() - // Create and add the new repeat line ... + // Create and add the new repeat line if (type === 'Notes') { // ...either in same project note - outline += ` >${newRepeatDate}` - // console.log(`\toutline: ${outline}`) - await Editor.insertParagraphAfterParagraph(outline, p, 'open') - console.log(`\tInserted new para after line ${p.lineIndex}`) - } else { + outputLine += ` >${newRepeatDate}` + // logWarn(pluginJson, `\toutputLine: ${outputLine}`) + await Editor.insertParagraphAfterParagraph(outputLine, p, 'open') + log(pluginJson, ` Inserted new para after line ${p.lineIndex}`) + } + else { // ... or in the future daily note (prepend) - // console.log(' -> ' + outline) const newRepeatDateShorter = unhyphenateString(newRepeatDate) const newDailyNote = await DataStore.calendarNoteByDateString(newRepeatDateShorter) if (newDailyNote?.title != null) { - // console.log(newDailyNote.filename) - await newDailyNote.appendTodo(outline) - console.log(`\tInserted new repeat in daily note ${newRepeatDateShorter}`) + // logWarn(pluginJson, newDailyNote.filename) + await newDailyNote.appendTodo(outputLine) + log(pluginJson, ` Inserted new repeat in daily note ${newRepeatDateShorter}`) } else { // After a fix to future calendar note creation in r635, we shouldn't get here. // But just in case, we'll create new repeat in today's daily note - outline += ` >${newRepeatDate}` - console.log(`\toutline: ${outline}`) + outputLine += ` >${newRepeatDate}` + log(pluginJson, `\toutputLine: ${outputLine}`) - await Editor.insertParagraphAfterParagraph(outline, p, 'open') - console.log('\tInserted new repeat in original daily note') + await Editor.insertParagraphAfterParagraph(outputLine, p, 'open') + logWarn(pluginJson, 'Inserted new repeat in original daily note') } } } } } if (repeatCount === 0) { - await showMessage('No suitable completed repeats found') - console.log('\tNote: no suitable completed repeats found') + await showMessage('No suitable completed repeats found', 'OK', 'Repeat Extensions') + logWarn(pluginJson, 'No suitable completed repeats found') } } diff --git a/np.Templating/README.md b/np.Templating/README.md index b95d1a241..57e0c0916 100644 --- a/np.Templating/README.md +++ b/np.Templating/README.md @@ -1,33 +1,56 @@ -# NotePlan Templating Noteplan Plugin +# ๐Ÿงฉ np.Templating templating plugin for Noteplan -[You will delete this text and replace it with a readme about your plugin -- not ever seen by users, but good for people looking at your code. Before you delete though, you should know:] +## Overview +**np.Templating** is a template language plugin for NotePlan that lets you insert variables and method results into your notes. It will also let you execute custom JavaScript constructed in the templates providing a rich note taking system. -You do not need all of this scaffolding for a basic NP plugin. As the instructions state [Creating Plugins](https://help.noteplan.co/article/65-commandbar-plugins), you can create a plugin with just two files: `plugin.json` and `script.js`. Please read that whole page before proceeding here. +

+ np.Templating +

-However, for more complex plugins, you may find that it's easier to write code in multiple files, incorporating code (helper functions, etc.) written (and *TESTED*) previously by others. We strongly recommend type checking (e.g. [Flow.io](https://flow.io)) to help validate the code you write. If either of those is interesting to you, you're in the right place. Before going any further, make sure you follow the development environment [setup instructions](https://github.com/NotePlan/plugins). +## Documentation +๐Ÿ“– This README provides a quick overview of np.Templating, visit [np.Templating website](https://nptemplating-docs.netlify.app/) for comprehensive documention. -## Creating NotePlan Plugin +## Commands +All commands can be invoked using the _NotePlan Command Bar_ (`Command-J` then ` / `) which NotePlan has reserved for plugin activation. Or by selecting `๐Ÿงฉ np.Templating` from the **Plugins** menu) -You can create a NotePlan plugin by executing: +

+ np.Templating Command Bar +

-```bash -noteplan-cli plugin:create -``` +

+ np.Templating Menu +

-Open up a terminal folder and change directory to the plugins repository root. Run the command `npm run autowatch` which will keep looking for changes to all plugin files and will re-compile when JavaScript changes are made. It will also transpile ES6 and ES7 code to ES5 which will run on virtually all Macs, and will copy the file(s) to the NotePlan Plugins folder, so you can immediately test in Noteplan. +Once the command bar is displayed, you can continue typing any of the following commands to invoke the appropriate plugin command. In some case where specifically noted, you can alternately invoke the plugin command at the current insertion pointer within your document. -### NotePlan Plugins Directory -You can find all your currently installed NotePlan Plugins here +๐Ÿ“– Visit [np.Templating website](https://nptemplating-docs.netlify.app/) for comprehensive documention -```bash -/Users//Library/Containers/co.noteplan.NotePlan3/Data/Library/Application Support/co.noteplan.NotePlan3/Plugins -``` +| Command | Available Inline | Description | +| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------- | +| np:init | Yes | Initilalizes np.Templating Settings (only use if you want to reset settings to default) | +| np:insert | Yes | Insert selected template at cursor (will show list of all available templates) | +| np:append | Yes | Appends selected template at end of current note (will show list of all available templates) | +| np:new | Yes | Creates a new note from selected template and supplied note name | +| np:advice | Yes | Inserts random advice at cursor location | +| np:affirmation | Yes | Inserts random affirmation at cursor location | +| np:quote | Yes | Inserts random quote at cursor location | +| np:verse | Yes | Inserts random bible verse at cursor location | +| np:weather | Yes | Inserts current weather for your location at cursor location | +| np:update | Yes | Invokes settings update method | -Keep in mind that you can code/test without updating the plugin version property in `plugin.json`, however when you push the code to the Plugins repository (or create a PR), you should update the version number so that other NotePlan users who have installed your plugin will know that an updated version is available. +## License -Further to that point, you can use your plugin locally, or you can use `git` to create a Pull Request to get it merged in the Noteplan/plugins repository and potentially available for all users through the `NotePlan > Preferences > Plugins` tab. +Copyright © 2022 Mike Erickson +Released under the MIT license -That's it. Happy coding! +## Credits -## NotePlan Plugin Team -Hat-tip to @eduard, @nmn, @jgclark, @dwertheimer and @codedungeon, who made all this fancy cool stuff. +**Codedugeon Toolbox for NotePlan** written by **Mike Erickson** + +E-Mail: [codedungeon@gmail.com](mailto:codedungeon@gmail.com) + +Support: [https://github.com/NotePlan/plugins/issues](https://github.com/NotePlan/plugins/issues) + +Twitter: [@codedungeon](http://twitter.com/codedungeon) + +Website: [codedungeon.io](http://codedungeon.io) diff --git a/np.Templating/docs/images/command-bar.png b/np.Templating/docs/images/command-bar.png new file mode 100644 index 000000000..d478ca7d0 Binary files /dev/null and b/np.Templating/docs/images/command-bar.png differ diff --git a/np.Templating/docs/images/menu.png b/np.Templating/docs/images/menu.png new file mode 100644 index 000000000..145aee3f5 Binary files /dev/null and b/np.Templating/docs/images/menu.png differ diff --git a/np.Templating/docs/images/npTemplating-intro.png b/np.Templating/docs/images/npTemplating-intro.png new file mode 100644 index 000000000..76d42ac2c Binary files /dev/null and b/np.Templating/docs/images/npTemplating-intro.png differ diff --git a/np.Templating/lib/NPTemplating.js b/np.Templating/lib/NPTemplating.js index 4441b0382..71fd6b91f 100644 --- a/np.Templating/lib/NPTemplating.js +++ b/np.Templating/lib/NPTemplating.js @@ -1,13 +1,14 @@ // @flow /*------------------------------------------------------------------------------------------- - * Copyright (c) 2021-2022 Mike Erickson / Codedungeon. All rights reserved. + * Copyright (c) 2022 Mike Erickson / Codedungeon. All rights reserved. * Licensed under the MIT license. See LICENSE in the project root for license information. * -----------------------------------------------------------------------------------------*/ import { semverVersionToNumber } from '@helpers/general' import pluginJson from '../plugin.json' import FrontmatterModule from './support/modules/FrontmatterModule' -import { log } from '@helpers/dev' +import { log, logError } from '@helpers/dev' +import globals from './globals' /*eslint-disable */ import TemplatingEngine from './TemplatingEngine' @@ -19,6 +20,15 @@ const TEMPLATE_FOLDER_NAME = '๐Ÿ“‹ Templates' // - if a new module has been added, make sure it has been added to this list const TEMPLATE_MODULES = ['date', 'frontmatter', 'note', 'system', 'time', 'user', 'utility'] +const getProperyValue = (object: any, key: string): any => { + key.split('.').forEach((token) => { + // $FlowIgnorew + if (object) object = object[token] + }) + + return object +} + export const selection = async (): Promise => { return Editor.selectedParagraphs.map((para) => para.rawContent).join('\n') } @@ -140,6 +150,23 @@ export default class NPTemplating { // constructor method required to access instance config (see setup method) } + static _filterTemplateResult(templateResult: string = ''): string { + let result = templateResult + + result = result.replace('ejs', 'template') + result = result.replace('If the above error is not helpful, you may want to try EJS-Lint:', '') + // result = result.replace(/(?:https?|ftp):\/\/[\n\S]+/g, 'HTTP_REMOVED') + result = result.replace('https://github.com/RyanZim/EJS-Lint', 'HTTP_REMOVED') + if (result.includes('HTTP_REMOVED')) { + result += 'For more information on proper template syntax, refer to:\n' + result += 'https://nptemplating-docs.netlify.app/' + result = result.replace('HTTP_REMOVED', '') + } + // result = result.replace('\n\n', '\n') + + return result + } + static async updateOrInstall(currentSettings: any, currentVersion: string): Promise { const settingsData = { ...currentSettings } @@ -226,15 +253,11 @@ export default class NPTemplating { let templateFilename = `${templateFolderName}/${templateName}.md` let selectedTemplate = '' - log(pluginJson, templateFilename) - try { selectedTemplate = await DataStore.projectNoteByFilename(templateFilename) - // if the template can't be found using actual filename (as it is on disk) // this will occur due to a bug in NotePlan which is not properly renaming files on disk to match note name if (!selectedTemplate) { - console.log('hรคr') const parts = templateName.split('/') if (parts.length > 0) { templateFilename = parts[parts.length - 1] @@ -279,27 +302,53 @@ export default class NPTemplating { return this.constructor.templateConfig } - static async renderTemplate(templateName: string = '', userData: any = {}, userOptions: any = {}): Promise { + static async renderTemplate(templateName: string = '', userData: any = {}, userOptions: any = { usePrompts: false }): Promise { try { await this.setup() let sessionData = { ...userData } let templateData = (await this.getTemplate(templateName)) || '' + let globalData = {} + Object.getOwnPropertyNames(globals).forEach((key) => { + globalData[key] = getProperyValue(globals, key) + }) + + sessionData.methods = { ...sessionData.methods, ...globalData } + + templateData = templateData.replace('<%@', '<%= prompt') + if (userOptions?.usePrompts) { - const promptData = await this.processPrompts(templateData, userData, '<%', '%>') + const promptData = await this.processPrompts(templateData, sessionData, '<%', '%>') templateData = promptData.sessionTemplateData sessionData = promptData.sessionData } + templateData = await this.preProcess(templateData) + const renderedData = await new TemplatingEngine(this.constructor.templateConfig).render(templateData, sessionData, userOptions) - return renderedData + return this._filterTemplateResult(renderedData) } catch (error) { return this.templateErrorMessage(error) } } + static async preProcess(templateData: string): Promise { + let newTemplateData = templateData + const tags = (await this.getTags(templateData)) || [] + tags.forEach((tag) => { + if (!tag.includes('await') && tag.includes('(')) { + let tempTag = tag.replace('<%-', '<%- await') + newTemplateData = newTemplateData.replace(tag, tempTag) + + tempTag = tag.replace('<%=', '<%- await') + newTemplateData = newTemplateData.replace(tag, tempTag) + } + }) + return newTemplateData + } + static async render(templateData: string = '', userData: any = {}, userOptions: any = {}): Promise { try { await this.setup() @@ -338,7 +387,9 @@ export default class NPTemplating { } static async getPromptParameters(promptTag: string = ''): mixed { - let tagValue = promptTag.replace(/prompt|[()]|<%=|<%|-%>|%>/gi, '').trim() + let tagValue = '' + tagValue = promptTag.replace(/\bask\b|prompt|[()]|<%=|<%|-%>|%>/gi, '').trim() + // tagValue = promptTag.replace(/ask|[()]|<%=|<%|-%>|%>/gi, '').trim() let varName = '' let promptMessage = '' let options = [] @@ -379,9 +430,12 @@ export default class NPTemplating { return { varName, promptMessage, options } } - static async prompt(message: string, options: Array = []): Promise { + static async prompt(message: string, options: Array = []): Promise { if (options.length === 0) { - return await CommandBar.showInput(message, 'OK') + const result = await CommandBar.textPrompt('', message.replace('_', ' '), '') + return result + // return await CommandBar.textPrompt(message, '') + // return await CommandBar.showInput(message, 'OK') } else { const { index } = await CommandBar.showOptions(options, message) return options[index] @@ -390,11 +444,17 @@ export default class NPTemplating { static async processPrompts(templateData: string, userData: any, startTag: string = '<%', endTag: string = '%>'): Promise { const sessionData = { ...userData } - let sessionTemplateData = templateData - - for (const tag of await this.getTags(templateData)) { + const methods = Object.keys(userData.methods) + let sessionTemplateData = templateData.replace('<%@', '%<= prompt') + for (const tag of await this.getTags(sessionTemplateData)) { // if tag is from module, it will contain period so we need to make sure this tag is not a module - if (!this.isVariableTag(tag) && !this.isTemplateModule(tag)) { + let isMethod = false + for (const method of methods) { + if (tag.includes(method)) { + isMethod = true + } + } + if (!this.isVariableTag(tag) && !this.isTemplateModule(tag) && !isMethod) { // $FlowIgnore const { varName, promptMessage, options } = await this.getPromptParameters(tag) if (!sessionData.hasOwnProperty(varName)) { @@ -406,7 +466,7 @@ export default class NPTemplating { sessionTemplateData = sessionTemplateData.replace(tag, `<% 'prompt' -%>`) } } else { - sessionTemplateData = sessionTemplateData.replace('user.', '') + // sessionTemplateData = sessionTemplateData.replace('user.', '') } } @@ -417,6 +477,10 @@ export default class NPTemplating { return tag.indexOf('const') > 0 || tag.indexOf('let') > 0 } + static isMethod(tag: string = ''): boolean { + return tag.indexOf('(') > 0 || tag.indexOf('@') > 0 || tag.indexOf('prompt') > 0 + } + static isTemplateModule(tag: string = ''): boolean { const tagValue = tag.replace('<%=', '').replace('<%-', '').replace('%>', '').trim() const pos = tagValue.indexOf('.') diff --git a/np.Templating/lib/TemplatingEngine.js b/np.Templating/lib/TemplatingEngine.js index 63c0ce4d2..4368725c0 100644 --- a/np.Templating/lib/TemplatingEngine.js +++ b/np.Templating/lib/TemplatingEngine.js @@ -114,6 +114,7 @@ export default class TemplatingEngine { let renderData = { ...helpers, ...userData } renderData = userData?.data ? { ...userData.data, ...renderData } : renderData renderData = userData?.methods ? { ...userData.methods, ...renderData } : renderData + // renderData = { ...renderData, ...globals } // apply custom plugin modules this.templateModules.forEach((moduleItem) => { @@ -231,10 +232,7 @@ export default class TemplatingEngine { if (obj.prototype === undefined) { return isCtorClass } - const isPrototypeCtorClass = - obj.prototype.constructor && - obj.prototype.constructor.toString && - obj.prototype.constructor.toString().substring(0, 5) === 'class' + const isPrototypeCtorClass = obj.prototype.constructor && obj.prototype.constructor.toString && obj.prototype.constructor.toString().substring(0, 5) === 'class' return isCtorClass || isPrototypeCtorClass } } diff --git a/np.Templating/lib/globals.js b/np.Templating/lib/globals.js index 1117b0810..363ee21cc 100644 --- a/np.Templating/lib/globals.js +++ b/np.Templating/lib/globals.js @@ -6,52 +6,91 @@ * -----------------------------------------------------------------------------------------*/ import { datePicker, askDateInterval } from '@helpers/userInput' -import { - get8601String, - getWeekDates, - formattedDateTimeTemplate, -} from '@plugins/dwertheimer.DateAutomations/src/dateFunctions' +import { get8601String, getWeekDates, formattedDateTimeTemplate } from '@plugins/dwertheimer.DateAutomations/src/dateFunctions' +import { getFormattedTime } from '@helpers/dateTime' import { listDaysEvents, listMatchingDaysEvents } from '@plugins/jgclark.EventHelpers/src/eventsToNotes' import { sweepTemplate } from '@plugins/nmn.sweep/src/sweepAll' import DateModule from './support/modules/DateModule' +import { getAffirmation } from './support/modules/affirmation' +import { getAdvice } from './support/modules/advice' +import { getDailyQuote } from './support/modules/quote' +import { getDaysInMonth } from 'date-fns' +import { insertProgressUpdate } from '@plugins/jgclark.Summaries/src' +import { getWeatherSummary } from './support/modules/weatherSummary' /* np.Templating Global Methods */ -module.exports = { +const globals = { + affirmation: async (): Promise => { + return getAffirmation() + }, + + advice: async (): Promise => { + return getAdvice() + }, + + quote: async (): Promise => { + return getDailyQuote() + }, + + progressUpdate: async (params: any): Promise => { + // $FlowIgnore + return await insertProgressUpdate(params) + }, + + weather: async (params: any): Promise => { + return await getWeatherSummary(params) + }, + date8601: async (): Promise => { return await get8601String() }, + // NOTE: This specific method would create a collision against DateModule I believe (needs testing) date: (): string => { return new DateModule().now() }, + pickDate: async (dateParams: string = '', config: { [string]: ?mixed }): Promise => { return await datePicker(dateParams, config) }, + pickDateInterval: async (dateParams: string): Promise => { return await askDateInterval(dateParams) }, + events: async (dateParams?: string): Promise => { return await listDaysEvents(dateParams) }, + listTodaysEvents: async (paramString?: string = ''): Promise => { return await listDaysEvents(paramString) }, + matchingEvents: async (params: ?string = ''): Promise => { return await listMatchingDaysEvents(params) }, + listMatchingEvents: async (params: ?string = ''): Promise => { return await listMatchingDaysEvents(params) }, + sweepTasks: async (params: string = ''): Promise => { - return await sweepTemplate() + return await sweepTemplate(params) }, - formattedDateTime: async (params: string = ''): Promise => { - return await formattedDateTimeTemplate(params) + + formattedDateTime: (params: any): string => { + let dateFormat = '' + dateFormat = typeof params === 'object' && params.hasOwnProperty('format') ? params.format : '' + return getFormattedTime(dateFormat) }, + weekDates: async (params: string = ''): Promise => { return await getWeekDates(params) }, } + +// module.exports = globals +export default globals diff --git a/np.Templating/lib/support/modules/weather.js b/np.Templating/lib/support/modules/weather.js index cdb0e4321..eec2fa806 100644 --- a/np.Templating/lib/support/modules/weather.js +++ b/np.Templating/lib/support/modules/weather.js @@ -2,8 +2,8 @@ export async function getWeather(): Promise { try { - // $FlowFixMe // return 'wttr.in unreachable' + // $FlowFixMe return await fetch('https://wttr.in?format=3') } catch (error) { return '**An error occurred accessing weather service**' diff --git a/np.Templating/lib/support/modules/weatherSummary.js b/np.Templating/lib/support/modules/weatherSummary.js index a0add5c00..6ce3f9322 100644 --- a/np.Templating/lib/support/modules/weatherSummary.js +++ b/np.Templating/lib/support/modules/weatherSummary.js @@ -1,69 +1,41 @@ // @flow -// TODO: -// - ideally find a way to get current location. It must be possible as Scriptable achieves this -// with await Location.current() and has a -// Location.reverseGeocode(latitude, longitude) field -> postal town etc. - -import { getTagParamsFromString, stringReplace, capitalize } from '@helpers/general' -import { getOrMakeConfigurationSection } from '@templating/toolbox' +import pluginJson from '@plugins/np.Templating/plugin.json' +import { capitalize } from '@helpers/general' +import { logError } from '@helpers/dev' //------------------------------------------------------------------------------ // Preference Settings -export const DEFAULT_WEATHER_CONFIG = `// configuration for weather data (used in Daily Note Template, for example) - weather: { - // API key for https://openweathermap.org/ - openWeatherAPIKey: '... put your API key here ...', // !!REQUIRED!! - // Required location for weather forecast - latPosition: 0.0, // !!REQUIRED!! - longPosition: 0.0, // !!REQUIRED!! - // Default units. Can be 'metric' (for Celsius), or 'imperial' (for Fahrenheit) - openWeatherUnits: 'metric', - }, -` +// API key for https://openweathermap.org/ -const MINIMUM_WEATHER_CONFIG = { - openWeatherAPIKey: 'string', - latPosition: 'number', - longPosition: 'number', - openWeatherUnits: 'string', +const defaultWeatherConfig = { + openWeatherAPIKey: '19a11168bcc123dc86c1b92682bfb74f', + latPosition: 0, + longPosition: 0, + openWeatherUnits: 'Celcius', } //------------------------------------------------------------------------------ /** * Get summary of today's weather in a line, using * https://openweathermap.org/api/one-call-api#data, for which you can get a free API key - * @author @jgclark, with customisation by @dwertheimer + * @author @jgclark, with customisation by @dwertheimer, adapted to np.Templating by @codedungeon * @param {string} weatherParams - optional customisation for how to display the results */ export async function getWeatherSummary(weatherParams: string): Promise { - const weatherDescText = [ - 'showers', - 'rain', - 'sunny intervals', - 'partly sunny', - 'sunny', - 'clear sky', - 'cloud', - 'snow ', - 'thunderstorm', - 'tornado', - ] + const weatherDescText = ['showers', 'rain', 'sunny intervals', 'partly sunny', 'sunny', 'clear sky', 'cloud', 'snow ', 'thunderstorm', 'tornado'] const weatherDescIcons = ['๐ŸŒฆ๏ธ', '๐ŸŒง๏ธ', '๐ŸŒค', 'โ›…', 'โ˜€๏ธ', 'โ˜€๏ธ', 'โ˜๏ธ', '๐ŸŒจ๏ธ', 'โ›ˆ', '๐ŸŒช'] // Get config settings from Template folder _configuration note - const weatherConfig = await getOrMakeConfigurationSection('weather', DEFAULT_WEATHER_CONFIG, MINIMUM_WEATHER_CONFIG) + const weatherConfig = { ...defaultWeatherConfig } - // Get config settings from Template folder _configuration note - // $FlowIgnore[incompatible-type] - if (weatherConfig == null) { - return "Error: Cannot find 'weather' settings in Templates/_configuration note." - } + let { openWeatherAPIKey, latPosition, longPosition, openWeatherUnits } = weatherConfig + openWeatherUnits = openWeatherUnits === 'Fahrenheit' ? 'imperial' : 'metric' - const { openWeatherAPIKey, latPosition, longPosition, openWeatherUnits } = weatherConfig // $FlowIgnore[incompatible-use] if (openWeatherAPIKey !== null && !openWeatherAPIKey?.match(/[a-f0-9]{32}/)) { - return "Error: Cannot find a valid API Key 'weather' settings in Templates/_configuration note." + logError('Invalid Open Weather API Key') + await CommandBar.prompt('Weather Lookup', 'Invalid Open Weather API Key') } const getWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${ @@ -80,40 +52,24 @@ export async function getWeatherSummary(weatherParams: string): Promise // $FlowFixMe }&appid=${encodeURIComponent(openWeatherAPIKey)}` - // ** The following is the more correct way, but doesn't work. - // So have to use a way that Flow doesn't like. - // See Issue 7 ** - // const response = await fetch(getWeatherURL) - // console.log(response.status) - // console.log(response.statusText) - // console.log(response.type) - // console.log(response.url) - // let jsonIn - // if (response.ok) { // if HTTP-status is 200-299 - // jsonIn = await response.json() - // } else { - // return `Sorry; error ${response.status} in Weather lookup` - // } - - // console.log(getWeatherURL) let jsonIn, allWeatherData try { jsonIn = await fetch(getWeatherURL) - - // console.log(` HTTP response ${jsonIn.status}`) // .status always returns 'undefined', even when it works?! } catch (err) { - return `${err.message} parsing Weather data lookup. Please check your _configuration note.` + return logError(pluginJson, 'An error occurred getting weather') } + if (jsonIn != null) { try { // $FlowIgnore[incompatible-call] allWeatherData = JSON.parse(jsonIn) } catch (err) { - return `${err.message} parsing Weather data lookup. Please check your _configuration note.` + await CommandBar.prompt('Weather Lookup', `${err.message} parsing Weather data. Please check weather settings`) + return logError(pluginJson, `${err.message} parsing Weather data. Please check weather settings`) } - // console.log(`WeatherData: ${JSON.stringify(allWeatherData)}`) if (allWeatherData.cod === 401) { - return `Weather: Invalid configuration settings. ${allWeatherData.message}` + await CommandBar.prompt('Weather Lookup', `Weather: Invalid configuration settings. ${allWeatherData.message}`) + return logError(`Weather: Invalid configuration settings. ${allWeatherData.message}`) } const weatherTodayAll = allWeatherData?.daily['0'] @@ -134,27 +90,21 @@ export async function getWeatherSummary(weatherParams: string): Promise break } } - const replacements = [ - { key: '|FEELS_LIKE_LOW|', value: fMin }, - { key: '|FEELS_LIKE_HIGH|', value: fMax }, - { key: '|LOW_TEMP|', value: minTemp }, - { key: '|HIGH_TEMP|', value: maxTemp }, - { key: '|DESCRIPTION|', value: capitalize(weatherDesc) }, - { key: '|TIMEZONE|', value: timezone }, - { key: '|UNITS|', value: units }, - { key: '|WEATHER_ICON|', value: weatherIcon }, - ] - const defaultWeatherLine = `Weather: |WEATHER_ICON| |DESCRIPTION| |LOW_TEMP||UNITS|-|HIGH_TEMP||UNITS|; Feels like: |FEELS_LIKE_LOW||UNITS|-|FEELS_LIKE_HIGH||UNITS|` + let WEATHER_ICON = weatherIcon + let DESCRIPTION = capitalize(weatherDesc) + let LOW_TEMP = minTemp + let HIGH_TEMP = maxTemp + let UNITS = units + let FEELS_LIKE_LOW = fMin + let FEELS_LIKE_HIGH = fMax - const template = await getTagParamsFromString(weatherParams, 'template', defaultWeatherLine) - // const template = - // (weatherParams !== '' && getTagParams(weatherParams, 'template') !== '') - // ? getTagParams(weatherParams, 'template') - // : defaultWeatherLine - return stringReplace(template, replacements) + const defaultWeatherLine = `Weather: ${WEATHER_ICON} ${DESCRIPTION} ${LOW_TEMP}${UNITS}-${HIGH_TEMP}${UNITS}; Feels like: ${FEELS_LIKE_LOW}${UNITS}-${FEELS_LIKE_HIGH}${UNITS}` + // $FlowIgnore + return weatherParams.length > 0 ? Function('`' + weatherParams + '`') : defaultWeatherLine } else { // $FlowFixMe[incompatible-type] - return `Problem in Weather data lookup for ${latPosition}/${longPosition}. Please check your _configuration note.` + await CommandBar.prompt('Weather Lookup', `An error occurred in data lookup for ${latPosition}/${longPosition}. Please review settings.`) + return logError(pluginJson, `An error occurred in data lookup for ${latPosition}/${longPosition}. Please review settings.`) } } diff --git a/np.Templating/lib/test.js b/np.Templating/lib/test.js new file mode 100644 index 000000000..e58104ef9 --- /dev/null +++ b/np.Templating/lib/test.js @@ -0,0 +1,13 @@ +import { get8601String } from '../../dwertheimer.DateAutomations/src/dateFunctions' + +const test = { + hello: () => { + return 'hello world' + }, + date8601: async (): Promise => { + // return await get8601String() + return 'hello world' + }, +} + +export default test diff --git a/np.Templating/plugin.json b/np.Templating/plugin.json index c631333af..eb8f396a2 100644 --- a/np.Templating/plugin.json +++ b/np.Templating/plugin.json @@ -3,12 +3,13 @@ "noteplan.minAppVersion": "3.4.1", "plugin.id": "np.Templating", "plugin.name": " ๐Ÿ“’ np.Templating", - "plugin.version": "1.0.0-beta.01", + "plugin.version": "1.0.0-beta.02", "plugin.description": "Templating Plugin for NotePlan", "plugin.author": "Mike Erickson (@codedungeon)", "plugin.dependencies": [], "plugin.script": "script.js", - "plugin.url": "https://github.com/NotePlan/plugins/blob/main/np.Templating/README.md", + "plugin.url.old": "https://github.com/NotePlan/plugins/blob/main/np.Templating/README.md", + "plugin.url": "https://nptemplating-docs.netlify.app/", "plugin.commands": [ { "name": "np:init", @@ -184,6 +185,48 @@ "type": "hidden", "default": true }, + { + "type": "separator" + }, + { + "key": "weatherApiKey", + "title": "Weather API Key", + "description": "Open Weather API Key \n- visit https://openweathermap.org/ to obtain API Key", + "type": "string", + "default": "", + "required": false + }, + { + "key": "weatherLatPosition", + "title": "Weather Latitude Position", + "description": "Desired Latitude Position \n- leave zero to find position based on location", + "type": "string", + "default": "0", + "required": false + }, + { + "key": "weatherLongPosition", + "title": "Weather Longitude Position", + "description": "Desired Longitude Position\n- leave zero to find position based on location", + "type": "string", + "default": "0", + "required": false + }, + { + "key": "weatherUnits", + "title": "Weather Units", + "description": "Open Weather Units \n(celcius = metric, fareheit = imperial)", + "type": "string", + "choices": [ + "celcius", + "fahrenheit" + ], + "default": "fahrenheit", + "required": false + }, + { + "type": "separator" + }, { "key": "services", "title": "Web Services", diff --git a/np.Templating/src/Templating.js b/np.Templating/src/Templating.js index 1e4810410..f9613aca5 100644 --- a/np.Templating/src/Templating.js +++ b/np.Templating/src/Templating.js @@ -54,10 +54,10 @@ export async function templateInit(): Promise { } export async function templateInsert(): Promise { - if (!Editor.content) { - await CommandBar.prompt('Template Error', 'You must have a Project Note or Calendar Note opened where you wish to insert template.') - return - } + // if (!Editor.content) { + // await CommandBar.prompt('Template Error', 'You must have a Project Note or Calendar Note opened where you wish to insert template.') + // return + // } const options = await getTemplateList() @@ -65,16 +65,18 @@ export async function templateInsert(): Promise { const templateTitle = selectedTemplate?.title - const result = await NPTemplating.renderTemplate(templateTitle) + const result = await NPTemplating.renderTemplate(templateTitle, null, { usePrompts: true }) Editor.insertTextAtCursor(result) } export async function templateAppend(): Promise { - if (!Editor.content) { - await CommandBar.prompt('Template Notice', 'You must have a Project Note or Calendar Note opened where you wish to append template.') - return - } + // if (!Editor.content) { + // await CommandBar.prompt('Template Notice', 'You must have a Project Note or Calendar Note opened where you wish to append template.') + // return + // } + + log('np.Templating', 'Hello World') const content: string = Editor.content || '' @@ -84,7 +86,7 @@ export async function templateAppend(): Promise { const templateTitle = selectedTemplate?.title - const renderedTemplate = await NPTemplating.renderTemplate(templateTitle) + const renderedTemplate = await NPTemplating.renderTemplate(templateTitle, null, { usePrompts: true }) const processed = await NPTemplating.postProcess(renderedTemplate) Editor.insertTextAtCharacterIndex(renderedTemplate, content.length) @@ -112,7 +114,7 @@ export async function templateNew(): Promise { const noteTitle = title.toString() const filename = DataStore.newNote(noteTitle, folder) || '' if (filename) { - const templateResult = await NPTemplating.renderTemplate(templateName) + const templateResult = await NPTemplating.renderTemplate(templateName, null, { usePrompts: true }) await Editor.openNoteByFilename(filename) Editor.content = `# ${noteTitle}\n${templateResult}` } @@ -132,7 +134,7 @@ export async function selectTemplate(): Promise { export async function templateWeather(): Promise { try { // $FlowIgnore - const weather: string = getWeather() + const weather: string = await getWeather() Editor.insertTextAtCursor(weather) } catch (error) { diff --git a/package.json b/package.json index b0b9dbeb3..ea0d3cf23 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "np.plugins", "packageName": "noteplan-cli", "info": "noteplan plugin development toolbox", - "version": "3.0.2", + "version": "3.1.0", "build": "236", "description": "noteplan-cli", "repository": "https://github.com/NotePlan/plugins", @@ -104,7 +104,7 @@ "singleQuote": true }, "dependencies": { - "@codedungeon/gunner": "0.75.2", + "@codedungeon/gunner": "0.76.0", "@rollup/plugin-alias": "3.1.8", "@samverschueren/stream-to-observable": "0.3.1", "axios": "0.21.4", diff --git a/scripts/releases.js b/scripts/releases.js index ecd6d8c3f..5c4e08c3d 100644 --- a/scripts/releases.js +++ b/scripts/releases.js @@ -1,5 +1,5 @@ // @flow -const TEST = true // when set to true, doesn't actually create or delete anything. Just a dry run +const TEST = false // when set to true, doesn't actually create or delete anything. Just a dry run const COMMAND = 'Plugin Release' // $FlowIgnore @@ -9,13 +9,7 @@ const colors = require('chalk') const Messenger = require('@codedungeon/messenger') const { program } = require('commander') -const { - getFolderFromCommandLine, - runShellCommand, - getPluginFileContents, - fileExists, - getCopyTargetPath, -} = require('./shared') +const { getFolderFromCommandLine, runShellCommand, getPluginFileContents, fileExists, getCopyTargetPath } = require('./shared') // Command line options program.option('-d, --debug', 'Rollup: allow for better JS debugging - no minification or transpiling') @@ -62,10 +56,7 @@ ${colors.cyan.italic( - Once you have "gh" installed and you have received access to repository, come back here to run the command again! ` if (TEST) { - Messenger.warn( - 'Creating draft release (which should be deleted) and not deleting existing release without permission', - 'TEST MODE', - ) + Messenger.warn('Creating draft release (which should be deleted) and not deleting existing release without permission', 'TEST MODE') console.log('') } @@ -171,9 +162,7 @@ async function getReleaseFileList(pluginFullPath, appPluginsPath) { //$FlowFixMe - see note above fileList.changelog = fullPath(name) } else { - Messenger.note( - `==> ${COMMAND}: Missing ${colors.cyan('CHANGELOG.md')} or ${colors.cyan('README.md')} in ${pluginFullPath}`, - ) + Messenger.note(`==> ${COMMAND}: Missing ${colors.cyan('CHANGELOG.md')} or ${colors.cyan('README.md')} in ${pluginFullPath}`) } } // Grab the minified/cleaned version of the plugin.json file @@ -213,29 +202,18 @@ async function getReleaseFileList(pluginFullPath, appPluginsPath) { } function wrongArgsMessage(limitToFolders) { - console.log( - `==> ${COMMAND}: ${limitToFolders ? String(limitToFolders.length) : ''} file(s): ${ - JSON.stringify(limitToFolders) || '' - }`, - ) - console.log( - colors.red(`\nERROR:\n Invalid Arguments (you may only release one plugin at a time)`), - colors.yellow(`\n\nUsage:\n npm run release "dwertheimer.dateAutomations"`), - ) + console.log(`==> ${COMMAND}: ${limitToFolders ? String(limitToFolders.length) : ''} file(s): ${JSON.stringify(limitToFolders) || ''}`) + console.log(colors.red(`\nERROR:\n Invalid Arguments (you may only release one plugin at a time)`), colors.yellow(`\n\nUsage:\n npm run release "dwertheimer.dateAutomations"`)) } function ensureVersionIsNew(existingRelease, versionedTagName) { if (existingRelease && versionedTagName) { if (existingRelease.tag === versionedTagName) { Messenger.note( - `==> ${COMMAND}: Found existing release with tag name: ${colors.cyan( - versionedTagName, - )}, which matches the version number in your ${colors.cyan('plugin.json')}`, + `==> ${COMMAND}: Found existing release with tag name: ${colors.cyan(versionedTagName)}, which matches the version number in your ${colors.cyan('plugin.json')}`, ) Messenger.log( - ` New releases must contain a unique name/tag. Update ${colors.magenta('plugin.version')} in ${colors.cyan( - 'plugin.json, CHANGELOG.md or README.md', - )} and try again.`, + ` New releases must contain a unique name/tag. Update ${colors.magenta('plugin.version')} in ${colors.cyan('plugin.json, CHANGELOG.md or README.md')} and try again.`, ) console.log('') const testMessage = TEST ? '(Test Mode)' : '' @@ -247,13 +225,9 @@ function ensureVersionIsNew(existingRelease, versionedTagName) { function getReleaseCommand(version, pluginTitle, fileList, sendToGithub = false) { const changeLog = fileList.changelog ? `-F "${fileList.changelog}"` : '' - const cmd = `gh release create "${version}" -t "${pluginTitle}" ${changeLog} ${ - !sendToGithub ? `--draft` : '' - } ${fileList.files.map((m) => `"${m}"`).join(' ')}` + const cmd = `gh release create "${version}" -t "${pluginTitle}" ${changeLog} ${!sendToGithub ? `--draft` : ''} ${fileList.files.map((m) => `"${m}"`).join(' ')}` if (!sendToGithub) { - console.log( - `==> ${COMMAND}: Release command:\n\t${cmd}\n\nYou can run that by hand. The script is not doing it in TEST mode.\n`, - ) + console.log(`==> ${COMMAND}: Release command:\n\t${cmd}\n\nYou can run that by hand. The script is not doing it in TEST mode.\n`) } return cmd } @@ -261,9 +235,7 @@ function getReleaseCommand(version, pluginTitle, fileList, sendToGithub = false) function getRemoveCommand(version, sendToGithub = false) { const cmd = `gh release delete "${version}" ${sendToGithub ? `` : '-y'}` // -y removes the release without prompting if (!sendToGithub) { - console.log( - `==> ${COMMAND}: Pre-existing release remove command:\n\t${cmd}\n\nYou can run that by hand. The script is not doing it in TEST mode.\n`, - ) + console.log(`==> ${COMMAND}: Pre-existing release remove command:\n\t${cmd}\n\nYou can run that by hand. The script is not doing it in TEST mode.\n`) } return cmd } diff --git a/scripts/rollup.js b/scripts/rollup.js index e55b65bb0..7d5942cb4 100644 --- a/scripts/rollup.js +++ b/scripts/rollup.js @@ -30,13 +30,7 @@ const createPluginListing = require('./createPluginListing') let progress -const { - getFolderFromCommandLine, - getPluginFileContents, - writeMinifiedPluginFileContents, - getCopyTargetPath, - getPluginConfig, -} = require('./shared') +const { getFolderFromCommandLine, getPluginFileContents, writeMinifiedPluginFileContents, getCopyTargetPath, getPluginConfig } = require('./shared') const FOLDERS_TO_IGNORE = ['scripts', 'flow-typed', 'node_modules', 'np.plugin-flow-skeleton'] const rootFolderPath = path.join(__dirname, '..') @@ -69,8 +63,7 @@ const copyBuild = async (outputFile = '', isBuildTask = false) => { let msg = COMPACT ? `${dateTime} - ${pluginFolder} (v${pluginJsonData['plugin.version']})` - : colors.cyan(`${dateTime} -- ${pluginFolder} (v${pluginJsonData['plugin.version']})`) + - '\n Built and copied to the "Plugins" folder.' + : colors.cyan(`${dateTime} -- ${pluginFolder} (v${pluginJsonData['plugin.version']})`) + '\n Built and copied to the "Plugins" folder.' if (DEBUGGING) { msg += colors.yellow(`\n Built in DEBUG mode. Not ready to deploy.\n`) @@ -183,10 +176,7 @@ async function main() { const rootLevelFolders = rootFolder .filter( (dirent) => - dirent.isDirectory() && - !dirent.name.startsWith('.') && - !FOLDERS_TO_IGNORE.includes(dirent.name) && - (limitToFolders.length === 0 || limitToFolders.includes(dirent.name)), + dirent.isDirectory() && !dirent.name.startsWith('.') && !FOLDERS_TO_IGNORE.includes(dirent.name) && (limitToFolders.length === 0 || limitToFolders.includes(dirent.name)), ) .map(async (dirent) => { const pluginFolder = path.join(__dirname, '..', dirent.name) @@ -255,10 +245,7 @@ async function build() { const rootLevelFolders = rootFolder .filter( (dirent) => - dirent.isDirectory() && - !dirent.name.startsWith('.') && - !FOLDERS_TO_IGNORE.includes(dirent.name) && - (limitToFolders.length === 0 || limitToFolders.includes(dirent.name)), + dirent.isDirectory() && !dirent.name.startsWith('.') && !FOLDERS_TO_IGNORE.includes(dirent.name) && (limitToFolders.length === 0 || limitToFolders.includes(dirent.name)), ) .map(async (dirent) => { const pluginFolder = path.join(__dirname, '..', dirent.name) diff --git a/src/commands/PluginRelease.js b/src/commands/PluginRelease.js index f1f1d02eb..13c9f4d7b 100644 --- a/src/commands/PluginRelease.js +++ b/src/commands/PluginRelease.js @@ -1,4 +1,4 @@ -const { colors, helpers, print, strings, system, prompt } = require('@codedungeon/gunner') +const { colors, helpers, print, strings, system, prompt, filesystem, path } = require('@codedungeon/gunner') const Messenger = require('@codedungeon/messenger') const appUtils = require('../utils/app') const security = require('../utils/security.lib') @@ -6,6 +6,7 @@ const pluginUtils = require('./support/plugin-utils') const pluginRelease = require('./support/plugin-release') const releasePrompts = require('./support/plugin-release/release-prompts') const github = require('./support/github') +const { defaultsDeep } = require('lodash') module.exports = { name: 'plugin:release', @@ -60,18 +61,23 @@ module.exports = { }, async execute(toolbox) { - const answers = await prompt.password('Enter Password') - if (typeof answers !== 'object') { - console.log('') - print.warn('Release Aborted', 'ABORT') + // make sure gh is installed, otherwise abort + if (!github.ghInstalled()) { + print.error('"plugin:release" requires github to be installed.', 'ERROR') process.exit() } + // const answers = await prompt.password('Enter Password') + // if (typeof answers !== 'object') { + // console.log('') + // print.warn('Release Aborted', 'ABORT') + // process.exit() + // } - if (!security.validate(answers.password)) { - console.log('') - print.error('Invalid Password', 'ABORT') - process.exit() - } + // if (!security.validate(answers.password)) { + // console.log('') + // print.error('Invalid Password', 'ABORT') + // process.exit() + // } if (toolbox.plugin.length === 0) { // no plugin supplied, use `plugin.prompt` interface @@ -86,13 +92,23 @@ module.exports = { console.log('') const args = helpers.getArguments(toolbox.arguments, this, { initializeNullValues: true }) - const pluginName = args.plugin || toolbox.arguments.plugin || null + const pluginName = args.plugin || toolbox.arguments.plugin || toolbox.commandName || null + + const result = filesystem.directoryList().filter((dirItem) => { + const filename = filesystem.filename(dirItem) + return filename.indexOf(pluginName) !== -1 + }) + const draft = args.draft || false const preview = args.preview || false const force = args.force || false const noTests = args.noTests || false const noBuild = args.noBuild || false + if (result.length === 0) { + toolbox.print.error(`Unable to locate plugin ${pluginName}, make sure you are at the project root directory`, 'ERROR') + process.exit() + } const configData = pluginUtils.getPluginConfig(pluginName) const pluginVersion = configData['plugin.version'] diff --git a/src/commands/support/github.js b/src/commands/support/github.js index c61812c16..251b16a38 100644 --- a/src/commands/support/github.js +++ b/src/commands/support/github.js @@ -7,7 +7,7 @@ const gitCheck = util.promisify(git.check) module.exports = { ghInstalled: function () { - return filesystem.existsSync(`/usr/local/bin/gh`) + return shell.which('git') }, ghVersion: async function () { @@ -43,9 +43,7 @@ module.exports = { getReleaseCommand: async function (version = null, pluginTitle = null, fileList = null, sendToGithub = false) { const changeLog = fileList?.changelog ? `-F "${fileList.changelog}"` : '' - const cmd = `gh release create "${version}" -t "${pluginTitle}" ${changeLog} ${ - !sendToGithub ? `--draft` : '' - } ${fileList.files.map((m) => `"${m}"`).join(' ')}` + const cmd = `gh release create "${version}" -t "${pluginTitle}" ${changeLog} ${!sendToGithub ? `--draft` : ''} ${fileList.files.map((m) => `"${m}"`).join(' ')}` return cmd }, diff --git a/src/commands/support/plugin-utils.js b/src/commands/support/plugin-utils.js index 72655ef26..006e68465 100644 --- a/src/commands/support/plugin-utils.js +++ b/src/commands/support/plugin-utils.js @@ -57,7 +57,7 @@ module.exports = { }, getPluginConfig(pluginName = null) { - const pluginJsonFilename = path.join(pluginName, 'plugin.json') + const pluginJsonFilename = path.resolve(pluginName, 'plugin.json') if (filesystem.existsSync(pluginJsonFilename)) { const configData = filesystem.readFileSync(pluginJsonFilename) if (configData.length > 0) { @@ -130,9 +130,7 @@ module.exports = { pluginAliases.forEach((alias) => { pluginCommands.push({ pluginId: pluginObj.hasOwnProperty('plugin.id') ? pluginObj['plugin.id'] : 'missing plugin-id', - pluginName: pluginObj.hasOwnProperty('plugin.name') - ? pluginObj['plugin.name'] - : 'missing plugin-name', + pluginName: pluginObj.hasOwnProperty('plugin.name') ? pluginObj['plugin.name'] : 'missing plugin-name', name: alias, description: command.description, jsFunction: command.jsFunction, diff --git a/src/templates/np.plugin.starter/CHANGELOG.md b/src/templates/np.plugin.starter/CHANGELOG.md index 97dac7468..c8ebd8adf 100644 --- a/src/templates/np.plugin.starter/CHANGELOG.md +++ b/src/templates/np.plugin.starter/CHANGELOG.md @@ -1,16 +1,5 @@ # {{pluginId}} Changelog -## Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## Plugin Versioning Uses Semver - -All NotePlan plugins follow `semver` versioning. For details, please refer to [semver website](https://semver.org/) - ## About {{pluginId}} Plugin See Plugin [README](https://github.com/NotePlan/plugins/blob/main/{{pluginId}}/README.md) for details on available commands and use case. @@ -25,3 +14,14 @@ List what has changed. If nothing has been changed, this section can be removed. ### Removed List what has removed. If nothing has been removed, this section can be removed. + +## Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Plugin Versioning Uses Semver + +All NotePlan plugins follow `semver` versioning. For details, please refer to [semver website](https://semver.org/) diff --git a/src/templates/np.plugin.starter/plugin.json b/src/templates/np.plugin.starter/plugin.json index 1f76c7283..3cb9f76d7 100644 --- a/src/templates/np.plugin.starter/plugin.json +++ b/src/templates/np.plugin.starter/plugin.json @@ -27,7 +27,7 @@ { "key": "version", "type": "hidden", - "{{pluginName}} Settings Version" + "description": "{{pluginName}} Settings Version" } ] }