From 645d8d06802b87fc03c6a5c8bf82dab7a6cb8e0f Mon Sep 17 00:00:00 2001 From: Jonathan Clark Date: Sat, 12 Jun 2021 16:50:56 +0100 Subject: [PATCH 1/4] Structure and readability changes only, I hope. --- nmn.Templates/plugin.json | 4 +- nmn.Templates/script.js | 135 ++++++++++++++++++++++----- nmn.Templates/src/index.js | 49 ++++++++-- nmn.Templates/src/interpolation.js | 41 ++++++-- nmn.Templates/src/tagFunctions.js | 51 ++++++++++ nmn.Templates/src/template-folder.js | 2 +- 6 files changed, 240 insertions(+), 42 deletions(-) create mode 100644 nmn.Templates/src/tagFunctions.js diff --git a/nmn.Templates/plugin.json b/nmn.Templates/plugin.json index e57070627..915dcc94b 100644 --- a/nmn.Templates/plugin.json +++ b/nmn.Templates/plugin.json @@ -6,7 +6,7 @@ "plugin.icon": "f120", "plugin.author": "Naman Goel", "plugin.repoUrl": "tbd", - "plugin.version": "0.0.1", + "plugin.version": "0.1.0", "plugin.dependencies": [ ], "plugin.script": "script.js", "plugin.isRemote": "false", @@ -14,7 +14,7 @@ { "name": "it", "description": "Choose a Template to apply to an existing Note", - "jsFunction": "addTemplate" + "jsFunction": "applyTemplate" }, { "name": "nn", diff --git a/nmn.Templates/script.js b/nmn.Templates/script.js index 2ad7af218..2fb7aa8f8 100644 --- a/nmn.Templates/script.js +++ b/nmn.Templates/script.js @@ -9329,7 +9329,7 @@ The first code-block within the note will always be used. So edit the default co // can be "short", "medium", "long" or "full" dateStyle: 'short', // optional key, can be "short", "medium", "long" or "full" - timeStyle: 'short, + timeStyle: 'short', }, // default values for custom tags. @@ -9529,6 +9529,58 @@ lastName = "Doe" } } + // Using https://openweathermap.org/api/one-call-api#data, for which you can get a free API key + + async function getWeatherSummary(weatherParams, config) { + const weatherDescText = ['showers', 'rain', 'sunny intervals', 'partly sunny', 'sunny', 'cloud', 'snow ', 'thunderstorm', 'tornado']; + const weatherDescIcons = ["🌦️", "🌧️", "🌤", "⛅", "☀️", "☁️", "🌨️", "⛈", "🌪"]; // Get config settings from Template folder _configuration note + + const weatherConfig = config.weather ?? null; + + if (weatherConfig == null) { + await showMessage("Cannot find 'weather' settings in Templates/_configuration note"); + return ''; + } + + const pref_openWeatherAPIKey = weatherConfig.openWeatherAPIKey; + const pref_latPosition = weatherConfig.latPosition; + const pref_longPosition = weatherConfig.longPosition; + const pref_openWeatherUnits = weatherConfig.openWeatherUnits; // TODO: probably getDefaultConfiguration rather than parseJSON5 ? + + console.log(`getWeatherSummary: Params: '${weatherParams}'`); + const paramConfig = weatherParams.trim() ? await parseJSON5(weatherParams) : {}; + console.log(paramConfig); + const getWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${pref_latPosition}&lon=${pref_longPosition}&exclude=current,hourly,minutely&units=${pref_openWeatherUnits}&appid=${pref_openWeatherAPIKey}`; // TODO: Allow for more customisation of what is pulled out from the API's data structure + // using weatherParams + + const jsonIn = await fetch(getWeatherURL); + + if (jsonIn != null) { + const weatherTodayAll = JSON.parse(jsonIn).daily["0"]; + const maxTemp = weatherTodayAll.feels_like.day.toFixed(0); + const minTemp = weatherTodayAll.feels_like.night.toFixed(0); + const weatherDesc = weatherTodayAll.weather["0"].description; // see if we can fix an icon for this as well, according to returned description. Main terms are: + // thunderstorm, drizzle, shower > rain, snow, sleet, clear sky, mist, fog, dust, tornado, overcast > clouds + // with 'light' modifier for rain and snow + + let weatherIcon = ''; + + for (let i = 0; i < weatherDescText.length; i++) { + if (weatherDesc.match(weatherDescText[i])) { + weatherIcon = weatherDescIcons[i]; + break; + } + } + + const summaryLine = `${maxTemp}/${minTemp} ${weatherIcon}${weatherDesc}`; + console.log(`\t${summaryLine}`); + return summaryLine; + } else { + await showMessage('Sorry; error in Weather lookup'); + return 'sorry; error in Weather lookup'; + } + } + async function processTemplate(content, config) { const tagStart = content.indexOf('{{'); const tagEnd = content.indexOf('}}'); @@ -9543,20 +9595,16 @@ lastName = "Doe" const tag = content.slice(tagStart + 2, tagEnd); try { - const tagProcessed = await processTag(tag, config); + const tagProcessed = await processTags(tag, config); const restProcessed = await processTemplate(afterTag, config); return beforeTag + tagProcessed + restProcessed; } catch (e) { console.log(e); return content; } - } - - async function processTag(tag, config) { - if (tag.startsWith('date(') && tag.endsWith(')')) { - return await processDate(tag.slice(5, tag.length - 1), config); - } + } // Apply any matching tag values, asking user for value if not found in configuration + async function processTagValues(tag, config) { const valueInConfig = tag // eslint-disable-next-line no-useless-escape .split(/[\.\[\]]/).filter(Boolean).reduce((path, key) => path != null && typeof path === 'object' ? path[key] : null, config.tagValue); @@ -9565,15 +9613,33 @@ lastName = "Doe" } return await getInput(`Value for ${tag}`); - } + } // Apply any matching tag functions - async function processDate(dateConfig, config) { + + async function processTags(tag, config) { + if (tag.startsWith('date(') && tag.endsWith(')')) { + return await processDate(tag.slice(5, tag.length - 1), config); + } else if (tag.startsWith('weather(') && tag.endsWith(')')) { + return await getWeatherSummary(tag.slice(8, tag.length - 1), config); + } // **Add other extension function calls here** + // Can call functions defined in other plugins, by appropriate use + // of imports at top of file (e.g. getWeatherSummary) + // Or declare below (e.g. processDate) + else { + // no matching funcs, so now attempt to match defined tag values instead + return processTagValues(tag, config); + } + } // ---------------------------------------------------------------- + // Define new tag functions here ... + + async function processDate(dateParams, config) { const defaultConfig = config.date ?? {}; - const paramConfig = dateConfig.trim() ? await parseJSON5(dateConfig) : {}; // console.log(`param config: ${dateConfig} as ${JSON.stringify(paramConfig)}`); + const paramConfig = dateParams.trim() ? await parseJSON5(dateParams) : {}; // console.log(`param config: ${dateParams} as ${JSON.stringify(paramConfig)}`); const finalArguments = { ...defaultConfig, ...paramConfig - }; + }; // ... = "gather the remaining parameters into an array" + const { locale, ...otherParams @@ -9587,11 +9653,35 @@ lastName = "Doe" return new Intl.DateTimeFormat(localeParam, secondParam).format(new Date()); } - async function addTemplate(newNote) { + async function applyNamedTemplate(templateName) { + console.log(`applyNamedTemplate: for template '${templateName}'`); const templateFolder = await getTemplateFolder(); if (templateFolder == null) { - console.log(`addTemplate: templateFolder is null`); + console.log(`\twarning: templateFolder is null`); + await makeTemplateFolder(); + await showMessage('Try using this command again to use a template'); + return; + } + + const selectedTemplate = DataStore.projectNoteByTitle(templateName, true, false)[0]; + let templateContent = selectedTemplate?.content; + + if (templateContent == null || templateContent.length === 0) { + console.log(`\twarning: template '${templateName}' is null or empty`); + return; + } + + templateContent = templateContent.split('\n---\n').slice(1).join('\n---\n'); + const config = (await getDefaultConfiguration()) ?? {}; + const processedTemplateContent = await processTemplate(templateContent, config); + Editor.content = [Editor.content, processedTemplateContent].filter(Boolean).join('\n'); + } + async function applyTemplate(newNote) { + const templateFolder = await getTemplateFolder(); + + if (templateFolder == null) { + console.log(`applyTemplate: warning: templateFolder is null`); await makeTemplateFolder(); await showMessage('Try using this command again to use a template'); return; @@ -9601,8 +9691,8 @@ lastName = "Doe" label: note.title, value: note }).filter(Boolean); - console.log(`addTemplate: found ${options.length} options`); - console.log(`addTemplate: asking user which template:`); + console.log(`applyTemplate: found ${options.length} options`); // console.log(`applyTemplate: asking user which template:`) + const selectedTemplate = await chooseOption('Choose Template', options); let templateContent = selectedTemplate?.content; @@ -9642,15 +9732,15 @@ lastName = "Doe" if (!title) { console.log('\tError: undefined or empty title'); - await showMessage('Cannot create a not with an empty title'); + await showMessage('Cannot create a note with an empty title'); return; } const templateFolder = await getTemplateFolder(); - let shouldAddTemplate = false; + let shouldApplyTemplate = false; if (templateFolder != null || templateFolder !== '') { - shouldAddTemplate = await chooseOption('Do you want to get started with a template?', [{ + shouldApplyTemplate = await chooseOption('Do you want to get started with a template?', [{ label: 'Yes', value: true }, { @@ -9659,8 +9749,8 @@ lastName = "Doe" }], false); } - if (shouldAddTemplate) { - await addTemplate([title, folder]); + if (shouldApplyTemplate) { + await applyTemplate([title, folder]); return; } @@ -9675,7 +9765,8 @@ lastName = "Doe" Editor.content = `# ${title}\n`; } - exports.addTemplate = addTemplate; + exports.applyNamedTemplate = applyNamedTemplate; + exports.applyTemplate = applyTemplate; exports.newNoteWithTemplate = newNoteWithTemplate; Object.defineProperty(exports, '__esModule', { value: true }); diff --git a/nmn.Templates/src/index.js b/nmn.Templates/src/index.js index aa6960ddf..a95b8d0b6 100644 --- a/nmn.Templates/src/index.js +++ b/nmn.Templates/src/index.js @@ -10,11 +10,42 @@ import { processTemplate } from './interpolation' import { getTemplateFolder, makeTemplateFolder } from './template-folder' -export async function addTemplate(newNote?: [string, string]) { +export async function applyNamedTemplate(templateName: string) { + console.log(`applyNamedTemplate: for template '${templateName}'`) + const templateFolder = await getTemplateFolder() + if (templateFolder == null) { + console.log(`\twarning: templateFolder is null`) + await makeTemplateFolder() + await showMessage('Try using this command again to use a template') + return + } + + const selectedTemplate = DataStore.projectNoteByTitle(templateName, true, false)[0] + + let templateContent = selectedTemplate?.content + if (templateContent == null || templateContent.length === 0) { + console.log(`\twarning: template '${templateName}' is null or empty`) + return + } + templateContent = templateContent.split('\n---\n').slice(1).join('\n---\n') + const config = (await getDefaultConfiguration()) ?? {} + + const processedTemplateContent = await processTemplate( + templateContent, + config, + ) + + Editor.content = [Editor.content, processedTemplateContent] + .filter(Boolean) + .join('\n') +} + +export async function applyTemplate(newNote?: [string, string]) { + const templateFolder = await getTemplateFolder() if (templateFolder == null) { - console.log(`addTemplate: templateFolder is null`) + console.log(`applyTemplate: warning: templateFolder is null`) await makeTemplateFolder() await showMessage('Try using this command again to use a template') return @@ -28,9 +59,9 @@ export async function addTemplate(newNote?: [string, string]) { ) .filter(Boolean) - console.log(`addTemplate: found ${options.length} options`) + console.log(`applyTemplate: found ${options.length} options`) - console.log(`addTemplate: asking user which template:`) + // console.log(`applyTemplate: asking user which template:`) const selectedTemplate = await chooseOption( 'Choose Template', options, @@ -84,15 +115,15 @@ export async function newNoteWithTemplate() { if (!title) { console.log('\tError: undefined or empty title') - await showMessage('Cannot create a not with an empty title') + await showMessage('Cannot create a note with an empty title') return } const templateFolder = await getTemplateFolder() - let shouldAddTemplate = false + let shouldApplyTemplate = false if (templateFolder != null || templateFolder !== '') { - shouldAddTemplate = await chooseOption( + shouldApplyTemplate = await chooseOption( 'Do you want to get started with a template?', [ { label: 'Yes', value: true }, @@ -101,8 +132,8 @@ export async function newNoteWithTemplate() { false, ) } - if (shouldAddTemplate) { - await addTemplate([title, folder]) + if (shouldApplyTemplate) { + await applyTemplate([title, folder]) return } diff --git a/nmn.Templates/src/interpolation.js b/nmn.Templates/src/interpolation.js index d63b3f14a..48b8a87da 100644 --- a/nmn.Templates/src/interpolation.js +++ b/nmn.Templates/src/interpolation.js @@ -1,6 +1,7 @@ // @flow import { getInput } from '../../nmn.sweep/src/userInput' +import { getWeatherSummary } from '../../jgclark.DailyJournal/src/weather' import { parseJSON5 } from './configuration' export async function processTemplate( @@ -19,7 +20,7 @@ export async function processTemplate( const tag = content.slice(tagStart + 2, tagEnd) try { - const tagProcessed = await processTag(tag, config) + const tagProcessed = await processTags(tag, config) const restProcessed = await processTemplate(afterTag, config) return beforeTag + tagProcessed + restProcessed } catch (e) { @@ -28,13 +29,11 @@ export async function processTemplate( } } -async function processTag( +// Apply any matching tag values, asking user for value if not found in configuration +async function processTagValues( tag: string, config: { [string]: ?mixed }, ): Promise { - if (tag.startsWith('date(') && tag.endsWith(')')) { - return await processDate(tag.slice(5, tag.length - 1), config) - } const valueInConfig = tag // eslint-disable-next-line no-useless-escape .split(/[\.\[\]]/) @@ -50,18 +49,44 @@ async function processTag( return await getInput(`Value for ${tag}`) } +// Apply any matching tag functions +export async function processTags( + tag: string, + config: { [string]: ?mixed }, +): Promise { + if (tag.startsWith('date(') && tag.endsWith(')')) { + return await processDate(tag.slice(5, tag.length - 1), config) + } + else if (tag.startsWith('weather(') && tag.endsWith(')')) { + return await getWeatherSummary(tag.slice(8, tag.length - 1), config) + } + + // **Add other extension function calls here** + // Can call functions defined in other plugins, by appropriate use + // of imports at top of file (e.g. getWeatherSummary) + // Or declare below (e.g. processDate) + + else { // no matching funcs, so now attempt to match defined tag values instead + return processTagValues(tag, config) + } +} + +// ---------------------------------------------------------------- +// Define new tag functions here ... + async function processDate( - dateConfig: string, + dateParams: string, config: { [string]: ?mixed }, ): Promise { const defaultConfig = config.date ?? {} - const paramConfig = dateConfig.trim() ? await parseJSON5(dateConfig) : {} - // console.log(`param config: ${dateConfig} as ${JSON.stringify(paramConfig)}`); + const paramConfig = dateParams.trim() ? await parseJSON5(dateParams) : {} + // console.log(`param config: ${dateParams} as ${JSON.stringify(paramConfig)}`); const finalArguments: { [string]: mixed } = { ...defaultConfig, ...paramConfig, } + // ... = "gather the remaining parameters into an array" const { locale, ...otherParams } = (finalArguments: any) const localeParam = locale != null ? String(locale) : [] diff --git a/nmn.Templates/src/tagFunctions.js b/nmn.Templates/src/tagFunctions.js new file mode 100644 index 000000000..5e0fd6400 --- /dev/null +++ b/nmn.Templates/src/tagFunctions.js @@ -0,0 +1,51 @@ +// @flow + +import { getWeatherSummary } from '../../jgclark.DailyJournal/src/weather' +import { processTagValues } from './interpolation.js' +import { parseJSON5 } from './configuration' + +// Apply any matching tag functions +export async function processTags( + tag: string, + config: { [string]: ?mixed }, +): Promise { + if (tag.startsWith('date(') && tag.endsWith(')')) { + return await processDate(tag.slice(5, tag.length - 1), config) + } + else if (tag.startsWith('weather(') && tag.endsWith(')')) { + return await getWeatherSummary(tag.slice(8, tag.length - 1), config) + } + + // **Add other extension function calls here** + // Can call functions defined in other plugins, by appropriate use + // of imports at top of file (e.g. getWeatherSummary) + // Or declare below (e.g. processDate) + + else { // no matching funcs, so now attempt to match defined tag values instead + return processTagValues(tag, config) + } +} + +async function processDate( + dateParams: string, + config: { [string]: ?mixed }, +): Promise { + const defaultConfig = config.date ?? {} + const paramConfig = dateParams.trim() ? await parseJSON5(dateParams) : {} + // console.log(`param config: ${dateParams} as ${JSON.stringify(paramConfig)}`); + const finalArguments: { [string]: mixed } = { + ...defaultConfig, + ...paramConfig, + } + + const { locale, ...otherParams } = (finalArguments: any) + + const localeParam = locale != null ? String(locale) : [] + const secondParam = { + dateStyle: 'short', + ...otherParams, + } + // console.log(`${JSON.stringify(localeParam)}, ${JSON.stringify(secondParam)}`); + + return new Intl.DateTimeFormat(localeParam, secondParam).format(new Date()) +} diff --git a/nmn.Templates/src/template-folder.js b/nmn.Templates/src/template-folder.js index 2738c8a06..af7d2a1c4 100644 --- a/nmn.Templates/src/template-folder.js +++ b/nmn.Templates/src/template-folder.js @@ -115,7 +115,7 @@ The first code-block within the note will always be used. So edit the default co // can be "short", "medium", "long" or "full" dateStyle: 'short', // optional key, can be "short", "medium", "long" or "full" - timeStyle: 'short, + timeStyle: 'short', }, // default values for custom tags. From 905b0a967b5ce38b1c7e84546c704c7432e172f2 Mon Sep 17 00:00:00 2001 From: Jonathan Clark Date: Sat, 12 Jun 2021 21:51:20 +0100 Subject: [PATCH 2/4] #14 fixes I had hoped to make two smaller files from interpolation.js to make it easier for other authors to know where to put functions that extend the Templates. But it wasn't working out as being simpler, so this is now mostly tweaks to names. PS I don't find 'interpolation.js' very clear, but I'm not going to try and complicate the PR further by renaming it myself. I would suggest 'processTemplate' though. --- nmn.Templates/script.js | 24 +++++++------- nmn.Templates/src/interpolation.js | 40 +++++++++++------------ nmn.Templates/src/tagFunctions.js | 51 ------------------------------ 3 files changed, 32 insertions(+), 83 deletions(-) delete mode 100644 nmn.Templates/src/tagFunctions.js diff --git a/nmn.Templates/script.js b/nmn.Templates/script.js index 2fb7aa8f8..e49bbc27c 100644 --- a/nmn.Templates/script.js +++ b/nmn.Templates/script.js @@ -9602,20 +9602,8 @@ lastName = "Doe" console.log(e); return content; } - } // Apply any matching tag values, asking user for value if not found in configuration - - async function processTagValues(tag, config) { - const valueInConfig = tag // eslint-disable-next-line no-useless-escape - .split(/[\.\[\]]/).filter(Boolean).reduce((path, key) => path != null && typeof path === 'object' ? path[key] : null, config.tagValue); - - if (valueInConfig != null) { - return String(valueInConfig); - } - - return await getInput(`Value for ${tag}`); } // Apply any matching tag functions - async function processTags(tag, config) { if (tag.startsWith('date(') && tag.endsWith(')')) { return await processDate(tag.slice(5, tag.length - 1), config); @@ -9629,9 +9617,21 @@ lastName = "Doe" // no matching funcs, so now attempt to match defined tag values instead return processTagValues(tag, config); } + } // Apply any matching tag values, asking user for value if not found in configuration + + async function processTagValues(tag, config) { + const valueInConfig = tag // eslint-disable-next-line no-useless-escape + .split(/[\.\[\]]/).filter(Boolean).reduce((path, key) => path != null && typeof path === 'object' ? path[key] : null, config.tagValue); + + if (valueInConfig != null) { + return String(valueInConfig); + } + + return await getInput(`Value for ${tag}`); } // ---------------------------------------------------------------- // Define new tag functions here ... + async function processDate(dateParams, config) { const defaultConfig = config.date ?? {}; const paramConfig = dateParams.trim() ? await parseJSON5(dateParams) : {}; // console.log(`param config: ${dateParams} as ${JSON.stringify(paramConfig)}`); diff --git a/nmn.Templates/src/interpolation.js b/nmn.Templates/src/interpolation.js index 48b8a87da..a3ad0fa1c 100644 --- a/nmn.Templates/src/interpolation.js +++ b/nmn.Templates/src/interpolation.js @@ -29,26 +29,6 @@ export async function processTemplate( } } -// Apply any matching tag values, asking user for value if not found in configuration -async function processTagValues( - tag: string, - config: { [string]: ?mixed }, -): Promise { - const valueInConfig = tag - // eslint-disable-next-line no-useless-escape - .split(/[\.\[\]]/) - .filter(Boolean) - .reduce( - (path, key: string) => - path != null && typeof path === 'object' ? path[key] : null, - config.tagValue, - ) - if (valueInConfig != null) { - return String(valueInConfig) - } - return await getInput(`Value for ${tag}`) -} - // Apply any matching tag functions export async function processTags( tag: string, @@ -71,6 +51,26 @@ export async function processTags( } } +// Apply any matching tag values, asking user for value if not found in configuration +async function processTagValues( + tag: string, + config: { [string]: ?mixed }, +): Promise { + const valueInConfig = tag + // eslint-disable-next-line no-useless-escape + .split(/[\.\[\]]/) + .filter(Boolean) + .reduce( + (path, key: string) => + path != null && typeof path === 'object' ? path[key] : null, + config.tagValue, + ) + if (valueInConfig != null) { + return String(valueInConfig) + } + return await getInput(`Value for ${tag}`) +} + // ---------------------------------------------------------------- // Define new tag functions here ... diff --git a/nmn.Templates/src/tagFunctions.js b/nmn.Templates/src/tagFunctions.js deleted file mode 100644 index 5e0fd6400..000000000 --- a/nmn.Templates/src/tagFunctions.js +++ /dev/null @@ -1,51 +0,0 @@ -// @flow - -import { getWeatherSummary } from '../../jgclark.DailyJournal/src/weather' -import { processTagValues } from './interpolation.js' -import { parseJSON5 } from './configuration' - -// Apply any matching tag functions -export async function processTags( - tag: string, - config: { [string]: ?mixed }, -): Promise { - if (tag.startsWith('date(') && tag.endsWith(')')) { - return await processDate(tag.slice(5, tag.length - 1), config) - } - else if (tag.startsWith('weather(') && tag.endsWith(')')) { - return await getWeatherSummary(tag.slice(8, tag.length - 1), config) - } - - // **Add other extension function calls here** - // Can call functions defined in other plugins, by appropriate use - // of imports at top of file (e.g. getWeatherSummary) - // Or declare below (e.g. processDate) - - else { // no matching funcs, so now attempt to match defined tag values instead - return processTagValues(tag, config) - } -} - -async function processDate( - dateParams: string, - config: { [string]: ?mixed }, -): Promise { - const defaultConfig = config.date ?? {} - const paramConfig = dateParams.trim() ? await parseJSON5(dateParams) : {} - // console.log(`param config: ${dateParams} as ${JSON.stringify(paramConfig)}`); - const finalArguments: { [string]: mixed } = { - ...defaultConfig, - ...paramConfig, - } - - const { locale, ...otherParams } = (finalArguments: any) - - const localeParam = locale != null ? String(locale) : [] - const secondParam = { - dateStyle: 'short', - ...otherParams, - } - // console.log(`${JSON.stringify(localeParam)}, ${JSON.stringify(secondParam)}`); - - return new Intl.DateTimeFormat(localeParam, secondParam).format(new Date()) -} From 55b2805f8cfc823b06d3fbedf4a12829024cd7e8 Mon Sep 17 00:00:00 2001 From: Jonathan Clark Date: Fri, 18 Jun 2021 20:15:28 +0100 Subject: [PATCH 3/4] Trying to close off Template PR #11 --- jgclark.DailyJournal/plugin.json | 4 +- jgclark.DailyJournal/script.js | 357 +++++++++--------- jgclark.DailyJournal/src/index.js | 7 +- jgclark.DailyJournal/src/script.js | 3 - nmn.Templates/src/interpolation.js | 5 +- nmn.Templates/src/template-folder.js | 56 ++- .../src/weather.js | 12 +- package.json | 8 + 8 files changed, 238 insertions(+), 214 deletions(-) rename {jgclark.DailyJournal => nmn.Templates}/src/weather.js (96%) diff --git a/jgclark.DailyJournal/plugin.json b/jgclark.DailyJournal/plugin.json index 33ee3b3d9..b3d5a822f 100644 --- a/jgclark.DailyJournal/plugin.json +++ b/jgclark.DailyJournal/plugin.json @@ -6,14 +6,14 @@ "plugin.icon": "", "plugin.author": "Jonathan Clark", "plugin.url": "https://github.com/NotePlan/plugins/blob/main/jgclark.DailyJournal/README.md", - "plugin.version": "0.6.2", + "plugin.version": "0.7.0", "plugin.dependencies": [], "plugin.script": "script.js", "plugin.isRemote": "false", "plugin.commands": [ { "name": "dayStart", - "description": "Start today's daily note with a template, including local weather lookup (requires configuring)", + "description": "Start today's daily note with a template (requires configuring)", "jsFunction": "dayStart", "requested_interval": "24h" }, diff --git a/jgclark.DailyJournal/script.js b/jgclark.DailyJournal/script.js index c9dfe9cd5..c15b22711 100644 --- a/jgclark.DailyJournal/script.js +++ b/jgclark.DailyJournal/script.js @@ -1,19 +1,6 @@ var exports = (function (exports) { 'use strict'; - async function chooseOption(title, options, defaultValue) { - const { - index - } = await CommandBar.showOptions(options.map(option => option.label), title); - return options[index]?.value ?? defaultValue; - } - async function showMessage(title, okLabel = 'OK') { - await CommandBar.showOptions([okLabel], title); - } - async function getInput(title, okLabel = 'OK') { - return await CommandBar.showInput(title, okLabel); - } - var parser$1 = function () { /* * Generated by PEG.js 0.8.0. @@ -9235,6 +9222,19 @@ var exports = (function (exports) { }; var load = loader.load; + async function chooseOption(title, options, defaultValue) { + const { + index + } = await CommandBar.showOptions(options.map(option => option.label), title); + return options[index]?.value ?? defaultValue; + } + async function showMessage(title, okLabel = 'OK') { + await CommandBar.showOptions([okLabel], title); + } + async function getInput(title, okLabel = 'OK') { + return await CommandBar.showInput(title, okLabel); + } + const staticTemplateFolder = '📋 Templates'; function getTemplateFolder() { return DataStore.folders.find(f => f.includes(staticTemplateFolder)); @@ -9329,7 +9329,20 @@ The first code-block within the note will always be used. So edit the default co // can be "short", "medium", "long" or "full" dateStyle: 'short', // optional key, can be "short", "medium", "long" or "full" - timeStyle: 'short', + timeStyle: 'short, + }, + + // configuration for weather data + weather: { + // API key for https://openweathermap.org/ + // !!REQUIRED!! + openWeatherAPIKey: '... put your API key here ...', + // Default location for weather forcast + latPosition: 0.0, + longPosition: 0.0, + // Default units. Can be 'metric' (for Celsius), or 'metric' (for Fahrenheit) + openWeatherUnits: 'metric', + // When using a weather tag, you can customize these options. }, // default values for custom tags. @@ -9364,6 +9377,18 @@ date: # can be null (to skip time), "short", "medium", "long" or "full" timeStyle: short +# configuration for weather data lookups, if wanted +weather: + # API key for https://openweathermap.org/ + # !!REQUIRED!! + openWeatherAPIKey: + # Default location for weather forcast + latPosition: 0.0 + longPosition: 0.0 + # Default units. Can be 'metric' (for Celsius), or 'metric' (for Fahrenheit) + openWeatherUnits: metric + # When using a weather tag, you can customize these options. + # default values for custom tags. # These tags cannot be functions, but you may choose to have nested objects. # feel free to edit this value however you see fit. @@ -9392,6 +9417,18 @@ dateStyle = "short" # can be null (to skip time), "short", "medium", "long" or "full" timeStyle = "short" +// configuration for weather data +[weather] +// API key for https://openweathermap.org/ +# !!REQUIRED!! +openWeatherAPIKey = +# Default location for weather forcast +latPosition = 0.0 +longPosition = 0.0 +# Default units. Can be 'metric' (for Celsius), or 'metric' (for Fahrenheit) +openWeatherUnits = 'metric' +# When using a weather tag, you can customize these options. + # default values for custom tags. [tagValue] # These tags cannot be functions, but you may choose to have nested objects. @@ -9404,24 +9441,6 @@ firstName = "John" lastName = "Doe" \`\`\` `; - /** - * - * The following should be added to the default configuration - * once the weather function works. - * - // configuration for weather data - weather: { - // API key for https://openweathermap.org/ - // !!REQUIRED!! - apiKey: '... put your API key here ...', - // Default location for weather forcast - lattitude: 0, - longitude: 0, - // Default temperature unit. Can be "C" (Celcius), "K" (Kelvin) or "F" (Fahrenheit) - unit: 'C', - // When using a weather tag, you can customize these options. - }, - */ const ALLOWED_FORMATS = ['javascript', 'json', 'json5', 'yaml', 'toml', 'ini']; const FORMAT_MAP = { @@ -9529,59 +9548,8 @@ lastName = "Doe" } } - // Using https://openweathermap.org/api/one-call-api#data, for which you can get a free API key - - async function getWeatherSummary(weatherParams, config) { - const weatherDescText = ['showers', 'rain', 'sunny intervals', 'partly sunny', 'sunny', 'cloud', 'snow ', 'thunderstorm', 'tornado']; - const weatherDescIcons = ["🌦️", "🌧️", "🌤", "⛅", "☀️", "☁️", "🌨️", "⛈", "🌪"]; // Get config settings from Template folder _configuration note - - const weatherConfig = config.weather ?? null; - - if (weatherConfig == null) { - await showMessage("Cannot find 'weather' settings in Templates/_configuration note"); - return ''; - } - - const pref_openWeatherAPIKey = weatherConfig.openWeatherAPIKey; - const pref_latPosition = weatherConfig.latPosition; - const pref_longPosition = weatherConfig.longPosition; - const pref_openWeatherUnits = weatherConfig.openWeatherUnits; // TODO: probably getDefaultConfiguration rather than parseJSON5 ? - - console.log(`getWeatherSummary: Params: '${weatherParams}'`); - const paramConfig = weatherParams.trim() ? await parseJSON5(weatherParams) : {}; - console.log(paramConfig); - const getWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${pref_latPosition}&lon=${pref_longPosition}&exclude=current,hourly,minutely&units=${pref_openWeatherUnits}&appid=${pref_openWeatherAPIKey}`; // TODO: Allow for more customisation of what is pulled out from the API's data structure - // using weatherParams - - const jsonIn = await fetch(getWeatherURL); - - if (jsonIn != null) { - const weatherTodayAll = JSON.parse(jsonIn).daily["0"]; - const maxTemp = weatherTodayAll.feels_like.day.toFixed(0); - const minTemp = weatherTodayAll.feels_like.night.toFixed(0); - const weatherDesc = weatherTodayAll.weather["0"].description; // see if we can fix an icon for this as well, according to returned description. Main terms are: - // thunderstorm, drizzle, shower > rain, snow, sleet, clear sky, mist, fog, dust, tornado, overcast > clouds - // with 'light' modifier for rain and snow - - let weatherIcon = ''; - - for (let i = 0; i < weatherDescText.length; i++) { - if (weatherDesc.match(weatherDescText[i])) { - weatherIcon = weatherDescIcons[i]; - break; - } - } - - const summaryLine = `Weather: ${maxTemp}/${minTemp} ${weatherIcon}${weatherDesc}`; - console.log(`\t${summaryLine}`); - return summaryLine; - } else { - await showMessage('Sorry; error in Weather lookup'); - return 'sorry; error in Weather lookup'; - } - } // globalThis.getWeatherSummary = getWeatherSummary - async function processTemplate(content, config) { + console.log(`processTemplate: ${content}`); const tagStart = content.indexOf('{{'); const tagEnd = content.indexOf('}}'); const hasTag = tagStart !== -1 && tagEnd !== -1 && tagStart < tagEnd; @@ -9595,16 +9563,22 @@ lastName = "Doe" const tag = content.slice(tagStart + 2, tagEnd); try { - const tagProcessed = await processTags(tag, config); + const tagProcessed = await processTag(tag, config); const restProcessed = await processTemplate(afterTag, config); return beforeTag + tagProcessed + restProcessed; } catch (e) { console.log(e); return content; } - } // Apply any matching tag values, asking user for value if not found in configuration + } + + async function processTag(tag, config) { + console.log(`processTag: ${tag}`); + + if (tag.startsWith('date(') && tag.endsWith(')')) { + return await processDate(tag.slice(5, tag.length - 1), config); + } - async function processTagValues(tag, config) { const valueInConfig = tag // eslint-disable-next-line no-useless-escape .split(/[\.\[\]]/).filter(Boolean).reduce((path, key) => path != null && typeof path === 'object' ? path[key] : null, config.tagValue); @@ -9613,33 +9587,16 @@ lastName = "Doe" } return await getInput(`Value for ${tag}`); - } // Apply any matching tag functions - + } - async function processTags(tag, config) { - if (tag.startsWith('date(') && tag.endsWith(')')) { - return await processDate(tag.slice(5, tag.length - 1), config); - } else if (tag.startsWith('weather(') && tag.endsWith(')')) { - return await getWeatherSummary(tag.slice(8, tag.length - 1), config); - } // **Add other extension function calls here** - // Can call functions defined in other plugins, by appropriate use - // of imports at top of file (e.g. getWeatherSummary) - // Or declare below (e.g. processDate) - else { - // no matching funcs, so now attempt to match defined tag values instead - return processTagValues(tag, config); - } - } // ---------------------------------------------------------------- - // Define new tag functions here ... - - async function processDate(dateParams, config) { + async function processDate(dateConfig, config) { + console.log(`processDate: ${dateConfig}`); const defaultConfig = config.date ?? {}; - const paramConfig = dateParams.trim() ? await parseJSON5(dateParams) : {}; // console.log(`param config: ${dateParams} as ${JSON.stringify(paramConfig)}`); + const paramConfig = dateConfig.trim() ? await parseJSON5(dateConfig) : {}; // console.log(`param config: ${dateConfig} as ${JSON.stringify(paramConfig)}`); const finalArguments = { ...defaultConfig, ...paramConfig - }; // ... = "gather the remaining parameters into an array" - + }; const { locale, ...otherParams @@ -9670,89 +9627,77 @@ lastName = "Doe" if (templateContent == null || templateContent.length === 0) { console.log(`\twarning: template '${templateName}' is null or empty`); return; - } + } // console.log(templateContent) + + + templateContent = templateContent.split('\n---\n').slice(1).join('\n---\n'); // console.log(`\n${templateContent}`) - templateContent = templateContent.split('\n---\n').slice(1).join('\n---\n'); const config = (await getDefaultConfiguration()) ?? {}; const processedTemplateContent = await processTemplate(templateContent, config); Editor.content = [Editor.content, processedTemplateContent].filter(Boolean).join('\n'); } //-------------------------------------------------------------------------------------------------------------------- - // Daily Journal plugin for NotePlan - // Jonathan Clark - // v0.6.0, 8.6.2021 - //-------------------------------------------------------------------------------------------------------------------- - // Title of template note to use as Daily template - // const staticTemplateFolder = '📋 Templates'; - const pref_templateTitle = 'Daily Note Template'; // Settings that should come from the Preference framework in time: - const pref_reviewSectionHeading = 'Journal'; - const pref_reviewQuestions = ['@work()', '@fruitveg()', 'Exercise:: ', 'Mood:: ', 'Gratitude:: ', 'God was:: ', 'Alive:: ', 'Not Great:: ', 'Wife:: ', 'Remember:: '].join('\n'); - const pref_moods = ['🤩 Great', '🙂 Good', '😇 Blessed', '🥱 Tired', '😫 Stressed', '😤 Frustrated', '😡 Angry', '😔 Low', '🥵 Sick', 'Other'].join(','); // Globals - - const todaysDate = new Date().toISOString().slice(0, 10).replace(/-/g, ''); - const pref_moodArray = pref_moods.split(','); // with a proper config system, this won't be needed + const pref_templateTitle = 'Daily Note Template'; + const todaysDate = new Date().toISOString().slice(0, 10).replace(/-/g, ''); //------------------------------------------------------------------ + // Helper functions // test for integer // taken from https://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript function isInt(value) { const x = parseFloat(value); return !isNaN(value) && (x | 0) === x; - } // function templateFolder() { - // return DataStore.folders.find((f) => f.includes(staticTemplateFolder)); - // } - // async function getTemplateContent(templateTitle) { - // const folder = templateFolder(); - // if (folder == null) { - // // template folder not found - // console.log("Error: Failed to find the '📋 Templates' folder"); - // return; - // } - // // Get list of templates from its folder - // const templateNotes = DataStore.projectNotes.filter((n) => - // n.filename.includes(folder), - // ); - // const templateNote = templateNotes.find((note) => note.title === templateTitle); - // // Now cut out everything above "---" (second line), which is there so we can have a more meaningful title for the template note - // if (templateNote != null) { - // const lines = [...templateNote.paragraphs]; - // if (lines.length > 0 && lines[1].content == '---') { - // lines.splice(1, 1); - // lines.splice(0, 1); - // } - // return lines.map((l) => l.rawContent).join('\n'); - // } else { - // console.log(`Error: Failed to get template note '${ templateTitle }' from the index`); - // } - // } - //------------------------------------------------------------------ + } //------------------------------------------------------------------ // Start today's daily note with a template, including local weather lookup if configured async function dayStart() { console.log(`\ndayStart for ${todaysDate}`); // open today's date in the main window, and read content - await Editor.openNoteByDate(new Date(), false); // get daily template's content - NB: deprecating this in favour of newer nmn.Templates plugin - // const templateText = await getTemplateContent(pref_templateTitle); - // console.log(`\tRead template text from Template '${ pref_templateTitle }'`); - // let newContent = `${templateText }\n`; + await Editor.openNoteByDate(new Date(), false); // apply daily template, using @nmn Template system + + await applyNamedTemplate(pref_templateTitle); + } // Test for problems with dayStart code on iPad + + async function testDayStart() { + await showMessage(`testDayStart for ${todaysDate}`); // open today's date in the main window, and read content - await applyNamedTemplate(pref_templateTitle); // Now add the weather, if wanted - // if (pref_openWeatherAPIKey != "") { - // const weatherLine = await getWeatherSummary() - // Editor.insertParagraph(`${weatherLine }\n`, 0, 'empty') - // } + await Editor.openNoteByDate(new Date(), false); + await showMessage('should have opened todays date in editor'); // apply daily template, using @nmn Template system + + await applyNamedTemplate(pref_templateTitle); + await showMessage('should have applied template'); + const pos = await CommandBar.showInput('Text position to jump to', '0'); + Editor.select(pos, 0); } //------------------------------------------------------------------ // Gather answers to set questions, and append to the daily note - // TODO: use NP API function calls which are now available. - // TODO: can this use nmn.Template functions at all? async function dayReview() { - console.log(`\ndailyReview for ${todaysDate}`); - Editor.openNoteByDate(new Date()); // open today's date in main window + console.log(`\ndailyReview for ${todaysDate}`); // Get config settings from Template folder _configuration note + + const config = (await getDefaultConfiguration()) ?? {}; + const journalConfig = config.dailyJournal ?? null; + + if (journalConfig == null) { + console.log("\tWarning: Cannot find 'journal' settings in Templates/_configuration note. Stopping."); + await showMessage("Cannot find 'journal' settings in Templates/_configuration note"); + return; + } + + const pref_reviewQuestions = journalConfig.reviewQuestions ?? null; + + if (pref_reviewQuestions == null) { + console.log("\tWarning: Cannot find any 'reviewQuestions' setting in Templates/_configuration note. Stopping."); + await showMessage("Cannot find any 'reviewQuestions' setting in Templates/_configuration note"); + return; + } - const questionsString = pref_reviewQuestions; // Plugin.preference.review_questions + const pref_reviewSectionHeading = journalConfig.reviewSectionHeading ?? "Journal"; + const pref_moods = journalConfig.moods ?? ['🤩 Great', '🙂 Good', '😇 Blessed', '🥱 Tired', '😫 Stressed', '😤 Frustrated', '😡 Angry', '😔 Low', '🥵 Sick', 'Other'].join(','); + const pref_moodArray = pref_moods.split(','); // with a proper config system, this won't be needed + + Editor.openNoteByDate(new Date()); // open today's date in main window const question = []; const questionType = []; @@ -9760,14 +9705,14 @@ lastName = "Doe" let i = 0; // Parse preference string to make array of questions and input types const typeRE = new RegExp('<(.*)>'); - const questionLine = questionsString.split('\n'); - const numQs = questionLine.length; + const questionLines = pref_reviewQuestions.split('\n'); + const numQs = questionLines.length; console.log(`\tFound ${numQs} question lines`); for (i = 0; i < numQs; i++) { // remove type indicators from the question string - question[i] = questionLine[i].replace(/:|\(|\)|||/g, '').trim(); - const reArray = questionLine[i].match(typeRE); + question[i] = questionLines[i].replace(/:|\(|\)|||/g, '').trim(); + const reArray = questionLines[i].match(typeRE); questionType[i] = reArray[1]; // console.log("\t" + i + ": " + question[i] + " / " + questionType[i]) } // Ask each question in turn @@ -9783,9 +9728,9 @@ lastName = "Doe" { const reply = await CommandBar.showInput(questionType[i], `${question[i]}: %@`); - if (reply != undefined && isInt(reply)) { + if (reply != null && isInt(reply)) { // console.log(reply) - reviewLine = questionLine[i].replace(//, reply); + reviewLine = questionLines[i].replace(//, reply); } else { console.log(`\tERROR trying to get integer answer for question '${question[i]}'`); } @@ -9797,9 +9742,9 @@ lastName = "Doe" { const replyString = await CommandBar.showInput(questionType[i], `${question[i]}: %@`); - if (replyString != undefined) { + if (replyString != null) { // console.log(replyString) - reviewLine = replyString != '' ? questionLine[i].replace(//, replyString) : ''; + reviewLine = replyString !== '' ? questionLines[i].replace(//, replyString) : ''; } else { console.log(`\tERROR trying to get string answer for question '${question[i]}'`); } @@ -9812,9 +9757,9 @@ lastName = "Doe" const reply = await CommandBar.showOptions(pref_moodArray, 'Choose appropriate mood'); const replyMood = pref_moodArray[reply.index]; - if (replyMood != undefined && replyMood != '') { + if (replyMood != null && replyMood !== '') { // console.log(replyMood) - reviewLine = questionLine[i].replace(//, replyMood); + reviewLine = questionLines[i].replace(//, replyMood); } else { console.log('\tERROR trying to get mood answer'); } @@ -9824,7 +9769,7 @@ lastName = "Doe" } // console.log("\tAnswer to '" + question[i] + "' = " + reviewLine[i]) - if (reviewLine != '') { + if (reviewLine !== '') { output += `${reviewLine}\n`; } } // add the finished review text to the current daily note, @@ -9832,15 +9777,67 @@ lastName = "Doe" // If this doesn't exist, then append it first. - console.log(`Appending to heading '${pref_reviewSectionHeading}' the text:${output}`); + console.log(`\tAppending to heading '${pref_reviewSectionHeading}' the text:${output}`); Editor.note.addParagraphBelowHeadingTitle(output, '', pref_reviewSectionHeading, true, true); + } // globalThis.dayStart = dayStart + // globalThis.dayReview = dayReview + + // Using https://openweathermap.org/api/one-call-api#data, for which you can get a free API key + + async function getWeatherSummary(weatherParams, config) { + const weatherDescText = ['showers', 'rain', 'sunny intervals', 'partly sunny', 'sunny', 'cloud', 'snow ', 'thunderstorm', 'tornado']; + const weatherDescIcons = ["🌦️", "🌧️", "🌤", "⛅", "☀️", "☁️", "🌨️", "⛈", "🌪"]; // Get config settings from Template folder _configuration note + + const weatherConfig = config.weather ?? null; + + if (weatherConfig == null) { + await showMessage("Cannot find 'weather' settings in Templates/_configuration note"); + return ''; + } + + const pref_openWeatherAPIKey = weatherConfig.openWeatherAPIKey; + const pref_latPosition = weatherConfig.latPosition; + const pref_longPosition = weatherConfig.longPosition; + const pref_openWeatherUnits = weatherConfig.openWeatherUnits; // TODO: probably getDefaultConfiguration rather than parseJSON5 ? + + console.log(`getWeatherSummary: Params: '${weatherParams}'`); + const paramConfig = weatherParams.trim() ? await parseJSON5(weatherParams) : {}; + console.log(paramConfig); + const getWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${pref_latPosition}&lon=${pref_longPosition}&exclude=current,hourly,minutely&units=${pref_openWeatherUnits}&appid=${pref_openWeatherAPIKey}`; // TODO: Allow for more customisation of what is pulled out from the API's data structure + // using weatherParams + + const jsonIn = await fetch(getWeatherURL); + + if (jsonIn != null) { + const weatherTodayAll = JSON.parse(jsonIn).daily["0"]; + const maxTemp = weatherTodayAll.feels_like.day.toFixed(0); + const minTemp = weatherTodayAll.feels_like.night.toFixed(0); + const weatherDesc = weatherTodayAll.weather["0"].description; // see if we can fix an icon for this as well, according to returned description. Main terms are: + // thunderstorm, drizzle, shower > rain, snow, sleet, clear sky, mist, fog, dust, tornado, overcast > clouds + // with 'light' modifier for rain and snow + + let weatherIcon = ''; + + for (let i = 0; i < weatherDescText.length; i++) { + if (weatherDesc.match(weatherDescText[i])) { + weatherIcon = weatherDescIcons[i]; + break; + } + } + + const summaryLine = `${maxTemp}/${minTemp} ${weatherIcon}${weatherDesc}`; + console.log(`\t${summaryLine}`); + return summaryLine; + } else { + await showMessage('Sorry; error in Weather lookup'); + return 'sorry; error in Weather lookup'; + } } - globalThis.dayStart = dayStart; - globalThis.dayReview = dayReview; exports.dayReview = dayReview; exports.dayStart = dayStart; exports.getWeatherSummary = getWeatherSummary; + exports.testDayStart = testDayStart; Object.defineProperty(exports, '__esModule', { value: true }); diff --git a/jgclark.DailyJournal/src/index.js b/jgclark.DailyJournal/src/index.js index 0e672fdbf..2321a7050 100644 --- a/jgclark.DailyJournal/src/index.js +++ b/jgclark.DailyJournal/src/index.js @@ -1,11 +1,10 @@ // @flow //----------------------------------------------------------------------------- -// Statistic commands for notes and projects -// Jonathan Clark & Eduard Metzger -// v0.6.0, 8.6.2021 +// Daily Journal commands +// Jonathan Clark +// v0.6.2, 18.6.2021 //----------------------------------------------------------------------------- // export { default as showNoteCount } from './showNoteCount' export { dayStart, dayReview } from './script' -export { getWeatherSummary } from './weather' diff --git a/jgclark.DailyJournal/src/script.js b/jgclark.DailyJournal/src/script.js index 90c198266..d5950ac8f 100644 --- a/jgclark.DailyJournal/src/script.js +++ b/jgclark.DailyJournal/src/script.js @@ -171,6 +171,3 @@ export async function dayReview() { true, ) } - -// globalThis.dayStart = dayStart -// globalThis.dayReview = dayReview diff --git a/nmn.Templates/src/interpolation.js b/nmn.Templates/src/interpolation.js index a3ad0fa1c..7ea5ca16a 100644 --- a/nmn.Templates/src/interpolation.js +++ b/nmn.Templates/src/interpolation.js @@ -1,13 +1,14 @@ // @flow import { getInput } from '../../nmn.sweep/src/userInput' -import { getWeatherSummary } from '../../jgclark.DailyJournal/src/weather' +import { getWeatherSummary } from './weather' import { parseJSON5 } from './configuration' export async function processTemplate( content: string, config: { [string]: ?mixed }, ): Promise { + console.log(`processTemplate: ${content}`) const tagStart = content.indexOf('{{') const tagEnd = content.indexOf('}}') const hasTag = tagStart !== -1 && tagEnd !== -1 && tagStart < tagEnd @@ -34,6 +35,7 @@ export async function processTags( tag: string, config: { [string]: ?mixed }, ): Promise { + console.log(`processTag: ${tag}`) if (tag.startsWith('date(') && tag.endsWith(')')) { return await processDate(tag.slice(5, tag.length - 1), config) } @@ -78,6 +80,7 @@ async function processDate( dateParams: string, config: { [string]: ?mixed }, ): Promise { + console.log(`processDate: ${dateConfig}`) const defaultConfig = config.date ?? {} const paramConfig = dateParams.trim() ? await parseJSON5(dateParams) : {} // console.log(`param config: ${dateParams} as ${JSON.stringify(paramConfig)}`); diff --git a/nmn.Templates/src/template-folder.js b/nmn.Templates/src/template-folder.js index af7d2a1c4..9e8ac24f0 100644 --- a/nmn.Templates/src/template-folder.js +++ b/nmn.Templates/src/template-folder.js @@ -118,6 +118,19 @@ The first code-block within the note will always be used. So edit the default co timeStyle: 'short', }, + // configuration for weather data + weather: { + // API key for https://openweathermap.org/ + // !!REQUIRED!! + openWeatherAPIKey: '... put your API key here ...', + // Default location for weather forcast + latPosition: 0.0, + longPosition: 0.0, + // Default units. Can be 'metric' (for Celsius), or 'metric' (for Fahrenheit) + openWeatherUnits: 'metric', + // When using a weather tag, you can customize these options. + }, + // default values for custom tags. // These tags cannot be functions, but you may choose to have nested objects. // feel free to edit this value however you see fit. @@ -150,6 +163,18 @@ date: # can be null (to skip time), "short", "medium", "long" or "full" timeStyle: short +# configuration for weather data lookups, if wanted +weather: + # API key for https://openweathermap.org/ + # !!REQUIRED!! + openWeatherAPIKey: + # Default location for weather forcast + latPosition: 0.0 + longPosition: 0.0 + # Default units. Can be 'metric' (for Celsius), or 'metric' (for Fahrenheit) + openWeatherUnits: metric + # When using a weather tag, you can customize these options. + # default values for custom tags. # These tags cannot be functions, but you may choose to have nested objects. # feel free to edit this value however you see fit. @@ -178,6 +203,18 @@ dateStyle = "short" # can be null (to skip time), "short", "medium", "long" or "full" timeStyle = "short" +// configuration for weather data +[weather] +// API key for https://openweathermap.org/ +# !!REQUIRED!! +openWeatherAPIKey = +# Default location for weather forcast +latPosition = 0.0 +longPosition = 0.0 +# Default units. Can be 'metric' (for Celsius), or 'metric' (for Fahrenheit) +openWeatherUnits = 'metric' +# When using a weather tag, you can customize these options. + # default values for custom tags. [tagValue] # These tags cannot be functions, but you may choose to have nested objects. @@ -190,22 +227,3 @@ firstName = "John" lastName = "Doe" \`\`\` ` - -/** - * - * The following should be added to the default configuration - * once the weather function works. - * - // configuration for weather data - weather: { - // API key for https://openweathermap.org/ - // !!REQUIRED!! - apiKey: '... put your API key here ...', - // Default location for weather forcast - lattitude: 0, - longitude: 0, - // Default temperature unit. Can be "C" (Celcius), "K" (Kelvin) or "F" (Fahrenheit) - unit: 'C', - // When using a weather tag, you can customize these options. - }, -*/ diff --git a/jgclark.DailyJournal/src/weather.js b/nmn.Templates/src/weather.js similarity index 96% rename from jgclark.DailyJournal/src/weather.js rename to nmn.Templates/src/weather.js index 9a0fca427..e20e114b4 100644 --- a/jgclark.DailyJournal/src/weather.js +++ b/nmn.Templates/src/weather.js @@ -44,10 +44,9 @@ export async function getWeatherSummary( const pref_longPosition = weatherConfig.longPosition const pref_openWeatherUnits = weatherConfig.openWeatherUnits - // TODO: probably getDefaultConfiguration rather than parseJSON5 ? console.log(`getWeatherSummary: Params: '${weatherParams}'`) const paramConfig = weatherParams.trim() ? await parseJSON5(weatherParams) : {} - console.log(paramConfig) + // console.log(paramConfig) const getWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${ @@ -59,15 +58,13 @@ export async function getWeatherSummary( }&appid=${ pref_openWeatherAPIKey}` - // TODO: Allow for more customisation of what is pulled out from the API's data structure - // using weatherParams - const jsonIn = await fetch(getWeatherURL) if (jsonIn != null) { const weatherTodayAll = JSON.parse(jsonIn).daily["0"] const maxTemp = weatherTodayAll.feels_like.day.toFixed(0) const minTemp = weatherTodayAll.feels_like.night.toFixed(0) const weatherDesc = weatherTodayAll.weather["0"].description + // see if we can fix an icon for this as well, according to returned description. Main terms are: // thunderstorm, drizzle, shower > rain, snow, sleet, clear sky, mist, fog, dust, tornado, overcast > clouds // with 'light' modifier for rain and snow @@ -78,9 +75,14 @@ export async function getWeatherSummary( break } } + + // TODO: Allow for more customisation of what is pulled out from the API's data structure + // using weatherParams + const summaryLine = `${maxTemp}/${minTemp} ${weatherIcon}${weatherDesc}` console.log(`\t${summaryLine}`) return summaryLine + } else { await showMessage('Sorry; error in Weather lookup') return 'sorry; error in Weather lookup' diff --git a/package.json b/package.json index 9a1db70d7..c33dc8689 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,10 @@ "build:jgclark.DailyJournal": "rollup -c ./jgclark.DailyJournal/rollup.config.js", "build:jgclark.Filer": "rollup -c ./jgclark.Filer/rollup.config.js", "build:jgclark.RepeatExtensions": "rollup -c ./jgclark.RepeatExtensions/rollup.config.js", + "build:jgclark.CalendarHelpers": "rollup -c ./jgclark.CalendarHelpers/rollup.config.js", + "build:jgclark.QuickCapture": "rollup -c ./jgclark.QuickCapture/rollup.config.js", + "build:jgclark.NoteHelpers": "rollup -c ./jgclark.NoteHelpers/rollup.config.js", + "build:jgclark.GTD": "rollup -c ./jgclark.GTD/rollup.config.js", "build": "concurrently \"npm:build:*\"", "watch:nmn.sweep": "rollup -c ./nmn.sweep/rollup.config.js --watch", "watch:nmn.Templates": "rollup -c ./nmn.Templates/rollup.config.js --watch", @@ -36,6 +40,10 @@ "watch:jgclark.DailyJournal": "rollup -c ./jgclark.DailyJournal/rollup.config.js --watch", "watch:jgclark.Filer": "rollup -c ./jgclark.Filer/rollup.config.js --watch", "watch:jgclark.RepeatExtensions": "rollup -c ./jgclark.RepeatExtensions/rollup.config.js --watch", + "watch:jgclark.CalendarHelpers": "rollup -c ./jgclark.CalendarHelpers/rollup.config.js --watch", + "watch:jgclark.QuickCapture": "rollup -c ./jgclark.QuickCapture/rollup.config.js --watch", + "watch:jgclark.NoteHelpers": "rollup -c ./jgclark.NoteHelpers/rollup.config.js --watch", + "watch:jgclark.GTD": "rollup -c ./jgclark.GTD/rollup.config.js --watch", "watch": "concurrently \"npm:watch:*\"", "autowatch": "node scripts/rollup.js", "lint": "eslint .", From ee86a613158c6a9bfd0a03cb96730e5e9b524dd8 Mon Sep 17 00:00:00 2001 From: Jonathan Clark Date: Fri, 18 Jun 2021 20:20:37 +0100 Subject: [PATCH 4/4] Knock-on changes to DailyJournal too --- jgclark.DailyJournal/plugin.json | 2 +- jgclark.DailyJournal/script.js | 158 +++++++++++++++--------------- jgclark.DailyJournal/src/index.js | 3 +- nmn.Templates/script.js | 72 +++++++++----- 4 files changed, 126 insertions(+), 109 deletions(-) diff --git a/jgclark.DailyJournal/plugin.json b/jgclark.DailyJournal/plugin.json index b3d5a822f..c5bcece49 100644 --- a/jgclark.DailyJournal/plugin.json +++ b/jgclark.DailyJournal/plugin.json @@ -6,7 +6,7 @@ "plugin.icon": "", "plugin.author": "Jonathan Clark", "plugin.url": "https://github.com/NotePlan/plugins/blob/main/jgclark.DailyJournal/README.md", - "plugin.version": "0.7.0", + "plugin.version": "0.6.4", "plugin.dependencies": [], "plugin.script": "script.js", "plugin.isRemote": "false", diff --git a/jgclark.DailyJournal/script.js b/jgclark.DailyJournal/script.js index c15b22711..cd7e56bba 100644 --- a/jgclark.DailyJournal/script.js +++ b/jgclark.DailyJournal/script.js @@ -9329,7 +9329,7 @@ The first code-block within the note will always be used. So edit the default co // can be "short", "medium", "long" or "full" dateStyle: 'short', // optional key, can be "short", "medium", "long" or "full" - timeStyle: 'short, + timeStyle: 'short', }, // configuration for weather data @@ -9548,6 +9548,57 @@ lastName = "Doe" } } + // Using https://openweathermap.org/api/one-call-api#data, for which you can get a free API key + + async function getWeatherSummary(weatherParams, config) { + const weatherDescText = ['showers', 'rain', 'sunny intervals', 'partly sunny', 'sunny', 'cloud', 'snow ', 'thunderstorm', 'tornado']; + const weatherDescIcons = ["🌦️", "🌧️", "🌤", "⛅", "☀️", "☁️", "🌨️", "⛈", "🌪"]; // Get config settings from Template folder _configuration note + + const weatherConfig = config.weather ?? null; + + if (weatherConfig == null) { + await showMessage("Cannot find 'weather' settings in Templates/_configuration note"); + return ''; + } + + const pref_openWeatherAPIKey = weatherConfig.openWeatherAPIKey; + const pref_latPosition = weatherConfig.latPosition; + const pref_longPosition = weatherConfig.longPosition; + const pref_openWeatherUnits = weatherConfig.openWeatherUnits; + console.log(`getWeatherSummary: Params: '${weatherParams}'`); + weatherParams.trim() ? await parseJSON5(weatherParams) : {}; // console.log(paramConfig) + + const getWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${pref_latPosition}&lon=${pref_longPosition}&exclude=current,hourly,minutely&units=${pref_openWeatherUnits}&appid=${pref_openWeatherAPIKey}`; + const jsonIn = await fetch(getWeatherURL); + + if (jsonIn != null) { + const weatherTodayAll = JSON.parse(jsonIn).daily["0"]; + const maxTemp = weatherTodayAll.feels_like.day.toFixed(0); + const minTemp = weatherTodayAll.feels_like.night.toFixed(0); + const weatherDesc = weatherTodayAll.weather["0"].description; // see if we can fix an icon for this as well, according to returned description. Main terms are: + // thunderstorm, drizzle, shower > rain, snow, sleet, clear sky, mist, fog, dust, tornado, overcast > clouds + // with 'light' modifier for rain and snow + + let weatherIcon = ''; + + for (let i = 0; i < weatherDescText.length; i++) { + if (weatherDesc.match(weatherDescText[i])) { + weatherIcon = weatherDescIcons[i]; + break; + } + } // TODO: Allow for more customisation of what is pulled out from the API's data structure + // using weatherParams + + + const summaryLine = `${maxTemp}/${minTemp} ${weatherIcon}${weatherDesc}`; + console.log(`\t${summaryLine}`); + return summaryLine; + } else { + await showMessage('Sorry; error in Weather lookup'); + return 'sorry; error in Weather lookup'; + } + } + async function processTemplate(content, config) { console.log(`processTemplate: ${content}`); const tagStart = content.indexOf('{{'); @@ -9563,22 +9614,33 @@ lastName = "Doe" const tag = content.slice(tagStart + 2, tagEnd); try { - const tagProcessed = await processTag(tag, config); + const tagProcessed = await processTags(tag, config); const restProcessed = await processTemplate(afterTag, config); return beforeTag + tagProcessed + restProcessed; } catch (e) { console.log(e); return content; } - } + } // Apply any matching tag functions - async function processTag(tag, config) { + async function processTags(tag, config) { console.log(`processTag: ${tag}`); if (tag.startsWith('date(') && tag.endsWith(')')) { return await processDate(tag.slice(5, tag.length - 1), config); - } - + } else if (tag.startsWith('weather(') && tag.endsWith(')')) { + return await getWeatherSummary(tag.slice(8, tag.length - 1), config); + } // **Add other extension function calls here** + // Can call functions defined in other plugins, by appropriate use + // of imports at top of file (e.g. getWeatherSummary) + // Or declare below (e.g. processDate) + else { + // no matching funcs, so now attempt to match defined tag values instead + return processTagValues(tag, config); + } + } // Apply any matching tag values, asking user for value if not found in configuration + + async function processTagValues(tag, config) { const valueInConfig = tag // eslint-disable-next-line no-useless-escape .split(/[\.\[\]]/).filter(Boolean).reduce((path, key) => path != null && typeof path === 'object' ? path[key] : null, config.tagValue); @@ -9587,16 +9649,19 @@ lastName = "Doe" } return await getInput(`Value for ${tag}`); - } + } // ---------------------------------------------------------------- + // Define new tag functions here ... - async function processDate(dateConfig, config) { + + async function processDate(dateParams, config) { console.log(`processDate: ${dateConfig}`); const defaultConfig = config.date ?? {}; - const paramConfig = dateConfig.trim() ? await parseJSON5(dateConfig) : {}; // console.log(`param config: ${dateConfig} as ${JSON.stringify(paramConfig)}`); + const paramConfig = dateParams.trim() ? await parseJSON5(dateParams) : {}; // console.log(`param config: ${dateParams} as ${JSON.stringify(paramConfig)}`); const finalArguments = { ...defaultConfig, ...paramConfig - }; + }; // ... = "gather the remaining parameters into an array" + const { locale, ...otherParams @@ -9627,11 +9692,9 @@ lastName = "Doe" if (templateContent == null || templateContent.length === 0) { console.log(`\twarning: template '${templateName}' is null or empty`); return; - } // console.log(templateContent) - - - templateContent = templateContent.split('\n---\n').slice(1).join('\n---\n'); // console.log(`\n${templateContent}`) + } + templateContent = templateContent.split('\n---\n').slice(1).join('\n---\n'); const config = (await getDefaultConfiguration()) ?? {}; const processedTemplateContent = await processTemplate(templateContent, config); Editor.content = [Editor.content, processedTemplateContent].filter(Boolean).join('\n'); @@ -9658,18 +9721,6 @@ lastName = "Doe" await Editor.openNoteByDate(new Date(), false); // apply daily template, using @nmn Template system await applyNamedTemplate(pref_templateTitle); - } // Test for problems with dayStart code on iPad - - async function testDayStart() { - await showMessage(`testDayStart for ${todaysDate}`); // open today's date in the main window, and read content - - await Editor.openNoteByDate(new Date(), false); - await showMessage('should have opened todays date in editor'); // apply daily template, using @nmn Template system - - await applyNamedTemplate(pref_templateTitle); - await showMessage('should have applied template'); - const pos = await CommandBar.showInput('Text position to jump to', '0'); - Editor.select(pos, 0); } //------------------------------------------------------------------ // Gather answers to set questions, and append to the daily note @@ -9779,65 +9830,10 @@ lastName = "Doe" console.log(`\tAppending to heading '${pref_reviewSectionHeading}' the text:${output}`); Editor.note.addParagraphBelowHeadingTitle(output, '', pref_reviewSectionHeading, true, true); - } // globalThis.dayStart = dayStart - // globalThis.dayReview = dayReview - - // Using https://openweathermap.org/api/one-call-api#data, for which you can get a free API key - - async function getWeatherSummary(weatherParams, config) { - const weatherDescText = ['showers', 'rain', 'sunny intervals', 'partly sunny', 'sunny', 'cloud', 'snow ', 'thunderstorm', 'tornado']; - const weatherDescIcons = ["🌦️", "🌧️", "🌤", "⛅", "☀️", "☁️", "🌨️", "⛈", "🌪"]; // Get config settings from Template folder _configuration note - - const weatherConfig = config.weather ?? null; - - if (weatherConfig == null) { - await showMessage("Cannot find 'weather' settings in Templates/_configuration note"); - return ''; - } - - const pref_openWeatherAPIKey = weatherConfig.openWeatherAPIKey; - const pref_latPosition = weatherConfig.latPosition; - const pref_longPosition = weatherConfig.longPosition; - const pref_openWeatherUnits = weatherConfig.openWeatherUnits; // TODO: probably getDefaultConfiguration rather than parseJSON5 ? - - console.log(`getWeatherSummary: Params: '${weatherParams}'`); - const paramConfig = weatherParams.trim() ? await parseJSON5(weatherParams) : {}; - console.log(paramConfig); - const getWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${pref_latPosition}&lon=${pref_longPosition}&exclude=current,hourly,minutely&units=${pref_openWeatherUnits}&appid=${pref_openWeatherAPIKey}`; // TODO: Allow for more customisation of what is pulled out from the API's data structure - // using weatherParams - - const jsonIn = await fetch(getWeatherURL); - - if (jsonIn != null) { - const weatherTodayAll = JSON.parse(jsonIn).daily["0"]; - const maxTemp = weatherTodayAll.feels_like.day.toFixed(0); - const minTemp = weatherTodayAll.feels_like.night.toFixed(0); - const weatherDesc = weatherTodayAll.weather["0"].description; // see if we can fix an icon for this as well, according to returned description. Main terms are: - // thunderstorm, drizzle, shower > rain, snow, sleet, clear sky, mist, fog, dust, tornado, overcast > clouds - // with 'light' modifier for rain and snow - - let weatherIcon = ''; - - for (let i = 0; i < weatherDescText.length; i++) { - if (weatherDesc.match(weatherDescText[i])) { - weatherIcon = weatherDescIcons[i]; - break; - } - } - - const summaryLine = `${maxTemp}/${minTemp} ${weatherIcon}${weatherDesc}`; - console.log(`\t${summaryLine}`); - return summaryLine; - } else { - await showMessage('Sorry; error in Weather lookup'); - return 'sorry; error in Weather lookup'; - } } exports.dayReview = dayReview; exports.dayStart = dayStart; - exports.getWeatherSummary = getWeatherSummary; - exports.testDayStart = testDayStart; Object.defineProperty(exports, '__esModule', { value: true }); diff --git a/jgclark.DailyJournal/src/index.js b/jgclark.DailyJournal/src/index.js index 2321a7050..a529ed46f 100644 --- a/jgclark.DailyJournal/src/index.js +++ b/jgclark.DailyJournal/src/index.js @@ -3,8 +3,7 @@ //----------------------------------------------------------------------------- // Daily Journal commands // Jonathan Clark -// v0.6.2, 18.6.2021 +// v0.6.4, 18.6.2021 //----------------------------------------------------------------------------- -// export { default as showNoteCount } from './showNoteCount' export { dayStart, dayReview } from './script' diff --git a/nmn.Templates/script.js b/nmn.Templates/script.js index e49bbc27c..96424d195 100644 --- a/nmn.Templates/script.js +++ b/nmn.Templates/script.js @@ -9332,6 +9332,19 @@ The first code-block within the note will always be used. So edit the default co timeStyle: 'short', }, + // configuration for weather data + weather: { + // API key for https://openweathermap.org/ + // !!REQUIRED!! + openWeatherAPIKey: '... put your API key here ...', + // Default location for weather forcast + latPosition: 0.0, + longPosition: 0.0, + // Default units. Can be 'metric' (for Celsius), or 'metric' (for Fahrenheit) + openWeatherUnits: 'metric', + // When using a weather tag, you can customize these options. + }, + // default values for custom tags. // These tags cannot be functions, but you may choose to have nested objects. // feel free to edit this value however you see fit. @@ -9364,6 +9377,18 @@ date: # can be null (to skip time), "short", "medium", "long" or "full" timeStyle: short +# configuration for weather data lookups, if wanted +weather: + # API key for https://openweathermap.org/ + # !!REQUIRED!! + openWeatherAPIKey: + # Default location for weather forcast + latPosition: 0.0 + longPosition: 0.0 + # Default units. Can be 'metric' (for Celsius), or 'metric' (for Fahrenheit) + openWeatherUnits: metric + # When using a weather tag, you can customize these options. + # default values for custom tags. # These tags cannot be functions, but you may choose to have nested objects. # feel free to edit this value however you see fit. @@ -9392,6 +9417,18 @@ dateStyle = "short" # can be null (to skip time), "short", "medium", "long" or "full" timeStyle = "short" +// configuration for weather data +[weather] +// API key for https://openweathermap.org/ +# !!REQUIRED!! +openWeatherAPIKey = +# Default location for weather forcast +latPosition = 0.0 +longPosition = 0.0 +# Default units. Can be 'metric' (for Celsius), or 'metric' (for Fahrenheit) +openWeatherUnits = 'metric' +# When using a weather tag, you can customize these options. + # default values for custom tags. [tagValue] # These tags cannot be functions, but you may choose to have nested objects. @@ -9404,24 +9441,6 @@ firstName = "John" lastName = "Doe" \`\`\` `; - /** - * - * The following should be added to the default configuration - * once the weather function works. - * - // configuration for weather data - weather: { - // API key for https://openweathermap.org/ - // !!REQUIRED!! - apiKey: '... put your API key here ...', - // Default location for weather forcast - lattitude: 0, - longitude: 0, - // Default temperature unit. Can be "C" (Celcius), "K" (Kelvin) or "F" (Fahrenheit) - unit: 'C', - // When using a weather tag, you can customize these options. - }, - */ const ALLOWED_FORMATS = ['javascript', 'json', 'json5', 'yaml', 'toml', 'ini']; const FORMAT_MAP = { @@ -9545,14 +9564,11 @@ lastName = "Doe" const pref_openWeatherAPIKey = weatherConfig.openWeatherAPIKey; const pref_latPosition = weatherConfig.latPosition; const pref_longPosition = weatherConfig.longPosition; - const pref_openWeatherUnits = weatherConfig.openWeatherUnits; // TODO: probably getDefaultConfiguration rather than parseJSON5 ? - + const pref_openWeatherUnits = weatherConfig.openWeatherUnits; console.log(`getWeatherSummary: Params: '${weatherParams}'`); - const paramConfig = weatherParams.trim() ? await parseJSON5(weatherParams) : {}; - console.log(paramConfig); - const getWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${pref_latPosition}&lon=${pref_longPosition}&exclude=current,hourly,minutely&units=${pref_openWeatherUnits}&appid=${pref_openWeatherAPIKey}`; // TODO: Allow for more customisation of what is pulled out from the API's data structure - // using weatherParams + weatherParams.trim() ? await parseJSON5(weatherParams) : {}; // console.log(paramConfig) + const getWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${pref_latPosition}&lon=${pref_longPosition}&exclude=current,hourly,minutely&units=${pref_openWeatherUnits}&appid=${pref_openWeatherAPIKey}`; const jsonIn = await fetch(getWeatherURL); if (jsonIn != null) { @@ -9570,7 +9586,9 @@ lastName = "Doe" weatherIcon = weatherDescIcons[i]; break; } - } + } // TODO: Allow for more customisation of what is pulled out from the API's data structure + // using weatherParams + const summaryLine = `${maxTemp}/${minTemp} ${weatherIcon}${weatherDesc}`; console.log(`\t${summaryLine}`); @@ -9582,6 +9600,7 @@ lastName = "Doe" } async function processTemplate(content, config) { + console.log(`processTemplate: ${content}`); const tagStart = content.indexOf('{{'); const tagEnd = content.indexOf('}}'); const hasTag = tagStart !== -1 && tagEnd !== -1 && tagStart < tagEnd; @@ -9605,6 +9624,8 @@ lastName = "Doe" } // Apply any matching tag functions async function processTags(tag, config) { + console.log(`processTag: ${tag}`); + if (tag.startsWith('date(') && tag.endsWith(')')) { return await processDate(tag.slice(5, tag.length - 1), config); } else if (tag.startsWith('weather(') && tag.endsWith(')')) { @@ -9633,6 +9654,7 @@ lastName = "Doe" async function processDate(dateParams, config) { + console.log(`processDate: ${dateConfig}`); const defaultConfig = config.date ?? {}; const paramConfig = dateParams.trim() ? await parseJSON5(dateParams) : {}; // console.log(`param config: ${dateParams} as ${JSON.stringify(paramConfig)}`);