diff --git a/helpers/stringTransforms.js b/helpers/stringTransforms.js index 7624549e5..cb98598fd 100644 --- a/helpers/stringTransforms.js +++ b/helpers/stringTransforms.js @@ -39,9 +39,7 @@ export function changeBareLinksToHTMLLink(original: string, addWebIcon: boolean // clo(captures, `${String(captures.length)} results from bare URL matches:`) for (const capture of captures) { const linkURL = capture[3] - const URLForDisplay = (truncateIfNecessary && linkURL.length > 20) - ? linkURL.slice(0, 50) + '...' - : linkURL + const URLForDisplay = truncateIfNecessary && linkURL.length > 20 ? linkURL.slice(0, 50) + '...' : linkURL // logDebug('changeBareLinksToHTMLLink', `${linkURL} / ${URLForDisplay}`) if (addWebIcon) { // not displaying icon @@ -359,11 +357,11 @@ export function encodeRFC3986URIComponent(input: string): string { .replace(/\[/g, '%5B') .replace(/\]/g, '%5D') .replace(/!/g, '%21') - .replace(/'/g, "%27") + .replace(/'/g, '%27') .replace(/\(/g, '%28') .replace(/\)/g, '%29') .replace(/\*/g, '%2A') - // .replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`) + // .replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`) } /** @@ -374,13 +372,98 @@ export function encodeRFC3986URIComponent(input: string): string { * @returns {string} */ export function decodeRFC3986URIComponent(input: string): string { - const decodedSpecials = input - .replace(/%5B/g, '[') - .replace(/%5D/g, ']') - .replace(/%21/g, '!') - .replace(/%27/g, "'") - .replace(/%28/g, '(') - .replace(/%29/g, ')') - .replace(/%2A/g, '*') + const decodedSpecials = input.replace(/%5B/g, '[').replace(/%5D/g, ']').replace(/%21/g, '!').replace(/%27/g, "'").replace(/%28/g, '(').replace(/%29/g, ')').replace(/%2A/g, '*') return decodeURIComponent(decodedSpecials) } + +/** + * @method truncateOnWord(length, [from] = 'right', [ellipsis] = '...') + * @returns String + * @short Truncates a string without splitting up words. + * @extra [from] can be `'right'`, `'left'`, or `'middle'`. If the string is + * shorter than `length`, [ellipsis] will not be added. A "word" is + * defined as any sequence of non-whitespace characters. + * + * @example + * + * 'here we go'.truncateOnWord(5) -> 'here...' + * 'here we go'.truncateOnWord(5, 'left') -> '...we go' + * + * @param {number} length + * @param {string} [from] can be `'right'`, `'left'`, or `'middle'`. + * @param {boolean} [fromLeft] - whether to truncate from the left or not + * + * @author Sugar.js https://github.com/andrewplummer/Sugar/blob/b757c66c4e6361af710431117eadcafc5c7d42bc/lib/string.js + **/ +function truncateOnWord(str: string, limit: number, fromLeft: boolean = false): string { + if (fromLeft) { + return truncateOnWord(str.split('').reverse().join(''), limit).split('').reverse().join('') + } + // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category. + const TRIM_CHARS = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF' + const TRUNC_REG = RegExp(`(?=[${TRIM_CHARS}])`) + const words = str.split(TRUNC_REG) + let count = 0 + const result = [] + + for (const word of words) { + count += word.length + if (count <= limit) { + result.push(word) + } else { + break + } + } + + return result.join('') +} + +/** + * @method truncateString(length, [from] = 'right', [split] = true) + * @returns String + * @short Truncates a string. + * @extra [from] can be `'right'`, `'left'`, or `'middle'`. If the string is + * shorter than `length`, [ellipsis] will not be added. + * + * @example + * + * 'sittin on the dock'.truncate(10) -> 'sittin on ...' + * 'sittin on the dock'.truncate(10, 'left') -> '...n the dock' + * 'sittin on the dock'.truncate(10, 'middle') -> 'sitti... dock' + * + * @param {number} length + * @param {string} [from] can be `'right'`, `'left'`, or `'middle'`. + * @param {boolean} [split] - whether to split on words or not + * + * @author Sugar.js https://github.com/andrewplummer/Sugar/blob/b757c66c4e6361af710431117eadcafc5c7d42bc/lib/string.js + **/ +export function truncateString(str: string, length: number, from: string, splitOnWord: boolean = true): string { + let str1, str2, len1, len2 + if (str.length <= length) { + return str.toString() + } + const ellipsisStr = '...' + switch (from) { + case 'left': + str2 = splitOnWord ? truncateOnWord(str, length, true) : str.slice(str.length - length) + return ellipsisStr + str2 + case 'middle': + len1 = Math.ceil(length / 2) + len2 = Math.floor(length / 2) + str1 = splitOnWord ? truncateOnWord(str, len1) : str.slice(0, len1) + str2 = splitOnWord ? truncateOnWord(str, len2, true) : str.slice(str.length - len2) + return str1 + ellipsisStr + str2 + default: + str1 = splitOnWord ? truncateOnWord(str, length) : str.slice(0, length) + return str1 + ellipsisStr + } +} + +/** + * Remove text between () inclusive + * @param {string} str + * @returns {string} + */ +export function removeTextBetweenParentheses(str: string): string { + return str.replace(/\(.*?\)/g, '') +} \ No newline at end of file diff --git a/jgclark.Summaries/README.md b/jgclark.Summaries/README.md index 5bbec723c..ff2e9bca2 100644 --- a/jgclark.Summaries/README.md +++ b/jgclark.Summaries/README.md @@ -57,22 +57,23 @@ All notes in the special folders (@Archive, @Templates and @Trash) are **ignored Note: **Why use `@run(...)` (mentions) rather than `#run(...)` (hashtags)**? Well, it just felt more right to use `@run(...)` as there are already `@done(...)` and `@repeat(...)` mentions in use in NotePlan that include a value in the brackets. And in NotePlan, hashtags that end with a number ignore the fractional part (e.g. `#run/5.3` ignores the `.3`) but they are not ignored inside `@run(5.3)`. However, you _can_ use a `#hashtag/value` if you don't mind this limitation. -## Tracking checklist completetion +## Tracking checklist completion -To track checklist completion you must create a referance checklist in the template folder: +To track checklist completion you must create a reference checklist in the template folder: + +Checklist completion -![alt text](checklist-1.png) Add the title of this template to settings: -![alt text](checklist.png) + Checklist settings If you want to use this template in another note it can be imported using `<%- import("Daily tasks”) -%>` Completion is tracked using the 'appendProgressUpdate' command -![alt text](checklist-2.png) + Checklist count ## 'heatmap for complete tasks' command This displays a 'heatmap' chart of many tasks you've completed on each day (see example above). It uses the `@done(...)` dates in all daily, weekly and project notes over the number of weeks you specify to look back (via the 'Chart Duration (in weeks)' setting). If you set this to 0, the plugin will generate a sensible longish period between 6 and 12 months. It also counts completed tasks without `@done(...)` dates on Calendar notes, and assumes the tasks were completed on the day or start of week in question. diff --git a/jgclark.Summaries/checklist-2.png b/jgclark.Summaries/checklist_count.png similarity index 100% rename from jgclark.Summaries/checklist-2.png rename to jgclark.Summaries/checklist_count.png diff --git a/jgclark.Summaries/checklist-1.png b/jgclark.Summaries/checklist_refnote.png similarity index 100% rename from jgclark.Summaries/checklist-1.png rename to jgclark.Summaries/checklist_refnote.png diff --git a/jgclark.Summaries/checklist.png b/jgclark.Summaries/checklist_settings.png similarity index 100% rename from jgclark.Summaries/checklist.png rename to jgclark.Summaries/checklist_settings.png diff --git a/jgclark.Summaries/plugin.json b/jgclark.Summaries/plugin.json index 98edb1161..d8a8eff5d 100644 --- a/jgclark.Summaries/plugin.json +++ b/jgclark.Summaries/plugin.json @@ -8,8 +8,8 @@ "plugin.author": "Jonathan Clark", "plugin.url": "https://github.com/NotePlan/plugins/tree/main/jgclark.Summaries/", "plugin.changelog": "https://github.com/NotePlan/plugins/blob/main/jgclark.Summaries/CHANGELOG.md", - "plugin.version": "0.22.0", - "plugin.lastUpdateInfo": "0.22.0: Add support for checklist progress.\n0.21.0: Add Mermaid charting command. Fix to simple weekly CSV generation.\n0.20.3: Bug fix for progressUpdate() in templates.\n0.20.2: Added x-callback options for /periodStats.\n0.20.1: fix refresh after '/append progress update' command. Logging change.\n0.20.0: add new '/today progress' and '/heatmap for tag' commands, and add refresh button to /periodStats output.\n0.19.3: bug fixes on 'weekly stats generation' commands.\n0.19.2: change date library.\n0.19.1: bug fix\n. 0.19.0: adds totals and averages for hashtags as well. Improve output of averages.", + "plugin.version": "0.22.1", + "plugin.lastUpdateInfo": "0.22.1 Fix formatting with long lines.\n0.22.0: Add support for checklist progress.\n0.21.0: Add Mermaid charting command. Fix to simple weekly CSV generation.\n0.20.3: Bug fix for progressUpdate() in templates.\n0.20.2: Added x-callback options for /periodStats.\n0.20.1: fix refresh after '/append progress update' command. Logging change.\n0.20.0: add new '/today progress' and '/heatmap for tag' commands, and add refresh button to /periodStats output.\n0.19.3: bug fixes on 'weekly stats generation' commands.\n0.19.2: change date library.\n0.19.1: bug fix\n. 0.19.0: adds totals and averages for hashtags as well. Improve output of averages.", "plugin.dependencies": [], "plugin.script": "script.js", "plugin.isRemote": "false", diff --git a/jgclark.Summaries/src/summaryHelpers.js b/jgclark.Summaries/src/summaryHelpers.js index 330e98726..12d7b3efb 100644 --- a/jgclark.Summaries/src/summaryHelpers.js +++ b/jgclark.Summaries/src/summaryHelpers.js @@ -30,6 +30,11 @@ import { isHashtagWanted, isMentionWanted, } from '@helpers/search' +import { + removeTextBetweenParentheses, + truncateString, +} from '@helpers/stringTransforms' +import NPTemplating from 'NPTemplating' //------------------------------------------------------------------------------ // Get settings @@ -719,11 +724,24 @@ export async function generateProgressUpdate(occObjs: Array, peri const daysBetween = toDateMom.diff(fromDateMom, 'days') // Include sparklines only if this period is a month or less const showSparklines = (requestToShowSparklines && daysBetween <= 31) + + await occObjs.forEach(async (m) => { + if (m.term.includes('<%') && m.term.includes('%>')) { + m.term = await NPTemplating.render(m.term) + logError('generateProgressUpdate', `rendered term: ${m.term}`) + } + if (showSparklines) { + m.term = truncateString(removeTextBetweenParentheses(m.term), 60, 'middle') + } + }) + + logError('generateProgressUpdate', `this should be after the await`) + // Get length of longest progress term (to use with sparklines) const maxTermLen = Math.max(...occObjs.map((m) => m.term.length)) - let outputArray: Array = [] - for (let occObj of occObjs) { + const outputArray: Array = [] + for (const occObj of occObjs) { // occObj.logValuesMap() let thisOutput = '' switch (style) {