diff --git a/.eslintrc b/.eslintrc index 641bad42b..69d3d0639 100644 --- a/.eslintrc +++ b/.eslintrc @@ -104,8 +104,8 @@ "globalThis": true, "fetch": true, - "$ReadOnlyArray": true, - "$ReadOnly": true + "ReadonlyArray": true, + "Readonly": true }, "ignorePatterns": [ ".history", diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index 848d76e07..000000000 --- a/.flowconfig +++ /dev/null @@ -1,44 +0,0 @@ -[ignore] -# removed to fix issue with flow reporting errors with non-typed modules -# details here - https://github.com/facebook/flow/issues/6646#issuecomment-447272772 -# /node_modules/.* - -# This particular sub-folder should be ignored because it includes malformed JSON -/node_modules/resolve/test/.* -/flow-typed/**/*.* -/flow-typed/Noteplan.js -/private/**/*.* -/**/.history/.* - -[include] - -[libs] -flow-typed - -[lints] -# FIXME: all of the below seem to crash flow in VSCode -#all=warn # warn on everything, except those specified. I don't see why this isn't working. -#sketchy-null=warn -#sketchy-number=warn -#unnecessary-optional-chain=warn -#unused-promise=warn # was not working for @jgclark at v0.202 - -[options] -autoimports=false -emoji=true -exact_by_default=true -experimental.const_params=true -module.use_strict=true -suppress_type=$FlowIgnore -suppress_type=$FlowFixMe -# suppress_type=$FlowTODO -module.name_mapper='^@plugins' ->'' -module.name_mapper='^@helpers' ->'/helpers' -module.name_mapper='^@mocks' ->'/__mocks__' -module.name_mapper='^NPTemplating' ->'/np.Templating/lib/NPTemplating' -module.name_mapper='^TemplatingEngine' ->'/np.Templating/lib/TemplatingEngine' -module.name_mapper='^@templating' ->'/np.Templating/lib' -module.name_mapper='^@templatingModules' ->'/np.Templating/lib/support/modules' -module.name_mapper='^NPGlobals' ->'/np.Globals/lib/NPGlobals' - -[strict] diff --git a/.prettierrc b/.prettierrc index dceda2d7d..9d28c7a26 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ { - "semi": false + "trailingComma": "all", + "semi": false } \ No newline at end of file diff --git a/.watchmanconfig b/.watchmanconfig deleted file mode 100644 index 2c63c0851..000000000 --- a/.watchmanconfig +++ /dev/null @@ -1,2 +0,0 @@ -{ -} diff --git a/Flow_Guide.md b/Flow_Guide.md index 45625edca..06bcee945 100644 --- a/Flow_Guide.md +++ b/Flow_Guide.md @@ -190,7 +190,7 @@ if let str = str { translates to: ```typescript -let str: ?string; +let str: null | void | string; let err: string = str; // type error diff --git a/KimMachineGun.Raindrop/src/NPPluginMain.js b/KimMachineGun.Raindrop/src/NPPluginMain.js deleted file mode 100644 index 8ae47dde7..000000000 --- a/KimMachineGun.Raindrop/src/NPPluginMain.js +++ /dev/null @@ -1,182 +0,0 @@ -// @flow -// Plugin code goes in files like this. Can be one per command, or several in a file. -// `export async function [name of jsFunction called by Noteplan]` -// then include that function name as an export in the index.js file also -// About Flow: https://flow.org/en/docs/usage/#toc-write-flow-code -// Getting started with Flow in NotePlan: https://github.com/NotePlan/plugins/blob/main/Flow_Guide.md - -// NOTE: This file is named NPPluginMain.js (you could change that name and change the reference to it in index.js) -// As a matter of convention, we use NP at the beginning of files which contain calls to NotePlan APIs (Editor, DataStore, etc.) -// Because you cannot easily write tests for code that calls NotePlan APIs, we try to keep the code in the NP files as lean as possible -// and put the majority of the work in the /support folder files which have Jest tests for each function -// support/helpers is an example of a testable file that is used by the plugin command -// REMINDER, to build this plugin as you work on it: -// From the command line: -// `noteplan-cli plugin:dev KimMachineGun.Raindrop --test --watch --coverage` - -/** - * LOGGING - * A user will be able to set their logging level in the plugin's settings (if you used the plugin:create command) - * As a general rule, you should use logDebug (see below) for messages while you're developing. As developer, - * you will set your log level in your plugin preferences to DEBUG and you will see these messages but - * an ordinary user will not. When you want to output a message,you can use the following - * logging level commands for different levels of messages: - * - * logDebug(pluginJson,"Only developers or people helping debug will see these messages") - * log(pluginJson,"Ordinary users will see these informational messages") - * logWarn(pluginJson,"All users will see these warning/non-fatal messages") - * logError(pluginJson,"All users will see these fatal/error messages") - */ -import pluginJson from '../plugin.json' -import {JSP, logError} from '@helpers/dev' - - -export async function searchAndInsertOrCopy(): Promise { - await searchInRaindrop(insertOrCopyRaindropTitle) -} - -export async function searchAndCreateNote(): Promise { - await searchInRaindrop(createRaindropNote) -} - -async function searchInRaindrop(cb: (raindrop: Raindrop) => Promise): Promise { - const settings = DataStore.settings - const accessToken = settings.accessToken ?? '' - if (accessToken === '') { - logError(pluginJson, `Please configure your access token first.`) - return - } - - // every command/plugin entry point should always be wrapped in a try/catch block - try { - const search = await CommandBar.showInput(`Search`, `Search in Raindrop.io with '%@'`) ?? '' - if (search === '') { - logError(pluginJson, `Too short search term.`) - return - } - - let raindrops = [] - for (let i = 0; ; i++) { - const raw = await requestToRaindrop('GET', `https://api.raindrop.io/rest/v1/raindrops/0?search=${encodeURIComponent(search)}&page=${encodeURIComponent(i)}&perPage=50`) - - const response = JSON.parse(raw) - if (!response.result) { - logError(pluginJson, `An error occurred during searching.`) - return - } - - const raindropsInPage: Array = response.items ?? [] - raindrops = raindrops.concat(raindropsInPage) - if (raindrops.length === 0) { - await CommandBar.prompt( - 'Not Found', - `Nothing found in all raindrops with '${search}'`, - ) - logError(pluginJson, `Nothing found in all raindrops.`) - return - } - - const titles: string[] = raindrops.map((x) => { - if (x.tags.length === 0) { - return x.title - } - const tags = x.tags.map(x => `#${x}`).join(',') - return `${x.title} / ${tags}` - }) - titles.push('Load More...') - - const selected = await CommandBar.showOptions(titles, `Found ${raindrops.length} raindrops`) - if (selected.index === titles.length - 1) { - continue - } - - await cb(raindrops[selected.index]) - break - } - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -async function insertOrCopyRaindropTitle(rd: Raindrop) { - let linkedTitle = `[${rd.title}](${rd.link})` - if (rd.tags.length > 0) { - linkedTitle = `${linkedTitle} ${rd.tags.map(formatTag).join(' ')}` - } - - if (Editor.note == null) { - Clipboard.string = linkedTitle - await CommandBar.prompt( - 'Note Not Opened', - `Copy '${linkedTitle}' to your clipboard.`, - ) - } else { - Editor.insertTextAtCursor(linkedTitle) - } -} - -async function createRaindropNote(rd: Raindrop) { - const settings = DataStore.settings - const noteFolder = settings.noteFolder ?? '' - - const title = `[${rd.title}](${rd.link})` - - let body = '' - const collection = await fetchCollection(rd.collection.$id) - if (collection) { - body = `${body}**Collection:** \`${collection._id === -1 ? 'Unsorted' : collection.title}\`\n` - } - if (rd.excerpt !== '' || rd.highlight.body !== '') { - body = `${body}**Description:**\n> ${rd.excerpt || rd.highlight.body}\n` - } - if (rd.tags.length !== 0) { - body = `${body}**Tags:**\n${rd.tags.map(formatTag).map(x => `- ${x}`).join('\n')}\n` - } - body = `${body}---\n` - - const filename = await createNoteIfNotExists(title, noteFolder, body) - await Editor.openNoteByFilename(filename) -} - -async function createNoteIfNotExists(title: string, folder: string, content?: string): string { - const existingNotes = DataStore.projectNoteByTitle(title, true, false) ?? [] - if (existingNotes.length === 0) { - if (content) { - content = `# ${title}\n${content}` - return await DataStore.newNoteWithContent(content, folder) - } else { - return await DataStore.newNote(title, folder) - } - } -} - -function formatTag(tag: string): string { - const prefix = DataStore.settings.tagPrefix ?? '' - return `#${prefix}${tag.replaceAll(' ', '_').toLowerCase()}` -} - -async function fetchCollection(id: number): ?Collection { - const raw = await requestToRaindrop('GET', `https://api.raindrop.io/rest/v1/collection/${id}`) - const response = JSON.parse(raw) - if (!response.result) { - logError(pluginJson, `An error occurred during fetching collection.`) - return null - } - return response.item -} - -async function requestToRaindrop(method: string, url: string, init?: RequestInit): Promise { - const settings = DataStore.settings - const accessToken = settings.accessToken ?? '' - if (accessToken === '') { - logError(pluginJson, `Please configure your access token first.`) - } - - return await fetch(url, { - method: method, - headers: { - 'Authorization': `Bearer ${accessToken}`, - }, - ...init - }) -} diff --git a/KimMachineGun.Raindrop/src/index.js b/KimMachineGun.Raindrop/src/index.js deleted file mode 100644 index 23e812423..000000000 --- a/KimMachineGun.Raindrop/src/index.js +++ /dev/null @@ -1,61 +0,0 @@ -// @flow -// Flow typing is important for reducing errors and improving the quality of the code. -// About Flow: https://flow.org/en/docs/usage/#toc-write-flow-code -// Getting started with Flow in NotePlan: https://github.com/NotePlan/plugins/blob/main/Flow_Guide.md -// Note: As you will see in this plugin folder, you can have multiple files -- e.g. one file per command or logical group of commands -// ...and separate files for helper/support functions that can be tested in isolation -// The `autowatch` packager combines them all into one script.js file for NotePlan to read -// From the command line: -// `noteplan-cli plugin:dev {{pluginId}} --test --watch --coverage` -// ...will watch for changes and will compile the Plugin script code -// and copy it to your plugins directory where NotePlan can find it -// Since NP reloads the Javascript every time you CMD-J to insert a plugin, -// you can immediately test the new code without restarting NotePlan -// This index.js file is where the packager starts looking for files to combine into one script.js file -// So you need to add a line below for each function that you want NP to have access to. -// Typically, listed below are only the top-level plug-in functions listed in plugin.json - -export { searchAndInsertOrCopy, searchAndCreateNote } from './NPPluginMain' // add one of these for every command specifified in plugin.json (the function could be in any file as long as it's exported) - -// Do not change this line. This is here so your plugin will get recompiled every time you change your plugin.json file -import pluginJson from '../plugin.json' - -/* - * NOTEPLAN HOOKS - * The rest of these functions are called by NotePlan automatically under certain conditions - * It is unlikely you will need to edit/add anything below this line - */ - -// eslint-disable-next-line import/order -import { updateSettingData, pluginUpdated } from '@helpers/NPConfiguration' -import { logError, JSP } from '@helpers/dev' -/** - * NotePlan calls this function after the plugin is installed or updated. - * The `updateSettingData` function looks through the new plugin settings in plugin.json and updates - * the user preferences to include any new fields - */ -export async function onUpdateOrInstall(): Promise { - await updateSettingData(pluginJson) -} - -/** - * NotePlan calls this function every time the plugin is run (any command in this plugin) - * You should not need to edit this function. All work should be done in the commands themselves - */ -// eslint-disable-next-line require-await -export async function init(): Promise { - try { - // Check for the latest version of this plugin, and if a minor update is available, install it and show a message - DataStore.installOrUpdatePluginsByID([pluginJson['plugin.id']], false, false, false).then((r) => - pluginUpdated(pluginJson, r), - ) - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * NotePlan calls this function settings are updated in the Preferences panel - * You should not need to edit this function - */ -export async function onSettingsUpdated(): Promise {} diff --git a/__mocks__/Note.mock.js b/__mocks__/Note.mock.js index 97e4755c8..e7ab104ca 100644 --- a/__mocks__/Note.mock.js +++ b/__mocks__/Note.mock.js @@ -6,7 +6,7 @@ import { logDebug } from '../helpers/dev' * Usage: const myNote = new Note({ param changes here }) * */ -import { textWithoutSyncedCopyTag } from '@helpers/syncedCopies' +import { textWithoutSyncedCopyTag } from '@np/helpers/syncedCopies' export class Note { // Properties backlinks = [] /* sample: [ SOMETHING ], */ diff --git a/__mocks__/_README-Mocks.md b/__mocks__/_README-Mocks.md index 5233fd975..d2f4e048a 100644 --- a/__mocks__/_README-Mocks.md +++ b/__mocks__/_README-Mocks.md @@ -93,7 +93,7 @@ Editor.note now has some basic properties, but to look like a real NotePlan `Not /* global describe, test, jest, expect */ import * as mainFile from '../src/NPPluginMain' -import { copyObject } from '@helpers/dev' +import { copyObject } from '@np/helpers/dev' import { Calendar, Clipboard, CommandBar, DataStore, Editor, NotePlan, Note, Paragraph, Backlink, Range, CalendarItem, PluginObject, PluginCommandObject } from '@mocks/index' diff --git a/aaronpoweruser.ReadwiseUnofficial/src/NPReadwise.js b/aaronpoweruser.ReadwiseUnofficial/src/NPReadwise.js deleted file mode 100644 index 52d72f474..000000000 --- a/aaronpoweruser.ReadwiseUnofficial/src/NPReadwise.js +++ /dev/null @@ -1,140 +0,0 @@ -// @flow -import { showMessage } from '../../helpers/userInput' -import pluginJson from '../plugin.json' -import { checkAccessToken, escapeTwitterHandle, getParagraphTypeChar, removeInvalidChars, removeNewlines } from './NPReadwiseHelpers' -import { parseHighlightsAndWriteToNote } from './NPReadwiseNotes' -import { startReadwiseSyncLog, finishReadwiseSyncLog } from './NPReadwiseSyncLog' -import { log, logDebug, logError } from '@helpers/dev' - -const LAST_SYNC_TIME = 'last_sync_time' - -/** - * Syncs new readwise highlights - */ -export async function readwiseSync(): Promise { - checkAccessToken() - const response = await getReadwise(false) - await handleReadwiseSync(response) -} - -/** - * Rebuilds all readwise highlights - */ -export async function readwiseRebuild(): Promise { - checkAccessToken() - const response = await getReadwise(true) - await handleReadwiseSync(response) -} - -/** - * Gets the daily review highlights from Readwise - * @returns {string} - the highlights as a string - */ -export async function readwiseDailyReview(): Promise { - checkAccessToken() - return await getReadwiseDailyReview() -} - -async function handleReadwiseSync(response: any): Promise { - let downloadHighlightCount = 0, - updatedSourceCount = 0 - await startReadwiseSyncLog() - response.forEach((highlightSource) => { - updatedSourceCount++ - downloadHighlightCount += highlightSource.highlights.length - parseHighlightsAndWriteToNote(highlightSource) - }) - log(pluginJson, `Downloaded ${downloadHighlightCount} highlights from Readwise. Updated ${updatedSourceCount} notes.`) - await showMessage(`Downloaded ${downloadHighlightCount} highlights from Readwise. Updated ${updatedSourceCount} notes.`) - await finishReadwiseSyncLog(downloadHighlightCount, updatedSourceCount) -} - -/** - * Gets the readwise data from the API - * @param {boolean} force - if true, will ignore the last sync time and get all data - * @returns {*} - the readwise data as a JSON object - * @see https://readwise.io/api_deets - */ -async function getReadwise(force: boolean): Promise { - const accessToken = DataStore.settings.accessToken ?? '' - let lastFetchTime = DataStore.loadData(LAST_SYNC_TIME, true) ?? '' - if (DataStore.settings.forceSync === 'true' || force === true) { - lastFetchTime = '' - } - log(pluginJson, `last fetch time is : ${lastFetchTime}`) - logDebug(pluginJson, `base folder is : ${DataStore.settings.baseFolder}`) - - return await doReadWiseFetch(accessToken, lastFetchTime, 0, '') -} - -/* - * Recursively fetches readwise data - * @param {string} accessToken - the readwise access token - * @param {string} lastFetchTime - the last time the data was fetched - * @param {int} downloadCount - the number of highlights downloaded - * @param {string} nextPageCursor - the cursor for the next page of data - * @returns {*} - the readwise data as a JSON object - * @see https://readwise.io/api_deets - */ -async function doReadWiseFetch(accessToken: string, lastFetchTime: string, downloadCount: number, nextPageCursor: string): Promise { - try { - const url = `https://readwise.io/api/v2/export/?updatedAfter=${lastFetchTime}&pageCursor=${nextPageCursor}` - - const options = { - method: 'GET', - headers: { - Authorization: `token ${accessToken}`, - }, - } - const response = await fetch(url, options) - DataStore.saveData(new Date().toISOString(), LAST_SYNC_TIME, true) - - const parsedJson = JSON.parse(response) - const pageCursor = parsedJson.nextPageCursor - logDebug(pluginJson, `page cursor is : ${pageCursor}`) - - let data: any = [] - const count = parsedJson.count + downloadCount - if (pageCursor !== null && pageCursor !== '') { - data = await doReadWiseFetch(accessToken, lastFetchTime, count, pageCursor) - } - const result = parsedJson.results.concat(data) - // DataStore.saveData(JSON.stringify(result), 'readwise_data.json', true) - return result - } catch (error) { - logError(pluginJson, error) - } -} -/* - * Gets the users Daily review from the readwise api - * @returns {string} - the daily review highlights - */ -async function getReadwiseDailyReview(): Promise { - const accessToken = DataStore.settings.accessToken ?? '' - let highlightString = '' - try { - const url = `https://readwise.io/api/v2/review/` - - const options = { - method: 'GET', - headers: { - Authorization: `token ${accessToken}`, - }, - } - const response = await fetch(url, options) - const highlights = JSON.parse(response).highlights - - await highlights.map((highlight) => { - const formattedHighlight = `${removeNewlines(highlight.text)} [ [[${removeInvalidChars(highlight.title)}]], [[${escapeTwitterHandle(highlight.author)}]] ]` - highlightString += `${getParagraphTypeChar()} ${formattedHighlight}\n` - }) - if (highlightString.endsWith('\n')) { - // remove the last newline - highlightString = highlightString.substring(0, highlightString.length - 1) - } - logDebug(pluginJson, `Daily review highlights are\n\n ${highlightString}`) - } catch (error) { - logError(pluginJson, error) - } - return highlightString -} diff --git a/aaronpoweruser.ReadwiseUnofficial/src/NPReadwiseHelpers.js b/aaronpoweruser.ReadwiseUnofficial/src/NPReadwiseHelpers.js deleted file mode 100644 index 7de50a6b0..000000000 --- a/aaronpoweruser.ReadwiseUnofficial/src/NPReadwiseHelpers.js +++ /dev/null @@ -1,152 +0,0 @@ -// @flow -import { showMessage } from '../../helpers/userInput' -import pluginJson from '../plugin.json' -import { logDebug } from '@helpers/dev' - -const READWISE_API_KEY_LENGTH = 50 - -/** - * Checks if the readwise access token is valid - */ -export async function checkAccessToken(): Promise { - const accessToken = DataStore.settings.accessToken ?? '' - logDebug(pluginJson, `access token is : ${accessToken}`) - - if (accessToken === '') { - await showMessage('No access token found. Please add your Readwise access token in the plugin settings.') - } else if (accessToken.length !== READWISE_API_KEY_LENGTH) { - await showMessage('Invalid access token. Please check your Readwise access token in the plugin settings.') - } -} - -/** - * @param {*} source Readwise data as a JSON object - * @returns Note title - */ -export function buildReadwiseNoteTitle(source: any): string { - if (source.readable_title !== '') { - return removeInvalidChars(source.readable_title) - } else if (source.title !== '') { - return removeInvalidChars(source.title) - } else { - return removeInvalidChars(source.author) - } -} - -/** - * Sanitize the string by removing invalid characters - * @param {string} string - the string to sanitize - * @returns {string} - the sanitized string - */ -export function removeInvalidChars(string: string): string { - return removeNewlines( - string - .replace(/^"/, '') // remove leading double quote - .trim(), - ) -} - -/** - * Parse readwise data and generate front matter - * @param {*} source - the readwise data as a JSON object - * @returns - */ -export function buildReadwiseFrontMatter(source: any): any { - const frontMatter = {} - // $FlowIgnore[prop-missing] - intentionally setting properties dynamically as frontMatter keys are dynamic - frontMatter.author = `[[${escapeTwitterHandle(source.author)}]]` - if (source.readable_title.toLowerCase().trim() !== source.title.toLowerCase().trim()) { - // $FlowIgnore[prop-missing] - intentionally setting properties dynamically as frontMatter keys are dynamic - frontMatter.long_title = removeInvalidChars(source.title) - } - if (source.book_tags !== null && source.book_tags.length > 0) { - // $FlowIgnore[prop-missing] - intentionally setting properties dynamically as frontMatter keys are dynamic - frontMatter.tags = source.book_tags.map((tag) => `${formatTag(tag.name)}`).join(', ') - } - if (source.unique_url !== null) { - // $FlowIgnore[prop-missing] - we are intentionally setting properties dynamically - frontMatter.url = source.unique_url - } - return frontMatter -} - -/** - * Creates the metadata heading for the note - * @param {*} source - the readwise data as a JSON object - * @returns {string} - the formatted heading - */ -export function buildReadwiseMetadataHeading(source: any): string { - let metadata = `author: [[${escapeTwitterHandle(source.author)}]]\n` - if (source.book_tags !== null && source.book_tags.length > 0) { - metadata += `tags: ${source.book_tags.map((tag) => `${formatTag(tag.name)}`).join(', ')}\n` - } - if (source.unique_url !== null) { - metadata += `url: ${source.unique_url}` - } - if (source.readable_title.toLowerCase().trim() !== source.title.toLowerCase().trim()) { - metadata += `long_title: ${removeInvalidChars(source.title)}` - } - return metadata -} - -/** - * Formats the note tag using the prefix from plugin settings - * @param {string} tag - the tag to format - * @returns {string} - the formatted tag - */ -function formatTag(tag: string): string { - const prefix = DataStore.settings.tagPrefix ?? '' - if (prefix === '') { - return `#${tag}` - } else { - return `#${prefix}/${tag}` - } -} - -/** - * Remove all newline characters from a string - * @param {string} text - the text to remove newline characters from - * @returns {string} - the text with newline characters removed - */ -export function removeNewlines(text: string): string { - return text.replaceAll(/\n/g, ' ') -} - -/** - * Escapes Twitter handles by adding 'Twitter/' before the '@' symbol - * to avoid creating a mention in Noteplan - * and removing 'on Twitter' from the handle - * @param {string} handle - the Twitter handle to escape - * @returns {string} - the escaped Twitter handle - */ -export function escapeTwitterHandle(handle: string): string { - if (handle.startsWith('@') && handle.endsWith(' on Twitter')) { - return handle.replace('@', 'Twitter/@').replace(' on Twitter', '') - } - return handle -} - -/** - * Gets the date in iso format with the local timezone - * @returns {string} - the local date - */ -export function getLocalDate(): string { - const local_dateTime_in_mills = new Date().setHours(new Date().getHours() - new Date().getTimezoneOffset() / 60) - const local_dateTime = new Date(local_dateTime_in_mills).toISOString() - return local_dateTime.split('T')[0] -} - -/** - * Get the paragraph type character based on settings - * @returns {string} - the paragraph type character - */ -export function getParagraphTypeChar(): string { - const paragraphType = DataStore.settings.paragraphType ?? 'quote' - let paragraphChar = '>' - if (paragraphType === 'quote') { - paragraphChar = '>' - } else if (paragraphType === 'list') { - paragraphChar = '-' - } - return paragraphChar -} diff --git a/aaronpoweruser.ReadwiseUnofficial/src/NPReadwiseNotes.js b/aaronpoweruser.ReadwiseUnofficial/src/NPReadwiseNotes.js deleted file mode 100644 index 134836443..000000000 --- a/aaronpoweruser.ReadwiseUnofficial/src/NPReadwiseNotes.js +++ /dev/null @@ -1,104 +0,0 @@ -// @flow -import pluginJson from '../plugin.json' -import { setFrontMatterVars } from '../../helpers/NPFrontMatter' -import { findEndOfActivePartOfNote } from '../../helpers/paragraph' -import { buildReadwiseFrontMatter, buildReadwiseMetadataHeading, buildReadwiseNoteTitle, removeNewlines } from './NPReadwiseHelpers' -import { writeReadwiseSyncLogLine } from './NPReadwiseSyncLog' -import { logDebug, logError } from '@helpers/dev' -import { getOrMakeNote } from '@helpers/note' - -/** - * Gets or creates the note for the readwise data - * @param {string} title - the title of the note - * @param {string} category - the category of the note - * @returns {TNote} - the note - */ -async function getOrCreateReadwiseNote(title: string, category: string): Promise { - const rootFolder = DataStore.settings.baseFolder ?? 'Readwise' - let baseFolder = rootFolder - let outputNote: ?TNote - if (DataStore.settings.groupByType === true) { - // Note: supplementals are not guaranteed to have user generated highlights - if (DataStore.settings.ignoreSupplementals === true && category === 'supplementals') { - baseFolder = `${rootFolder}/books` - } else { - baseFolder = `${rootFolder}/${category}` - } - } - try { - outputNote = await getOrMakeNote(title, baseFolder, '') - } catch (error) { - logError(pluginJson, error) - } - return outputNote -} - -/** - * Parses the readwise data and writes it to a note - * @param {*} source - the readwise data as a JSON object - */ -export async function parseHighlightsAndWriteToNote(highlightSource: any): Promise { - try { - const noteTitle: string = buildReadwiseNoteTitle(highlightSource) - const outputNote: ?TNote = await getOrCreateReadwiseNote(noteTitle, highlightSource.category) - const useFrontMatter = DataStore.settings.useFrontMatter === 'FrontMatter' - if (outputNote) { - if (!useFrontMatter) { - //TODO: Support updating metadata (tags) - if (!outputNote?.content?.includes('## Metadata')) { - outputNote?.addParagraphBelowHeadingTitle(buildReadwiseMetadataHeading(highlightSource), 'text', 'Metadata', true, true) - } - } else { - setFrontMatterVars(outputNote, buildReadwiseFrontMatter(highlightSource)) - } - if (!outputNote?.content?.includes('# Highlights')) { - outputNote.insertHeading('Highlights', findEndOfActivePartOfNote(outputNote) + 1, 1) - } - } - if (outputNote) { - await writeReadwiseSyncLogLine(noteTitle, highlightSource.highlights.length) - await highlightSource.highlights.map((highlight) => appendHighlightToNote(outputNote, highlight, highlightSource.source, highlightSource.asin)) - } - } catch (error) { - logError(pluginJson, error) - } -} - -/** - * Appends the highlight with a link to the note - * @param {TNote} outputNote - the note to append to - * @param {*} highlight - the readwise highlight as a JSON object - * @param {string} category - the source of the highlight - * @param {string} asin - the asin of the book - */ -function appendHighlightToNote(outputNote: TNote, highlight: any, category: string, asin: string): void { - let linkToHighlightOnWeb = '' - let userNote = '' - - if (highlight.tags !== null && highlight.tags !== '') { - for (const tag of highlight.tags) { - if (tag.name !== null && tag.name !== '' && tag.name.toLowerCase().startsWith('h') && tag.name.length === 2) { - const headingLevel = parseInt(tag.name.substring(1)) + 1 - if (headingLevel <= 8) { - outputNote.insertHeading(removeNewlines(highlight.text), findEndOfActivePartOfNote(outputNote) + 1, headingLevel) - } - } - } - } - - if (highlight.note !== null && highlight.note !== '') { - userNote = `(${highlight.note})` - } - - if (DataStore.settings.showLinkToHighlight === true) { - if (category === 'supplemental') { - linkToHighlightOnWeb = ` [View highlight](${highlight.readwise_url})` - } else if (asin !== null && highlight.location !== null) { - linkToHighlightOnWeb = ` [Location ${highlight.location}](https://read.amazon.com/?asin=${asin})` - } else if (highlight.url !== null) { - linkToHighlightOnWeb = ` [View highlight](${highlight.url})` - } - } - const paragraphType = DataStore.settings.paragraphType ?? 'quote' - outputNote.appendParagraph(removeNewlines(highlight.text) + userNote + linkToHighlightOnWeb, paragraphType) -} diff --git a/aaronpoweruser.ReadwiseUnofficial/src/NPReadwiseSyncLog.js b/aaronpoweruser.ReadwiseUnofficial/src/NPReadwiseSyncLog.js deleted file mode 100644 index 7c8a89d2e..000000000 --- a/aaronpoweruser.ReadwiseUnofficial/src/NPReadwiseSyncLog.js +++ /dev/null @@ -1,32 +0,0 @@ -// @flow -import { getLocalDate } from './NPReadwiseHelpers' -import { getOrMakeNote } from '@helpers/note' - -const SYNC_LOG_TOKEN = 'readWiseToken' -const SYNC_NOTE_TITLE = 'Readwise Syncs' - -export async function startReadwiseSyncLog(): Promise { - if (DataStore.settings.writeSyncLog === true) { - const outputNote = await getOrMakeNote(SYNC_NOTE_TITLE, DataStore.settings.baseFolder) - await outputNote?.insertHeading(SYNC_LOG_TOKEN, 1, 2) - } -} - -export async function writeReadwiseSyncLogLine(title: string, count: number): Promise { - if (DataStore.settings.writeSyncLog === true) { - const outputNote = await getOrMakeNote(SYNC_NOTE_TITLE, DataStore.settings.baseFolder) - await outputNote?.addParagraphBelowHeadingTitle(`${count} highlight${count !== 1 ? 's' : ''} from [[${title}]]`, 'list', SYNC_LOG_TOKEN, true, false) - } -} - -export async function finishReadwiseSyncLog(downloadHighlightCount: number, updatedSourceCount: number): Promise { - if (DataStore.settings.writeSyncLog === true) { - const outputNote = await getOrMakeNote(SYNC_NOTE_TITLE, DataStore.settings.baseFolder, '') - const dateString = - `[[${getLocalDate()}]] ${new Date().toLocaleTimeString([], { timeStyle: 'short' })} ` + - `— synced ${downloadHighlightCount} highlight${downloadHighlightCount !== 1 ? 's' : ''} from ${updatedSourceCount} documents.` - if (outputNote) { - outputNote.content = outputNote.content?.replace(SYNC_LOG_TOKEN, dateString) - } - } -} diff --git a/aaronpoweruser.ReadwiseUnofficial/src/NPTriggers-Hooks.js b/aaronpoweruser.ReadwiseUnofficial/src/NPTriggers-Hooks.js deleted file mode 100644 index a3514a1cb..000000000 --- a/aaronpoweruser.ReadwiseUnofficial/src/NPTriggers-Hooks.js +++ /dev/null @@ -1,94 +0,0 @@ -/* eslint-disable require-await */ -// @flow - -import pluginJson from '../plugin.json' // gives you access to the contents of plugin.json -import { log, logError, logDebug, timer, clo, JSP } from '@helpers/dev' -import { updateSettingData, pluginUpdated } from '@helpers/NPConfiguration' - -/** - * NOTEPLAN PER-NOTE TRIGGERS - * - * The following functions are called by NotePlan automatically - * if a note has a triggers: section in its frontmatter - * See the documentation: https://help.noteplan.co/article/173-plugin-note-triggers - */ - -/** - * onOpen - * Plugin entrypoint for command: "/onOpen" - * Called when a note is opened and that note - * has a triggers: onOpen in its frontmatter - * @param {TNote} note - current note in Editor - */ -export async function onOpen(note: TNote): Promise { - try { - logDebug(pluginJson, `onOpen running for note:"${String(note.filename)}"`) - // Try to guard against infinite loops of opens/refreshing - // You can delete this code if you are sure that your onOpen trigger will not cause an infinite loop - // But the safest thing to do is put your code inside the if loop below to ensure it runs no more than once every 15s - const now = new Date() - if (Editor?.note?.changedDate) { - const lastEdit = new Date(Editor?.note?.changedDate) - if (now - lastEdit > 15000) { - logDebug(pluginJson, `onOpen ${timer(lastEdit)} since last edit`) - // Put your code here or call a function that does the work - } else { - logDebug(pluginJson, `onOpen: Only ${timer(lastEdit)} since last edit (hasn't been 15s)`) - } - } - } catch (error) { - logError(pluginJson, `onOpen: ${JSP(error)}`) - } -} - -/** - * onEditorWillSave - * Plugin entrypoint for command: "/onEditorWillSave" - */ -export async function onEditorWillSave() { - try { - logDebug(pluginJson, `onEditorWillSave running with note in Editor:"${String(Editor.filename)}"`) - // Put your code here or call a function that does the work - // Note: as stated in the documentation, if you want to change any content in the Editor - // before the file is written, you should NOT use the *note* variable here to change content - // Instead, use Editor.* commands (e.g. Editor.insertTextAtCursor()) or Editor.updateParagraphs() - } catch (error) { - logError(pluginJson, `onEditorWillSave: ${JSP(error)}`) - } -} - -/* - * NOTEPLAN GLOBAL PLUGIN HOOKS - * - * The rest of these functions are called by NotePlan automatically under certain conditions - * It is unlikely you will need to edit/add anything below this line - * - */ - -/** - * NotePlan calls this function after the plugin is installed or updated. - * The `updateSettingData` function looks through the new plugin settings in plugin.json and updates - * the user preferences to include any new fields - */ -export async function onUpdateOrInstall(): Promise { - log(pluginJson, 'NPThemeChooser::onUpdateOrInstall running') - await updateSettingData(pluginJson) -} - -/** - * NotePlan calls this function every time the plugin is run (any command in this plugin, including triggers) - * You should not need to edit this function. All work should be done in the commands themselves - */ -export function init(): void { - logDebug(pluginJson, `${pluginJson['plugin.id']} :: init running`) - // clo(DataStore.settings, `${pluginJson['plugin.id']} Plugin Settings`) - DataStore.installOrUpdatePluginsByID([pluginJson['plugin.id']], true, false, false).then((r) => pluginUpdated(pluginJson, r)) -} - -/** - * NotePlan calls this function settings are updated in the Preferences panel - * You should not need to edit this function - */ -export async function onSettingsUpdated(): Promise { - logDebug(pluginJson, `${pluginJson['plugin.id']} :: onSettingsUpdated running`) -} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 000000000..ad0216033 Binary files /dev/null and b/bun.lockb differ diff --git a/dbludeau.TodoistNoteplanSync/__tests__/NPPluginMain.NOTACTIVE.js b/dbludeau.TodoistNoteplanSync/__tests__/NPPluginMain.NOTACTIVE.js deleted file mode 100644 index 1935f9709..000000000 --- a/dbludeau.TodoistNoteplanSync/__tests__/NPPluginMain.NOTACTIVE.js +++ /dev/null @@ -1,130 +0,0 @@ -/* - * THIS FILE SHOULD BE RENAMED WITH ".test.js" AT THE END SO JEST WILL FIND AND RUN IT - * It is included here as an example/starting point for your own tests - */ - -/* global jest, describe, test, expect, beforeAll, afterAll, beforeEach, afterEach */ -// Jest testing docs: https://jestjs.io/docs/using-matchers -/* eslint-disable */ - -// import * as f from '../src/sortTasks' -// import { CustomConsole, LogType, LogMessage } from '@jest/console' // see note below -// import { Calendar, Clipboard, CommandBar, DataStore, Editor, NotePlan, simpleFormatter /* Note, mockWasCalledWithString, Paragraph */ } from '@mocks/index' - -// const PLUGIN_NAME = `{{pluginID}}` -// const FILENAME = `NPPluginMain` - -// beforeAll(() => { -// global.Calendar = Calendar -// global.Clipboard = Clipboard -// global.CommandBar = CommandBar -// global.DataStore = DataStore -// global.Editor = Editor -// global.NotePlan = NotePlan -// global.console = new CustomConsole(process.stdout, process.stderr, simpleFormatter) // minimize log footprint -// DataStore.settings['_logLevel'] = 'DEBUG' //change this to DEBUG to get more logging (or 'none' for none) -// }) - -/* Samples: -expect(result).toMatch(/someString/) -expect(result).not.toMatch(/someString/) -expect(result).toEqual([]) -import { mockWasCalledWith } from '@mocks/mockHelpers' - const spy = jest.spyOn(console, 'log') - const result = mainFile.getConfig() - expect(mockWasCalledWith(spy, /config was empty/)).toBe(true) - spy.mockRestore() - - test('should return the command object', () => { - const result = f.getPluginCommands({ 'plugin.commands': [{ a: 'foo' }] }) - expect(result).toEqual([{ a: 'foo' }]) - }) -*/ - -describe('Placeholder', () => { - test('Placeholder', async () => { - expect(true).toBe(true) - }) -}) - -// describe.skip('dbludeau.TodoistNoteplanSync' /* pluginID */, () => { -// describe('NPPluginMain' /* file */, () => { -// describe('sayHello' /* function */, () => { -// test('should insert text if called with a string param', async () => { -// // tests start with "should" to describe the expected behavior -// const spy = jest.spyOn(Editor, 'insertTextAtCursor') -// const result = await mainFile.sayHello('Testing...') -// expect(spy).toHaveBeenCalled() -// expect(spy).toHaveBeenNthCalledWith( -// 1, -// `***You clicked the link!*** The message at the end of the link is "Testing...". Now the rest of the plugin will run just as before...\n\n`, -// ) -// spy.mockRestore() -// }) -// test('should write result to console', async () => { -// // tests start with "should" to describe the expected behavior -// const spy = jest.spyOn(console, 'log') -// const result = await mainFile.sayHello() -// expect(spy).toHaveBeenCalled() -// expect(spy).toHaveBeenNthCalledWith(1, expect.stringMatching(/The plugin says: HELLO WORLD FROM TEST PLUGIN!/)) -// spy.mockRestore() -// }) -// test('should call DataStore.settings', async () => { -// // tests start with "should" to describe the expected behavior -// const oldValue = DataStore.settings -// DataStore.settings = { settingsString: 'settingTest' } -// const spy = jest.spyOn(Editor, 'insertTextAtCursor') -// const _ = await mainFile.sayHello() -// expect(spy).toHaveBeenCalled() -// expect(spy).toHaveBeenNthCalledWith(1, expect.stringMatching(/settingTest/)) -// DataStore.settings = oldValue -// spy.mockRestore() -// }) -// test('should call DataStore.settings if no value set', async () => { -// // tests start with "should" to describe the expected behavior -// const oldValue = DataStore.settings -// DataStore.settings = { settingsString: undefined } -// const spy = jest.spyOn(Editor, 'insertTextAtCursor') -// const _ = await mainFile.sayHello() -// expect(spy).toHaveBeenCalled() -// expect(spy).toHaveBeenNthCalledWith(1, expect.stringMatching(/\*\*\"\"\*\*/)) -// DataStore.settings = oldValue -// spy.mockRestore() -// }) -// test('should CLO write note.paragraphs to console', async () => { -// // tests start with "should" to describe the expected behavior -// const prevEditorNoteValue = copyObject(Editor.note || {}) -// Editor.note = new Note({ filename: 'testingFile' }) -// Editor.note.paragraphs = [new Paragraph({ content: 'testingParagraph' })] -// const spy = jest.spyOn(console, 'log') -// const result = await mainFile.sayHello() -// expect(spy).toHaveBeenCalled() -// expect(spy).toHaveBeenNthCalledWith(2, expect.stringMatching(/\"content\": \"testingParagraph\"/)) -// Editor.note = prevEditorNoteValue -// spy.mockRestore() -// }) -// test('should insert a link if not called with a string param', async () => { -// // tests start with "should" to describe the expected behavior -// const spy = jest.spyOn(Editor, 'insertTextAtCursor') -// const result = await mainFile.sayHello('') -// expect(spy).toHaveBeenCalled() -// expect(spy).toHaveBeenLastCalledWith(expect.stringMatching(/noteplan:\/\/x-callback-url\/runPlugin/)) -// spy.mockRestore() -// }) -// test('should write an error to console on throw', async () => { -// // tests start with "should" to describe the expected behavior -// const spy = jest.spyOn(console, 'log') -// const oldValue = Editor.insertTextAtCursor -// delete Editor.insertTextAtCursor -// try { -// const result = await mainFile.sayHello() -// } catch (e) { -// expect(e.message).stringMatching(/ERROR/) -// } -// expect(spy).toHaveBeenCalledWith(expect.stringMatching(/ERROR/)) -// Editor.insertTextAtCursor = oldValue -// spy.mockRestore() -// }) -// }) -// }) -// }) diff --git a/dbludeau.TodoistNoteplanSync/src/NPPluginMain.js b/dbludeau.TodoistNoteplanSync/src/NPPluginMain.js deleted file mode 100644 index d477eddb4..000000000 --- a/dbludeau.TodoistNoteplanSync/src/NPPluginMain.js +++ /dev/null @@ -1,722 +0,0 @@ -// @flow -// Plugin code goes in files like this. Can be one per command, or several in a file. -// `export async function [name of jsFunction called by Noteplan]` -// then include that function name as an export in the index.js file also -// About Flow: https://flow.org/en/docs/usage/#toc-write-flow-code -// Getting started with Flow in NotePlan: https://github.com/NotePlan/plugins/blob/main/Flow_Guide.md - -// NOTE: This file is named NPPluginMain.js (you could change that name and change the reference to it in index.js) -// As a matter of convention, we use NP at the beginning of files which contain calls to NotePlan APIs (Editor, DataStore, etc.) -// Because you cannot easily write tests for code that calls NotePlan APIs, we try to keep the code in the NP files as lean as possible -// and put the majority of the work in the /support folder files which have Jest tests for each function -// support/helpers is an example of a testable file that is used by the plugin command -// REMINDER, to build this plugin as you work on it: -// From the command line: -// `noteplan-cli plugin:dev dbludeau.TodoistNoteplanSync --test --watch --coverage` -// IMPORTANT: It's a good idea for you to open the settings ASAP in NotePlan Preferences > Plugins and set your plugin's logging level to DEBUG - -/** - * LOGGING - * A user will be able to set their logging level in the plugin's settings (if you used the plugin:create command) - * As a general rule, you should use logDebug (see below) for messages while you're developing. As developer, - * you will set your log level in your plugin preferences to DEBUG and you will see these messages but - * an ordinary user will not. When you want to output a message,you can use the following. - * logging level commands for different levels of messages: - * - * logDebug(pluginJson,"Only developers or people helping debug will see these messages") - * log(pluginJson,"Ordinary users will see these informational messages") - * logWarn(pluginJson,"All users will see these warning/non-fatal messages") - * logError(pluginJson,"All users will see these fatal/error messages") - */ - -import { getFrontMatterAttributes } from '../../helpers/NPFrontMatter' -import { getTodaysDateAsArrowDate, getTodaysDateUnhyphenated } from '../../helpers/dateTime' -import pluginJson from '../plugin.json' -import { log, logInfo, logDebug, logError, logWarn, clo, JSP } from '@helpers/dev' - -const todo_api: string = 'https://api.todoist.com/rest/v2' - -// set some defaults that can be changed in settings -const setup: { - token: string, - folder: string, - addDates: boolean, - addPriorities: boolean, - addTags: boolean, - teamAccount: boolean, - addUnassigned: boolean, - header: string, - newFolder: any, - newToken: any, - useTeamAccount: any, - syncDates: any, - syncPriorities: any, - syncTags: any, - syncUnassigned: any, - newHeader: any, -} = { - token: '', - folder: 'Todoist', - addDates: false, - addPriorities: false, - addTags: false, - teamAccount: false, - addUnassigned: false, - header: '', - - /** - * @param {string} passedToken - */ - set newToken(passedToken: string) { - setup.token = passedToken - }, - /** - * @param {string} passedFolder - */ - set newFolder(passedFolder: string) { - // remove leading and tailing slashes - passedFolder = passedFolder.replace(/\/+$/, "") - passedFolder = passedFolder.replace(/^\/+/, "") - this.folder = passedFolder - }, - /** - * @param {boolean} passedSyncDates - */ - set syncDates(passedSyncDates: boolean) { - setup.addDates = passedSyncDates - }, - /** - * @param {boolean} passedSyncPriorities - */ - set syncPriorities(passedSyncPriorities: true) { - setup.addPriorities = passedSyncPriorities - }, - /** - * @param {boolean} passedSyncTags - */ - set syncTags(passedSyncTags: boolean) { - setup.addTags = passedSyncTags - }, - /** - * @param {boolean} passedTeamAccount - */ - set teamAccount(passedTeamAccount: boolean) { - this.useTeamAccount = passedTeamAccount - }, - /** - * @param {boolean} passedSyncUnassigned - */ - set syncUnassigned(passedSyncUnassigned: boolean) { - this.addUnassigned = passedSyncUnassigned - }, - /** - * @param {string} passedHeader - */ - set newHeader(passedHeader: string) { - setup.header = passedHeader - }, -} - -const closed: Array = [] -const just_written: Array = [] -const existing: Array = [] -const existingHeader: { - exists: boolean, - headerExists: string | boolean, -} = { - exists: false, - /** - * @param {boolean} passedHeaderExists - */ - set headerExists(passedHeaderExists: string) { - existingHeader.exists = !!passedHeaderExists - }, -} - -/** - * Synchronizes everything. - * - * @returns {Promise} A promise that resolves once synchronization is complete. - */ -// eslint-disable-next-line require-await -export async function syncEverything() { - setSettings() - - logDebug(pluginJson, `Folder for everything notes: ${setup.folder}`) - const folders: Array = DataStore.folders.filter((f) => f.startsWith(setup.folder)) ?? [] - - // if we can't find a matching folder, create it - if (folders.length === 0) { - try { - DataStore.createFolder(setup.folder) - logDebug(pluginJson, `New folder has been created (${setup.folder})`) - } catch (error) { - logError(pluginJson, `Unable to create new folder (${setup.folder}) in Noteplan (${JSON.stringify(error)})`) - process.exit(1) - } - } - - // get the todoist projects and write out the new ones - // needs to be broken into smaller functions, but could not get it to return correctly - const projects: Array = await getTodoistProjects() - - if (projects.length > 0) { - for (let i = 0; i < projects.length; i++) { - // see if there is an existing note or create it if not - const note_info: ?Object = getExistingNote(projects[i].project_name) - if (note_info) { - //console.log(note_info.title) - const note: ?TNote = DataStore.projectNoteByFilename(note_info.filename) - //console.log(note?.filename) - if (note) { - // get the completed tasks in Noteplan and close them in Todoist - reviewExistingNoteplanTasks(note) - - // grab the tasks and write them out with sections - const id: string = projects[i].project_id - await projectSync(note, id) - } - } - - //close the tasks in Todoist if they are complete in Noteplan` - closed.forEach(async (t) => { - await closeTodoistTask(t) - }) - } - } - // completed correctly (in theory) - logDebug(pluginJson, 'Plugin completed without errors') -} - -/** - * Synchronize the current linked project. - * - * @returns {Promise} A promise that resolves once synchronization is complete - */ -// eslint-disable-next-line require-await -export async function syncProject() { - setSettings() - const note: ?TNote = Editor.note - if (note) { - // check to see if this has any frontmatter - const frontmatter: ?Object = getFrontMatterAttributes(note) - clo(frontmatter) - let check: boolean = true - if (frontmatter) { - if ('todoist_id' in frontmatter) { - logDebug(pluginJson, `Frontmatter has link to Todoist project -> ${frontmatter.todoist_id}`) - - const paragraphs: ?$ReadOnlyArray = note.paragraphs - if (paragraphs) { - paragraphs.forEach((paragraph) => { - checkParagraph(paragraph) - }) - } - - await projectSync(note, frontmatter.todoist_id) - - //close the tasks in Todoist if they are complete in Noteplan` - closed.forEach(async (t) => { - await closeTodoistTask(t) - }) - } else { - check = false - } - } else { - check = false - } - if (!check) { - logWarn(pluginJson, 'Current note has no Todoist project linked currently') - } - } -} - -/** - * Syncronize all linked projects. - * - * @returns {Promise} A promise that resolves once synchronization is complete - */ -export async function syncAllProjects() { - setSettings() - - // sync all projects - await syncThemAll() -} - -/** - * Syncronize all linked projects and today note. - * This will check for duplication between the projects and today. - * - * @returns {Promise} - */ -async function syncThemAll() { - const search_string = 'todoist_id:' - const paragraphs: ?$ReadOnlyArray = await DataStore.searchProjectNotes(search_string) - - if (paragraphs) { - for (let i = 0; i < paragraphs.length; i++) { - const filename = paragraphs[i].filename - if (filename) { - logInfo(pluginJson, `Working on note: ${filename}`) - const note: ?TNote = DataStore.projectNoteByFilename(filename) - - if (note) { - const paragraphs_to_check: $ReadOnlyArray = note?.paragraphs - if (paragraphs_to_check) { - paragraphs_to_check.forEach((paragraph_to_check) => { - checkParagraph(paragraph_to_check) - }) - } - - // get the ID - let id: string = paragraphs[i].content.split(':')[1] - id = id.trim() - - logInfo(pluginJson, `Matches up to Todoist project id: ${id}`) - await projectSync(note, id) - - //close the tasks in Todoist if they are complete in Noteplan` - closed.forEach(async (t) => { - await closeTodoistTask(t) - }) - } else { - logError(pluginJson, `Unable to open note asked requested by script (${filename})`) - } - } else { - logError(pluginJson, `Unable to find filename associated with search results`) - } - } - } else { - logInfo(pluginJson, `No results found in notes for term: todoist_id. Make sure frontmatter is set according to plugin instructions`) - } -} - -/** - * Synchronize tasks for today. - * - * @returns {Promise} A promise that resolves once synchronization is complete. - */ -// eslint-disable-next-line require-await -export async function syncToday() { - setSettings() - - // sync today tasks - await syncTodayTasks() -} - -/** - * Do the actual work of getting and syncing today tasks - * - * @returns {Promise} - */ -async function syncTodayTasks() { - console.log(existing) - // get todays date in the correct format - const date: string = getTodaysDateUnhyphenated() ?? '' - const date_string: string = getTodaysDateAsArrowDate() ?? '' - logInfo(pluginJson, `Todays date is: ${date_string}`) - - if (date) { - const note: ?TNote = DataStore?.calendarNoteByDateString(date) - if (note === null) { - logError(pluginJson, 'unable to find todays note in Noteplan') - HTMLView.showSheet(`

Unable to find daily note for ${date_string}

`, 450, 50) - process.exit(1) - } - // check to see if that heading already exists and tab what tasks already exist - const paragraphs: ?$ReadOnlyArray = note?.paragraphs - if (paragraphs) { - paragraphs.forEach((paragraph) => { - checkParagraph(paragraph) - }) - } - - logInfo(pluginJson, `Todays note was found, pulling Today Todoist tasks...`) - const response = await pullTodoistTasksForToday() - const tasks: Array = JSON.parse(response) - - if (tasks.length > 0 && note) { - for (let i = 0; i < tasks.length; i++) { - await writeOutTask(note, tasks[i]) - } - - //close the tasks in Todoist if they are complete in Noteplan` - closed.forEach(async (t) => { - await closeTodoistTask(t) - }) - } - } -} - -/** - * Get Todoist project tasks and write them out one by one - * - * @param {TNote} note - note that will be written to - * @param {string} id - Todoist project ID - * @returns {Promise} - */ -async function projectSync(note: TNote, id: string): Promise { - console.log(`ID is ${id}`) - const task_result = await pullTodoistTasksByProject(id) - console.log(task_result) - const tasks: Array = JSON.parse(task_result) - for (let j = 0; j < tasks.length; j++) { - await writeOutTask(note, tasks[j]) - } -} - -/** - * Pull todoist tasks from list matching the ID provided - * - * @param {string} project_id - the id of the Todoist project - * @returns {Promise} - promise that resolves into array of task objects or null - */ -async function pullTodoistTasksByProject(project_id: string): Promise { - if (project_id !== '') { - let filter = '' - if (setup.useTeamAccount) { - if (setup.addUnassigned) { - filter = "?filter=!assigned to: others" - } else { - filter = "?filter=assigned to: me" - } - } - const result = await fetch(`${todo_api}/tasks?project_id=${project_id}${filter}`, getRequestObject()) - return result - } - return null -} - -/** - * Pull todoist tasks with a due date of today - * - * @returns {Promise} - promise that resolves into array of task objects or null - */ -async function pullTodoistTasksForToday(): Promise { - let filter = '?filter=today' - if (setup.useTeamAccount) { - if (setup.addUnassigned) { - filter = `${filter} & !assigned to: others` - } else { - filter = `${filter} & assigned to: me` - } - } - const result = await fetch(`${todo_api}/tasks${filter}`, getRequestObject()) - if (result) { - return result - } - return null -} - -/** - * Check a paragraph for specific conditions and update the state accordingly. - * - * @param {TParagraph} paragraph - The paragraph to check. - * @returns {void} - */ -function checkParagraph(paragraph: TParagraph) { - const string: string = `### ${setup.header}` - const regex: any = new RegExp(string) - if (paragraph.rawContent.match(regex)) { - existingHeader.headerExists = true - } - - if (paragraph.type === 'done' || paragraph.type === 'cancelled') { - const content: string = paragraph.content - logDebug(pluginJson, `Done or Cancelled Task content: ${content}`) - - // close these ones in Todoist if they are closed in Noteplan and are todoist tasks - const found: ?Array = content.match(/app\/task\/(.*?)\)/) - if (found && found.length > 1) { - logInfo(pluginJson, `Todoist ID found in Noteplan note (${found[1]})`) - closed.push(found[1]) - // add to existing as well so they do not get rewritten if the timing on closing them is slow - existing.push(found[1]) - } - } else if (paragraph.type === 'open') { - const content: string = paragraph.content - logDebug(pluginJson, `Open Task content: ${content}`) - const found: ?Array = content.match(/app\/task\/(.*?)\)/) - if (found && found.length > 1) { - logInfo(pluginJson, `Todoist ID found in Noteplan note (${found[1]})`) - // check to see if it is already closed in Todoist. - fetch(`${todo_api}/tasks/${found[1]}`, getRequestObject()).then((task_info: Object) => { - const completed: boolean = task_info?.is_completed ?? false - if (completed === true) { - logDebug(pluginJson, `Going to mark this one closed in Noteplan: ${task_info.content}`) - paragraph.type = 'done' - } - }) - existing.push(found[1]) - } - } -} - -/** - * Format task details for display. - * - * @param {Object} task - The task object to format. - * @returns {string} The formatted task details. - */ -function formatTaskDetails(task: Object): string { - let task_write: string = '' - - // get the priority - let priorities: string = '' - if (setup.addPriorities) { - if (task.priority === 4) { - priorities = '!!! ' - } else if (task.priority === 3) { - priorities = '!! ' - } else if (task.priority === 2) { - priorities = '! ' - } - } - - // concat the priorities to the front of the string - task_write = `${priorities}${task.content}` - - // add the Todoist URL to the end of the string - task_write = `${task_write}[^](${task.url})` - - // add the lables after the URL - if (setup.addTags) { - task.labels.forEach((label) => { - task_write = `${task_write} #${label}` - }) - } - - // finnally add the due dates at the very end - if (setup.addDates && task.due !== null) { - task_write = `${task_write} >${task.due.date}` - } - - logDebug(pluginJson, `Formatted task details: ${task_write}`) - return task_write -} - -/** - * Parse the settings set by the user for use throughout the script. - * - * @returns {void} - */ -function setSettings() { - const settings: Object = DataStore.settings ?? {} - logDebug(pluginJson, JSON.stringify(settings)) - - if (settings) { - // required options that should kill the script if not set - if ('apiToken' in settings && settings.apiToken !== '') { - setup.newToken = settings.apiToken - } else { - logError(pluginJson, 'Missing API Token') - HTMLView.showSheet(`

Missing API token. Must be set in settings options

`, 450, 50) - process.exit(1) - } - - // optional settings - if ('folderToUse' in settings && settings.folderToUse !== '') { - setup.newFolder = settings.folderToUse - } - - if ('syncDue' in settings) { - setup.syncDates = settings.syncDue - } - - if ('syncPriorities' in settings) { - setup.syncPriorities = settings.syncPriorities - } - - if ('syncTags' in settings) { - setup.syncTags = settings.syncTags - } - - if ('teamAccount' in settings) { - setup.useTeamAccount = settings.teamAccount - } - - if ('syncUnassigned' in settings) { - setup.syncUnassigned = settings.syncUnassigned - } - - if ('headerToUse' in settings && settings.headerToUse !== '') { - setup.newHeader = settings.headerToUse - } - } -} - -/** - * Format and write task to correct noteplan note - * - * @param {TNote} note - the note object that will get the task - * @param {Object} task - the task object that will be written - */ -async function writeOutTask(note: TNote, task: Object) { - if (note) { - //console.log(note.content) - logDebug(pluginJson, task) - const formatted = formatTaskDetails(task) - if (task.section_id !== null) { - let section = await fetch(`${todo_api}/sections/${task.section_id}`, getRequestObject()) - section = JSON.parse(section) - if (section) { - if (!existing.includes(task.id) && !just_written.includes(task.id)) { - logInfo(pluginJson, `Task will be added Noteplan (${task.id})`) - note.addTodoBelowHeadingTitle(formatted, section.name, true, true) - // add to just_written so they do not get duplicated in the Today note when updating all projects and today - just_written.push(task.id) - } else { - logInfo(pluginJson, `Task is already in Noteplan ${task.id}`) - } - } else { - // this one has a section ID but Todoist will not return a name - // Put it in with no heading - logWarn(pluginJson, `Section ID ${task.section_id} did not return a section name`) - if (!existing.includes(task.id) && !just_written.includes(task.id)) { - logInfo(pluginJson, `Task will be added to Noteplan (${task.id})`) - note.appendTodo(formatted) - // add to just_written so they do not get duplicated in the Today note when updating all projects and today - just_written.push(task.id) - } else { - logInfo(pluginJson, `Task is already in Noteplan (${task.id})`) - } - } - } else { - // check for a default heading - // if there is a predefined header in settings - if (setup.header !== '') { - if (!existing.includes(task.id) && !just_written.includes(task.id)) { - logInfo(pluginJson, `Adding task from Todoist to Note`) - note?.addTodoBelowHeadingTitle(formatted, setup.header, true, true) - // add to just_written so they do not get duplicated in the Today note when updating all projects and today - just_written.push(task.id) - } - } else { - if (!existing.includes(task.id) && !just_written.includes(task.id)) { - logInfo(pluginJson, `Task will be added to Noteplan (${task.id})`) - note.appendTodo(formatted) - // add to just_written so they do not get duplicated in the Today note when updating all projects and today - just_written.push(task.id) - } - } - } - } -} - -/** - * Create the fetch parameters for a GET operation - * - * @returns {Object} - */ -function getRequestObject() { - const obj: Object = { - method: 'GET', - headers: { - Authorization: `Bearer ${setup.token}`, - 'Content-Type': 'application/json', - }, - } - return obj -} - -/** - * Create the fetch parameters for a POST operation - * - * @returns {Object} - */ -function postRequestObject() { - const obj: Object = { - method: 'POST', - headers: { - Authorization: `Bearer ${setup.token}`, - }, - } - return obj -} - -/** - * Will search Noteplan in the set folder for a note that matches the Todoist project name. - * Will create if it does not exist - * @param {string} project_name - * @return {Object} - */ -function getExistingNote(project_name: string): Object { - let filename = '' - const existing_notes = DataStore.projectNotes.filter((n) => n.filename.startsWith(`${setup.folder}/${project_name}`)) - if (existing_notes.length > 0) { - logDebug(pluginJson, `Pulling existing note matching project: ${project_name}. Note found: ${existing_notes[0].filename}`) - filename = existing_notes[0].filename - } else { - logDebug(pluginJson, `Creating note: ${project_name} in: ${setup.folder}`) - try { - filename = DataStore.newNote(project_name, setup.folder) - } catch (error) { - logError(pluginJson, `Unable to create new note (${JSON.stringify(error)}`) - } - } - return { filename: filename } -} - -/** - * Review existing tasks in Noteplan. - * - * @param {TNote} note - The note to review tasks for. - * @returns {void} - */ -function reviewExistingNoteplanTasks(note: TNote) { - // we only need to work on the ones that have a page associated with them - const paragraphs: $ReadOnlyArray = note.paragraphs ?? [] - if (paragraphs) { - paragraphs.forEach((paragraph) => { - checkParagraph(paragraph) - }) - } -} - -/** - * Get Todoist projects and synchronize tasks. - * - * @returns {Array} - */ -async function getTodoistProjects() { - const project_list = [] - const results = await fetch(`${todo_api}/projects`, getRequestObject()) - const projects: ?Array = JSON.parse(results) - if (projects) { - projects.forEach((project) => { - logDebug(pluginJson, `Project name: ${project.name} Project ID: ${project.id}`) - project_list.push({ project_name: project.name, project_id: project.id }) - }) - } - return project_list -} - -/** - * Close a Todoist task. - * - * @param {string} task_id - The ID of the task to close. - * @returns {Promise} A promise that resolves once the task is closed. - */ -async function closeTodoistTask(task_id: string) { - try { - await fetch(`${todo_api}/tasks/${task_id}/close`, postRequestObject()) - logInfo(pluginJson, `Closed task (${task_id}) in Todoist`) - } catch (error) { - logError(pluginJson, `Unable to close task (${task_id}) ${JSON.stringify(error)}`) - } -} diff --git a/dbludeau.TodoistNoteplanSync/src/NPTriggers-Hooks.js b/dbludeau.TodoistNoteplanSync/src/NPTriggers-Hooks.js deleted file mode 100644 index 304fbdc93..000000000 --- a/dbludeau.TodoistNoteplanSync/src/NPTriggers-Hooks.js +++ /dev/null @@ -1,94 +0,0 @@ -/* eslint-disable require-await */ -// @flow - -import pluginJson from '../plugin.json' // gives you access to the contents of plugin.json -import { log, logError, logDebug, timer, clo, JSP } from '@helpers/dev' -import { updateSettingData, pluginUpdated } from '@helpers/NPConfiguration' - -/** - * NOTEPLAN PER-NOTE TRIGGERS - * - * The following functions are called by NotePlan automatically - * if a note has a triggers: section in its frontmatter - * See the documentation: https://help.noteplan.co/article/173-plugin-note-triggers - */ - -/** - * onOpen - * Plugin entrypoint for command: "/onOpen" - * Called when a note is opened and that note - * has a triggers: onOpen in its frontmatter - * @param {TNote} note - current note in Editor - */ -export async function onOpen(note: TNote): Promise { - try { - logDebug(pluginJson, `onOpen running for note:"${String(note.filename)}"`) - // Try to guard against infinite loops of opens/refreshing - // You can delete this code if you are sure that your onOpen trigger will not cause an infinite loop - // But the safest thing to do is put your code inside the if loop below to ensure it runs no more than once every 15s - const now = new Date() - if (Editor?.note?.changedDate) { - const lastEdit = new Date(Editor?.note?.changedDate) - if (now.getTime() - lastEdit.getTime() > 15000) { - logDebug(pluginJson, `onOpen ${timer(lastEdit)} since last edit`) - // Put your code here or call a function that does the work - } else { - logDebug(pluginJson, `onOpen: Only ${timer(lastEdit)} since last edit (hasn't been 15s)`) - } - } - } catch (error) { - logError(pluginJson, `onOpen: ${JSP(error)}`) - } -} - -/** - * onEditorWillSave - * Plugin entrypoint for command: "/onEditorWillSave" - */ -export async function onEditorWillSave() { - try { - logDebug(pluginJson, `onEditorWillSave running with note in Editor:"${String(Editor.filename)}"`) - // Put your code here or call a function that does the work - // Note: as stated in the documentation, if you want to change any content in the Editor - // before the file is written, you should NOT use the *note* variable here to change content - // Instead, use Editor.* commands (e.g. Editor.insertTextAtCursor()) or Editor.updateParagraphs() - } catch (error) { - logError(pluginJson, `onEditorWillSave: ${JSP(error)}`) - } -} - -/* - * NOTEPLAN GLOBAL PLUGIN HOOKS - * - * The rest of these functions are called by NotePlan automatically under certain conditions - * It is unlikely you will need to edit/add anything below this line - * - */ - -/** - * NotePlan calls this function after the plugin is installed or updated. - * The `updateSettingData` function looks through the new plugin settings in plugin.json and updates - * the user preferences to include any new fields - */ -export async function onUpdateOrInstall(): Promise { - log(pluginJson, 'NPThemeChooser::onUpdateOrInstall running') - await updateSettingData(pluginJson) -} - -/** - * NotePlan calls this function every time the plugin is run (any command in this plugin, including triggers) - * You should not need to edit this function. All work should be done in the commands themselves - */ -export function init(): void { - logDebug(pluginJson, `${pluginJson['plugin.id']} :: init running`) - // clo(DataStore.settings, `${pluginJson['plugin.id']} Plugin Settings`) - DataStore.installOrUpdatePluginsByID([pluginJson['plugin.id']], true, false, false).then((r) => pluginUpdated(pluginJson, r)) -} - -/** - * NotePlan calls this function settings are updated in the Preferences panel - * You should not need to edit this function - */ -export async function onSettingsUpdated(): Promise { - logDebug(pluginJson, `${pluginJson['plugin.id']} :: onSettingsUpdated running`) -} diff --git a/dbludeau.TodoistNoteplanSync/src/index.js b/dbludeau.TodoistNoteplanSync/src/index.js deleted file mode 100644 index 461e45b94..000000000 --- a/dbludeau.TodoistNoteplanSync/src/index.js +++ /dev/null @@ -1,34 +0,0 @@ -// @flow -// Flow typing is important for reducing errors and improving the quality of the code. -// About Flow: https://flow.org/en/docs/usage/#toc-write-flow-code -// Getting started with Flow in NotePlan: https://github.com/NotePlan/plugins/blob/main/Flow_Guide.md -// Note: As you will see in this plugin folder, you can have multiple files -- e.g. one file per command or logical group of commands -// ...and separate files for helper/support functions that can be tested in isolation -// The `autowatch` packager combines them all into one script.js file for NotePlan to read -// From the command line: -// `noteplan-cli plugin:dev {{pluginId}} --test --watch --coverage` -// ...will watch for changes and will compile the Plugin script code -// and copy it to your plugins directory where NotePlan can find it -// Since NP reloads the Javascript every time you CMD-J to insert a plugin, -// you can immediately test the new code without restarting NotePlan -// This index.js file is where the packager starts looking for files to combine into one script.js file -// So you need to add a line below for each function that you want NP to have access to. -// Typically, listed below are only the top-level plug-in functions listed in plugin.json - -export { syncToday, syncEverything, syncProject, syncAllProjects, syncAllProjectsAndToday } from './NPPluginMain' - -// FETCH mocking for offline testing -// If you want to use external server calls in your plugin, it can be useful to mock the server responses -// while you are developing the plugin. This allows you to test the plugin without having to -// have a server running or having to have a network connection (or wait/pay for the server calls) -// Comment the following import line out if you want to use live fetch/server endpoints (normal operation) -// Uncomment it for using server mocks (fake/canned responses) you define in support/fetchOverrides.js -// import './support/fetchOverrides' - -/** - * Other imports/exports - you will normally not need to edit these - */ -// eslint-disable-next-line import/order -export { editSettings } from '@helpers/NPSettings' -export { onUpdateOrInstall, init, onSettingsUpdated } from './NPTriggers-Hooks' -export { onOpen, onEditorWillSave } from './NPTriggers-Hooks' diff --git a/dbludeau.TodoistNoteplanSync/src/support/fetchOverrides.js b/dbludeau.TodoistNoteplanSync/src/support/fetchOverrides.js deleted file mode 100644 index e3a8ec141..000000000 --- a/dbludeau.TodoistNoteplanSync/src/support/fetchOverrides.js +++ /dev/null @@ -1,57 +0,0 @@ -// @flow - -// This file is only loaded and fetch is overridden if the import is enabled in the index file - -/** - * FETCH MOCKING - * This file is used to override the fetch function (calls to an external server) with a fake response - * This allows you to test your plugin without having to have a server running or having to have a network connection - * or wait/pay for the server calls - * You can define your fake responses in this file or in a separate file (see below) - * ...and when your code makes a fetch call to a server, it will get (an appropriate) fake response instead - * You define the responses and the text that must be in the fetch call to yield a particular response - * (see the mockResponses array below) - */ - -/** - * 1) Import any of your fake responses that are saved as files here (or see below for defining them as strings) - * The file should contain the exact response that the live server would return - * You can save the response as a JSON file (like sampleFileResponse below) or as a string (like sampleTextResponse below) - */ -import sampleFileResponse from './fetchResponses/google.search-for-something.json' // a sample fake response saved as a JSON file - -// Other necessary imports -import { FetchMock, type FetchMockResponse } from '@mocks/Fetch.mock' -import { logDebug } from '@helpers/dev' - -/** - * 2) Or you can define your fake responses as strings in this file: - */ -// You could also just put all the fake responses here in this file -// A little messier, but if you don't have very many responses, or they are small/strings, it's fine -const sampleTextWeatherResponse = `Nuremberg: ☀️ +9°F` - -// 3) So the mock knows when to send back which response, you need to define the match and response for each mock response -// Fill in the match and response for each mock response you want to use -// The match object hast following properties: -// url: string - the url to match (can be a partial string, or can even be a string that includes regex) -// optionsBody: string - a partial string or string/regex included in the POST body of the request to match (sent in options.body to fetch) -// optionsBody is optional. If you don't need to match on the POST body (matching URL is enough), just leave it out -// The response MUST BE A STRING. So either use a string response (like sampleTextWeatherResponse above) or -// JSON.stringify your response object (like sampleFileResponse above) -const mockResponses: Array = [ - // the first mock below will match a POST request to google.com with the words "search for something" in the POST body - { match: { url: 'google.com', optionsBody: 'search for something' }, response: JSON.stringify(sampleFileResponse) }, - // the mock below will match any GET or POST request to "wttr.in/Nuremberg?format=3" regardless of the body - { match: { url: 'wttr.in/Nuremberg?format=3' }, response: sampleTextWeatherResponse }, -] - -/** - * DO NOT TOUCH ANYTHING BELOW THIS LINE - */ - -const fm = new FetchMock(mockResponses) // add one object to array for each mock response -fetch = async (url, opts) => { - logDebug(`fetchOverrides.js`, `FetchMock faking response from: "${url}" (turn on/off in index.js)`) - return await fm.fetch(url, opts) -} //override the global fetch diff --git a/deleteme.testPluginDownload/src/index.js b/deleteme.testPluginDownload/src/index.js deleted file mode 100644 index f800bcf2c..000000000 --- a/deleteme.testPluginDownload/src/index.js +++ /dev/null @@ -1,29 +0,0 @@ -// @flow -import pluginJson from '../plugin.json' // gives you access to the contents of plugin.json -import { updateSettingData, pluginUpdated } from '@helpers/NPConfiguration' -import { log, logError, logDebug, timer, clo, JSP } from '@helpers/dev' - -/** - * NotePlan calls this function after the plugin is installed or updated. - * The `updateSettingData` function looks through the new plugin settings in plugin.json and updates - * the user preferences to include any new fields - */ -export async function onUpdateOrInstall(): Promise { - try { - logDebug(pluginJson, `${pluginJson['plugin.id']} :: onUpdateOrInstall running`) - await updateSettingData(pluginJson) - } catch (error) { - logError(pluginJson, `onUpdateOrInstall: ${JSP(error)}`) - } -} - -export async function runOnInstallOrUpdate(): Promise { - try { - logDebug(pluginJson, `runOnInstallOrUpdate running`) - // test as after the plugin is installed or updated. the following command updates the plugin's settings data - const r = { code: 1 /* updated */, message: 'plugin updated message' } - await pluginUpdated(pluginJson, r) - } catch (error) { - logError(pluginJson, `runOnInstallOrUpdate: ${JSP(error)}`) - } -} diff --git a/dwertheimer.DateAutomations/src/dateFunctions.js b/dwertheimer.DateAutomations/src/dateFunctions.js deleted file mode 100644 index 0d952ea54..000000000 --- a/dwertheimer.DateAutomations/src/dateFunctions.js +++ /dev/null @@ -1,239 +0,0 @@ -// @noflow -// TODO: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat - -import strftime from 'strftime' -import { format as dateFormat, startOfWeek, endOfWeek } from 'date-fns' -import { hyphenatedDateString, getFormattedTime } from '../../helpers/dateTime' -import { getTagParamsFromString } from '../../helpers/general' -import { showMessage } from '../../helpers/userInput' - -type DateConfig = $ReadOnly<{ - timezone: string, - locale: string, - dateStyle?: string, - timeStyle?: string, - hour12?: boolean, - format?: string, - ... -}> -// This is a function that verifies that an object is of the type -// DateConfig. If it is, it returns an object with the correct type -// If it's not, it returns undefined. -function asDateConfig(obj: mixed): ?DateConfig { - if (typeof obj === 'object' && obj != null && typeof obj.timezone === 'string' && typeof obj.locale === 'string') { - const { format, timezone, locale, dateStyle, timeStyle, hour12, ...other } = obj - return { - ...other, - timezone, - locale, - format: typeof format === 'string' ? format : undefined, - dateStyle: typeof dateStyle === 'string' ? dateStyle : undefined, - timeStyle: typeof timeStyle === 'string' ? timeStyle : undefined, - hour12: typeof hour12 === 'boolean' ? hour12 : undefined, - } - } -} - -function getDateConfig(): DateConfig { - const config = DataStore.settings - const dateConfig = asDateConfig(config) - if (dateConfig) { - return dateConfig - } else { - return { - // Default timezone for date and time. - timezone: 'automatic', - // Default locale to format date and time. - // e.g. en-US will result in mm/dd/yyyy, while en_GB will be dd/mm/yyyy - locale: 'en-US', - // can be "short", "medium", "long" or "full" - dateStyle: 'full', - // optional key, can be "short", "medium", "long" or "full" - timeStyle: 'short', - // can force 24 hour time format, even in america! - hour12: true, - // custom format using strftime - format: '%Y-%m-%d %I:%M:%S %P', - } - } -} - -/** - * Create a list of options for combinations of date & time formats - * @returns [{allDateOptions}] props: dateStyle, timeStyle, label, text (to be inserted if chosen) - */ -function getFormattedDateTime() { - // pull options and create options for various dateStyles and timeStyles - const dateConfig = getDateConfig() - const dateStyles = ['short', 'medium', 'long'] // pulling out 'full' for now - const timeStyles = ['', 'short', 'medium', 'long'] // pulling out 'full' for now - const hour12 = [false, true] - - const format = dateConfig?.format ? dateConfig.format : '%Y-%m-%d %I:%M:%S %P' - - // Pluck all values except `dateStyle` and `timeStyle` - // eslint-disable-next-line no-unused-vars - const { dateStyle: _1, timeStyle: _2, ...config } = { ...dateConfig } - - // Get user default locale - const locales = [] - locales.push((dateConfig && dateConfig.locale) || 'en-US') - // if (dateConfig.locale !== 'sv-SE') locales.push('sv-SE') - const str8601 = get8601String() - - const formatted = strftime(format) - - const options = [ - { - dateStyle: 'formatted', - timeStyle: '', - label: `${formatted} (formatted date/time)`, - text: formatted, - }, - { - dateStyle: 'sv-SE', - timeStyle: 'medium', - label: `${str8601} (sv-SE,short,medium,[not set])`, - text: `${str8601}`, - }, - ] - locales.forEach((loc) => { - dateStyles.forEach((ds) => - timeStyles.forEach((ts) => { - hour12.forEach((h12) => { - // conditionall add those keys to config - if (ds !== '') { - // Ignore type error for now - // $FlowFixMe - config.dateStyle = ds - } - if (ts !== '') { - // $FlowFixMe - config.timeStyle = ts - } - config.hour12 = h12 - - const text = new Intl.DateTimeFormat( - loc, - // $FlowFixMe - config, - ).format() - - options.push({ - dateStyle: ds !== '' ? ds : null, - timeStyle: ts !== '' ? ds : null, - label: `${text} (${loc}/${ds ? ds : '[not set]'}/${ts ? ts : '[not-set]'}/${String(h12)})`, - text: `${text}`, - }) - }) - }), - ) - }) - - return options -} - -// /iso -export function insertISODate() { - const nowISO = new Date().toISOString() - Editor.insertTextAtCursor(nowISO) -} - -// /date -export function insertDate() { - // eslint-disable-next-line no-unused-vars - const { timeStyle: _, ...dateConfig } = getDateConfig() - const dateText = new Intl.DateTimeFormat(dateConfig.locale, dateConfig).format() - Editor.insertTextAtCursor(dateText) -} - -// /now -export function insertDateTime() { - const _dateConfig = getDateConfig() - const dateConfig = { - ..._dateConfig, - dateStyle: _dateConfig.dateStyle ?? 'full', - timeStyle: _dateConfig.timeStyle ?? 'short', - } - const dateText = new Intl.DateTimeFormat(dateConfig.locale, dateConfig).format() - Editor.insertTextAtCursor(`${dateText}`) -} - -export function get8601String(): string { - return strftime(`%Y-%m-%d`) -} - -// /now -export function insertDateTime8601() { - Editor.insertTextAtCursor(`${strftime(`%Y-%m-%d %H:%M`)}`) -} - -// /time -export function insertTime() { - // eslint-disable-next-line no-unused-vars - const { dateStyle: _, ...dateConfig } = getDateConfig() - const editableConfig = { ...dateConfig } - if (!editableConfig.timeStyle) editableConfig.timeStyle = 'medium' - - const timeText = new Intl.DateTimeFormat(dateConfig.locale, editableConfig).format() - Editor.insertTextAtCursor(timeText) -} - -// /ldn -export function insertCalendarNoteLink() { - Editor.insertTextAtCursor(`[[${hyphenatedDateString(new Date())}]]`) -} - -// /dp -export async function dateFormatPicker() { - const dateChoices = getFormattedDateTime() - - const re = await CommandBar.showOptions( - dateChoices.map((d) => d.label), - 'Choose format (locale/dateStyle/timeStyle/hour12)', - ) - Editor.insertTextAtCursor(dateChoices[re.index].text) -} - -// /formatted -export function insertStrftime() { - const dateConfig = DataStore.settings - const format = dateConfig?.format ? dateConfig.format : '%Y-%m-%d %I:%M:%S %P' - const strftimeFormatted = strftime(format) - Editor.insertTextAtCursor(strftimeFormatted) -} - -export async function formattedDateTimeTemplate(paramStr: string = ''): Promise { - let retVal = '' - if (paramStr === '') { - retVal = getFormattedTime() // default - } else { - const format = await getTagParamsFromString(paramStr, 'format', '') - retVal = getFormattedTime(format ? String(format) : undefined) - } - return retVal -} - -//TODO FIXME: figure out formats and locales - WIP because the startMondy doesn't work -// {weekStartsOn:1, format:`'EEE yyyy-MM-dd'} // see [date-fns format](https://date-fns.org/v2.23.0/docs/format) -export async function getWeekDates(paramStr: string = ''): Promise { - const weekStartsOn = Number(await getTagParamsFromString(paramStr, 'weekStartsOn', 1)) - const format = String(await getTagParamsFromString(paramStr, 'format', 'EEE yyyy-MM-dd')) - // $FlowFixme complains about number literals even though I am checking them as numbers in arange - if (weekStartsOn >= 0 && weekStartsOn <= 6) { - // $FlowIgnore - console.log(dateFormat(new Date(startOfWeek(new Date(), { weekStartsOn: weekStartsOn })), 'yyyy-MM-dd')) - // $FlowIgnore - const start = dateFormat(new Date(startOfWeek(new Date(), { weekStartsOn: weekStartsOn })), format) - // $FlowIgnore - const end = dateFormat(new Date(endOfWeek(new Date(), { weekStartsOn: weekStartsOn })), format) - return `${start} - ${end}` - } else { - showMessage('Error in your format string') - return '' - } -} - -export async function insertWeekDates() { - await Editor.insertTextAtCursor(await getWeekDates()) -} diff --git a/dwertheimer.DateAutomations/src/index.js b/dwertheimer.DateAutomations/src/index.js deleted file mode 100644 index 225a45410..000000000 --- a/dwertheimer.DateAutomations/src/index.js +++ /dev/null @@ -1,27 +0,0 @@ -// @flow -import pluginJson from '../plugin.json' -import { updateSettingData } from '../../helpers/NPConfiguration' - -export { insertDate } from './dateFunctions' -export { insertDateTime } from './dateFunctions' -export { insertDateTime8601 } from './dateFunctions' -export { insertISODate } from './dateFunctions' -export { insertCalendarNoteLink } from './dateFunctions' -export { insertTime } from './dateFunctions' -export { dateFormatPicker } from './dateFunctions' -export { insertStrftime } from './dateFunctions' -export { insertWeekDates } from './dateFunctions' - -export { get8601String, getWeekDates } from './dateFunctions' - -const PLUGIN_ID = 'date' // the key that's used in _configuration note -export async function onUpdateOrInstall(config: any = { silent: false }): Promise { - try { - console.log(`${PLUGIN_ID}: onUpdateOrInstall running`) - const updateSettings = updateSettingData(pluginJson) - console.log(`${PLUGIN_ID}: onUpdateOrInstall updateSettingData code: ${updateSettings}`) - } catch (error) { - console.log(error) - } - console.log(`${PLUGIN_ID}: onUpdateOrInstall finished`) -} diff --git a/dwertheimer.EventAutomations/__tests__/NPTimeblocking.Integration.test.js b/dwertheimer.EventAutomations/__tests__/NPTimeblocking.Integration.test.js deleted file mode 100644 index a20041137..000000000 --- a/dwertheimer.EventAutomations/__tests__/NPTimeblocking.Integration.test.js +++ /dev/null @@ -1,218 +0,0 @@ -// @flow -// Jest testing docs: https://jestjs.io/docs/using-matchers -/* global describe, test, jest, expect, beforeAll */ - -// Note: expect(spy).toHaveBeenNthCalledWith(2, expect.stringMatching(/ERROR/)) - -import moment from 'moment' -import * as mainFile from '../src/NPTimeblocking' -import * as configFile from '../src/config' -import { filenameDateString, unhyphenatedDate } from '@helpers/dateTime' - -import { Calendar, Clipboard, CommandBar, DataStore, Editor, NotePlan, Note, Paragraph /*, mockWasCalledWithString */ } from '@mocks/index' - -beforeAll(() => { - global.Calendar = Calendar - global.Clipboard = Clipboard - global.CommandBar = CommandBar - global.DataStore = DataStore - global.Editor = Editor - global.NotePlan = NotePlan - global.console = { - log: jest.fn(), - // eslint-disable-next-line no-console - debug: console.debug, //these will pass through - // eslint-disable-next-line no-console - trace: console.trace, - // map other methods that you want to use like console.table - } - DataStore.settings['_logLevel'] = 'none' // change to DEBUG to see more output - DataStore.preference = () => '🕑' // 'timeblockTextMustContainString' -}) - -const ISOToday = moment().format('YYYY-MM-DD') -const filenameToday = `${filenameDateString(new Date())}.md` - -const paragraphs = [new Paragraph({ content: 'line1' }), new Paragraph({ content: 'line2' })] -const note = new Note({ paragraphs }) -note.filename = `${unhyphenatedDate(new Date())}.md` -Editor.note = note -Editor.filename = note.filename - -describe('dwertheimer.EventAutomations' /* pluginID */, () => { - /* - * insertTodosAsTimeblocks() - */ - describe('insertTodosAsTimeblocks() Integration Test' /* function */, () => { - // INTEGRATION TEST - test('should pe', async () => { - const oldSettings = DataStore.settings - let config = configFile.getTimeBlockingDefaults() - config = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 5, - defaultDuration: 5, - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '🕑', - includeLinks: 'OFF', - _logLevel: 'DEBUG', - } - const editorNote = new Note({ - title: ISOToday, - filename: filenameToday, - type: 'Calendar', - paragraphs: [ - { - content: '---', - rawContent: '---', - type: 'separator', - heading: '', - headingLevel: -1, - lineIndex: 0, - isRecurring: false, - indents: 0, - noteType: 'Calendar', - }, - { - content: 'triggers: onEditorWillSave => dwertheimer.EventAutomations.onEditorWillSave', - rawContent: 'triggers: onEditorWillSave => dwertheimer.EventAutomations.onEditorWillSave', - type: 'text', - heading: '', - headingLevel: -1, - lineIndex: 1, - isRecurring: false, - indents: 0, - noteType: 'Calendar', - }, - { - content: '---', - rawContent: '---', - type: 'separator', - heading: '', - headingLevel: -1, - lineIndex: 2, - isRecurring: false, - indents: 0, - noteType: 'Calendar', - }, - { - content: '', - rawContent: '', - type: 'empty', - heading: '', - headingLevel: -1, - lineIndex: 3, - isRecurring: false, - indents: 0, - noteType: 'Calendar', - }, - { - content: 'Do something #home ', - rawContent: '* Do something #home ', - type: 'open', - heading: '', - headingLevel: -1, - lineIndex: 4, - isRecurring: false, - indents: 0, - noteType: 'Calendar', - }, - { - content: '5-6:30pm #home 🕑', - rawContent: '+ 5-6:30pm #home 🕑', - type: 'checklist', - heading: '', - headingLevel: -1, - lineIndex: 5, - isRecurring: false, - indents: 0, - noteType: 'Calendar', - }, - { - content: '', - rawContent: '', - type: 'empty', - heading: '', - headingLevel: -1, - lineIndex: 6, - isRecurring: false, - indents: 0, - noteType: 'Calendar', - }, - ], - }) - global.Editor = { ...global.Editor, ...editorNote } - DataStore.settings = config - global.Editor.note = global.Editor - const backlinksNote = new Note({ - type: 'note', - content: '20230516.md', - rawContent: '20230516.md', - prefix: '', - lineIndex: 0, - date: '2023-05-16T07:00:00.000Z', - heading: '', - headingLevel: 0, - isRecurring: false, - indents: 0, - filename: '20230516.md', - noteType: 'Calendar', - linkedNoteTitles: [], - subItems: [ - { - type: 'title', - content: '>⭐️ Tasks<', - rawContent: '# >⭐️ Tasks<', - prefix: '# ', - contentRange: {}, - lineIndex: 6, - date: `${ISOToday}T07:00:00.000Z`, - heading: '', - headingLevel: 0, - isRecurring: false, - indents: 0, - filename: '20230516.md', - noteType: 'Calendar', - linkedNoteTitles: [], - subItems: [], - referencedBlocks: [], - note: {}, - }, - { - type: 'open', - content: `Make bible video samples >${ISOToday} ^0v7523`, - blockId: '^0v7523', - rawContent: `* Make bible video samples >${ISOToday} ^0v7523`, - prefix: '* ', - contentRange: {}, - lineIndex: 7, - date: `${ISOToday}T07:00:00.000Z`, - heading: '>⭐️ Tasks<', - headingRange: {}, - headingLevel: 2, - isRecurring: false, - indents: 0, - filename: '20230516.md', - noteType: 'Calendar', - linkedNoteTitles: [], - subItems: [], - referencedBlocks: [], - note: {}, - }, - ], - referencedBlocks: [], - }) - backlinksNote.note = backlinksNote - global.Editor.note.backlinks = [backlinksNote] - // const spy = jest.spyOn(global.Editor, 'insertParagraph') - await mainFile.insertTodosAsTimeblocks() - // insertParagraph doesn't work properly because it happens in the mock - // $FlowIgnore - jest doesn't know about this param - // expect(spy.mock.lastCall[1]).toEqual(`No todos/references marked for this day!`) - // spy.mockRestore() - DataStore.settings = oldSettings - }) - }) -}) diff --git a/dwertheimer.EventAutomations/__tests__/NPTimeblocking.test.js b/dwertheimer.EventAutomations/__tests__/NPTimeblocking.test.js deleted file mode 100644 index d66e1a154..000000000 --- a/dwertheimer.EventAutomations/__tests__/NPTimeblocking.test.js +++ /dev/null @@ -1,141 +0,0 @@ -// @flow -// Jest testing docs: https://jestjs.io/docs/using-matchers -/* global describe, test, jest, expect, beforeAll */ - -// Note: expect(spy).toHaveBeenNthCalledWith(2, expect.stringMatching(/ERROR/)) - -import * as mainFile from '../src/NPTimeblocking' -import * as timeBlockingShared from '../src/timeblocking-shared' -import * as configFile from '../src/config' - -import { Calendar, Clipboard, CommandBar, DataStore, Editor, NotePlan, Note, Paragraph, mockWasCalledWithString } from '@mocks/index' -import { unhyphenatedDate } from '@helpers/dateTime' - -beforeAll(() => { - global.Calendar = Calendar - global.Clipboard = Clipboard - global.CommandBar = CommandBar - global.DataStore = DataStore - global.Editor = Editor - global.NotePlan = NotePlan - global.console = { - log: jest.fn(), - // eslint-disable-next-line no-console - debug: console.debug, //these will pass through - // eslint-disable-next-line no-console - trace: console.trace, - // map other methods that you want to use like console.table - } - DataStore.settings['_logLevel'] = 'none' // change to DEBUG to see more output -}) - -const paragraphs = [new Paragraph({ content: 'line1' }), new Paragraph({ content: 'line2' })] -const note = new Note({ paragraphs }) -note.filename = `${unhyphenatedDate(new Date())}.md` -Editor.note = note -Editor.filename = note.filename - -describe('dwertheimer.EventAutomations' /* pluginID */, () => { - describe('NPTimeblocking.js' /* file */, () => { - /* - * getConfig() - */ - describe('getConfig()' /* function */, () => { - // test('should XXX', () => { - // const result = mainFile.getConfig() - // expect(result).toEqual(true) - // }) - test('should return default config if getting config fails', () => { - const result = timeBlockingShared.getConfig() - expect(Object.keys(result).length).toBeGreaterThan(1) - }) - test('should return default config if no settings set', () => { - const oldSettings = DataStore.settings - DataStore.settings = undefined - const spy = jest.spyOn(console, 'log') - const result = timeBlockingShared.getConfig() - expect(mockWasCalledWithString(spy, /config was empty/)).toBe(true) - expect(Object.keys(result).length).toBeGreaterThan(1) - spy.mockRestore() - DataStore.settings = oldSettings - }) - test('should return default config', () => { - const result = timeBlockingShared.getConfig() - expect(Object.keys(result).length).toBeGreaterThan(1) - }) - test.skip('should complain about improper config', () => { - //skipping for console noise - const oldSettings = { ...DataStore.settings } - DataStore.settings = { improper: 'key', __logLevel: 'DEBUG' } - const spy = jest.spyOn(console, 'log') - timeBlockingShared.getConfig() - expect(mockWasCalledWithString(spy, /Running with default settings/)).toBe(true) - spy.mockRestore() - DataStore.settings = oldSettings - }) - test('should return a proper config', () => { - const oldSettings = DataStore.settings - DataStore.settings = configFile.getTimeBlockingDefaults() - const c = timeBlockingShared.getConfig() - expect(c).toEqual(DataStore.settings) - DataStore.settings = oldSettings - }) - }) - /* - * editorIsOpenToToday() - */ - describe('editorIsOpenToToday()' /* function */, () => { - /* template: - test('should XXX', () => { - const result = mainFile.editorIsOpenToToday() - expect(result).toEqual(true) - }) - */ - test('should return false if filename is null', () => { - Editor.filename = null - const result = mainFile.editorIsOpenToToday() - expect(result).toEqual(false) - }) - test('should return false if Editor is open to another day', () => { - Editor.filename = `${unhyphenatedDate(new Date('2020-01-01'))}.md` - const result = mainFile.editorIsOpenToToday() - expect(result).toEqual(false) - }) - test('should return true if Editor is open to is today', () => { - Editor.filename = `${unhyphenatedDate(new Date())}.md` - const result = mainFile.editorIsOpenToToday() - expect(result).toEqual(true) - }) - }) - - /* - * insertTodosAsTimeblocks() - */ - describe('insertTodosAsTimeblocks()' /* function */, () => { - test.skip('should tell user there was a problem with config', async () => { - const spy = jest.spyOn(CommandBar, 'prompt') - await mainFile.insertTodosAsTimeblocks() - expect(spy.mock.calls[0][1]).toMatch(/Plugin Settings Error/) - spy.mockRestore() - }) - test.skip('should do nothing if there are no backlinks', async () => { - // DataStore.settings = {} //should get default settings - Editor.note.backlinks = [] - const spy = jest.spyOn(CommandBar, 'prompt') - await mainFile.insertTodosAsTimeblocks() - // $FlowIgnore - jest doesn't know about this param - expect(mockWasCalledWithString(spy, /No todos\/references marked for >today/)).toBe(true) - spy.mockRestore() - }) - // [WIP] - test.skip('should do something if there are backlinks', async () => { - Editor.note.backlinks = [{ subItems: [{ content: 'line1' }] }] - const spy = jest.spyOn(CommandBar, 'prompt') - await mainFile.insertTodosAsTimeblocks() - // $FlowIgnore - jest doesn't know about this param - expect(spy.mock.lastCall[1]).toEqual(`No todos/references marked for >today`) - spy.mockRestore() - }) - }) - }) -}) diff --git a/dwertheimer.EventAutomations/__tests__/timeblocking-helpers.test.js b/dwertheimer.EventAutomations/__tests__/timeblocking-helpers.test.js deleted file mode 100644 index e7d2b8e0a..000000000 --- a/dwertheimer.EventAutomations/__tests__/timeblocking-helpers.test.js +++ /dev/null @@ -1,1214 +0,0 @@ -/* globals describe, expect, test, beforeAll, afterAll */ -// import colors from 'chalk' -// import /* differenceInCalendarDays, endOfDay, startOfDay, eachMinuteOfInterval, */ 'date-fns' -import * as tb from '../src/timeblocking-helpers' -import * as byTagMode from '../src/byTagMode' -import { getTasksByType } from '@helpers/sorting' - -import { JSP } from '@helpers/dev' - -import { Calendar, Clipboard, CommandBar, DataStore, Editor, NotePlan, Note /* Paragraph */ } from '@mocks/index' - -beforeAll(() => { - global.Calendar = Calendar - global.Clipboard = Clipboard - global.CommandBar = CommandBar - global.DataStore = DataStore - global.Editor = Editor - global.NotePlan = NotePlan - DataStore.settings['_logLevel'] = 'none' //change this to DEBUG to get more logging -}) - -const PLUGIN_NAME = `dwertheimer.EventAutomations` -// const section = colors.blue - -const config = { - todoChar: '*' /* character at the front of a timeblock line - can be *,-,or a heading, e.g. #### */, - timeBlockTag: `#🕑` /* placed at the end of the timeblock to show it was created by this plugin */, - timeBlockHeading: 'Time Blocks' /* if this heading exists in the note, timeblocks will be placed under it */, - workDayStart: '08:00' /* needs to be in 24 hour format (two digits, leading zero) */, - workDayEnd: '18:00' /* needs to be in 24 hour format (two digits, leading zero) */, - durationMarker: "'" /* signifies how long a task is, e.g. apostrophe: '2h5m or use another character, e.g. tilde: ~2h5m */, - intervalMins: 5 /* inverval on which to calculate time blocks */, - removeDuration: true /* remove duration when creating timeblock text */, - nowStrOverride: '00:00' /* for testing */, - defaultDuration: 10 /* default duration of a task that has no duration/end time */, - includeLinks: '[[internal#links]]', -} - -// import { isNullableTypeAnnotation } from '@babel/types' - -// Jest suite -describe(`${PLUGIN_NAME}`, () => { - describe('timeblocking-helpers.js', () => { - describe('createIntervalMap ', () => { - test('should create timeMap of 5min intervals all day ', () => { - const result = tb.createIntervalMap({ start: new Date('2020-01-01 08:00:00'), end: new Date('2020-01-01 24:00:00') }, 'isSet', { - step: 5, - }) - expect(result[0]).toEqual({ start: '08:00', busy: 'isSet', index: 0 }) - expect(result[191]).toEqual({ start: '23:55', busy: 'isSet', index: 191 }) - expect(result.length).toEqual(193) - }) - test('should create timeMap of 3min intervals all day ', () => { - const result = tb.createIntervalMap({ start: new Date('2020-01-01 00:00:00'), end: new Date('2020-01-01 23:59:59') }, null, { - step: 3, - }) - expect(result[0]).toEqual({ start: '00:00', busy: null, index: 0 }) - expect(result[479]).toEqual({ start: '23:57', busy: null, index: 479 }) - expect(result.length).toEqual(480) - }) - test('should return null when params are null', () => { - const result = tb.createIntervalMap({ start: new Date('2020-01-01 00:00:00'), end: new Date('2020-01-01 23:59:59') }, null, {}) - expect(result).toEqual([]) - }) - }) - test('getBlankDayMap(5) returns all-day map of 5 min intervals ', () => { - const result = tb.getBlankDayMap(5) - expect(result[0]).toEqual({ start: '00:00', busy: false, index: 0 }) - expect(result[287]).toEqual({ start: '23:55', busy: false, index: 287 }) - expect(result.length).toEqual(288) - }) - describe('blockTimeFor ', () => { - test('should mark time map slots as busy (with title) for time of event in 2nd param', () => { - const map = tb.getBlankDayMap(5) - const result = tb.blockTimeFor(map, { start: '08:00', end: '09:00', title: 'testing' }, config) - expect(result.newMap[0]).toEqual({ start: '00:00', busy: false, index: 0 }) - expect(result.newMap[95]).toEqual({ start: '07:55', busy: false, index: 95 }) - expect(result.newMap[96]).toEqual({ start: '08:00', busy: 'testing', index: 96 }) - expect(result.newMap[107]).toEqual({ start: '08:55', busy: 'testing', index: 107 }) - expect(result.newMap[108]).toEqual({ start: '09:00', busy: false, index: 108 }) - expect(result.newMap[287]).toEqual({ start: '23:55', busy: false, index: 287 }) - }) - test('should mark time map as busy: true for item without name ', () => { - const map = tb.getBlankDayMap(5) - const result = tb.blockTimeFor(map, { start: '08:00', end: '09:00' }, config) - expect(result.itemText).toEqual('') - expect(result.newMap[0]).toEqual({ start: '00:00', busy: false, index: 0 }) - expect(result.newMap[96]).toEqual({ start: '08:00', busy: true, index: 96 }) - expect(result.newMap[107]).toEqual({ start: '08:55', busy: true, index: 107 }) - expect(result.newMap[108]).toEqual({ start: '09:00', busy: false, index: 108 }) - }) - }) - describe('attachTimeblockTag', () => { - test('should attach the given timeblock tag to a line of text', () => { - expect(tb.attachTimeblockTag('test', '#tag')).toEqual('test #tag') - }) - test('should not duplicate tag when attaching to a line which already has the tag', () => { - expect(tb.attachTimeblockTag('test #tag', '#tag')).toEqual('test #tag') - }) - }) - describe('createTimeBlockLine ', () => { - test('should create timeblock text in form "* HH:MM-HH:MM [name] #timeblocktag" ', () => { - const cfg = { ...config, timeBlockTag: '#tag', removeDuration: true } - expect(tb.createTimeBlockLine({ title: 'foo', start: '08:00', end: '09:00' }, cfg)).toEqual('* 08:00-09:00 foo #tag') - }) - test('should return empty string if title is empty', () => { - const cfg = { ...config, timeBlockTag: '#tag', removeDuration: true } - expect(tb.createTimeBlockLine({ title: '', start: '08:00', end: '09:00' }, cfg)).toEqual('') - }) - test("should not remove duration time signature ('2h22m) from text when removeDuration config is false", () => { - const cfg = { ...config, timeBlockTag: '#tag', removeDuration: false } - expect(tb.createTimeBlockLine({ title: "foo bar '2h22m", start: '08:00', end: '09:00' }, cfg)).toEqual("* 08:00-09:00 foo bar '2h22m #tag") - }) - test("should remove duration time signature ('2h22m) from text when removeDuration config is true", () => { - const cfg = { ...config, timeBlockTag: '#tag', removeDuration: true } - expect(tb.createTimeBlockLine({ title: "foo bar '2h22m", start: '08:00', end: '09:00' }, cfg)).toEqual('* 08:00-09:00 foo bar #tag') - }) - test('should add tb text when timeblockTextMustContainString is set', () => { - const cfg = { ...config, timeblockTextMustContainString: '#tb', removeDuration: true } - expect(tb.createTimeBlockLine({ title: "foo bar '2h22m", start: '08:00', end: '09:00' }, cfg)).toEqual('* 08:00-09:00 foo bar #🕑 #tb') - }) - test('should not add tb text when timeblockTextMustContainString is set and tb text is already in the string', () => { - const cfg = { ...config, timeblockTextMustContainString: '#tb', removeDuration: true } - expect(tb.createTimeBlockLine({ title: "foo bar#tb '2h22m", start: '08:00', end: '09:00' }, cfg)).toEqual('* 08:00-09:00 foo bar#tb #🕑') - }) - }) - describe('blockOutEvents ', () => { - test('should block (only) times on the time map for the event given', () => { - const map = tb.getBlankDayMap(5) - const events = [{ date: new Date('2021-01-01 00:10'), endDate: new Date('2021-01-01 00:21'), title: 'event1' }] - const returnVal = tb.blockOutEvents(events, map, config) - expect(returnVal[1].busy).toEqual(false) - expect(returnVal[2].busy).toEqual('event1') - expect(returnVal[5].busy).toEqual(false) - }) - test('should not block times for an event if availability flag is 1 (free)', () => { - const map = tb.getBlankDayMap(5) - const events = [{ date: new Date('2021-01-01 00:10'), endDate: new Date('2021-01-01 00:21'), title: 'event1', availability: 1 }] - const returnVal = tb.blockOutEvents(events, map, config) - expect(returnVal[1].busy).toEqual(false) - expect(returnVal[2].busy).toEqual(false) - expect(returnVal[5].busy).toEqual(false) - }) - test('overlapping events should get blocked with the later events reflected in the busy field', () => { - const map = tb.getBlankDayMap(5) - const events = [{ date: new Date('2021-01-01 00:10'), endDate: new Date('2021-01-01 00:21'), title: 'event1' }] - events.push({ - date: new Date('2021-01-01 00:20'), - endDate: new Date('2021-01-01 00:30'), - title: 'event2', - }) - const returnVal = tb.blockOutEvents(events, map, config) - expect(returnVal[4].busy).toEqual('event2') - expect(returnVal[6].busy).toEqual(false) - }) - test('If calendar items have no endDate, they are ignored', () => { - const map = tb.getBlankDayMap(5) - const events = [{ date: new Date('2021-01-01 00:20'), title: 'event3' }] - const returnVal = tb.blockOutEvents(events, map, config) - expect(returnVal.filter((t) => t.busy === 'event3')).toEqual([]) - }) - }) - - test('removeDurationParameter ', () => { - // hours and mins - expect(tb.removeDurationParameter('this is foo ~2h22m', '~')).toEqual('this is foo') - // minutes only - expect(tb.removeDurationParameter('this is foo ~22m', '~')).toEqual('this is foo') - // multiple splits (after the duration) - expect(tb.removeDurationParameter('this is foo ~22m (2)', '~')).toEqual('this is foo (2)') - // multiple splits (before the duration) - expect(tb.removeDurationParameter('this is foo (2) ~22m', '~')).toEqual('this is foo (2)') - }) - - test('getDurationFromLine ', () => { - expect(tb.getDurationFromLine('', "'")).toEqual(0) - expect(tb.getDurationFromLine('no time sig', "'")).toEqual(0) - expect(tb.getDurationFromLine(" '2m", "'")).toEqual(2) - expect(tb.getDurationFromLine(" '2h", "'")).toEqual(120) - expect(tb.getDurationFromLine(" '2.5h", "'")).toEqual(150) - expect(tb.getDurationFromLine(" '2.5m", "'")).toEqual(3) - expect(tb.getDurationFromLine(" '2h5m", "'")).toEqual(125) - }) - - test('addDurationToTasks ', () => { - const res = tb.addDurationToTasks([{ content: `foo '1h4m` }], config) - expect(res[0].duration).toEqual(64) - }) - - test('removeDateTagsFromArray ', () => { - const inputArray = [ - { indents: 1, type: 'open', content: 'thecontent >today', rawContent: '* thecontent >today' }, - { indents: 0, type: 'scheduled', content: '2thecontent >2021-01-01', rawContent: '* 2thecontent >2021-01-01' }, - { indents: 0, type: 'scheduled', content: '', rawContent: '' }, - ] - const returnval = tb.removeDateTagsFromArray(inputArray) - expect(returnval[0].content).toEqual('thecontent') - expect(returnval[1].rawContent).toEqual('* 2thecontent') - expect(returnval[2].content).toEqual('') - }) - - describe('timeIsAfterWorkHours', () => { - const config = { workDayStart: '08:00', workDayEnd: '17:00' } - test('should be true when now is >= workdayEnd', () => { - expect(tb.timeIsAfterWorkHours('17:00', config)).toEqual(true) - }) - test('should be false when now is < workdayEnd', () => { - expect(tb.timeIsAfterWorkHours('16:59', config)).toEqual(false) - }) - }) - - describe('filterTimeMapToOpenSlots', () => { - test('filterTimeMapToOpenSlots ', () => { - const timeMap = [ - { start: '00:00', busy: false }, - { start: '00:05', busy: false }, - ] - let cfg = { ...config, workDayStart: '00:00', workDayEnd: '23:59', nowStrOverride: '00:02 ' } - // expect(tb.filterTimeMapToOpenSlots(timeMap, nowStr, workDayStart, workDayEnd)).toEqual(true) - expect(tb.filterTimeMapToOpenSlots(timeMap, cfg)).toEqual(timeMap.slice(1, 2)) // now is after first item - cfg.nowStrOverride = '00:00' - expect(tb.filterTimeMapToOpenSlots(timeMap, cfg)).toEqual(timeMap) // now is equal to first item - cfg = { ...cfg, workDayStart: '00:01' } - expect(tb.filterTimeMapToOpenSlots(timeMap, cfg)).toEqual(timeMap.slice(1, 2)) // workDayStart is after first item - cfg = { ...cfg, workDayStart: '00:05' } - expect(tb.filterTimeMapToOpenSlots(timeMap, cfg)).toEqual(timeMap.slice(1, 2)) // workDayStart is equal to 2nd item - cfg = { ...config, workDayEnd: '00:00', nowStrOverride: '00:00' } - expect(tb.filterTimeMapToOpenSlots(timeMap, cfg)).toEqual([]) // workDayEnd is before 1st item - cfg = { ...config, workDayStart: '00:00', workDayEnd: '00:03', nowStrOverride: '00:00' } - expect(tb.filterTimeMapToOpenSlots(timeMap, cfg, '00:00')).toEqual(timeMap.slice(0, 1)) // workDayEnd is before 2st item - cfg = { ...config, workDayStart: '00:00', workDayEnd: '00:30', nowStrOverride: '00:00', timeblockTextMustContainString: '#tb', mode: 'BY_TIMEBLOCK_TAG' } - timeMap.push({ start: '00:10', busy: 'work #tb' }) - const result = tb.filterTimeMapToOpenSlots(timeMap, cfg, '00:00') - expect(result[2]).toEqual(timeMap[2]) // does not screen out timeblocks - }) - }) - - test('makeAllItemsTodos ', () => { - const types = ['open', 'done', 'scheduled', 'cancelled', 'title', 'quote', 'list', 'text'] - const expec = ['open', 'done', 'scheduled', 'cancelled', 'title', 'quote', 'open', 'open'] - const paras = types.map((type) => ({ type, content: `was:${type}` })) - const res = tb.makeAllItemsTodos(paras) - res.forEach((item, i) => { - expect(item.type).toEqual(expec[i]) - }) - }) - - test('createOpenBlockObject ', () => { - const cfg = { ...config, intervalMins: 2 } - expect(tb.createOpenBlockObject({ start: '00:00', end: '00:10' }, cfg, false).minsAvailable).toEqual(10) - expect(tb.createOpenBlockObject({ start: '00:00', end: '00:10' }, cfg, true).minsAvailable).toEqual(12) - expect(tb.createOpenBlockObject({ start: '00:00', end: '02:10' }, cfg, false).minsAvailable).toEqual(130) - expect(tb.createOpenBlockObject({ start: '00:00', end: '02:10' }, cfg, false)).toEqual({ - start: '00:00', - end: '02:10', - minsAvailable: 130, - }) - expect(tb.createOpenBlockObject({ start: '00:00', end: '02:10' }, cfg, true)).toEqual({ - start: '00:00', - end: '02:12', - minsAvailable: 132, - }) - expect(tb.createOpenBlockObject({ start: new Date(), end: '02:10' }, cfg, true)).toEqual(null) - }) - - test('findTimeBlocks ', () => { - // empty map should return empty array - expect(tb.findTimeBlocks([])).toEqual([]) - let timeMap = [ - { start: '00:05', busy: false, index: 0 }, - { start: '00:15', busy: false, index: 2 } /* this one should cause a break */, - { start: '00:20', busy: false, index: 3 }, - { start: '00:30', busy: false, index: 5 } /* this one should cause a break */, - ] - let timeBlocks = tb.findTimeBlocks(timeMap, config) - expect(timeBlocks[0]).toEqual({ start: '00:05', end: '00:10', minsAvailable: 5, title: '' }) - expect(timeBlocks[1]).toEqual({ start: '00:15', end: '00:25', minsAvailable: 10, title: '' }) - expect(timeBlocks[2]).toEqual({ start: '00:30', end: '00:35', minsAvailable: 5, title: '' }) - - timeMap = [ - // test the whole map is available/contiguous - { start: '00:15', busy: false, index: 2 }, - { start: '00:20', busy: false, index: 3 }, - { start: '00:25', busy: false, index: 4 }, - ] - timeBlocks = tb.findTimeBlocks(timeMap, config) - expect(timeBlocks.length).toEqual(1) - expect(timeBlocks[0]).toEqual({ start: '00:15', end: '00:30', minsAvailable: 15, title: '' }) - timeMap = [ - // one item and one contiguous block - { start: '00:00', busy: false, index: 0 }, - { start: '00:15', busy: false, index: 2 }, - { start: '00:20', busy: false, index: 3 }, - { start: '00:25', busy: false, index: 4 }, - ] - timeBlocks = tb.findTimeBlocks(timeMap, config) - expect(timeBlocks.length).toEqual(2) - expect(timeBlocks[0]).toEqual({ start: '00:00', end: '00:05', minsAvailable: 5, title: '' }) - expect(timeBlocks[1]).toEqual({ start: '00:15', end: '00:30', minsAvailable: 15, title: '' }) - timeMap = [ - // one item and one contiguous block - { start: '23:40', busy: false, index: 0 }, - { start: '23:45', busy: false, index: 1 }, - { start: '23:50', busy: false, index: 2 }, - { start: '23:55', busy: false, index: 3 }, - ] - timeBlocks = tb.findTimeBlocks(timeMap, config) - expect(timeBlocks.length).toEqual(1) - expect(timeBlocks[0]).toEqual({ start: '23:40', end: '23:59', minsAvailable: 20, title: '' }) //FIXME: this doesn't seem right! - timeMap = [ - // one item and one contiguous block - { start: '23:40', busy: false, index: 0 }, - { start: '23:45', busy: 'foo #tb', index: 1 }, - { start: '23:50', busy: 'foo #tb', index: 2 }, - { start: '23:55', busy: false, index: 3 }, - ] - timeBlocks = tb.findTimeBlocks(timeMap, config) - expect(timeBlocks.length).toEqual(3) - expect(timeBlocks[1].minsAvailable).toEqual(10) - expect(timeBlocks[1].title).toEqual('foo #tb') - }) - - describe('addMinutesToTimeText', () => { - test('should add time properly', () => { - expect(tb.addMinutesToTimeText('00:00', 21)).toEqual('00:21') - expect(tb.addMinutesToTimeText('00:00', 180)).toEqual('03:00') - expect(tb.addMinutesToTimeText('00:50', 20)).toEqual('01:10') - }) - test('should gracefully fail on bad input', () => { - expect(tb.addMinutesToTimeText(new Date(), 21)).toEqual('') - }) - }) - - describe('getTimeBlockTimesForEvents ', () => { - test('basic PRIORITY_FIRST test', () => { - const timeMap = [ - // one item and one contiguous block - { start: '00:00', busy: false, index: 0 }, - { start: '00:15', busy: false, index: 2 }, - { start: '00:20', busy: false, index: 3 }, - { start: '00:25', busy: false, index: 4 }, - ] - const todos = [ - { content: "!! line1 '8m", type: 'open' }, - { content: "! line2 '1m", type: 'open' }, - { content: "!!! line3 '7m", type: 'open' }, - ] - const todosByType = getTasksByType(todos) - const cfg = { - ...config, - workDayStart: '00:00', - workDayEnd: '23:59', - nowStrOverride: '00:00', - mode: 'PRIORITY_FIRST', - allowEventSplits: true, - } - // returns {blockList, timeBlockTextList, timeMap} - const res = tb.getTimeBlockTimesForEvents(timeMap, todosByType['open'], cfg) - expect(res.timeBlockTextList).toEqual([ - '* 00:00-00:05 !!! line3 (1) #🕑', - '* 00:15-00:17 !!! line3 (2) #🕑', - '* 00:20-00:28 !! line1 #🕑', - /* '* 00:20-00:21 ! line2 #🕑', LINE2 DOES NOT HAVE A SLOT */ - ]) - }) - test("should work if it's now later in the day ", () => { - // test with time later in the day - const todos = [ - { content: "!! line1 '8m", type: 'open' }, - { content: "! line2 '1m", type: 'open' }, - { content: "!!! line3 '7m", type: 'open' }, - ] - const todosByType = getTasksByType(todos) - const cfg = { - ...config, - workDayStart: '00:00', - workDayEnd: '23:59', - nowStrOverride: '00:19', - mode: 'PRIORITY_FIRST', - allowEventSplits: true, - } - const timeMap2 = [ - // one item and one contiguous block - { start: '00:00', busy: false, index: 0 }, - { start: '00:15', busy: false, index: 2 }, - { start: '00:20', busy: false, index: 3 }, - { start: '00:25', busy: false, index: 4 }, - ] - const res = tb.getTimeBlockTimesForEvents(timeMap2, todosByType['open'], cfg) - expect(res.timeBlockTextList).toEqual([`* 00:20-00:27 !!! line3 ${config.timeBlockTag}`]) - }) - test('calling options.mode = LARGEST_FIRST', () => { - // test with calling options.mode = 'LARGEST_FIRST' - const todos = [ - { content: "!! line1 '8m", type: 'open' }, - { content: "! line2 '1m", type: 'open' }, - { content: "!!! line3 '7m", type: 'open' }, - ] - const todosByType = getTasksByType(todos) - const cfg = { - ...config, - workDayStart: '00:00', - workDayEnd: '23:59', - nowStrOverride: '00:00', - mode: 'LARGEST_FIRST', - allowEventSplits: true, - } - const res = tb.getTimeBlockTimesForEvents([{ start: '00:00', busy: false, index: 0 }], todosByType['open'], cfg) - expect(res.timeBlockTextList).toEqual([ - '* 00:00-00:05 !! line1 (1) #🕑', - /* '* 00:20-00:21 ! line2 #🕑', LINE2 DOES NOT HAVE A SLOT */ - ]) - // test with calling no options.mode (just for coverage of switch statement) - }) - test('calling options.mode = tests default in switch', () => { - const todos = [ - { content: "!! line1 '8m", type: 'open' }, - { content: "! line2 '1m", type: 'open' }, - { content: "!!! line3 '7m", type: 'open' }, - ] - const todosByType = getTasksByType(todos) - - const cfg = { - ...config, - workDayStart: '00:00', - workDayEnd: '23:59', - nowStrOverride: '00:00', - mode: '', - allowEventSplits: true, - } - const res = tb.getTimeBlockTimesForEvents([{ start: '00:00', busy: false, index: 0 }], todosByType['open'], cfg) - expect(res.timeBlockTextList).toEqual([]) - }) - test('should place only items that fit in rest of day', () => { - const timeMap = [ - // one item and one contiguous block - { start: '23:40', busy: false, index: 0 }, - { start: '23:45', busy: false, index: 1 }, - { start: '23:50', busy: false, index: 2 }, - { start: '23:55', busy: false, index: 4 }, - ] - const todos = [ - { content: "!! line1 '8m", type: 'open' }, - { content: "! line2 '1m", type: 'open' }, - { content: "!!! line3 '7m", type: 'open' }, - ] - const todosByType = getTasksByType(todos) - - const cfg = { - ...config, - workDayStart: '23:00', - intervalMins: 5, - workDayEnd: '23:59', - nowStrOverride: '23:54', - mode: 'PRIORITY_FIRST', - allowEventSplits: false, - } - const res = tb.getTimeBlockTimesForEvents(timeMap, todosByType['open'], cfg) - expect(res.timeBlockTextList).toEqual(['* 23:55-23:56 ! line2 #🕑']) - }) - test('should place single BY_TIMEBLOCK_TAG items inside timeblock of that name', () => { - const timeMap = [ - // one item and one contiguous block - { start: '23:45', busy: 'timblock #tb', index: 1 }, - { start: '23:50', busy: 'timblock #tb', index: 2 }, - { start: '23:55', busy: 'timblock #tb', index: 3 }, - ] - const todos = [{ content: "! line2 '5m #timblock", type: 'open' }] - const todosByType = getTasksByType(todos) - - const cfg = { - ...config, - workDayStart: '23:00', - intervalMins: 5, - workDayEnd: '23:59', - nowStrOverride: '23:00', - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '#tb', - allowEventSplits: false, - } - const result = tb.getTimeBlockTimesForEvents(timeMap, todosByType['open'], cfg) - expect(result.timeBlockTextList).toEqual(expect.arrayContaining(['* 23:45-23:50 ! line2 #timblock #🕑 #tb'])) - }) - test('should place BY_TIMEBLOCK_TAG items inside timeblock of that name when there are other blocks', () => { - const timeMap = [ - // one item and one contiguous block - { start: '23:40', busy: false, index: 0 }, - { start: '23:45', busy: 'timblock #tb', index: 1 }, - { start: '23:50', busy: 'timblock #tb', index: 2 }, - { start: '23:55', busy: 'timblock #tb', index: 3 }, - { start: '24:00', busy: false, index: 4 }, - { start: '24:05', busy: false, index: 5 }, - ] - const todos = [ - { content: "!! line1 '2m", type: 'open' }, - { content: "! line2 '5m #timblock", type: 'open' }, - { content: "!!! line3 '5m", type: 'open' }, - ] - const todosByType = getTasksByType(todos) - - const cfg = { - ...config, - workDayStart: '23:00', - intervalMins: 5, - workDayEnd: '23:59', - nowStrOverride: '23:00', - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '#tb', - allowEventSplits: false, - } - const result = tb.getTimeBlockTimesForEvents(timeMap, todosByType['open'], cfg) - expect(result.timeBlockTextList).toEqual(expect.arrayContaining(['* 23:45-23:50 ! line2 #timblock #🕑 #tb'])) - }) - }) - - test('blockTimeAndCreateTimeBlockText ', () => { - let timeMap = [ - { start: '00:00', busy: false, index: 0 }, - { start: '00:05', busy: false, index: 1 }, - ] - let blockList = tb.findTimeBlocks(timeMap, config) - let block = { start: '00:00', end: '00:05', title: "test '2m" } - const timeBlockTextList = [] - let tbm = { timeMap, blockList, timeBlockTextList } - const cfg = { ...config, nowStrOverride: '00:00', workDayStart: '00:00' } - // (1) Base test. Block a time and return proper results - const result = tb.blockTimeAndCreateTimeBlockText(tbm, block, cfg) - expect(result).toEqual({ - blockList: [{ end: '00:10', minsAvailable: 5, start: '00:05', title: '' }], - timeBlockTextList: [`* 00:00-00:05 test ${config.timeBlockTag}`], - timeMap: [{ busy: false, index: 1, start: '00:05' }], - }) - // (2) Run a second test on the result of the first test. - // comes back with empty timeMap and blockList b/c interval is 5m - block = { start: '00:05', end: '00:07', title: "test2 '2m" } - const result2 = tb.blockTimeAndCreateTimeBlockText(result, block, cfg) - expect(result2).toEqual({ - blockList: [], - timeBlockTextList: [`* 00:00-00:05 test ${config.timeBlockTag}`, `* 00:05-00:07 test2 ${config.timeBlockTag}`], - timeMap: [], - }) - // (3) Run a third test - // but with a 2m interval. Should split the block and send back the remainder - block = { start: '00:00', end: '00:02', title: "test2 '2m" } - timeMap = [ - { start: '00:00', busy: false, index: 0 }, - { start: '00:02', busy: false, index: 1 }, - { start: '00:04', busy: false, index: 2 }, - { start: '00:06', busy: false, index: 3 }, - ] - cfg.intervalMins = 2 - blockList = tb.findTimeBlocks(timeMap, cfg) - tbm = { timeMap, blockList, timeBlockTextList: [] } - const result3 = tb.blockTimeAndCreateTimeBlockText(tbm, block, cfg) - expect(result3).toEqual({ - blockList: [{ start: '00:02', end: '00:08', minsAvailable: 6, title: '' }], - timeBlockTextList: [`* 00:00-00:02 test2 ${config.timeBlockTag}`], - timeMap: [ - { start: '00:02', busy: false, index: 1 }, - { start: '00:04', busy: false, index: 2 }, - { start: '00:06', busy: false, index: 3 }, - ], - }) - }) - - describe('matchTasksToSlots ', () => { - test('should insert content that fits without splitting ', () => { - const tasks = [{ content: "line1 '2m" }, { content: "line2 '1m" }] - const timeMap = [ - { start: '00:02', busy: false, index: 1 }, - { start: '00:04', busy: false, index: 2 }, - { start: '00:06', busy: false, index: 3 }, - /* block[0]: start:00:02 end:00:08 minsAvailable: 6 */ - { start: '00:20', busy: false, index: 10 }, - { start: '00:22', busy: false, index: 11 }, - /* block[1]: start:00:20 end:00:24 minsAvailable: 4 */ - ] - const timeBlocks = [{ start: '00:02', end: '00:08', minsAvailable: 6 }] - const cfg = { ...config, nowStrOverride: '00:00', workDayStart: '00:00', intervalMins: 2 } - // First check that items that fit inside the time block work properly - const res = tb.matchTasksToSlots(tasks, { blockList: timeBlocks, timeMap }, cfg) - expect(res.timeBlockTextList[0]).toEqual(`* 00:02-00:04 line1 ${config.timeBlockTag}`) - expect(res.timeBlockTextList[1]).toEqual(`* 00:04-00:05 line2 ${config.timeBlockTag}`) - expect(res.blockList[0]).toEqual({ start: '00:06', end: '00:08', minsAvailable: 2, title: '' }) - expect(res.blockList[1]).toEqual({ start: '00:20', end: '00:24', minsAvailable: 4, title: '' }) - expect(res.timeMap[0]).toEqual({ start: '00:06', busy: false, index: 3 }) - expect(res.timeMap[1]).toEqual({ start: '00:20', busy: false, index: 10 }) - expect(res.timeMap[2]).toEqual({ start: '00:22', busy: false, index: 11 }) - }) - test('items that do not fit in slots get split when allowEventSplits = true', () => { - // Now check that items that don't fit inside the time block get split properly - // Even if the whole task can't find a slot - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 2, - allowEventSplits: true, - } - const timeMap2 = [ - { start: '00:02', busy: false, index: 1 }, - { start: '00:04', busy: false, index: 2 }, - { start: '00:06', busy: false, index: 3 }, - /* block[0]: start:00:02 end:00:08 minsAvailable: 6 */ - { start: '00:20', busy: false, index: 10 }, - { start: '00:22', busy: false, index: 11 }, - /* block[1]: start:00:20 end:00:24 minsAvailable: 4 */ - ] - const nonFittingTask = [{ content: "line3 '12m" }] - const timeBlocks = [ - { start: '00:02', end: '00:08', minsAvailable: 6 }, - { start: '00:20', end: '00:24', minsAvailable: 4 }, - ] - const res = tb.matchTasksToSlots(nonFittingTask, { blockList: timeBlocks, timeMap: timeMap2, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList[0]).toEqual(`* 00:02-00:08 line3 (1) ${config.timeBlockTag}`) - expect(res.timeBlockTextList[1]).toEqual(`* 00:20-00:24 line3 (2) ${config.timeBlockTag}`) - expect(res.timeBlockTextList.length).toEqual(2) - expect(res.timeMap.length).toEqual(0) - expect(res.blockList.length).toEqual(0) - }) - test('no time for even one task', () => { - // Now check that items that don't fit inside the time block get split properly - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - workDayEnd: '00:08', - intervalMins: 2, - } - const timeMap2 = [ - { start: '00:00', busy: false, index: 0 }, - { start: '00:02', busy: false, index: 1 }, - { start: '00:04', busy: false, index: 2 }, - { start: '00:06', busy: false, index: 3 }, - ] - const nonFittingTasks = [{ content: "wont get placed '12m" }] - const timeBlocks = [] // irrelevant because will be rebuilt - const res = tb.matchTasksToSlots(nonFittingTasks, { blockList: timeBlocks, timeMap: timeMap2, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList.length).toEqual(0) - expect(Object.keys(res.noTimeForTasks).length).toEqual(1) - expect(res.noTimeForTasks['_'].length).toEqual(1) - expect(res.noTimeForTasks['_'][0]).toEqual({ content: `wont get placed '12m` }) - }) - test('items that do not fit in slots do not get split when allowEventSplits is missing', () => { - // Now check that items that don't fit inside the time block get split properly - // Even if the whole task can't find a slot - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 2, - } - const timeMap2 = [ - { start: '00:02', busy: false, index: 1 }, - { start: '00:04', busy: false, index: 2 }, - { start: '00:06', busy: false, index: 3 }, - /* block[0]: start:00:02 end:00:08 minsAvailable: 6 */ - { start: '00:20', busy: false, index: 10 }, - { start: '00:22', busy: false, index: 11 }, - { start: '00:24', busy: false, index: 12 }, - { start: '00:26', busy: false, index: 13 }, - { start: '00:28', busy: false, index: 14 }, - /* block[1]: start:00:20 end:00:24 minsAvailable: 4 */ - ] - const nonFittingTask = [{ content: "wont get placed '12m" }] - const fittingTask = [{ content: "gets placed '8m" }] - const timeBlocks = [] // irrelevant because will be rebuilt - const res = tb.matchTasksToSlots([...nonFittingTask, ...fittingTask], { blockList: timeBlocks, timeMap: timeMap2, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList.length).toEqual(1) - expect(res.timeBlockTextList[0]).toEqual(`* 00:20-00:28 gets placed ${config.timeBlockTag}`) - }) - test('items that do not fit in slots get split', () => { - // now test line which had no time attached - const timeBlocks = [{ start: '00:00', end: '00:20', minsAvailable: 20 }] - const timeMap = [{ start: '00:00', busy: false, index: 1 }] - const cfg = { ...config, nowStrOverride: '00:00', workDayStart: '00:00', intervalMins: 20, defaultDuration: 13 } - const res = tb.matchTasksToSlots([{ content: 'line4' }], { blockList: timeBlocks, timeMap: timeMap, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList).toEqual([`* 00:00-00:13 line4 ${config.timeBlockTag}`]) - }) - // dbw: skipping these tests for now because I think they are well covered elsewhere and this is not actually the path - // for the BY_TIMEBLOCK_TAG mode. Leaving them in case they are useful later. - test.skip('Mode: BY_TIMEBLOCK_TAG Put tasks inside timeblocks with their name - single item', () => { - // now test line which had no time attached - const timeBlocks = [{ start: '00:00', end: '00:20', minsAvailable: 20, title: 'timblock' }] - const timeMap = [{ start: '00:00', busy: false, index: 1 }] - const cfg = { ...config, nowStrOverride: '00:00', workDayStart: '00:00', intervalMins: 20, defaultDuration: 5, mode: 'BY_TIMEBLOCK_TAG' } - const res = tb.matchTasksToSlots([{ content: 'Do something #timblock' }], { blockList: timeBlocks, timeMap: timeMap, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList).toEqual(['* 00:00-00:05 Do something #timblock #🕑']) - }) - test.skip('Mode: BY_TIMEBLOCK_TAG Put tasks inside timeblocks with their name - multi items', () => { - // now test line which had no time attached - const timeBlocks = [ - { start: '00:00', end: '00:20', minsAvailable: 20, title: '' }, - { start: '00:40', end: '01:00', minsAvailable: 20, title: 'timblock' }, - { start: '01:30', end: '01:50', minsAvailable: 20, title: '' }, - ] - const timeMap = [ - { start: '00:00', busy: false, index: 1 }, - { start: '00:40', busy: 'timblock #tb', index: 5 }, - ] - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 20, - defaultDuration: 5, - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '#tb', - } - const sortedTaskList = [{ content: 'Do something #timblock' }, { content: 'line2' }, { content: 'line3' }] - const res = tb.matchTasksToSlots(sortedTaskList, { blockList: timeBlocks, timeMap: timeMap, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList).toEqual([`* 00:00-00:13 line4 ${config.timeBlockTag}`]) - }) - }) - - describe('getRegExOrString', () => { - test('should return items that are a string', () => { - const res = tb.getRegExOrString('a string') - expect(res).toEqual('a string') - expect(typeof res).toEqual('string') - }) - test('should return Regex for items that are regex', () => { - const res = tb.getRegExOrString('/a regex/') - expect(res).toEqual(new RegExp('a regex')) - }) - test('should work when there are spaces', () => { - const res = tb.getRegExOrString(' /a regex/ ') - expect(res).toEqual(new RegExp('a regex')) - }) - }) - - describe('includeTasksWithPatterns', () => { - test('should include only tasks that contain a string', () => { - const tasks = [{ content: 'foo' }, { content: 'bar' }, { content: 'baz' }] - const result = tb.includeTasksWithPatterns(tasks, 'ba') - expect(result.length).toEqual(2) - expect(result[0].content).toEqual('bar') - expect(result[1].content).toEqual('baz') - }) - test('should include only tasks that match a regex', () => { - const tasks = [{ content: 'foo' }, { content: 'bar' }, { content: 'baz' }] - const result = tb.includeTasksWithPatterns(tasks, /ba/) - expect(result.length).toEqual(2) - expect(result[0].content).toEqual('bar') - expect(result[1].content).toEqual('baz') - }) - test('should work when the input string is comma-separated list', () => { - const tasks = [{ content: 'foo' }, { content: 'bar' }, { content: 'baz' }] - const result = tb.includeTasksWithPatterns(tasks, 'foo,baz') - expect(result.length).toEqual(2) - expect(result[0].content).toEqual('foo') - expect(result[1].content).toEqual('baz') - }) - test('should work when the input string is comma-separated list with a regex', () => { - const tasks = [{ content: 'foo' }, { content: 'bar' }, { content: 'baz' }] - const result = tb.includeTasksWithPatterns(tasks, '/^f/,baz') - expect(result.length).toEqual(2) - expect(result[0].content).toEqual('foo') - expect(result[1].content).toEqual('baz') - }) - test('should include tasks that match an array of patterns', () => { - const tasks = [{ content: 'foo' }, { content: 'bar' }, { content: 'baz' }] - const result = tb.includeTasksWithPatterns(tasks, ['az', /^f/]) - expect(result.length).toEqual(2) - expect(result[0].content).toEqual('foo') - expect(result[1].content).toEqual('baz') - }) - test('should include tasks that match an array of patterns with spaces', () => { - const tasks = [{ content: 'foo' }, { content: 'bar' }, { content: 'baz' }] - const result = tb.includeTasksWithPatterns(tasks, ' foo, baz ') - expect(result.length).toEqual(2) - expect(result[0].content).toEqual('foo') - expect(result[1].content).toEqual('baz') - }) - }) - describe('excludeTasksWithPatterns', () => { - test('should include only tasks that do not contain the string/regex', () => { - const tasks = [{ content: 'foo' }, { content: 'bar' }, { content: 'baz' }] - const result = tb.excludeTasksWithPatterns(tasks, 'ba') - expect(result.length).toEqual(1) - expect(result[0].content).toEqual('foo') - }) - test('should exclude tasks that match a regex', () => { - const tasks = [{ content: 'foo' }, { content: 'bar' }, { content: 'baz' }] - const result = tb.excludeTasksWithPatterns(tasks, '/ba/') - expect(result[0].content).toEqual('foo') - }) - test('should exclude tasks with hashtags', () => { - const tasks = [{ content: 'foo #planning' }, { content: '#lao bar' }, { content: 'baz' }] - const result = tb.excludeTasksWithPatterns(tasks, '#planning,#lao') - expect(result.length).toEqual(1) - expect(result[0].content).toEqual('baz') - }) - test('should exclude tasks with spaces', () => { - const tasks = [{ content: 'foo #planning' }, { content: '#lao bar' }, { content: 'baz' }] - const result = tb.excludeTasksWithPatterns(tasks, '#planning , #lao') - expect(result.length).toEqual(1) - expect(result[0].content).toEqual('baz') - }) - }) - - describe('dwertheimer.EventAutomations - timeblocking.getFullParagraphsCorrespondingToSortList ', () => { - const origParas = [ - { content: 'foo', rawContent: 'foo' }, - { content: 'bar', rawContent: 'bar' }, - { content: 'baz', rawContent: 'baz' }, - ] - const sortList = [ - { content: 'baz', raw: 'baz' }, - { content: 'foo', raw: 'foo' }, - { content: 'bar', raw: 'bar' }, - ] - test('should match the order', () => { - const res = tb.getFullParagraphsCorrespondingToSortList(origParas, sortList) - expect(res.length).toEqual(3) - expect(res[0]).toEqual(origParas[2]) - }) - test('should exclude tasks with spaces', () => { - const res = tb.getFullParagraphsCorrespondingToSortList(origParas, sortList) - expect(res[0]).toEqual(origParas[2]) - }) - }) - - describe('appendLinkIfNecessary', () => { - let fname - beforeAll(() => { - fname = Editor.filename - Editor.filename = 'foo.md' - }) - afterAll(() => { - Editor.filename = fname - }) - const paragraphs = [ - { content: 'foo', type: 'done', filename: 'foof.md' }, - { content: 'bar', type: 'open', filename: 'barf.md' }, - { content: 'baz', type: 'list', filename: 'bazf.txt' }, - { content: 'baz', type: 'text', filename: 'bazf.txt' }, - ] - - test('should do nothing if includeLinks is OFF', () => { - const res = tb.appendLinkIfNecessary(paragraphs, { ...config, includeLinks: 'OFF' }) - expect(res).toEqual(paragraphs) - }) - test('should do nothing if todos array is empty', () => { - const res = tb.appendLinkIfNecessary([], config) - expect(res).toEqual([]) - }) - test('should do nothing if todo type is title', () => { - const p = [{ type: 'title', content: 'foo' }] - const res = tb.appendLinkIfNecessary(p, config) - expect(res).toEqual(p) - }) - test('should add wikilink to content in form of [[title#heading]]', () => { - const note = new Note({ title: 'foo', filename: Editor.filename }) - const p = [{ type: 'open', content: 'ugh', heading: 'bar', note }] - note.paragraphs = p - const res = tb.appendLinkIfNecessary(p, { ...config, includeLinks: '[[internal#links]]' }) - expect(res[0].content).toEqual('ugh [[foo^123456]]') - }) - test('should add url-style link to content in form of noteplan://', () => { - const note = new Note({ title: 'foo', filename: Editor.filename }) - const p = [{ type: 'open', content: 'ugh', heading: 'bar', filename: 'baz', note }] - note.paragraphs = p - const res = tb.appendLinkIfNecessary(p, { ...config, includeLinks: 'Pretty Links', linkText: '%' }) - expect(res[0].content).toEqual('ugh [%](noteplan://x-callback-url/openNote?noteTitle=foo%5E123456)') - }) - }) - /* - * cleanText() - */ - describe('cleanText()' /* function */, () => { - test('should do nothing if no text to replace', () => { - const before = '' - const after = before - const replacements = [] - const result = tb.cleanText(before, replacements) - expect(result).toEqual(after) - }) - test('should do nothing if matchers to replace', () => { - const before = 'foo bar baz' - const after = before - const replacements = [] - const result = tb.cleanText(before, replacements) - expect(result).toEqual(after) - }) - test('should do a basic string replace', () => { - const before = 'foo bar baz' - const after = 'foo baz' - const replacements = ['bar'] - const result = tb.cleanText(before, replacements) - expect(result).toEqual(after) - }) - test('should do a basic regex replace', () => { - const before = 'foo bar baz' - const after = 'foo baz' - const replacements = [/bar/] - const result = tb.cleanText(before, replacements) - expect(result).toEqual(after) - }) - test('should clean up double spaces', () => { - const before = 'foo bar baz' - const after = 'foo baz' - const replacements = [/bar/] - const result = tb.cleanText(before, replacements) - expect(result).toEqual(after) - }) - test('should remove timeblock at start', () => { - const before = '00:01-12:22 foo bar baz' - const after = 'foo bar baz' - const replacements = [new RegExp(`^\\d{2}:\\d{2}-\\d{2}:\\d{2} `)] - const result = tb.cleanText(before, replacements) - expect(result).toEqual(after) - }) - }) - - /* - * cleanTimeBlockLine() - */ - describe('cleanTimeBlockLine()' /* function */, () => { - test('should remove time', () => { - const before = '00:01-12:22 foo bar baz' - const after = 'foo bar baz' - const config = { timeBlockTag: '#🕑' } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove ATB tag', () => { - const before = 'foo bar baz #🕑' - const after = 'foo bar baz' - const config = { timeBlockTag: '#🕑' } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove duration 5m', () => { - const before = "foo bar baz '5m" - const after = 'foo bar baz' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove wiki link', () => { - const before = 'foo bar baz [[foo]]' - const after = 'foo bar baz' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove any url', () => { - const before = 'foo [bar](noteplan://baz)' - const after = 'foo' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('what if there are two urls on a line', () => { - const before = 'foo [bar](noteplan://baz) zoo [bar](noteplan://baz)' - const after = 'foo zoo' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove today tag', () => { - const before = 'foo >today' - const after = 'foo' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove date tag', () => { - const before = 'foo >2022-01-01' - const after = 'foo' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove week tag', () => { - const before = 'foo >2022-W01' - const after = 'foo' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove month tag', () => { - const before = 'foo >2022-01' - const after = 'foo' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove quarter tag', () => { - const before = 'foo >2022-Q1' - const after = 'foo' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - test('should remove year tag', () => { - const before = 'foo >2022' - const after = 'foo' - const config = { timeBlockTag: '🕑', durationMarker: "'" } - const result = tb.cleanTimeBlockLine(before, config) - expect(result).toEqual(after) - }) - }) - - // describe('isAutoTimeBlockLine', () => { - // test('should return null if there are no ATB lines', () => { - // const line = '222 no timeblock in here #foo' - // expect(tb.isAutoTimeBlockLine(line, {})).toEqual(null) - // }) - // test('should find a standard timeblock line', () => { - // const line = '- 21:00-21:15 Respond to x.la [–](noteplan://x-callback-url/openNote?filename=20220512.md) #🕑' - // const exp = 'Respond to x.la' - // expect(tb.isAutoTimeBlockLine(line, {})).toEqual(exp) - // }) - // test('should find a * at the front', () => { - // const line = '* 21:00-21:15 Respond to x.la [–](noteplan://x-callback-url/openNote?filename=20220512.md) #🕑' - // const exp = 'Respond to x.la' - // expect(tb.isAutoTimeBlockLine(line, {})).toEqual(exp) - // }) - // test('should find with nonstandard tag', () => { - // const line = - // '* 21:00-21:15 Respond to x.la [–](noteplan://x-callback-url/openNote?filename=20220512.md) #something' - // const exp = 'Respond to x.la' - // expect(tb.isAutoTimeBlockLine(line, {})).toEqual(exp) - // }) - // test('should find with a wiki link', () => { - // const line = '- 19:20-19:35 Send landing page howto [[yo#something]] #🕑' - // const exp = `Send landing page howto` - // expect(tb.isAutoTimeBlockLine(line, {})).toEqual(exp) - // }) - // }) - }) -}) - -/* -appendLinkIfNecessary -*/ - -/* - * processByTimeBlockTag() - */ -describe('processByTimeBlockTag()' /* function */, () => { - test('When in mode: BY_TIMEBLOCK_TAG, should do exactly the same as matchTaskToSlots if there are no named slots (e.g. you have not specified a matching time block)', () => { - // now test line which had no time attached - const timeBlocks = [ - { start: '00:00', end: '00:10', minsAvailable: 10, title: '' }, - { start: '00:40', end: '00:45', minsAvailable: 5, title: '' }, - ] - const timeMap = [ - { start: '00:00', busy: false, index: 1 }, - { start: '00:05', busy: false, index: 2 }, - { start: '00:40', busy: false, index: 5 }, - ] - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 5, - defaultDuration: 5, - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '#tb', - } - const sortedTaskList = [{ content: 'Do something #timblock' }, { content: 'line2' }, { content: 'line3' }] - const timeBlockTextList = ['* 00:00-00:05 Do something #timblock #🕑 #tb', '* 00:05-00:10 line2 #🕑 #tb', '* 00:40-00:45 line3 #🕑 #tb'] - const res = byTagMode.processByTimeBlockTag(sortedTaskList, { blockList: timeBlocks, timeMap: timeMap, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList).toEqual(timeBlockTextList) - }) - test('should place one named item', () => { - // now test line which had no time attached - const timeBlocks = [ - { start: '00:00', end: '00:20', minsAvailable: 20, title: '' }, - { start: '00:20', end: '00:30', minsAvailable: 10, title: 'foo' }, - { start: '00:30', end: '00:35', minsAvailable: 5, title: '' }, - ] - const timeMap = [ - { start: '00:00', busy: false, index: 1 }, - { start: '00:05', busy: false, index: 2 }, - { start: '00:10', busy: false, index: 3 }, - { start: '00:15', busy: false, index: 4 }, - { start: '00:20', busy: 'foo #tb', index: 5 }, - { start: '00:25', busy: 'foo #tb', index: 6 }, - { start: '00:30', busy: false, index: 7 }, - ] - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 20, - defaultDuration: 5, - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '#tb', - } - const sortedTaskList = [{ content: 'Do something #foo' }] - const res = byTagMode.processByTimeBlockTag(sortedTaskList, { blockList: timeBlocks, timeMap: timeMap, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList).toEqual([`* 00:20-00:25 Do something #foo #🕑 #tb`]) - }) - test('should place two tasks in one named timeblock', () => { - // now test line which had no time attached - const timeBlocks = [ - { start: '00:00', end: '00:20', minsAvailable: 20, title: '' }, - { start: '00:20', end: '00:30', minsAvailable: 10, title: 'foo' }, - { start: '00:30', end: '00:35', minsAvailable: 5, title: '' }, - ] - const timeMap = [ - { start: '00:00', busy: false, index: 1 }, - { start: '00:05', busy: false, index: 2 }, - { start: '00:10', busy: false, index: 3 }, - { start: '00:15', busy: false, index: 4 }, - { start: '00:20', busy: 'foo #tb', index: 5 }, - { start: '00:25', busy: 'foo #tb', index: 6 }, - { start: '00:30', busy: false, index: 7 }, - ] - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 20, - defaultDuration: 5, - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '#tb', - } - const sortedTaskList = [{ content: 'Do something #foo' }, { content: 'Do something else #foo' }] - const res = byTagMode.processByTimeBlockTag(sortedTaskList, { blockList: timeBlocks, timeMap: timeMap, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList).toEqual(['* 00:20-00:25 Do something #foo #🕑 #tb', '* 00:25-00:30 Do something else #foo #🕑 #tb']) - }) - test('should place two tasks in one named timeblock but not a third that doesnt fit', () => { - // now test line which had no time attached - const timeBlocks = [ - { start: '00:00', end: '00:20', minsAvailable: 20, title: '' }, - { start: '00:25', end: '00:35', minsAvailable: 10, title: 'foo' }, - { start: '00:40', end: '00:45', minsAvailable: 5, title: 'bar' }, - ] - const timeMap = [ - { start: '00:00', busy: false, index: 1 }, - { start: '00:05', busy: false, index: 2 }, - { start: '00:10', busy: false, index: 3 }, - { start: '00:15', busy: false, index: 4 }, - { start: '00:25', busy: 'foo #tb', index: 10 }, - { start: '00:30', busy: 'foo #tb', index: 11 }, - { start: '00:40', busy: 'bar #tb', index: 20 }, - ] - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 5, - defaultDuration: 5, - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '#tb', - } - const sortedTaskList = [{ content: 'Do something #foo' }, { content: 'Do something else #foo' }, { content: 'this wont fit #foo' }] - const res = byTagMode.processByTimeBlockTag(sortedTaskList, { blockList: timeBlocks, timeMap: timeMap, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList).toEqual(['* 00:25-00:30 Do something #foo #🕑 #tb', '* 00:30-00:35 Do something else #foo #🕑 #tb']) - }) - test('should place two tasks in one named timeblock and another in another one', () => { - // now test line which had no time attached - const timeBlocks = [ - { start: '00:00', end: '00:05', minsAvailable: 5, title: '' }, - { start: '00:20', end: '00:30', minsAvailable: 10, title: 'foo' }, - { start: '00:40', end: '00:50', minsAvailable: 10, title: 'bar' }, - ] - const timeMap = [ - { start: '00:00', busy: false, index: 1 }, - { start: '00:20', busy: '#foo #tb', index: 5 }, - { start: '00:25', busy: '#foo #tb', index: 6 }, - { start: '00:40', busy: '#bar #tb', index: 10 }, - { start: '00:45', busy: '#bar #tb', index: 11 }, - ] - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 5, - defaultDuration: 5, - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '#tb', - } - const sortedTaskList = [{ content: 'this is another #bar' }, { content: 'Do something #foo' }, { content: 'Do something else #foo' }] - const res = byTagMode.processByTimeBlockTag(sortedTaskList, { blockList: timeBlocks, timeMap: timeMap, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList).toEqual(expect.arrayContaining(['* 00:20-00:25 Do something #foo #🕑 #tb'])) - expect(res.timeBlockTextList).toEqual(expect.arrayContaining(['* 00:25-00:30 Do something else #foo #🕑 #tb'])) - expect(res.timeBlockTextList).toEqual(expect.arrayContaining(['* 00:40-00:45 this is another #bar #🕑 #tb'])) - }) - test('should place tasks in a named timeblock and a plain task in a regular open area', () => { - // now test line which had no time attached - const timeBlocks = [ - { start: '00:00', end: '00:05', minsAvailable: 5, title: '' }, - { start: '00:20', end: '00:30', minsAvailable: 10, title: 'foo' }, - { start: '00:40', end: '00:50', minsAvailable: 10, title: 'bar' }, - ] - const timeMap = [ - { start: '00:00', busy: false, index: 1 }, - { start: '00:20', busy: '#foo 🕑', index: 5 }, - { start: '00:25', busy: '#foo 🕑', index: 6 }, - { start: '00:40', busy: '#bar 🕑', index: 10 }, - { start: '00:45', busy: '#bar 🕑', index: 11 }, - ] - const cfg = { - ...config, - nowStrOverride: '00:00', - workDayStart: '00:00', - intervalMins: 5, - defaultDuration: 5, - mode: 'BY_TIMEBLOCK_TAG', - timeblockTextMustContainString: '🕑', - } - const sortedTaskList = [{ content: 'this is another #bar' }, { content: 'Do something #foo' }, { content: 'Do something outside' }] - const res = byTagMode.processByTimeBlockTag(sortedTaskList, { blockList: timeBlocks, timeMap: timeMap, timeBlockTextList: [] }, cfg) - expect(res.timeBlockTextList).toEqual(expect.arrayContaining(['* 00:00-00:05 Do something outside #🕑'])) - expect(res.timeBlockTextList).toEqual(expect.arrayContaining(['* 00:20-00:25 Do something #foo #🕑'])) - expect(res.timeBlockTextList).toEqual(expect.arrayContaining(['* 00:40-00:45 this is another #bar #🕑'])) - }) -}) diff --git a/dwertheimer.EventAutomations/__tests__/timeblocking-taskSorting.test.js b/dwertheimer.EventAutomations/__tests__/timeblocking-taskSorting.test.js deleted file mode 100644 index be0db8fc4..000000000 --- a/dwertheimer.EventAutomations/__tests__/timeblocking-taskSorting.test.js +++ /dev/null @@ -1,108 +0,0 @@ -/* globals describe, expect, it, test, DataStore */ -// import { differenceInCalendarDays, endOfDay, startOfDay, eachMinuteOfInterval, formatISO9075 } from 'date-fns' -import * as tb from '../src/timeblocking-helpers' -import { getTasksByType, sortListBy } from '@helpers/sorting' -// import { isNullableTypeAnnotation } from '@babel/types' - -// Jest suite -describe('taskSorting', () => { - // testing this here because i am using it here and want to be sure it works - // and the signatures don't change - test('dwertheimer.TaskAutomations - getTasksByType ', () => { - const paragraphs = [ - { - type: 'open', - indents: 0, - content: 'test content', - rawContent: '* test content', - }, - { - type: 'scheduled', - indents: 0, - content: 'test content', - rawContent: '* test content', - }, - ] - const taskList = getTasksByType(paragraphs) - expect(taskList['open'].length).toEqual(1) - expect(taskList['scheduled'].length).toEqual(1) - expect(taskList['open'][0].content).toEqual(paragraphs[0].content) - }) - - test('dwertheimer.TaskAutomations - sortListBy alphabetical', () => { - const paragraphs = [ - { - type: 'open', - indents: 0, - content: 'test content', - rawContent: '* test content', - }, - ] - let taskList = getTasksByType(paragraphs) - let sorted = sortListBy(taskList['open'], 'content') - expect(sorted[0].content).toEqual(paragraphs[0].content) - // - paragraphs.push({ - type: 'open', - indents: 0, - content: 'a test content', - rawContent: '* a test content', - }) - taskList = getTasksByType(paragraphs) - sorted = sortListBy(taskList['open'], 'content') - expect(sorted[0].content).toEqual(paragraphs[1].content) - }) - test('dwertheimer.TaskAutomations - sortListBy priority (!,!!,!!!) if at the front of the line', () => { - const paragraphs = [ - { - type: 'open', - indents: 0, - content: '! test content', - rawContent: '* ! test content', - }, - ] - paragraphs.push({ - type: 'open', - indents: 0, - content: '!!! a test content !!!', - rawContent: '* !!! a test content', - }) - paragraphs.push({ - type: 'open', - indents: 0, - content: '!! a test content', - rawContent: '* !! a test content', - }) - const taskList = getTasksByType(paragraphs) - const sorted = sortListBy(taskList['open'], 'priority') - expect(sorted[0].content).toEqual(paragraphs[0].content) - expect(sorted[1].content).toEqual(paragraphs[2].content) - }) - test('dwertheimer.TaskAutomations - sortListBy ignores priority if not at the front of the line', () => { - const paragraphs = [ - { - type: 'open', - indents: 0, - content: 'test content !', - rawContent: '* test content !', - }, - ] - paragraphs.push({ - type: 'open', - indents: 0, - content: 'a test content !!!', - rawContent: '* a test content !!!', - }) - paragraphs.push({ - type: 'open', - indents: 0, - content: 'a test content !!', - rawContent: '* a test content !!', - }) - const taskList = getTasksByType(paragraphs) - const sorted = sortListBy(taskList['open'], 'priority') - expect(sorted[0].content).toEqual(paragraphs[0].content) - expect(sorted[1].content).toEqual(paragraphs[1].content) - expect(sorted[2].content).toEqual(paragraphs[2].content) - }) -}) diff --git a/dwertheimer.EventAutomations/src/NPEventBlocks.js b/dwertheimer.EventAutomations/src/NPEventBlocks.js deleted file mode 100644 index cf12cf024..000000000 --- a/dwertheimer.EventAutomations/src/NPEventBlocks.js +++ /dev/null @@ -1,392 +0,0 @@ -// @flow - -import { addMinutes } from 'date-fns' -import pluginJson from '../plugin.json' -import { log, logError, JSP, clo, logWarn, logDebug } from '@helpers/dev' -import { chooseHeading, chooseOption, showMessage, showMessageYesNoCancel } from '@helpers/userInput' -import { findHeading, getBlockUnderHeading } from '@helpers/NPParagraph' -import { smartPrependPara, smartAppendPara } from '@helpers/paragraph' -import { isReallyAllDay } from '@helpers/dateTime' -import { checkOrGetCalendar } from '@helpers/NPCalendar' - -export const hasCalendarLink = (line: string): boolean => /\!\[📅\]/.test(line) - -export type EventBlocksConfig = { - confirm: boolean, - eventLength: string, - removeDateText: boolean, - linkText: string, - showResultingTimeDate: boolean, - version?: string, - calendar?: string, -} - -type ConfirmedEvent = { - revisedLine: string, - originalLine: string, - dateRangeInfo: ParsedTextDateRange, - paragraph: TParagraph, - index: number, -} - -/** - * (not used because NotePlan seems to rewrite the text on every page load so this is useless) - * Replace the text of the calendar link with different text - * @param {string} line - the line to check and replace - * @param {string} replaceWith - the text to replace the calendar link text with - * @returns - */ -export const replaceCalendarLinkText = (line: string, replaceWith: string): string => { - const parts = line.split(':::') - if (parts.length === 5) { - parts[3] = replaceWith - - return parts.join(':::') - } else { - logDebug(pluginJson, `replaceCalendarLinkText: could not split/find 4 parts for link: ${line}`) - return line - } -} - -/** - * This code is just here to test that the date processing does what you expect. - */ -export function parseDateTextChecker() { - const tests = [ - 'schedule something for now', - 'today', - 'today at 3', - 'today at 8', - 'ten minutes from now', - 'in 20 minutes', - 'tomorrow', - 'tomorrow at 2', - 'tomorrow at 4pm', - 'today', - 'tomorrow', - 'sunday', - 'monday', - 'tuesday', - 'wednesday at noon', - 'wednesday at 12', - 'thursday at 5', - 'friday at 8', - 'saturday at 9', - 'june 29th', - 'june 29th at 2', - 'next week', - 'next month', - 'last week', - 'last month,', - ] - Editor.appendParagraph(`${new Date().toISOString()} - now ISO`, 'text') - Editor.appendParagraph(`${new Date().toString()} - now`, 'text') - tests.forEach((element) => { - const val = Calendar.parseDateText(element) - Editor.appendParagraph(`${val[0].start.toString()} - "${element}" ${isReallyAllDay(val[0]) ? 'allday' : ''}`, 'text') - // clo(val, `NPEventBlocks.parseDateTextChecker: Element`) - console.log(JSON.stringify(val)) - }) -} - -/** - * Helper to get the plugin settings or defaults - * Doesn't do much. This may be replaced when this moves to a @jgclark plugin - * @returns - */ -export function getPluginSettings(): EventBlocksConfig { - const settings = DataStore.settings - if (settings && Object.keys(settings)) { - return settings - } else { - return { - confirm: true, - eventLength: '30', - removeDateText: true, - linkText: '→', - showResultingTimeDate: true, - } - } -} - -/** - * Ask the user to choose the heading for the events, and then return the paragraph for the heading (we need it in order to find the block) - * @param {*} note - * @returns {TParagraph} - the paragraph object for the heading - */ -export async function chooseTheHeading(note: TNote): Promise { - const heading = await chooseHeading(note, false, false, false) - const headingPara = findHeading(note, heading) - return headingPara -} - -/** - * NotePlan may return multiple potential time Range objects for this particular line. Ask the user to choose the right one - * @param {*} text - * @param {*} potentials - * @returns {object} returns a Range++ object for the correct time range - */ -export async function confirmPotentialTimeChoice(text: string, potentials: any): Promise { - const opts = potentials.map((potential, i) => ({ - label: `${potential.start.toLocaleString()} (text: "${potential.text}")`, - value: i, - start: potential.start, - end: potential.end, - text: potential.text, - index: potential.index, - })) - const val = await chooseOption(`Which of these is "${text}"?`, opts, opts[0].value) - return potentials[val] -} - -/** - * - * @param {string} title - The title of the event - * @param {object} range {start, end, text, etc.} - * @param {*} config - * @returns {Promis} - */ -export async function createEvent(title: string, range: { start: Date, end: Date }, config: any): Promise { - /* NOTE: TODO: add in missing fields (eg calendar) - create( - title: string, - date: Date, - endDate: Date | void, - type: CalenderItemType, - isAllDay ?: boolean, - calendar ?: string, - isCompleted ?: boolean, - notes ?: string, - url ?: string, -): TCalendarItem; -*/ - const isAllDayEvent = isReallyAllDay(range) // make an educated guess about whether this was intended to be an all day event - //logDebug(pluginJson, `createEvent: ${title} allday:${isReallyAllDay(range)}`) - if (!isAllDayEvent && range.start === range.end) { - // if it's not an all day event, and the start and end are the same, then it's probably "at 12" or something, so we add time to the end to make it an event - range.end = addMinutes(range.start, config.eventLength || '30') - } else if (isAllDayEvent) { - range.end = addMinutes(range.end, -1) // parseDateText returns 12am one day to 12am the next day for a full day event - } - logDebug( - pluginJson, - `createEvent: creating: title:"${title}" start:${String(range.start)} end:${String(range.end)} isAllDayEvent:${String(isAllDayEvent)} calendar:${config.calendar}} `, - ) - const calendarItem = await CalendarItem.create(title, range.start, range.end || null, 'event', isAllDayEvent, config.calendar || '') - const result = await Calendar.add(calendarItem) - return result || null -} - -/** - * Find the paragraphs that contain event text which needs to be created. If time/date text is ambiguous - * then ask the user to choose the correct one. Return the ConfirmedEvent data for each line to be turned into an event - * Skips blank lines and title lines - * NOTE: Calendar.parseDateText does not correctly return "today at" so we try to fix that here - * @param {Array} paragraphBlock - * @param {EventBlocksConfig} config - * @returns {Promise>} - the list of unambiguous event info to create - */ -export async function confirmEventTiming(paragraphBlock: Array, config: EventBlocksConfig): Promise> { - const { confirm /*, removeDateText */ } = config - const confirmedEventData = [] - for (let i = 0; i < paragraphBlock.length; i++) { - const line = paragraphBlock[i] - if (hasCalendarLink(line.content) || line.type === 'title' || line.content === '') { - logDebug(pluginJson, `Skipping line: ${line.content}`) - } else { - // create a regex to replace the words "today at" (or today @) with or whithout spaces around the @ with " at " - let lineText = line.content.replace(/today ?(at|\@) ?/gi, ' at ') // Calendar.parseDateText does not correctly return "today at" so we try to fix that here - // replace "tomorrow" with the ISO date for tomorrow - lineText = lineText.replace(/tomorrow/gi, new Date(Date.now() + 86400000).toLocaleDateString()) - const potentials = Calendar.parseDateText(lineText) //returns {start: Date, end: Date} - clo(potentials, `confirmEventTiming Calendar.parseDateText responses for "${line.content}"`) - if (potentials.length > 0) { - let chosenDateRange = { ...potentials[0] } - if (potentials.length > 1) { - if (confirm && line.content.length) { - const dateRangeItem = await confirmPotentialTimeChoice(line.content, potentials) - chosenDateRange = { ...dateRangeItem } - } - } - // Remove the timing part from the line now that we have a link - // Calendar.parseDateText = [{"start":"2022-06-24T13:00:00.000Z","end":"2022-06-24T13:00:00.000Z","text":"friday at 8","index":0}] - let revisedLine = lineText - .replace(chosenDateRange?.text?.length ? chosenDateRange.text : '', '') - .replace(/\s{2,}/g, ' ') - .trim() - if (revisedLine.length === 0) { - revisedLine = '...' // If the line was all a date description, we need something to show - logDebug(pluginJson, `processTimeLines could not separate line timing from rest of text="${revisedLine}"`) - } - confirmedEventData.push({ originalLine: line.content, revisedLine, dateRangeInfo: chosenDateRange, paragraph: line, index: i }) - } else { - // do nothing with this line? - logDebug(pluginJson, `processTimeLines no times found for "${line.content}"`) - } - } - } - return confirmedEventData -} - -/** - * Take in a array of TParagraphs (block of lines), loop through and create events for the ones that should be events - * Make changes to the paragraph lines and return all changed paragraphs as an array so they can be updated in one go - * @param {Array} block - * @param {{[string]:any}} config - * @param {string} calendar - the calendar to use for the events or blank to ask - * @returns {{paragraph:{TParagraph}, time:{Range++ object with start, end | null}}} - */ -export async function processTimeLines(paragraphBlock: Array, config: EventBlocksConfig, calendar?: string = ''): Promise> { - // parseDateTextChecker() - const timeLines = [] - try { - // First, we need to get all the data necessary to create this event, including user input - // before we can show a status bar - clo(paragraphBlock, `processTimeLines: paragraphBlock contains ${paragraphBlock.length} lines`) - const eventsToCreate = (await confirmEventTiming(paragraphBlock, config)) || [] - // Now that we have all the info we need, we can create the events with a status bar - config.calendar = (await checkOrGetCalendar(calendar, true)) || calendar || '' - CommandBar.showLoading(true, `Creating Events:\n(${0}/${eventsToCreate.length})`) - await CommandBar.onAsyncThread() - logDebug(pluginJson, `eventsToCreate.length=${eventsToCreate.length}`) - for (let j = 0; j < eventsToCreate.length; j++) { - const item = eventsToCreate[j] - clo(config, `processTimeLines: config`) - clo(item, `processTimeLines: item to create`) - CommandBar.showLoading(true, `Creating Events:\n(${j}/${eventsToCreate.length})`) - const range = { start: item.dateRangeInfo.start, end: item.dateRangeInfo.end } - const eventWithoutLink = await createEvent(item.revisedLine, range, config) - if (eventWithoutLink && eventWithoutLink.id !== null && typeof eventWithoutLink.id === 'string') { - logDebug(pluginJson, `created event ${eventWithoutLink.title}`) - const { id, title } = eventWithoutLink - const event = id ? await Calendar.eventByID(id) : null - if (event) { - clo(event, `Created event:`) - let { endDate } = event - const { calendarItemLink, date, isAllDay } = event - // work around the fact that eventByID sends back the wrong endDate for all day events - if (isAllDay && endDate) endDate = addMinutes(endDate, -1) // https://discord.com/channels/763107030223290449/1011492449769762836/1011492451460059246 - logDebug(pluginJson, `processTimeLines event=${title} event.calendarItemLink=${calendarItemLink}`) - const startDateString = date.toLocaleString().split(', ')[0] - const endDateString = endDate ? endDate.toLocaleString().split(', ')[0] : '' - const dateStr = isAllDay ? `${startDateString}${startDateString === endDateString ? '' : `-${endDateString}`}` : date.toLocaleString() - logDebug( - pluginJson, - `noDuration: ${String(startDateString === endDateString)} dateStr = "${dateStr}" endDate: ${endDate ? endDate.toString() : ''} ${ - endDate ? endDate.toLocaleString() : '' - }`, - ) - const created = config.showResultingTimeDate ? ` ${dateStr}` : '' - const editedLink = config.showResultingTimeDate ? replaceCalendarLinkText(calendarItemLink, created) : calendarItemLink - item.paragraph.content = `${config.removeDateText ? item.revisedLine : item.originalLine} ${editedLink}` - // timeLines.push({ time: item.dateRangeInfo, paragraph: item.paragraph, event }) - timeLines.push(item.paragraph) - //logDebug(pluginJson, `processTimeLines timeLines.length=${timeLines.length}`) - } - } else { - logDebug(pluginJson, `processTimeLines no event created for "${item.revisedLine}"`) - } - // confirmPotentialTimeChoices() - // CreateEvents() // + tag created events - } - await CommandBar.onMainThread() - CommandBar.showLoading(false) - logDebug(pluginJson, `processTimeLines RETURNING ${timeLines.length} processed lines`) - } catch (error) { - logError(pluginJson, `processTimeLines error=${JSP(error)}`) - } - return timeLines -} - -/** - * Create calendar events by writing (natural language) via prompt - * Plugin entrypoint for command: "/pevt - Prompt for Natural Language Event text" - * @author @dwertheimer - * @param {*} incoming - */ -export async function createEventPrompt(_heading?: string) { - try { - logDebug(pluginJson, `createEventPrompt running; heading set to: "${_heading || ''}"`) - // prompt user for event text - const eventText = await CommandBar.showInput('Event text:', 'Process Text') - if (eventText) { - // parse event text - const config = getPluginSettings() - config.confirm = true - // $FlowIgnore - const timeLines = await processTimeLines([{ content: eventText, type: 'text' }], config) - if (timeLines.length) { - // Editor.updateParagraphs(timeLines) - clo(timeLines, `createEventPrompt timeLines after creation:\n${timeLines.length}`) - const answer = _heading - ? 'Yes' - : await showMessageYesNoCancel( - `Created ${timeLines.length} event. Insert a link to the new event in the active note under a heading?`, - ['Yes', 'No'], - 'Insert Event Link', - ) - if (answer === 'Yes') { - const title = _heading ?? (Editor.note ? await chooseHeading(Editor, true, true, false) : '') - logDebug(pluginJson, `heading: "${title}"`) - switch (title) { - case '<>': - smartPrependPara(Editor, timeLines[0].content, 'text') - break - case '<>': - smartAppendPara(Editor, timeLines[0].content, 'text') - break - default: - Editor.addParagraphBelowHeadingTitle(timeLines[0].content, 'text', title, false, true) - break - } - } - } else { - logError(pluginJson, `No time lines found in "${eventText}"`) - await showMessage( - `Was not able to parse definitive date/time info for the text: "${eventText}". Have placed the text on the clipboard in case you want to edit and try it again.`, - ) - Clipboard.string = eventText - } - } - } catch (error) { - logError(pluginJson, `processTimeLines error=${JSP(error)}`) - } -} - -/** - * Create events from text in a note - * (plugin Entry point for "/cevt - Create Events") - * @param {*} heading - * @param {*} confirm - */ -export async function createEvents(heading: string = '', confirm?: string = 'yes', calendarName?: string = ''): Promise { - try { - logDebug(pluginJson, `createEvents running; heading="${heading}"; confirm="${confirm}"; calendarName="${calendarName}"`) - const note = Editor.note - if (note) { - const config = { ...DataStore.settings } - config.confirm = confirm === 'yes' || config.confirm - const headingPara = heading !== '' ? findHeading(note, heading, true) : await chooseTheHeading(note) - if (headingPara) { - const paragraphsBlock = getBlockUnderHeading(note, headingPara) - if (paragraphsBlock.length) { - const timeLines = await processTimeLines(paragraphsBlock, config, calendarName) - if (timeLines.length) { - Editor.updateParagraphs(timeLines) - } else { - logError(pluginJson, `No time lines found under heading: ${heading}`) - } - } - } else { - logDebug(pluginJson, `Could not find heading containing "${heading}"; headings in note:\n`) - const titles = note.paragraphs - .filter((p) => p.type === 'title') - .map((p) => p.content) - .join(`\n`) - clo(titles, `createEvents: titles in document were`) - } - } - } catch (error) { - logError(pluginJson, JSP(error)) - } -} diff --git a/dwertheimer.EventAutomations/src/NPTimeblocking.js b/dwertheimer.EventAutomations/src/NPTimeblocking.js deleted file mode 100644 index e8c3dc708..000000000 --- a/dwertheimer.EventAutomations/src/NPTimeblocking.js +++ /dev/null @@ -1,653 +0,0 @@ -// @flow - -/** - * WHERE AM I? - * TODO: update docs for limittotags, presets - * TODO: timeblocks need the filter DataStore.preference("timeblockTextMustContainString") - * * getTimeBlockingDefaults should read plugin.json and create the defaults - * * then validations should come from that file also - * * TODO: feedback if no items to timeblock - * impolement limitToTags[] but make it a textfilter regex - */ -import { addMinutes } from 'date-fns' -import pluginJson from '../plugin.json' -import { addTrigger } from '../../helpers/NPFrontMatter' -import type { SortableParagraphSubset } from '../../helpers/sorting' -import type { AutoTimeBlockingConfig } from './config' -import { - blockOutEvents, - getBlankDayMap, - getTimeBlockTimesForEvents, - removeDateTagsFromArray, - appendLinkIfNecessary, - getFullParagraphsCorrespondingToSortList, -} from './timeblocking-helpers' -import { - shouldRunCheckedItemChecksOriginal, - deleteParagraphsContainingString, - gatherAndPrepareTodos, - getConfig, - writeSyncedCopies, - insertItemsIntoNote, - getTodaysFilteredTodos, -} from './timeblocking-shared' -import { validateAutoTimeBlockingConfig } from './config' -import { getPresetOptions, setConfigForPreset } from './presets' -import type { IntervalMap, PartialCalendarItem } from './timeblocking-flow-types' -import { getTimedEntries, keepTodayPortionOnly } from '@helpers/calendar' -import { textWithoutSyncedCopyTag } from '@helpers/syncedCopies' -import { getEventsForDay } from '@helpers/NPCalendar' -import { getDateStringFromCalendarFilename, getTodaysDateHyphenated, getTodaysDateUnhyphenated, removeRepeats, removeDateTagsAndToday } from '@helpers/dateTime' -import { getTasksByType, sortListBy, isTask } from '@helpers/sorting' -import { showMessage, chooseOption } from '@helpers/userInput' -import { getTimeBlockString, isTimeBlockLine } from '@helpers/timeblocks' -import { JSP, clo, log, logError, logWarn, logDebug, clof } from '@helpers/dev' -import { checkNumber, checkWithDefault } from '@helpers/checkType' -import { getSyncedCopiesAsList } from '@helpers/NPSyncedCopies' -import { removeContentUnderHeading, removeContentUnderHeadingInAllNotes, selectedLinesIndex } from '@helpers/NPParagraph' -import { saveEditorIfNecessary } from '@helpers/editor' - -export const editorIsOpenToToday = (): boolean => { - const fileName = Editor.filename - if (fileName == null) { - return false - } - return getDateStringFromCalendarFilename(fileName) === getTodaysDateUnhyphenated() -} - -/** - * Scan note for user-entered timeblocks and return them as an array of Calendar Items - * @param {*} note - * @param {*} defaultDuration - * @returns - */ -function getExistingTimeBlocksFromNoteAsEvents(note: CoreNoteFields, defaultDuration: number): Array { - const timeBlocksAsEvents = [] - note.paragraphs.forEach((p) => { - if (isTimeBlockLine(p.content)) { - const timeblockDateRangePotentials = Calendar.parseDateText(p.content) - if (timeblockDateRangePotentials?.length) { - const e = timeblockDateRangePotentials[0] //use Noteplan/Chrono's best guess - // but this may not actually be a timeblock, so keep looking - const tbs = getTimeBlockString(p.content) - if (tbs && tbs.length > 0) { - const eventInfo = p.content.replace(tbs, '').trim() - timeBlocksAsEvents.push({ - title: eventInfo, - date: e.start, - endDate: e.end !== e.start ? e.end : addMinutes(e.start, defaultDuration), - type: 'event', - availability: 0, - }) - } - } - } - }) - return timeBlocksAsEvents -} - -/** - * Get a time map populated with the calendar events for the day - * @param {string} dateStr - * @param {number} intervalMins - * @param {AutoTimeBlockingConfig} config - * @returns - */ -async function getPopulatedTimeMapForToday(dateStr: string, intervalMins: number, config: AutoTimeBlockingConfig): Promise { - // const todayEvents = await Calendar.eventsToday() - const eventsArray = await getEventsForDay(dateStr) - const eventsWithStartAndEnd = getTimedEntries(eventsArray || []) - let eventsScheduledForToday = keepTodayPortionOnly(eventsWithStartAndEnd) - // remove the timebocks that NP wrote to the calendar and may not have been deleted yet due to latency - eventsScheduledForToday = eventsScheduledForToday.filter((e) => !e.notes.startsWith('NPTB:')) - clof(eventsScheduledForToday, `getPopulatedTimeMapForToday eventsScheduledForToday`, ['date', 'title'], true) - if (Editor) { - const duration = checkWithDefault(checkNumber, 60) - const userEnteredTimeblocks = getExistingTimeBlocksFromNoteAsEvents(Editor, duration) - eventsScheduledForToday = [...userEnteredTimeblocks, ...eventsScheduledForToday] - } - const blankDayMap = getBlankDayMap(parseInt(intervalMins)) - - // $FlowFixMe - [prop-missing] and [incompatible-variance] - const eventMap = blockOutEvents(eventsScheduledForToday, blankDayMap, config) - return eventMap -} - -export async function deleteCalendarEventsWithTag(tag: string, dateStr: string): Promise { - let dateString = dateStr - if (!dateStr) { - dateString = Editor.filename ? getDateStringFromCalendarFilename(Editor.filename) : null - } - if (dateString && tag) { - const eventsArray = (await getEventsForDay(dateString)) || [] - CommandBar.showLoading(true, 'Deleting Calendar Events') - await CommandBar.onAsyncThread() - for (let i = 0; i < eventsArray.length; i++) { - const event = eventsArray[i] - if (event?.title?.includes(tag)) { - // logDebug(pluginJson, `deleteCalendarEventsWithTag: deleting event ${event.title}`) - // clo(event, `deleteCalendarEventsWithTag; event=`) - await Calendar.remove(event) - CommandBar.showLoading(true, `Deleting Calendar Events\n(${i + 1}/${eventsArray.length})`, (i + 1) / eventsArray.length) - logDebug(pluginJson, `deleteCalendarEventsWithTag: deleted event: ${event.title}`) - } - } - await CommandBar.onMainThread() - CommandBar.showLoading(false) - } else { - await showMessage('deleteCalendarEventsWithTag could not delete events') - } -} - -/** - * Main function (called by multiple entry points) - * @param {AutoTimeBlockingConfig} config - * @param {Array} completedItems - items that were checked (we must be in the EditorWillSave hook) - * @returns - */ -export async function DELETEMEcreateTimeBlocksForTodaysTasks(config: AutoTimeBlockingConfig = getConfig(), completedItems: Array = []): Promise> { - logDebug(pluginJson, `Starting createTimeBlocksForTodaysTasks. Time is ${new Date().toLocaleTimeString()}`) - // clof(Editor.paragraphs, 'Editor.paragraphs', ['type', 'content'], true) - const { timeBlockTag, intervalMins, passBackResults } = config - if (shouldRunCheckedItemChecksOriginal(config)) addTrigger(Editor, 'onEditorWillSave', pluginJson['plugin.id'], 'onEditorWillSave') - const hypenatedDate = getTodaysDateHyphenated() - logDebug(pluginJson, `createTimeBlocksForTodaysTasks hypenatedDate=${hypenatedDate} Editor.paras=${Editor.paragraphs.length}`) - const date = getTodaysDateUnhyphenated() - logDebug(pluginJson, `createTimeBlocksForTodaysTasks date=${date}`) - const dateStr = Editor.filename ? getDateStringFromCalendarFilename(Editor.filename) : null - logDebug(pluginJson, `createTimeBlocksForTodaysTasks dateStr=${dateStr ?? 'null'} Editor.paras=${Editor.paragraphs.length}`) - if (dateStr && dateStr === date) { - logDebug(pluginJson, `createTimeBlocksForTodaysTasks dateStr=${dateStr} is today - starting Editor.paras=${Editor.paragraphs.length}`) - deleteParagraphsContainingString(Editor, timeBlockTag) - logDebug(pluginJson, `createTimeBlocksForTodaysTasks after deleteParagraphsContainingString(${timeBlockTag}) Editor.paras=${Editor.paragraphs.length}`) - - const todosParagraphs = await getTodaysFilteredTodos(config).filter( - (t) => t.filename !== Editor.filename || (t.filename === Editor.filename && !completedItems.find((c) => c.lineIndex === t.lineIndex)), - ) - logDebug(pluginJson, `Back from getTodaysFilteredTodos, ${todosParagraphs.length} potential items Editor.paras=${Editor.paragraphs.length}`) - // the following calls addBlockID and that must be called before any content changes are made that will not be saved - const todosWithLinksMaybe = appendLinkIfNecessary(todosParagraphs, config) - logDebug( - pluginJson, - `After appendLinkIfNecessary, ${todosWithLinksMaybe?.length ?? 0} potential items (may include headings or completed) Editor.paras=${Editor.paragraphs.length}`, - ) - const cleanTodayTodoParas = [...removeDateTagsFromArray(todosWithLinksMaybe)] - logDebug(pluginJson, `After removeDateTagsFromArray, ${cleanTodayTodoParas.length} potential items Editor.paras=${Editor.paragraphs.length}`) - const tasksByType = todosWithLinksMaybe.length ? getTasksByType(todosWithLinksMaybe, true) : null // puts in object by type of task and enriches with sort info (like priority) - // clo(tasksByType, 'createTimeBlocksForTodaysTasks: tasksByType') - logDebug( - pluginJson, - `After getTasksByType, ${tasksByType?.open.length ?? 0} OPEN items | ${tasksByType?.scheduled.length ?? 0} Scheduled (for today) items Editor.paras=${ - Editor.paragraphs.length - }`, - ) - // clo(tasksByType?.open, 'createTimeBlocksForTodaysTasks: tasksByType.open') - const openOrScheduledForToday = [...(tasksByType?.open ?? []), ...(tasksByType?.scheduled ?? [])] - if (openOrScheduledForToday) { - const sortedTodos = openOrScheduledForToday.length ? sortListBy(openOrScheduledForToday, '-priority') : [] - logDebug(pluginJson, `After sortListBy, ${sortedTodos.length} open items Editor.paras=${Editor.paragraphs.length}`) - // $FlowIgnore - if (timeBlockTag?.length) { - logDebug(pluginJson, `timeBlockTag: ("${timeBlockTag}"), Editor.paras=${Editor.paragraphs.length}`) - } else { - logError(pluginJson, `timeBlockTag was empty. That's not good. I told the user.`) - await showMessage( - `Your Event Automations settings have a blank field for timeBlockTag. I will continue, but the results probably won't be what you want. Please check your settings.`, - ) - } - const calendarMapWithEvents = await getPopulatedTimeMapForToday(dateStr, intervalMins, config) - // clo(calendarMapWithEvents, `calendarMapWithEvents: ${calendarMapWithEvents.length} items`) - logDebug( - pluginJson, - `After getPopulatedTimeMapForToday, ${calendarMapWithEvents.length} timeMap slots; last = ${JSON.stringify( - calendarMapWithEvents[calendarMapWithEvents.length - 1], - )} Editor.paras=${Editor.paragraphs.length}`, - ) - // logDebug(pluginJson, `sortedTodos[0]: ${sortedTodos[0].content} Editor.paras=${Editor.paragraphs.length}`) - // logDebug(pluginJson, `sortedParas[0]: ${sortedParas[0].content}`) - const eventsToTimeblock = getTimeBlockTimesForEvents(calendarMapWithEvents, sortedTodos, config) - logDebug(`\n\n>>>>>>> after getTimeBlockTimesForEvents <<<<<<<<<<<<\n\n`) - clo(eventsToTimeblock, `createTimeBlocksForTodaysTasks eventsToTimeblock`) - const { blockList, noTimeForTasks } = eventsToTimeblock - let { timeBlockTextList } = eventsToTimeblock - - clo(timeBlockTextList, `timeBlockTextList`) - logDebug( - pluginJson, - `After getTimeBlockTimesForEvents, blocks:\n\tblockList.length=${String(blockList?.length)} \n\ttimeBlockTextList.length=${String( - timeBlockTextList?.length, - )} Editor.paras=${Editor.paragraphs.length}`, - ) - logDebug(pluginJson, `After getTimeBlockTimesForEvents, Editor.paras=${Editor.paragraphs.length}`) - - logDebug(pluginJson, `About to insert ${String(timeBlockTextList?.length)} timeblock items into note Editor.paras=${Editor.paragraphs.length}`) - if (!String(config.timeBlockHeading)?.length) { - await showMessage(`You need to set a time block heading title in the plugin settings`) - return - } else { - if (noTimeForTasks && Object.keys(noTimeForTasks).length) { - // removeContentUnderHeading(Editor, config.timeBlockHeading, false, true) -- too dangerous, will delete stuff people write underneath - if (!timeBlockTextList) timeBlockTextList = [] - Object.keys(noTimeForTasks).forEach((key) => - noTimeForTasks[key].forEach( - (p) => - timeBlockTextList && - timeBlockTextList.push( - `+ No time ${key === '_' ? 'available' : `in timeblock *${key}*`} for task: **- ${textWithoutSyncedCopyTag(removeRepeats(removeDateTagsAndToday(p.content)))}** ${ - config.timeBlockTag - }`, - ), - ), - ) - } - // double check that we are not creating any synced lines by accident - timeBlockTextList = timeBlockTextList?.map((t) => textWithoutSyncedCopyTag(t)) ?? [] - clo( - timeBlockTextList, - `getTimeBlockTimesForEvents Before writing: Editor.paras=${Editor.paragraphs.length} Editor.note.paras=${Editor.note?.paragraphs.length || 0}; timeBlockTextList=`, - ) - await insertItemsIntoNote(Editor, timeBlockTextList, config.timeBlockHeading, config.foldTimeBlockHeading, config) - } - - logDebug(pluginJson, `\n\nAUTOTIMEBLOCKING SUMMARY:\n\n`) - logDebug(pluginJson, `After cleaning, ${tasksByType?.open?.length ?? 0} open items`) - logDebug(pluginJson, `createTimeBlocksForTodaysTasks inserted ${String(timeBlockTextList?.length)} items Editor.paras=${Editor.paragraphs.length}`) - - if (config.createSyncedCopies && todosWithLinksMaybe?.length) { - logDebug( - pluginJson, - `createSyncedCopies is true, so we will create synced copies of the todosParagraphs: ${todosParagraphs.length} timeblocks Editor.paras=${Editor.paragraphs.length}`, - ) - clof(todosParagraphs, `createTimeBlocksForTodaysTasks todosParagraphs`, ['rawContent', 'raw', 'filename'], true) - clof(sortedTodos, `createTimeBlocksForTodaysTasks sortedTodos`, ['rawContent', 'raw', 'filename'], true) - const sortedParas = getFullParagraphsCorrespondingToSortList(todosParagraphs, sortedTodos).filter((p) => p.filename !== Editor.filename) - clof(sortedParas, `createTimeBlocksForTodaysTasks sortedParas`, ['filename', 'content'], true) - const sortedParasExcludingCurrentNote = sortedParas.filter((p) => p.filename !== Editor.filename) - clof(sortedParasExcludingCurrentNote, `createTimeBlocksForTodaysTasks sortedParasExcludingCurrentNote`, ['filename', 'content'], true) - // $FlowFixMe - await writeSyncedCopies(...sortedParasExcludingCurrentNote, { runSilently: true, ...config }) - } - return passBackResults ? timeBlockTextList : [] - } else { - logDebug(pluginJson, 'No todos/references marked for >today Editor.paras=${Editor.paragraphs.length}') - if (!passBackResults) { - await showMessage(`No todos/references marked for >today`) - } - } - } else { - if (!passBackResults) { - // logDebug(pluginJson,`You need to be in Today's Calendar Note to use this function Editor.paras=${Editor.paragraphs.length}`) - await showMessage(`You need to be in Today's Calendar Note to use this function`) - } - } - return [] -} - -/** - * Write a list of synced copies of today items (in the references section) to the Editor - * (entry point for /writeSyncedCopies) - */ -export async function insertSyncedCopiesOfTodayTodos(passBackResults?: string): Promise { - try { - logDebug(pluginJson, `insertSyncedCopiesOfTodayTodos running, passBackResults:${String(passBackResults)}`) - const config = await getConfig() - clo(config, 'insertSyncedCopiesOfTodayTodos config') - // if (!editorIsOpenToToday()) await Editor.openNoteByDate(new Date(), false) //open editor to today - logDebug(pluginJson, `insertSyncedCopiesOfTodayTodos before saveEditorIfNecessary`) - if (Editor) await removeContentUnderHeading(Editor, String(config.syncedCopiesTitle), true, true) - await saveEditorIfNecessary() - logDebug(pluginJson, `insertSyncedCopiesOfTodayTodos after saveEditorIfNecessary`) - const start = Editor.selection?.start // try to keep it from scrolling to end of doc - const todosParagraphs = await getTodaysFilteredTodos(config, true) - clof(todosParagraphs, 'insertSyncedCopiesOfTodayTodos todosParagraphs', ['filename', 'type', 'content'], true) - const sortedParasExcludingCurrentNote = sortListBy( - todosParagraphs.filter((p) => p.filename !== Editor.filename), - 'content', - ) - clof(sortedParasExcludingCurrentNote, 'insertSyncedCopiesOfTodayTodos sortedParasExcludingCurrentNote ${sortedParasExcludingCurrentNote.length} items', [ - 'filename', - 'type', - 'content', - ]) - if (passBackResults && /[Yy]es/.test(passBackResults)) { - // called from a template, so send a string back - const syncedList = getSyncedCopiesAsList(sortedParasExcludingCurrentNote) - logDebug(`insertSyncedCopiesOfTodayTodos`, `sending ${syncedList.length} synced line items to template`) - return syncedList.join('\n') - } - await writeSyncedCopies(sortedParasExcludingCurrentNote, config) - await saveEditorIfNecessary() - if (start) { - Editor.select(0, 0) - } - } catch (error) { - logError(pluginJson, `insertSyncedCopiesOfTodayTodos error: ${JSP(error)}`) - } -} - -/** - * Remove previously written Time Blocks (written by this plugin) in the Editor - * (entry point for /removeTimeBlocks) - */ -export async function removeTimeBlocks(note: TNote | null = null): Promise { - try { - logDebug(pluginJson, `removeTimeBlocks running`) - await removeContentUnderHeading(note || Editor, String(DataStore.settings.timeBlockHeading), false, false) - } catch (error) { - logError(pluginJson, `removeTimeBlocks error: ${JSP(error)}`) - } -} - -/** - * Remove all previously written synced copies of today items in all notes - * (entry point for /removePreviousTimeBlocks) - * @param {boolean} runSilently - whether to show CommandBar popups - you should set it to 'yes' when running from a template - */ -export async function removePreviousTimeBlocks(runSilently: string = 'no'): Promise { - try { - logDebug(pluginJson, `removePreviousTimeBlocks running`) - const { timeBlockHeading } = DataStore.settings - await removeContentUnderHeadingInAllNotes(['calendar'], timeBlockHeading, false, runSilently) - } catch (error) { - logError(pluginJson, `removePreviousTimeBlocks error: ${JSP(error)}`) - } -} - -/** - * Insert todos marked >today into the editor - * (entry point for /atb) - * @param {*} note - */ -export async function insertTodosAsTimeblocks(/* note: TNote */): Promise { - try { - logDebug(pluginJson, `====== /atb =======\nStarting insertTodosAsTimeblocks`) - if (!editorIsOpenToToday()) await Editor.openNoteByDate(new Date(), false) //open editor to today - const config = await getConfig() - clo(config, 'atb config') - if (config) { - logDebug(pluginJson, `Config found. Calling createTimeBlocksForTodaysTasks`) - await createTimeBlocksForTodaysTasks(config) - } else { - logDebug(pluginJson, `insertTodosAsTimeblocks: stopping after config create`) - } - } catch (error) { - logError(pluginJson, `insertTodosAsTimeblocks error: ${JSP(error)}`) - } -} - -export async function insertTodosAsTimeblocksWithPresets(/* note: TNote */): Promise { - // logDebug(pluginJson,`====== /atbp =======\nStarting insertTodosAsTimeblocksWithPresets`) - // if (!editorIsOpenToToday()) await Editor.openNoteByDate(new Date(), false) //open editor to today - let config = await getConfig() - if (config) { - // logDebug(pluginJson,`Config found. Checking for presets`) - if (config.presets && config.presets.length) { - const options = getPresetOptions(config.presets) - const presetIndex = await chooseOption('Choose an AutoTimeBlocking Preset:', options, -1) - const overrides = config.presets ? config.presets[presetIndex] : [] - // logDebug(pluginJson,`Utilizing preset: ${JSON.stringify(config.presets[presetIndex])}`) - config = setConfigForPreset(config, overrides) - try { - await validateAutoTimeBlockingConfig(config) // check to make sure the overrides were valid - } catch (error) { - await showMessage(error) - // logDebug(pluginJson,`insertTodosAsTimeblocksWithPresets: invalid config: ${error}`) - } - await createTimeBlocksForTodaysTasks(config) - } else { - await showMessage('No presets found. Please read docs.') - } - } else { - // logDebug(pluginJson,`insertTodosAsTimeblocksWithPresets: no config`) - } -} - -/** - * Mark task on current line done and run /atb to re-create timeblocks - * Plugin entrypoint for command: "/mdatb" - * @param {*} incoming - */ -export async function markDoneAndRecreateTimeblocks(incoming: string | null = null) { - try { - logDebug(pluginJson, `markDoneAndRecreateTimeblocks running with incoming:${String(incoming)}`) - if (Editor?.selection && Editor?.paragraphs) { - // const updatedParas = [] - const [startIndex, endIndex] = selectedLinesIndex(Editor.selection, Editor.paragraphs) - if (endIndex >= startIndex) { - for (let index = startIndex; index <= endIndex; index++) { - const para = Editor.paragraphs[index] - if (para) { - // logDebug(pluginJson, `markDoneAndRecreateTimeblocks: paragraph[${index}] of ${startIndex} to ${endIndex}: "${para.content || ''}"`) - if (para && isTask(para)) { - // clo(para, `markDoneAndRecreateTimeblocks: before update paragraph[${index}]`) - para.type = 'done' - if (Editor) { - Editor.updateParagraph(para) - // clo(para, `markDoneAndRecreateTimeblocks: para after update paragraph[${index}]`) - // clo(Editor?.paragraphs[para.lineIndex], `markDoneAndRecreateTimeblocks: note.paragraphs[${index}]`) - } else { - logError(pluginJson, `markDoneAndRecreateTimeblocks: no Editor`) - } - } - } - } - await insertTodosAsTimeblocks() - } else { - logDebug(pluginJson, `markDoneAndRecreateTimeblocks: no selection`) - } - } - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * Initializes the function execution and logs the start time. It also checks - * and adds a trigger for checked items if necessary, and validates the time - * block tag configuration. - * - * @param {AutoTimeBlockingConfig} config - The configuration object for auto time blocking. - * @param {Object} pluginJson - Plugin metadata for logging purposes. - */ -function initializeAndLogStart(config: AutoTimeBlockingConfig): void { - const { timeBlockTag } = config - - // Log the start of the operation with the current time - logDebug(pluginJson, `Starting createTimeBlocksForTodaysTasks. Time is ${new Date().toLocaleTimeString()}`) - - // Validate the presence of a time block tag in the configuration - if (!timeBlockTag || typeof timeBlockTag !== 'string' || timeBlockTag.length === 0) { - logError(pluginJson, `timeBlockTag was empty. That's not good. I told the user.`) - showMessage( - `Your Event Automations settings have a blank field for timeBlockTag. I will continue, but the results probably won't be what you want. Please check your settings.`, - ) - } -} - -/** - * Categorizes tasks by their type (open or scheduled) and sorts them by priority. - * This organization is necessary for preparing the tasks for time block calculation. - * - * @param {Array} todosWithLinks - The list of todo items, potentially with appended links. - * @param {Object} pluginJson - Plugin metadata for logging purposes. - * @returns {Array} - The sorted list of todo items, ready for time block allocation. - */ -function categorizeAndSortTasks(todosWithLinks: $ReadOnlyArray, config: AutoTimeBlockingConfig): Array { - if (todosWithLinks.length === 0) { - logDebug(pluginJson, `No todos to categorize and sort.`) - return [] - } - - // Categorize tasks by type and enrich them with sorting information - const tasksByType = getTasksByType(todosWithLinks, true) - logDebug(pluginJson, `After getTasksByType, categorized tasks by type. Open tasks=${tasksByType.open.length} | Scheduled tasks=${tasksByType.scheduled.length}`) - - // Combine open and scheduled tasks, then sort by priority - const openOrScheduledForToday = [...(tasksByType.open ?? []), ...(tasksByType.scheduled ?? [])] - clof(openOrScheduledForToday, `categorizeAndSortTasks openOrScheduledForToday`, ['filename', 'type', 'content'], true) - const sortKeys = config.mode === 'MANUAL_ORDERING' ? ['lineIndex'] : ['-priority', 'filename', '-duration'] - const sortedTodos = sortListBy(openOrScheduledForToday, sortKeys) - clof(sortedTodos, `categorizeAndSortTasks sortedTodos`, ['priority', 'filename', 'duration', 'content'], true) - logDebug(pluginJson, `After sortListBy, sorted ${sortedTodos.length} tasks by priority.`) - - return sortedTodos -} - -/** - * Handles scenarios where no todo items are marked for today or when specific - * results are expected but not found. Shows messages to the user based on the - * configuration and outcomes of the time blocking process, and returns the - * appropriate results. - * - * @param {boolean} passBackResults - Flag indicating whether to pass back results. - * @param {?Array} timeBlockTextList - The list of time block text entries, if any. - * @param {Object} pluginJson - Plugin metadata for logging and debugging. - * @returns {Promise>} - The results to be passed back, if any. - */ -function handleNoTodosOrResults(passBackResults: boolean, timeBlockTextList: ?Array): Array { - // Check if there are no todos or results to pass back - if (!passBackResults) { - const message = - timeBlockTextList && timeBlockTextList.length > 0 - ? `Processed ${timeBlockTextList.length} time blocks for today.` - : 'No todos/references marked for >today or no time blocks created.' - - logDebug(pluginJson, message) - } - - // Return the results depending on whether this is being called by a template or not - return passBackResults ? timeBlockTextList || [] : [] -} - -/** - * Inserts the prepared time block text entries into the note under a specified - * heading, applies specified formatting options, and logs a summary of the - * autotimeblocking operation. Optionally creates synced copies of todo items - * if required by the configuration. - * - * @param {Array} timeBlockTextList - The list of text entries for time blocks. - * @param {AutoTimeBlockingConfig} config - The configuration object for auto time blocking. - * @param {Object} pluginJson - Plugin metadata for logging and debugging. - * @param {Array} todosWithLinksMaybe - The list of todo items with potential links appended. - * @returns {Promise} - */ -async function insertAndFinalizeTimeBlocks( - timeBlockTextList: Array, - config: AutoTimeBlockingConfig, - pluginJson: Object, - todosWithLinksMaybe: Array, -): Promise { - // Insert time block text entries into the note - await insertItemsIntoNote(Editor, timeBlockTextList, config.timeBlockHeading, config.foldTimeBlockHeading, config) - logDebug(pluginJson, `Inserted ${timeBlockTextList.length} timeblock items into note`) - - // Log autotimeblocking summary - logDebug(pluginJson, `\n\nAUTOTIMEBLOCKING SUMMARY:\n\n`) - logDebug(pluginJson, `Inserted ${timeBlockTextList.length} items`) - - // Create synced copies of todo items if configured - // DELETEME: COMMENTING OUT FOR NOW (REMOVING THIS FEATURE WHICH MADE NO SENSE) DELETE THIS COMMENT AFTER A WHILE 2024-02-23 - // if (config.createSyncedCopies && todosWithLinksMaybe.length) { - // await createSyncedCopies(todosWithLinksMaybe, config) - // logDebug(pluginJson, `Created synced copies of todos`) - // } - - // Check and add trigger for checked items if configured to do so - if (shouldRunCheckedItemChecksOriginal(config)) { - addTrigger(Editor, 'onEditorWillSave', pluginJson['plugin.id'], 'onEditorWillSave') - } -} - -/** - * Generates time blocks for tasks by creating a populated time map for the day, - * calculating time blocks for events based on this map and the sorted tasks, and - * preparing the list of text entries for these time blocks. Special handling is - * included for tasks that couldn't be allocated time. - * - * @param {Array} sortedTodos - The sorted list of todo items for today. - * @param {string} dateStr - The date string for today, used to identify relevant events. - * @param {AutoTimeBlockingConfig} config - The configuration object for auto time blocking. - * @param {Object} pluginJson - Plugin metadata for logging and debugging. - * @returns {Promise<{blockList: Array, noTimeForTasks: Object, timeBlockTextList: Array}>} - * An object containing the lists of time block text entries and tasks with no allocated time. - */ -async function generateTimeBlocks( - sortedTodos: Array, - dateStr: string, - config: AutoTimeBlockingConfig, - pluginJson: Object, -): Promise<{ blockList: Array, noTimeForTasks: Object, timeBlockTextList: Array }> { - // Generate a populated time map for today - const calendarMapWithEvents = await getPopulatedTimeMapForToday(dateStr, config.intervalMins, config) - logDebug(pluginJson, `generateTimeBlocks: After getPopulatedTimeMapForToday, ${calendarMapWithEvents.length} timeMap slots`) - clof(sortedTodos, `generateTimeBlocks sortedTodos`, ['lineIndex', 'content'], true) - // Calculate time blocks for events based on the populated time map and sorted todo tasks - const eventsToTimeblock = getTimeBlockTimesForEvents(calendarMapWithEvents, sortedTodos, config) - logDebug( - pluginJson, - `generateTimeBlocks after getTimeBlockTimesForEvents; eventsToTimeblock.timeMap.length=${eventsToTimeblock.timeMap.length}, eventsToTimeblock.blockList.length=${ - eventsToTimeblock.blockList.length - } eventsToTimeblock.timeBlockTextList.length=${eventsToTimeblock?.timeBlockTextList?.toString() || ''}`, - ) - // Prepare the list of text entries for time blocks - const { blockList, noTimeForTasks } = eventsToTimeblock - let { timeBlockTextList } = eventsToTimeblock - - // Handle tasks with no allocated time - if (noTimeForTasks && Object.keys(noTimeForTasks).length) { - if (!timeBlockTextList) timeBlockTextList = [] - Object.keys(noTimeForTasks).forEach((key) => - noTimeForTasks[key].forEach((task) => - timeBlockTextList ? timeBlockTextList.push(`+ No time ${key === '_' ? 'available' : `in timeblock *${key}*`} for task: **- ${task.content}** ${config.timeBlockTag}`) : [], - ), - ) - } - - // Ensure no synced lines are created by accident - timeBlockTextList = timeBlockTextList?.map((t) => textWithoutSyncedCopyTag(t)) || [] - - return { blockList: blockList ?? [], noTimeForTasks, timeBlockTextList } -} - -/** - * Main function for creating time blocks for today's tasks, utilizing modular functions - * for improved maintainability and readability. - * - * @param {AutoTimeBlockingConfig} config - The configuration object for auto time blocking, with defaults provided by getConfig(). - * @param {Array} completedItems - Items that were checked, typically provided in the EditorWillSave hook context. - * @returns {Promise>} - An optional array of strings to be passed back, depending on the configuration. - */ -export async function createTimeBlocksForTodaysTasks(config: AutoTimeBlockingConfig = getConfig(), completedItems: Array = []): Promise> { - // Step 1: Initialize and log start - initializeAndLogStart(config) - - // Step 2: Fetch and prepare todos - const cleanTodayTodoParas = await gatherAndPrepareTodos(config, completedItems) - - // Step 3: Categorize and sort tasks - const sortedTodos = categorizeAndSortTasks(cleanTodayTodoParas, config) - - // Step 4: Generate time blocks - const dateStr = getDateStringFromCalendarFilename(Editor.filename) // Assuming this utility function exists and is accurate - if (!dateStr) { - return handleNoTodosOrResults(config.passBackResults || false, null) - } - - const { blockList, noTimeForTasks, timeBlockTextList } = await generateTimeBlocks(sortedTodos, dateStr, config) - - // Check if time blocks were successfully generated - if (blockList.length === 0 && Object.keys(noTimeForTasks).length === 0) { - // If no blocks were created and no tasks are without time, handle accordingly - return handleNoTodosOrResults(config.passBackResults || false, null) - } - - // Step 5: Insert and finalize time blocks - await insertAndFinalizeTimeBlocks(timeBlockTextList, config, pluginJson, sortedTodos) - // Check and add trigger for checked items if configured to do so - if (shouldRunCheckedItemChecksOriginal(config)) { - addTrigger(Editor, 'onEditorWillSave', pluginJson['plugin.id'], 'onEditorWillSave') - } - - // Step 6: Handle no todos or results scenario and return results - return handleNoTodosOrResults(config.passBackResults || false, timeBlockTextList) -} diff --git a/dwertheimer.EventAutomations/src/byTagMode.js b/dwertheimer.EventAutomations/src/byTagMode.js deleted file mode 100644 index 82215aa00..000000000 --- a/dwertheimer.EventAutomations/src/byTagMode.js +++ /dev/null @@ -1,233 +0,0 @@ -// @flow - -// import pluginJson from '../plugin.json' -import { sortListBy } from '../../helpers/sorting' -import type { AutoTimeBlockingConfig } from './config' -import type { OpenBlock, ParagraphWithDuration, TimeBlocksWithMap } from './timeblocking-flow-types' -import { filterTimeMapToOpenSlots, findTimeBlocks, matchTasksToSlots, namedTagExistsInLine, splitItemsByTags } from './timeblocking-helpers' - -import { JSP, clo, log, logError, logWarn, logDebug, clof, deepCopy } from '@helpers/dev' -/** - * Processes the tasks that have a named tag in them (e.g., @work or #work) - * and schedules them within the specified time blocks. - * - * @param {Array} sortedTaskList - The list of tasks sorted by some criteria. - * @param {TimeBlockWithMap} tmb - The current time block with map. - * @param {Config} config - Configuration options for processing. - * @returns {TimeBlockWithMap} - The updated time block with map after processing. - */ -export function processByTimeBlockTag(sortedTaskList: Array, tmb: TimeBlocksWithMap, config: AutoTimeBlockingConfig): TimeBlocksWithMap { - // Destructure the initial time block with map structure - const { blockList, timeMap } = tmb - - // Process tasks by matching them to named time blocks based on tags - const { newBlockList, unprocessedTasks, results, noTimeForTasks } = processTasksByTimeBlockTag(sortedTaskList, blockList || [], timeMap, config) - - // Handle the unprocessed tasks according to the specified orphanTaggedTasks strategy - const unprocessedTasksResult = handleUnprocessedTasks(unprocessedTasks, noTimeForTasks, config, newBlockList, timeMap) - - // Combine results from named blocks processing and final processing - const combinedResults = [...results, unprocessedTasksResult] - const combinedNoTimeForTasks = { ...noTimeForTasks, ...unprocessedTasksResult.noTimeForTasks } - - // Prepare the final return structure - return { - noTimeForTasks: combinedNoTimeForTasks, - timeMap, - blockList: newBlockList, - timeBlockTextList: combinedResults.reduce((acc, currentValue) => acc.concat(currentValue.timeBlockTextList), []).sort(), - } -} - -/** - * Handles the rest of the non-named-block/unprocessed tasks according to the specified orphanTaggedTasks strategy in the config. - * - * @param {Array} unprocessedTasks - List of tasks that remain unprocessed. - * @param { [key: string]: Array } noTimeForTasks - Object containing tasks for which no time could be found, keyed by block title. - * @param {AutoTimeBlockingConfig} config - Configuration options for processing. - * @param {Array} newBlockList - The list of new blocks after processing. - * @param {Array} timeMap - The current time map. - * @returns {Object} - Returns the final result of matching tasks to slots, including unprocessed tasks handled as per config. - */ -export function handleUnprocessedTasks( - unprocessedTasks: Array, - noTimeForTasks: { [key: string]: Array }, - config: AutoTimeBlockingConfig, - newBlockList: Array, - timeMap: Array, -): TimeBlocksWithMap { - let finalUnprocessedTasks = unprocessedTasks || [] - const noTimeTasks = Object.values(noTimeForTasks)?.flat() || [] - clof(noTimeTasks, `handleUnprocessedTasks noTimeTasks=`, ['content', 'duration'], true) - - switch (config.orphanTagggedTasks) { - case 'IGNORE_THEM': - // If ignoring, do nothing further with noTimeTasks - break - case "OUTPUT_FOR_INFO (but don't schedule them)": - // If outputting for info, log or store these tasks separately (not shown here) - break - case 'SCHEDULE_ELSEWHERE_LAST': - finalUnprocessedTasks = [...finalUnprocessedTasks, ...noTimeTasks] - break - case 'SCHEDULE_ELSEWHERE_FIRST': - finalUnprocessedTasks = [...noTimeTasks, ...finalUnprocessedTasks] - break - } - - config.mode = 'PRIORITY_FIRST' - clof(finalUnprocessedTasks, `handleUnprocessedTasks finalUnprocessedTasks=`, 'content', true) - - return matchTasksToSlots(finalUnprocessedTasks, { blockList: newBlockList, timeMap }, config) -} - -/** - * Processes tasks by matching them to named time blocks based on tags. - * - * @param {Array} sortedTaskList - The list of tasks sorted by some criteria. - * @param {Array} blockList - The current list of time blocks. - * @param {Array} timeMap - The current time map. - * @param {Config} config - Configuration options for processing. - * @returns {Object} - Returns an object containing the updated block list, unprocessed tasks, results, and no time for tasks. - */ -export function processTasksByTimeBlockTag(sortedTaskList: Array, blockList: Array, timeMap: Array, config: AutoTimeBlockingConfig): Object { - let newBlockList = [...(blockList || [])] - let results = [] - let timeBlockTextList: any = [] - const noTimeForTasks = {} - - // MOVE THIS TO ITS OWN FUNCTION - // Split tasks into matched and unmatched based on tags - clo(config.timeframes, `processTasksByTimeBlockTag config.timeframes=`) - clo(blockList, `processTasksByTimeBlockTag blockList=`) - const { matched, unmatched } = splitItemsByTags(sortedTaskList, config.timeframes || {}) - let unprocessedTasks = unmatched || [] // tasks that do not match a timeframe will flow through to the next processing step - clof(matched, `processTasksByTimeBlockTag matched=`, null, true) - clof(unmatched, `processTasksByTimeBlockTag unmatched=`, ['content'], true) - const keys = Object.keys(matched) - if (keys.length) { - logDebug(`"STARTING TIMEFRAME PROCESSING": ${keys.length} timeframes matched in tasks`) - let newTimeMapWithBlocks = { timeBlockTextList: [], timeMap: [], blockList: [] } - // process tasks by timeframe key - keys.forEach((key) => { - const tasksMatchingThisTimeframe = matched[key] - const [start, end] = config.timeframes[key] - let timeMapCopy = deepCopy(timeMap) // timeMap.slice() - // process one task in the timeframe at a time - const sortedTasksMatchingTimeframe = sortListBy(tasksMatchingThisTimeframe, ['-priority', '-duration']) - sortedTasksMatchingTimeframe.forEach((task) => { - logDebug(`processTasksByTimeBlockTag TIMEFRAME:"${key}": start=${start} end=${end}`) - // blank out all slots that are not in the timeframe in question - timeMapCopy.forEach((t, i) => { - if (t.start < start || t.start >= end) { - timeMapCopy[i].busy = true // remove times from consideration that are not in the timeframe in question - } - }) - // filter the map to only open slots and then find open timeblocks - const openTimesForTimeframe = filterTimeMapToOpenSlots(timeMapCopy, config) - const blocksForTimeframe = findTimeBlocks(openTimesForTimeframe, config) - clof(blocksForTimeframe, `processTasksByTimeBlockTag blocksForTimeframe ${key} =`, ['start', 'minsAvailable'], true) - newTimeMapWithBlocks = matchTasksToSlots([task], { blockList: blocksForTimeframe, timeMap: openTimesForTimeframe }, config) - results.push(newTimeMapWithBlocks) - - const { timeMap: timeMapAfterTimeframePlacement, noTimeForTasks: nTftAfterTimeframePlacement, timeBlockTextList: timeBlockTextListAfterPlacement } = newTimeMapWithBlocks - timeMapCopy = timeMapAfterTimeframePlacement - // update the master timeMap with the changes that were made - // timemap slots that were used will be missing in the result, so we will just mark them as busy in the master timeMap - // function: updateMasterTimeMapWithTimeMapChanges - openTimesForTimeframe.forEach((t, i) => { - if (!timeMapAfterTimeframePlacement.find((nt) => nt.start === t.start)) { - ;(timeMap.find((tm) => tm.start === t.start) ?? {}).busy = true - } - }) - // save no time for tasks - if (nTftAfterTimeframePlacement) { - Object.keys(nTftAfterTimeframePlacement).forEach((key) => { - if (!noTimeForTasks[key]) noTimeForTasks[key] = [] - noTimeForTasks[key] = noTimeForTasks[key].concat(nTftAfterTimeframePlacement[key]) - }) - } - // save timeblocktextlist - if (timeBlockTextListAfterPlacement.length) { - timeBlockTextList = timeBlockTextList.concat(timeBlockTextListAfterPlacement) - } - //FIXME: I am here -- need to fix the circular dependency oy vey - }) - }) - } - clof(timeBlockTextList, `processTasksByTimeBlockTag timeBlockTextList=`, null, false) - clof(noTimeForTasks, `processTasksByTimeBlockTag noTimeForTasks=`, ['_', 'content'], true) - // end split - - logDebug(`"STARTING BY TIMEBLOCK TAG PROCESSING": ${unprocessedTasks.length} unprocessedTasks`) - clof(unprocessedTasks, `processTasksByTimeBlockTag unprocessedTasks=`, ['content'], true) - const filteredMap = filterTimeMapToOpenSlots(timeMap, config) - newBlockList = findTimeBlocks(filteredMap, config) - const namedBlocks = getNamedTimeBlocks(newBlockList ?? []) - namedBlocks.forEach((block) => { - const blockName = block.title || '' - logDebug(`PROCESSING BLOCK: "${blockName}" (tasks will be limited to this tag): ${unprocessedTasks.length} unprocessedTasks`) - const { - unprocessedTasks: updatedUnprocessedTasks, - results: blockResults, - noTimeForTasks: blockNoTimeForTasks, - } = processTasksForNamedTimeBlock(block, unprocessedTasks, timeMap, config) - clof(updatedUnprocessedTasks, `processTasksByTimeBlockTag updatedUnprocessedTasks after looking for block "${blockName}"`, null, true) - unprocessedTasks = updatedUnprocessedTasks - results = results.concat(blockResults) - Object.keys(blockNoTimeForTasks).forEach((key) => { - if (!noTimeForTasks[key]) noTimeForTasks[key] = [] - noTimeForTasks[key] = noTimeForTasks[key].concat(blockNoTimeForTasks[key]) - }) - - newBlockList = newBlockList.filter((b) => b !== block) - }) - - return { newBlockList, unprocessedTasks, results, noTimeForTasks } -} - -/** - * Get the timeblocks that have names/titles (e.g. a user set them up "Work" or "Home" or whatever) - * @param {Array} blockList - * @param {*} config - * @returns {Array} the filtered blockList - */ -export function getNamedTimeBlocks(blockList: Array): Array { - return blockList.filter((b) => b.title && b.title !== '') -} - -/** - * Processes tasks for a single named time block, updating tasks and no time for tasks accordingly. - * - * @param {TimeBlock} block - The current time block being processed. - * @param {Array} unprocessedTasks - List of tasks that have not been processed yet. - * @param {Array} timeMap - The current time map. - * @param {Config} config - Configuration options for processing. - * @returns {Object} - Returns an object containing the updated list of unprocessed tasks, results, and no time for tasks for this block. - */ -export function processTasksForNamedTimeBlock( - block: OpenBlock, - incomingUnprocessedTasks: Array, - timeMap: Array, - config: AutoTimeBlockingConfig, -): Object { - const results = [] - const noTimeForTasks = {} - const blockTitle = (block.title || '').replace(config.timeblockTextMustContainString, '').replace(/ {2,}/g, ' ').trim() - let unprocessedTasks = incomingUnprocessedTasks - const tasksMatchingThisNamedTimeblock = unprocessedTasks.filter((task) => block.title && namedTagExistsInLine(blockTitle, task.content)) - tasksMatchingThisNamedTimeblock.forEach((task, i) => { - const filteredTimeMap = timeMap.filter((t) => typeof t.busy === 'string' && t.busy.includes(blockTitle) && t.busy.includes(config.timeblockTextMustContainString)) - const newTimeBlockWithMap = matchTasksToSlots([task], { blockList: [block], timeMap: filteredTimeMap }, config) - unprocessedTasks = unprocessedTasks.filter((t) => t !== task) - const foundTimeForTask = newTimeBlockWithMap.timeBlockTextList && newTimeBlockWithMap.timeBlockTextList.length > 0 - if (foundTimeForTask) { - results.push(newTimeBlockWithMap) - } else { - if (!noTimeForTasks[blockTitle]) noTimeForTasks[blockTitle] = [] - noTimeForTasks[blockTitle].push(task) - } - }) - clof(unprocessedTasks, `processTasksForNamedTimeBlock newUnprocessedTasks after looking for block "${blockTitle}"`, null, true) - return { unprocessedTasks, results, noTimeForTasks } -} diff --git a/dwertheimer.EventAutomations/src/config.js b/dwertheimer.EventAutomations/src/config.js deleted file mode 100644 index 05e9143e9..000000000 --- a/dwertheimer.EventAutomations/src/config.js +++ /dev/null @@ -1,124 +0,0 @@ -// @flow - -import { validateConfigProperties } from '../../helpers/config' -import { logDebug } from '@helpers/dev' - -export function getTimeBlockingDefaults(): AutoTimeBlockingConfig { - return { - todoChar: '+' /* character at the front of a timeblock line - can be *,-,or a heading, e.g. #### */, - checkedItemChecksOriginal: false /* if true, checked items will check the original item, not the timeblock */, - timeBlockTag: `#🕑` /* placed at the end of the timeblock to show it was created by this plugin */, - timeBlockHeading: - '[Time Blocks](noteplan://runPlugin?pluginID=dwertheimer.EventAutomations&command=atb%20-%20Create%20AutoTimeBlocks%20for%20%3Etoday%27s%20Tasks)' /* if this heading exists in the note, timeblocks will be placed under it */, - foldTimeBlockHeading: false, - workDayStart: '00:00' /* needs to be in 24 hour format (two digits, leading zero) */, - workDayEnd: '23:59' /* needs to be in 24 hour format (two digits, leading zero) */, - durationMarker: "'" /* signifies how long a task is, e.g. apostrophe: '2h5m or use another character, e.g. tilde: ~2h5m */, - intervalMins: 5 /* inverval on which to calculate time blocks */, - removeDuration: true /* remove duration when creating timeblock text */, - defaultDuration: 20 /* default duration of a task that has no duration/end time */, - mode: 'PRIORITY_FIRST' /* 'PRIORITY_FIRST' or 'LARGEST_FIRST' or 'BY_TIMEBLOCK_TAG' */, - orphanTagggedTasks: "OUTPUT_FOR_INFO (but don't schedule them)", - allowEventSplits: false /* allow tasks to be split into multiple timeblocks */, - passBackResults: false /* pass back the results to the caller (e.g. for template calls) */, - includeTasksWithText: [] /* limit to tasks with ANY of these tags/text */, - excludeTasksWithText: [] /* exclude tasks with ANY of these tags/text */, - includeLinks: 'Pretty Links', - linkText: '📄', - syncedCopiesTitle: "Today's Synced Tasks", - foldSyncedCopiesHeading: false, - runSilently: false, - timeblockTextMustContainString: '' /* is set automatically when config is pulled */, - foldersToIgnore: [], - includeAllTodos: true, - presets: [{ label: 'Limit Time Blocks to Work Hours', workDayStart: '08:00', workDayEnd: '17:59' }] /* presets for the dropdown */, - /* OPTIONAL: nowStrOverride: "00:00" for testing, e.g. '00:00' */ - } -} - -const nonEmptyString: RegExp = /^(?!\s*$).+/ - -export function validateAutoTimeBlockingConfig(config: AutoTimeBlockingConfig): AutoTimeBlockingConfig { - const configTypeCheck = { - todoChar: /^(?!(?:.*\*){2})[\*|\-|\+|#{1,}]+$/, - timeBlockTag: /^.+/, - timeBlockHeading: /^[^#+].*/, - foldTimeBlockHeading: 'boolean', - workDayStart: /^\d{2}:\d{2}$/, - workDayEnd: /^\d{2}:\d{2}$/, - durationMarker: nonEmptyString, - intervalMins: 'number', - removeDuration: 'boolean', - syncedCopiesTitle: nonEmptyString, - foldSyncedCopiesHeading: 'boolean', - defaultDuration: 'number', - mode: 'string', - orphanTagggedTasks: 'string', - checkedItemChecksOriginal: 'boolean', - allowEventSplits: 'boolean', - runSilently: { type: 'boolean', optional: true }, - passBackResults: { type: 'boolean', optional: true }, - includeLinks: nonEmptyString, - linkText: nonEmptyString, - includeTasksWithText: { type: 'array', optional: true }, - excludeTasksWithText: { type: 'array', optional: true }, - foldersToIgnore: { type: 'array', optional: true }, - presets: { type: 'array', optional: true }, - nowStrOverride: { type: /^\d{2}:\d{2}$/, optional: true }, - timeblockTextMustContainString: 'string', - includeAllTodos: 'boolean', - } - try { - // $FlowIgnore - const validatedConfig = validateConfigProperties(config, configTypeCheck) - if (validatedConfig.checkedItemChecksOriginal && (validatedConfig.todoChar !== '+' || validatedConfig.includeLinks !== 'Pretty Links')) { - throw new Error( - `To use the checklist check to check the original, your timeblock character must be + and the 'Include links to task location in time blocks' setting must be set to 'Pretty Links'`, - ) - } - if (config.timeBlockTag === DataStore.preference('timeblockTextMustContainString')) { - throw new Error( - `Your AutoTimeBlocking Tag must be different from your NotePlan Preferences 'Timeblock Must Contain' setting. /ATB has to be able to identify the items that were created previously by the plugin so it can delete and re-generate them.`, - ) - } - // $FlowIgnore - return validatedConfig - } catch (error) { - // console.log(`NPTimeblocking::validateAutoTimeBlockingConfig: ${String(error)}\nInvalid config:\n${JSON.stringify(config)}`) - throw new Error(`${String(error)}`) - } -} - -export const arrayToCSV = (inStr: Array | string): string => (Array.isArray(inStr) ? inStr.join(', ') : inStr) - -export type AutoTimeBlockingConfig = { - todoChar: string, - checkedItemChecksOriginal: boolean, - timeBlockTag: string, - timeBlockHeading: string, - foldTimeBlockHeading: boolean, - workDayStart: string, - workDayEnd: string, - durationMarker: string, - intervalMins: number, - removeDuration: boolean, - syncedCopiesTitle: string, - foldSyncedCopiesHeading: boolean, - defaultDuration: number, - mode: string, - orphanTagggedTasks: string, - allowEventSplits: boolean, - runSilently?: boolean, - passBackResults?: boolean, - includeLinks: string, - linkText: string, - includeTasksWithText?: Array, - excludeTasksWithText?: Array, - foldersToIgnore?: Array, - presets?: any, - timeframes?: any, - nowStrOverride?: string, - timeblockTextMustContainString: string, - includeAllTodos: boolean, - dateFormat?: string, -} diff --git a/dwertheimer.EventAutomations/src/events.js b/dwertheimer.EventAutomations/src/events.js deleted file mode 100644 index 748b795fb..000000000 --- a/dwertheimer.EventAutomations/src/events.js +++ /dev/null @@ -1,93 +0,0 @@ -// @flow -import { getEventsForDay } from '../../helpers/NPCalendar' -import { getTodaysDateUnhyphenated, type HourMinObj, toLocaleTime } from '../../helpers/dateTime' -import { chooseOption, chooseFolder } from '../../helpers/userInput' -import pluginJson from '../plugin.json' -import { logDebug } from '@helpers/dev' - -function getTimeOffset(offset: HourMinObj = { h: 0, m: 0 }) { - const now = new Date() - let min = now.getMinutes() + offset.m - let hrCorrect = 0 - if (min < 0) { - min = 60 + min - hrCorrect = -1 - } - let hr = now.getHours() + offset.h + hrCorrect - if (hr < 0) hr = 0 - if (hr > 23) hr = 23 - // logDebug(pluginJson,`${hr}:${min}`) - return { h: hr, m: min } -} - -export async function createNoteForCalendarItemWithQuickTemplate(): Promise { - await createNoteForCalendarItem(true) -} - -export async function createNoteForCalendarItemWithoutQuickTemplate(): Promise { - await createNoteForCalendarItem(false) -} - -export async function createNoteForCalendarItem(useQuickTemplate: boolean = true): Promise { - const date = getTodaysDateUnhyphenated() - logDebug(pluginJson, `Creating note for today's date: ${date}`) - const allDaysEvents = await getEventsForDay(date) - logDebug(pluginJson, `Found ${allDaysEvents?.length || 0} events for today`) - const nowIshEvents = await getEventsForDay(date, [], getTimeOffset({ h: -1, m: 0 }), getTimeOffset({ h: +1, m: 0 })) // second param now implies consider all calendars - logDebug(pluginJson, `Found ${nowIshEvents?.length || 0} events for nowIsh`) - // const events = allDaysEvents - if (nowIshEvents && nowIshEvents.length > 0) { - // events = [...nowIshEvents, ...[{ title: '---' }], ...allDaysEvents] - } - // $FlowIgnore - const selections = allDaysEvents.map((event) => { - // $FlowIgnore - const time = toLocaleTime(event.date, [], { hour: '2-digit', minute: '2-digit', hour12: false }) - // $FlowIgnore - if (event.title) return { label: `${time}: ${event.title}`, value: event.title, time, date: event.date.toLocaleDateString() } - }) - // $FlowIgnore - const selectedEvent = await chooseOption('Choose an event to create a note for', selections, '') - // Override the quickTemplateNote title with the selected event - // $FlowIgnore - const selEvent = selections.find((event) => event.value === selectedEvent) - // $FlowIgnore - // const theTime = selEvent.time === '00:00' ? '' : selEvent.time - logDebug(pluginJson, `Selected event: ${selectedEvent} ${String(JSON.stringify(selEvent))}`) - // $FlowIgnore - // const theTitle = `${selectedEvent} {{date8601()}} ${theTime || ''}` - if (selectedEvent && useQuickTemplate) { - // quickTemplateNote is not defined! - // await quickTemplateNote(theTitle) - return - } - const useTemplate = await chooseOption( - 'Use a template?', - [ - { label: 'Yes', value: 'Yes' }, - { label: 'No', value: 'No' }, - ], - 'Yes', - ) - if (useTemplate !== 'No') { - // newNoteWithTemplate is not defined! - // await newNoteWithTemplate('', theTitle) - } else { - const folder = await chooseFolder('What folder should the note be in?') - if (selEvent) { - const title = `${selEvent.value} ${selEvent.date} ${selEvent.time && selEvent.time !== '00:00' ? selEvent.time : ''}` - const fname = (await DataStore.newNote(title, folder)) ?? '' - logDebug(pluginJson, `Creating note with title: ${title}, fname=${fname}`) - if (fname) { - await Editor.openNoteByFilename(fname, false) - } - } - } -} - -// function printEventsToConsole(events: Array): void { -// events.forEach((event) => { -// // ${event.notes} ${event.url} -// logDebug(pluginJson,`${event.title} ${event.date} ${event.endDate} ${event.isAllDay}`) -// }) -// } diff --git a/dwertheimer.EventAutomations/src/index.js b/dwertheimer.EventAutomations/src/index.js deleted file mode 100644 index 6b02b67a7..000000000 --- a/dwertheimer.EventAutomations/src/index.js +++ /dev/null @@ -1,42 +0,0 @@ -// @flow -import pluginJson from '../plugin.json' -import { updateSettingData, pluginUpdated } from '../../helpers/NPConfiguration' -import { log, logDebug, clo } from '../../helpers/dev' - -export { editSettings } from '@helpers/NPSettings' - -export { - insertTodosAsTimeblocks, - insertTodosAsTimeblocksWithPresets, - insertSyncedCopiesOfTodayTodos, - removeTimeBlocks, - removePreviousTimeBlocks, - markDoneAndRecreateTimeblocks, -} from './NPTimeblocking' - -export { onEditorWillSave } from './triggers' - -export { createEvents, createEventPrompt } from './NPEventBlocks' - -const PLUGIN_ID = 'autoTimeBlocking' // the key that's used in _configuration note - -export async function onUpdateOrInstall(): Promise { - try { - console.log(`${PLUGIN_ID}: onUpdateOrInstall running`) - // migrate _configuration data to data//settings.json (only executes migration once) - const updateSettings = updateSettingData(pluginJson) - console.log(`${PLUGIN_ID}: onUpdateOrInstall updateSettingData code: ${updateSettings}`) - } catch (error) { - await console.log(error) - } - console.log(`${PLUGIN_ID}: onUpdateOrInstall finished`) -} - -export function onSettingsUpdated() {} - -export function init(): void { - // this runs every time the plugin starts up (any command in this plugin is run) - // clo(DataStore.settings, `${pluginJson['plugin.id']} Plugin Settings`) - console.log(`\n\n\n`) - DataStore.installOrUpdatePluginsByID([pluginJson['plugin.id']], true, false, false).then((r) => pluginUpdated(pluginJson, r)) -} diff --git a/dwertheimer.EventAutomations/src/presets.js b/dwertheimer.EventAutomations/src/presets.js deleted file mode 100644 index 834bf3d58..000000000 --- a/dwertheimer.EventAutomations/src/presets.js +++ /dev/null @@ -1,35 +0,0 @@ -import { logDebug } from '@helpers/dev' - -/** - * This is used for the TimeBlocking presets in preferences - */ - -/** - * Presets have a label and other properties which overwrite the default config - * @param {*} config - the config object - * @param {*} preset - the preset object - */ -export function setConfigForPreset(config, preset) { - if (preset) { - Object.keys(preset).forEach((key) => { - if (key !== 'label') { - config[key] = preset[key] - } - }) - } - return config -} - -export const getPresetOptions = (presets) => { - return presets.map((p, i) => { - return { label: p.label, value: i } - }) -} - -export function getPreset(config) { - if (config.preset && config.preset.length) { - return config.preset - } else { - return null - } -} diff --git a/dwertheimer.EventAutomations/src/timeblocking-flow-types.js b/dwertheimer.EventAutomations/src/timeblocking-flow-types.js deleted file mode 100644 index e33656792..000000000 --- a/dwertheimer.EventAutomations/src/timeblocking-flow-types.js +++ /dev/null @@ -1,48 +0,0 @@ -// @flow - -export type IntervalMap = Array<{ start: string, busy: string | boolean, index: number }> -export type OpenBlock = { start: string, end: string, minsAvailable: number, title?: string } -export type BlockArray = Array -export type TimeBlocksWithMap = { timeMap: IntervalMap, blockList?: BlockArray, timeBlockTextList?: Array, noTimeForTasks?: { [string]: Array } | null } -export type TimeBlockTextList = Array -export type BlockTimeOptions = { mode: string } -export type BlockData = { start: string, end: string, title?: string } -export type TimeBlockDefaults = { - todoChar: string, - timeBlockTag: string, - timeBlockHeading: string, - workDayStart: string, - workDayEnd: string, - durationMarker: string, - intervalMins: number, - removeDuration: boolean, - defaultDuration: number, - nowStrOverride?: string /* for testing */, - mode: string, - allowEventSplits: boolean, - passBackResults: boolean, -} -export type PartialCalendarItem = { - title: string, - date: Date, - endDate: Date, - type: string, - availability: number, -} - -export interface ParagraphWithDuration extends TParagraph { - duration: number; -} - -export type Tags = { - [key: string]: Array, -} - -export type MatchedItems = { - [key: string]: Array, -} - -export type SplitResult = { - matched: MatchedItems, - unmatched: Array, -} diff --git a/dwertheimer.EventAutomations/src/timeblocking-helpers.js b/dwertheimer.EventAutomations/src/timeblocking-helpers.js deleted file mode 100644 index ecd46808a..000000000 --- a/dwertheimer.EventAutomations/src/timeblocking-helpers.js +++ /dev/null @@ -1,755 +0,0 @@ -// @flow -import { endOfDay, startOfDay, eachMinuteOfInterval, formatISO9075, addMinutes, differenceInMinutes } from 'date-fns' -import type { SortableParagraphSubset } from '../../helpers/sorting' -import { escapeRegExp } from '../../helpers/regex' -import { getTagsFromString } from '../../helpers/paragraph' -import type { - IntervalMap, - OpenBlock, - TimeBlocksWithMap, - BlockData, - TimeBlockDefaults, - PartialCalendarItem, - ParagraphWithDuration, - SplitResult, - MatchedItems, - Tags, -} from './timeblocking-flow-types' -import type { AutoTimeBlockingConfig } from './config' -import { processByTimeBlockTag } from './byTagMode' -import { getDateObjFromDateTimeString, getTimeStringFromDate, removeDateTagsAndToday, removeRepeats } from '@helpers/dateTime' -import { sortListBy } from '@helpers/sorting' -import { textWithoutSyncedCopyTag } from '@helpers/syncedCopies' -import { createPrettyLinkToLine, createWikiLinkToLine } from '@helpers/NPSyncedCopies' -import { logError, JSP, copyObject, clo, clof, logDebug, logWarn } from '@helpers/dev' - -// import { timeblockRegex1, timeblockRegex2 } from '../../helpers/markdown-regex' - -const pluginJson = `timeblocking-helpers.js` - -/** - * Create a map of the time intervals for a portion of day - * @param {*} start - * @param {*} end - * @param {*} valueToSet - * @param {*} options - * @returns Array of objects with the following properties: [{"start":"00:00","busy":false},{"start":"00:05","busy":false}...] - */ -export function createIntervalMap(time: { start: Date, end: Date }, valueToSet: false | string = false, options: { step: number } = { step: 5 }): IntervalMap { - const { start, end } = time - const { step } = options - if (step && step > 0) { - const intervals = eachMinuteOfInterval({ start, end }, { step }) - return intervals.map((interval, i) => { - const start = formatISO9075(interval).slice(0, -3) - const time = start.split(' ')[1] - return { start: time, busy: valueToSet, index: i } - }) - } - return [] -} - -export function getBlankDayMap(intervalMins: number): IntervalMap { - return createIntervalMap({ start: startOfDay(new Date()), end: endOfDay(new Date()) }, false, { step: intervalMins }) -} - -export function blockTimeFor(timeMap: IntervalMap, blockdata: BlockData, config: { [key: string]: any }): { newMap: IntervalMap, itemText: string } { - const { start, end, title } = blockdata - const newMap = timeMap.map((t) => { - if (t.start >= start && t.start < end) { - t.busy = title ?? true - } - return t - }) - const itemText = typeof title === 'boolean' ? '' : createTimeBlockLine({ title, start, end }, config) - return { newMap, itemText } -} - -/** - * Clean text using an array of regexes or strings to replace - */ -export function cleanText(text: string, replacements: Array): string { - let cleanString = text - replacements.forEach((r) => { - cleanString = cleanString.replace(r, ' ') - }) - cleanString = cleanString.replace(/ {2,}/g, ' ').trim() - return cleanString -} - -/** - * Remove all ATB-CREATED formatting from a timeblock line and return just the content (so we can do comparisons) - * e.g. "00:01-12:22 foo bar baz" -> "foo bar baz" - * @param {string} line - * @param {TimeBlockDefaults} config - * @returns {string} clean string - */ -export function cleanTimeBlockLine(line: string, config: { [key: string]: any }): string { - const { timeBlockTag, durationMarker } = config - // use .*? for non-greedy (match minimum chars) and make sure to use global flag for all matches - const cleanerRegexes = [ - new RegExp(`^\\d{2}:\\d{2}-\\d{2}:\\d{2} `, 'g'), - new RegExp(` ${escapeRegExp(timeBlockTag)}`, 'g'), - new RegExp(`\\[\\[.*?\\]\\]`, 'g'), - new RegExp(`\\[.*?\\]\\(.*?\\)`, 'g'), - ] - let clean = cleanText(line, cleanerRegexes) - clean = removeDurationParameter(clean, durationMarker) - clean = removeDateTagsAndToday(clean, true) - return clean - // cleanString = removeDateTagsAndToday(cleanString) -} - -export function attachTimeblockTag(content: string, timeblockTag: string): string { - const regEx = new RegExp(` ${escapeRegExp(timeblockTag)}`, 'g') //replace the existing tag if it's there - return `${content.replace(regEx, '')} ${timeblockTag}` -} - -export function createTimeBlockLine(blockData: BlockData, config: { [key: string]: any }): string { - if (blockData.title && blockData.title.length > 0) { - let newContentLine = blockData.title - if (config.removeDuration) { - newContentLine = removeDurationParameter(newContentLine, config.durationMarker) - } - newContentLine = attachTimeblockTag(newContentLine, config.timeBlockTag) - let tbLine = `${config.todoChar} ${blockData.start}-${blockData.end} ${newContentLine || blockData.title || ''}` - if (config.timeblockTextMustContainString?.length && !tbLine.includes(config.timeblockTextMustContainString)) { - tbLine = `${tbLine} ${config.timeblockTextMustContainString}` - } - return tbLine - } - return '' -} - -/** - * Takes in an array of calendar items and a timeMap for the day - * and returns the timeMap with the busy times updated to reflect the calendar items - * @author @dwertheimer - * - * @param {Array} events - * @param {IntervalMap} timeMap - * @param {TimeBlockDefaults} config - * @returns {IntervalMap} - the timeMap with the busy times updated to reflect the calendar items - */ -export function blockOutEvents(events: Array, timeMap: IntervalMap, config: { [key: string]: any }): IntervalMap { - let newTimeMap = [...timeMap] - events - .filter((e) => e.availability !== 1) - .forEach((event) => { - const start = getTimeStringFromDate(event.date) - const end = event.endDate ? getTimeStringFromDate(event.endDate) : '' - const obj = event.endDate ? blockTimeFor(newTimeMap, { start, end, title: event.title }, config) : { newMap: newTimeMap } - newTimeMap = obj.newMap - }) - return newTimeMap -} - -/** - * Typically we are looking for open tasks, but it is possible that some >today items - * might be bullets (type=='list'), so for timeblocking purposes, let's make them open tasks - * for the purposes of this script - * @author @dwertheimer - * - * @param {TParagraphs[]} paras - * @returns TParagraphs[] - with remapped items - */ -export function makeAllItemsTodos(paras: Array): Array { - const typesToRemap = ['list', 'text'] - // NOTEPLAN FRUSTRATION! YOU CANNOT SPREAD THE ...P AND GET THE - // UNDERLYING VALUES! - // return paras.map((p) => {p.type = ({ ...p, type: typesToRemap.indexOf(p.type) !== -1 ? 'open' : p.type })) - return paras.map((p) => { - p.type = typesToRemap.indexOf(p.type) !== -1 ? 'open' : p.type - return p - }) -} - -// $FlowIgnore - can't find a Flow type for RegExp -export const durationRegEx = (durationMarker: string) => - new RegExp(`\\s*${durationMarker}((?[0-9]+\\.?[0-9]*|\\.[0-9]+)(hours|hour|hr|h))?((?[0-9]+\\.?[0-9]*|\\.[0-9]+)(minutes|mins|min|m))?`, 'mg') - -export const removeDurationParameter = (text: string, durationMarker: string): string => text.replace(durationRegEx(durationMarker), '').trim() - -export function getDurationFromLine(line: string, durationMarker: string): number { - const regex = durationRegEx(durationMarker) - const match = regex.exec(line) - let mins = 0 - if (match) { - const hours = match?.groups?.hours ? Number(match.groups.hours) : 0 - const minutes = match?.groups?.minutes ? Number(match.groups.minutes) : 0 - mins = Math.ceil(hours * 60 + minutes) - } - clo(match, `+++++++ getDurationFromLine match=${String(match)}, so setting mins=${mins} for "${line}"; match groups=`) - return mins -} - -/** - * Remove >date and >today tags from a paragraphs array and return only the most important parts - * Note: rawContent is used later for mapping sorted tasks back to paragraphs - * @author @dwertheimer - * - * @param {*} paragraphsArray - * @returns - */ -export function removeDateTagsFromArray(paragraphsArray: $ReadOnlyArray): Array | $ReadOnlyArray { - try { - const newPA = paragraphsArray.map((p): any => { - const copy = copyObject(p) - copy.content = removeDateTagsAndToday(p.content) - copy.rawContent = removeDateTagsAndToday(p.rawContent) - return copy - }) - return newPA - } catch (error) { - logError(`timeblocking-helppers::removeDateTagsFromArray failed. Error:`, JSP(error)) - } - return paragraphsArray -} - -export const timeIsAfterWorkHours = (nowStr: string, config: TimeBlockDefaults): boolean => { - return nowStr >= config.workDayEnd -} - -/** - * Get the day map with only the slots that are open, after now and inside of the workday - * @author @dwertheimer - * - * @param {*} timeMap - * @param {*} config - * @returns {IntervalMap} remaining time map - */ -export function filterTimeMapToOpenSlots(timeMap: IntervalMap, config: { [key: string]: any }): IntervalMap { - const nowStr = config.nowStrOverride ?? getTimeStringFromDate(new Date()) - const byTagMode = config.mode === 'BY_TIMEBLOCK_TAG' - const retVal = timeMap.filter((t) => { - // should filter to only open slots but will also include slots that are busy but have the timeblock tag - DataStore.preference('timeblockTextMustContainString') - const isSet = typeof t.busy === 'string' - return ( - t.start >= nowStr && - t.start >= config.workDayStart && - t.start < config.workDayEnd && - (!t.busy || - (byTagMode && - ((isSet && String(t.busy).includes(config.timeBlockTag)) || - (config.timeblockTextMustContainString?.length && isSet && String(t.busy).includes(config.timeblockTextMustContainString))))) - ) - }) - // logDebug(`\n\nfilterTimeMapToOpenSlots: ${JSP(retVal)}`) - return retVal -} - -export function createOpenBlockObject(block: BlockData, config: { [key: string]: any }, includeLastSlotTime: boolean = true): OpenBlock | null { - let startTime, endTime - try { - startTime = getDateObjFromDateTimeString(`2021-01-01 ${block.start || '00:00'}`) - endTime = getDateObjFromDateTimeString(`2021-01-01 ${block.end || '23:59'}`) - } catch (error) { - console.log(error) - return null - } - endTime = endTime ? (includeLastSlotTime ? addMinutes(endTime, config.intervalMins) : endTime) : null - endTime = endTime && endTime <= endOfDay(startTime) ? endTime : endOfDay(startTime) // deal with edge case where end time is in the next day - if (!startTime || !endTime) return null - return { - start: getTimeStringFromDate(startTime), - // $FlowIgnore - end: getTimeStringFromDate(endTime), - // $FlowIgnore - minsAvailable: differenceInMinutes(endTime, startTime, { roundingMethod: 'ceil' }), - title: block.title, - } -} - -/** - * Given an array of open timeslots from a day's IntervalMap, sends back - * an array of the contiguous slots (assumes busy/unavailable slots have been - * eliminated before calling this function (eg using filterTimeMapToOpenSlots()). - * @param {IntervalMap} timeMap - * @param {number} intervalMins - * @returns array of OpenBlock objects - */ -export function findTimeBlocks(timeMap: IntervalMap, config: { [key: string]: any }): Array { - const blocks: Array = [] - if (timeMap?.length) { - let lastSlot = timeMap[0] - let blockStart = timeMap[0] - for (let i = 1; i < timeMap.length; i++) { - const slot = timeMap[i] - // console.log(`findTimeBlocks[${i}]: slot: ${slot.start} ${slot.index} ${slot.busy}}`) - const noBreakInContinuity = slot.index === lastSlot.index + 1 && i <= timeMap.length - 1 && lastSlot.busy === slot.busy - if (noBreakInContinuity) { - lastSlot = slot - continue - } else { - // there was a break in continuity - // logDebug(`findTimeBlocks: lastSlot break in continuity at ${i}: ${JSP(lastSlot)}`) - const title = - typeof lastSlot.busy === 'string' /* this was a named timeblock */ - ? lastSlot.busy - // .replace(config.timeblockTextMustContainString || '', '') //if you do this, then the tb will be screened out later - .replace(/ {2,}/g, ' ') - .trim() - : '' - // logDebug(`findTimeBlocks: creating block title: ${title}`) - const block = createOpenBlockObject({ start: blockStart.start, end: lastSlot.start, title }, config, true) - // clo(block, `findTimeBlocks: block created`) - if (block) blocks.push(block) - blockStart = slot - lastSlot = slot - } - } - if (timeMap.length && lastSlot === timeMap[timeMap.length - 1]) { - // pick up the last straggler edge case - const title = - typeof lastSlot.busy === 'string' - ? lastSlot.busy - // .replace(config.timeblockTextMustContainString || '', '') //if you do this, then the tb will be screened out later - .replace(/ {2,}/g, ' ') - .trim() - : '' - const lastBlock = createOpenBlockObject({ start: blockStart.start, end: lastSlot.start, title }, config, true) - if (lastBlock) blocks.push(lastBlock) - } - } else { - // console.log(`findTimeBlocks: timeMap array was empty`) - } - // console.log(`findTimeBlocks: found blocks: ${JSP(blocks)}`) - - return blocks -} - -export function addMinutesToTimeText(startTimeText: string, minutesToAdd: number): string { - try { - const startTime = getDateObjFromDateTimeString(`2021-01-01 ${startTimeText}`) - return startTime ? getTimeStringFromDate(addMinutes(startTime, minutesToAdd)) : '' - } catch (error) { - console.log(error) - return `` - } -} - -/** - * Blocks time for the block specified and returns a new IntervalMap, new BlockList, and new TextList of time blocks - * @param {*} tbm - * @param {*} block - * @param {*} config - * @returns TimeBlocksWithMap - */ -export function blockTimeAndCreateTimeBlockText(tbm: TimeBlocksWithMap, block: BlockData, config: { [key: string]: any }): TimeBlocksWithMap { - const timeBlockTextList = tbm.timeBlockTextList || [] - const obj = blockTimeFor(tbm.timeMap, block, config) //returns newMap, itemText - timeBlockTextList.push(textWithoutSyncedCopyTag(obj.itemText)) - const timeMap = filterTimeMapToOpenSlots(obj.newMap, config) - const blockList = findTimeBlocks(timeMap, config) - return { timeMap, blockList, timeBlockTextList } -} - -/** - * Finds a named hashtag or attag in a line of text. - * - * @param {string} blockName - The name of the block to search for. - * @param {string} line - The line of text to search in. - * @param {Object.} config - The configuration object. - * @return {?string} - The matched tag or null if no match is found. - */ -export function namedTagExistsInLine(blockName: string, line: string): boolean { - // Sanitize blockName to escape RegExp special characters - const sanitizedBlockName = escapeRegExp(blockName) - const regex = new RegExp(sanitizedBlockName, 'gi') - const match = regex.exec(line) - return match ? true : false -} - -/** - * Reduce an array of objects to a single object with the same keys and the values combined into arrays - * @param {*} arr - the array of objects to reduce - * @param {*} propToLookAt - if you only want to look at one key in each top level object - * @returns a single object with the same keys and the values combined into arrays - */ -function reduceArrayOfObjectsToSingleObject(arr: Array<{ [key: string]: any }>, propToLookAt? = null): { [key: string]: any } { - return arr.reduce((acc, obj) => { - const o = propToLookAt ? obj[propToLookAt] : obj - Object.keys(o).forEach((key) => { - if (acc[key]) { - acc[key] = [...acc[key], ...o[key]] - } else { - acc[key] = o[key] - } - }) - return acc - }, {}) -} - -/** - * Splits an array of items into two categories based on matching hashtags. - * For items with matching hashtags, it groups them under the matched tag using the tag as the key - * NOTE: an item can be matched to multiple tags - * - * @param {Array} arrayOfItems - The array of items to split, where each item has a 'hashtags' property. - * @param {Tags} tags - An object where each key represents a tag to match against the items' hashtags. - * @returns {SplitResult} An object containing a 'matched' object with properties for each tag and their corresponding matching items, and an 'unmatched' array for items without matching tags. - */ -export function splitItemsByTags(arrayOfItems: Array, tags: Tags): SplitResult { - const matched: MatchedItems = {} - const unmatched: Array = [] - - arrayOfItems.forEach((item) => { - let isMatched = false - const hashtags = getTagsFromString(item.content, false).hashtags - hashtags.forEach((hashtag) => { - if (tags.hasOwnProperty(hashtag)) { - if (!matched[hashtag]) { - matched[hashtag] = [] - } - matched[hashtag].push(item) - isMatched = true - } - }) - - if (!isMatched) { - unmatched.push(item) - } - }) - - return { matched, unmatched } -} - -/** - * Process the tasks that have a named tag in them (e.g. @work or #work) - * @param {*} sortedTaskList - * @param {TimeBlocksWithMap} tmb - * @param {*} config - * @returns {TimeBlocksWithMap} - */ -export function ORIGINALprocessByTimeBlockTag(sortedTaskList: Array, tmb: TimeBlocksWithMap, config: { [key: string]: any }): TimeBlocksWithMap { - const { blockList, timeMap } = tmb - let newBlockList = blockList - let unprocessedTasks = [...sortedTaskList] - const results = [] - let noTimeForTasks = {} - const { matched: timeframeMatches, unmatched: regularTasks } = splitItemsByTags(sortedTaskList, config.timeframes || {}) - const namedBlocks = getNamedTimeBlocks(newBlockList ?? []) - clo(blockList, `processByTimeBlockTag: blockList`) - // const namedBlocksIncludingTimeframes = getTimeframeNameBlocks(namedBlocks, timeframeMatches) - clo(namedBlocks, `processByTimeBlockTag: namedBlocks (timeblocks that are not ATB blocks)`) - - // start - if (namedBlocks.length === 0) { - logWarn(`processByTimeBlockTag we are in TIMEBLOCK_TAG mode but there are no named blocks`) - } else { - logDebug(`processByTimeBlockTag namedBlocks(${namedBlocks.length}):${namedBlocks.reduce((acc, val) => `${acc}, ${val.title || ''}`, '')}`) - } - namedBlocks.forEach((block) => { - const blockTitle = (block.title || '').replace(config.timeblockTextMustContainString, '').replace(/ {2,}/g, ' ').trim() - //$FlowIgnore - const tasksMatchingThisNamedTimeblock = unprocessedTasks.filter((task) => (block.title ? namedTagExistsInLine(blockTitle, task.content) : false)) - logDebug(`processByTimeBlockTag tasksMatchingThisNamedTimeblock (${blockTitle}): ${JSP(tasksMatchingThisNamedTimeblock.map((p) => p.content || ''))}`) - tasksMatchingThisNamedTimeblock.forEach((task, i) => { - // call matchTasksToSlots for each block as if the block all that's available - // remove from sortedTaskList - // $FlowIgnore - logDebug(pluginJson, `Calling matchTasksToSlots for item[${i}]: ${task.content} duration:${task.duration}`) - // const filteredTimeMap = timeMap.filter((t) => t.start >= block.start && t.start <= block.end) - const filteredTimeMap = timeMap.filter((t) => typeof t.busy === 'string' && t.busy.includes(blockTitle) && t.busy.includes(config.timeblockTextMustContainString)) - // Acting as if all blocks are just this named block - const newTimeBlockWithMap = matchTasksToSlots([task], { blockList: [block], timeMap: filteredTimeMap }, config) - unprocessedTasks = unprocessedTasks.filter((t) => t !== task) // remove the task from the list - const foundTimeForTask = newTimeBlockWithMap.timeBlockTextList && newTimeBlockWithMap.timeBlockTextList.length > 0 - if (foundTimeForTask) { - results.push(newTimeBlockWithMap) - } else { - if (!noTimeForTasks) noTimeForTasks = {} - if (!noTimeForTasks[blockTitle]) noTimeForTasks[blockTitle] = [] - noTimeForTasks[blockTitle].push(task) - } - }) - newBlockList = blockList?.filter((b) => b !== block) // remove the block from the list - }) - //stop - // ["IGNORE_THEM","OUTPUT_FOR_INFO (but don't schedule them)", "SCHEDULE_ELSEWHERE_LAST", "SCHEDULE_ELSEWHERE_FIRST"] - const noTimeTasks = Object.values(noTimeForTasks || {}).reduce((acc, val) => acc.concat(val), []) - switch (config.orphanTagggedTasks) { - case 'IGNORE_THEM': - noTimeForTasks = null - break - case "OUTPUT_FOR_INFO (but don't schedule them)": - break - case 'SCHEDULE_ELSEWHERE_LAST': - unprocessedTasks = [...unprocessedTasks, ...noTimeTasks] - break - case 'SCHEDULE_ELSEWHERE_FIRST': - unprocessedTasks = [...noTimeTasks, ...unprocessedTasks] - break - } - // process the rest of the tasks - newBlockList = blockList?.filter((b) => !b.title || b.title === '') || [] // remove the named blocks from the list - - config.mode = 'PRIORITY_FIRST' // now that we've processed the named blocks, we can process the rest of the tasks by priority - // $FlowIgnore - results.push(matchTasksToSlots(unprocessedTasks, { blockList: newBlockList, timeMap }, config)) - - return { - noTimeForTasks: reduceArrayOfObjectsToSingleObject(results, 'noTimeForTasks'), - timeMap, - blockList: newBlockList, - timeBlockTextList: results.reduce((acc, currentValue) => acc.concat(currentValue.timeBlockTextList), []).sort(), - } -} - -/** - * - * @param {Array} sortedTaskList - * @param {TimeBlocksWithMap} tmb - * @param {[key: string]: any} config - * @returns {TimeBlocksWithMap} - */ -export function matchTasksToSlots(sortedTaskList: Array, tmb: TimeBlocksWithMap, config: { [key: string]: any }): TimeBlocksWithMap { - const { timeMap } = tmb - let newMap = filterTimeMapToOpenSlots(timeMap, config) - let newBlockList = findTimeBlocks(newMap, config) - const { durationMarker } = config - let timeBlockTextList = [] - const noTimeForTasks = {} - - clof(sortedTaskList, `matchTasksToSlots: sortedTaskList`, null, true) - - // sortedTaskList.forEach((task) => { - for (let t = 0; t < sortedTaskList.length; t++) { - const task = sortedTaskList[t] - const taskTitle = removeRepeats(removeDateTagsAndToday(task.content)) - const taskDuration = task.duration || getDurationFromLine(task.content, durationMarker) || config.defaultDuration // default time is 15m - logDebug(`== matchTasksToSlots task="${t}: ${task.content}" newBlockList.length=${newBlockList?.length || ''}`) - if (newBlockList && newBlockList.length) { - let scheduling = true - let schedulingCount = 0 - let scheduledMins = 0 - // $FlowIgnore - flow doesn't like .length below but it is safe - for (let i = 0; i < newBlockList.length && scheduling; i++) { - if (newBlockList && newBlockList[i]) { - let block = newBlockList[i] - const blockDuration = block.minsAvailable - let endTime = '' - while (scheduling && scheduledMins < taskDuration) { - if (taskDuration <= blockDuration) { - endTime = addMinutesToTimeText(block.start, taskDuration) - scheduling = false - } else { - if (config.allowEventSplits) { - const minsToUse = Math.min(block.minsAvailable, taskDuration - scheduledMins) - endTime = addMinutesToTimeText(block.start, minsToUse) - schedulingCount++ - scheduledMins += minsToUse - } else { - break //look for the next block that could work - } - } - endTime = endTime !== '00:00' ? endTime : '23:59' //deal with edge case where end time is technically in the next day - const blockData = { - start: block.start, - end: endTime, - title: `${taskTitle}${schedulingCount ? ` (${schedulingCount})` : ''}`, - } - const newTimeBlockWithMap = blockTimeAndCreateTimeBlockText({ timeMap: newMap, blockList: newBlockList, timeBlockTextList }, blockData, config) - // Re-assign newMap, newBlockList, and timeBlockTextList for next loop run - ;({ timeMap: newMap, blockList: newBlockList, timeBlockTextList } = newTimeBlockWithMap) - if (newBlockList && newBlockList.length) { - block = newBlockList[0] - } else { - break - } - if (!scheduling) break - } - } - } - if (scheduling) { - logDebug(`matchTasksToSlots task[${t}]="${taskTitle}" scheduling:${String(scheduling)}. Pushing to noTimeForTasks array`) - if (!noTimeForTasks['_']) noTimeForTasks['_'] = [] - noTimeForTasks['_'].push(task) - } - } else { - logDebug(`matchTasksToSlots task[${t}]="${taskTitle}" no blocks. saving.`) - if (!noTimeForTasks['_']) noTimeForTasks['_'] = [] - noTimeForTasks['_'].push(task) - } - } - return { timeMap: newMap, blockList: newBlockList, timeBlockTextList, noTimeForTasks } -} - -/** - * Attach links to the underlying todo note/heading if open and is not a task in today's note and if the config calls for it - * @param { [todos] } todos - * @param { * } config - * @returns - */ -export function appendLinkIfNecessary(todos: Array, config: AutoTimeBlockingConfig): Array { - let todosWithLinks = [] - try { - if (todos.length && config.includeLinks !== 'OFF') { - todosWithLinks = [] - todos.forEach((e) => { - const isInToday = e.note?.filename === Editor.note?.filename - if (e.type === 'open' && !isInToday) { - // don't create URL links for tasks in the same note - let link = '' - if (config.includeLinks === '[[internal#links]]') { - // link = ` ${returnNoteLink(e.note?.title ?? '', e.heading)}` - link = ` ${createWikiLinkToLine(e)}` - } else { - if (config.includeLinks === 'Pretty Links') { - // link = ` ${createPrettyOpenNoteLink(config.linkText, e.filename ?? 'unknown', true, e.heading)}` - link = ` ${createPrettyLinkToLine(e, config.linkText)}` - } - } - e.content = `${textWithoutSyncedCopyTag(e.content).replace(link, '')}${link}` - } - todosWithLinks.push(e) - }) - } else { - todosWithLinks = todos - } - } catch (error) { - logError('timeblocking-helpers::appendLinkIfNecessary ${error}', JSP(error)) - } - return todosWithLinks -} - -export const addDurationToTasks = (tasks: Array, config: { [key: string]: any }): Array => { - const dTasks = tasks.map((t) => { - // $FlowIgnore - Flow doesn't like spreading interfaces - const copy = { ...t, duration: 0 } - copy.duration = getDurationFromLine(t.content, config.durationMarker) || config.defaultDuration - return copy - }) - return dTasks -} - -export function getTimeBlockTimesForEvents(timeMap: IntervalMap, todos: Array, config: { [key: string]: any }): TimeBlocksWithMap { - let newInfo = { timeMap, blockList: [], timeBlockTextList: [], noTimeForTasks: {} } - // $FlowIgnore - const availableTimes = filterTimeMapToOpenSlots(timeMap, config) // will be different for BY_TIMEBLOCK_TAG - if (availableTimes.length === 0) { - timeMap.forEach((m) => console.log(`getTimeBlockTimesForEvents no more times available: ${JSON.stringify(m)}`)) - } - const blocksAvailable = findTimeBlocks(availableTimes, config) - if (availableTimes.length && todos?.length && blocksAvailable?.length && timeMap?.length && config.mode) { - const todosWithDurations = addDurationToTasks(todos, config) - switch (config.mode) { - case 'PRIORITY_FIRST': { - // Go down priority list and split events if necessary - const sortedTaskList = sortListBy(todosWithDurations, ['-priority', 'duration']) - newInfo = matchTasksToSlots(sortedTaskList, { blockList: blocksAvailable, timeMap: availableTimes }, config) - logDebug(pluginJson, `getTimeBlockTimesForEvents newInfo.noTimeForTasks=${JSP(newInfo.noTimeForTasks)}`) - // const { timeBlockTextList, timeMap, blockList } = newInfo - break - } - case 'LARGEST_FIRST': { - // TODO: actually need to implement this - const sortedTaskList = sortListBy(todosWithDurations, ['-duration', '-priority']) - // const sortedBlockList = sortListBy(blocksAvailable, ['-minsAvailable']) //won't work because blocks gets recalced - newInfo = matchTasksToSlots(sortedTaskList, { blockList: blocksAvailable, timeMap: availableTimes }, config) - // FIXME: HERE AND RESULT IS NOT RIGHT - break - } - case 'BY_TIMEBLOCK_TAG': { - const sortedTaskList = sortListBy(todosWithDurations, ['-priority', 'filename', '-duration']) - // clo(blocksAvailable, `getTimeBlockTimesForEvents blocksAvailable`) - // clo(timeMap, `getTimeBlockTimesForEvents timeMap`) - // clo(sortedTaskList, `getTimeBlockTimesForEvents sortedTaskList`) - newInfo = processByTimeBlockTag(sortedTaskList, { blockList: blocksAvailable, timeMap: availableTimes }, config) - break - } - case 'MANUAL_ORDERING': { - const sortedTaskList = sortListBy(todosWithDurations, ['lineIndex']) - newInfo = matchTasksToSlots(sortedTaskList, { blockList: blocksAvailable, timeMap: availableTimes }, config) - } - } - } else { - logDebug( - `INFO: getTimeBlockTimesForEvents nothing will be entered because todos.length=${todos.length} blocksAvailable.length=${blocksAvailable.length} timeMap.length=${timeMap.length} config.mode=${config.mode}`, - ) - } - return newInfo -} - -/** - * (unused) - * Remove all the timeblock added text so as to not add it to the todo list (mostly for synced lines) - * @param {*} line - */ -// export function isAutoTimeBlockLine(line: string, config?: { [key: string]: any }): null | string { -// // otherwise, let's scan it for the ATB signature -// // this is probably superfluous, but it's here for completeness -// let re = /(?:[-|\*] \d{2}:\d{2}-\d{2}:\d{2} )(.*)(( \[.*\]\(.*\))|( \[\[.*\]\]))(?: #.*)/ -// let m = re.exec(line) -// if (m && m[1]) { -// return m[1] -// } -// return null -// } - -/** - * (unused) - * Remove items from paragraph list that are auto-time-block lines - * @param {*} paras - */ -// export function removeTimeBlockParas(paras: Array): Array { -// return paras.filter((p) => !isAutoTimeBlockLine(p.content)) -// } - -// pattern could be a string or a /regex/ in a string -export function getRegExOrString(input: string | RegExp): RegExp | string { - if (input instanceof RegExp) return input - const str = input.trim() - if (str.startsWith('/') && str.endsWith('/')) { - return new RegExp(str.slice(1, -1)) - } else { - return str - } -} - -export function includeTasksWithPatterns(tasks: $ReadOnlyArray, pattern: string | $ReadOnlyArray): Array { - if (Array.isArray(pattern)) { - return tasks.filter((t) => pattern.some((p) => t.content.match(getRegExOrString(p)))) - } else if (typeof pattern === 'string') { - const pattArr = pattern.split(',') - return tasks.filter((t) => pattArr.some((p) => t.content.match(getRegExOrString(p)))) - } else { - // must be a regex - return tasks.filter((t) => t.content.match(pattern)) - } -} - -export function excludeTasksWithPatterns(tasks: Array, pattern: string | Array): Array { - if (Array.isArray(pattern)) { - return tasks.filter((t) => !pattern.some((p) => t.content.match(getRegExOrString(p)))) - } else if (typeof pattern === 'string') { - const pattArr = pattern.split(',') - return tasks.filter((t) => !pattArr.some((p) => t.content.match(getRegExOrString(p)))) - } else { - return tasks.filter((t) => !t.content.match(pattern)) - } -} - -/** - * Take in a list of paragraphs and a sortList (not exactly paragraphs) and return an ordered list of paragraphs matching the sort list - * This was necessary because for Synced Lines, we want the Synced Lines to match the ordering of the Time Block List but by the - * Time we get through the sorting, we have custom Paragraphs, not paragraphs we can turn into synced lines. So we need to go back and - * Find the source paragraphs - * One challenge is that the sorted content has been cleaned (of dates, etc.) - * @param {Array} paragraphs - * @param {Array} sortList (FIXME: should provide a Flow type for this) - * @returns {Array} paragraphs sorted in the order of sortlist - */ -export function getFullParagraphsCorrespondingToSortList(paragraphs: Array, sortList: Array): Array { - if (sortList && paragraphs) { - const sortedParagraphs = - sortList - .map((s) => { - return paragraphs.find((p) => removeDateTagsAndToday(p.rawContent) === removeDateTagsAndToday(s.raw) && p.filename === s.filename) - }) - // Filter out nulls - ?.filter(Boolean) ?? [] - return sortedParagraphs - } - return [] -} diff --git a/dwertheimer.EventAutomations/src/timeblocking-shared.js b/dwertheimer.EventAutomations/src/timeblocking-shared.js deleted file mode 100644 index b243ca610..000000000 --- a/dwertheimer.EventAutomations/src/timeblocking-shared.js +++ /dev/null @@ -1,226 +0,0 @@ -// @flow - -import pluginJson from '../plugin.json' -import type { SortableParagraphSubset } from '../../helpers/sorting' -import type { AutoTimeBlockingConfig } from './config' -import { appendLinkIfNecessary, removeDateTagsFromArray, includeTasksWithPatterns, excludeTasksWithPatterns } from './timeblocking-helpers' -import { validateAutoTimeBlockingConfig, getTimeBlockingDefaults } from './config' -import { sortListBy } from '@helpers/sorting' -import { JSP, clo, log, logError, logWarn, logDebug, clof } from '@helpers/dev' -import { getTodaysReferences, findOpenTodosInNote } from '@helpers/NPnote' -import { showMessage } from '@helpers/userInput' -import { isTimeBlockLine } from '@helpers/timeblocks' -import { eliminateDuplicateSyncedParagraphs } from '@helpers/syncedCopies' -import { getSyncedCopiesAsList } from '@helpers/NPSyncedCopies' -import { insertContentUnderHeading } from '@helpers/NPParagraph' - -export const shouldRunCheckedItemChecksOriginal = (config: AutoTimeBlockingConfig): boolean => config.checkedItemChecksOriginal && ['+'].indexOf(config.todoChar) > -1 - -/** - * Deletes paragraphs containing a specific string from the given note and returns the strings without the leading time signature - * - * @param {CoreNoteFields} destNote - the note to delete paragraphs from - * @param {string} timeBlockTag - the string to search for in the paragraphs - * @return {Array} - the contents of the deleted paragraphs without the AutoTimeBlocking tag - */ -export function deleteParagraphsContainingString(destNote: CoreNoteFields, timeBlockTag: string): Array { - const destNoteParas = destNote.paragraphs - const parasToDelete = [] - for (let i = 0; i < destNoteParas.length; i++) { - const p = destNoteParas[i] - if (new RegExp(timeBlockTag, 'gm').test(p.content)) { - parasToDelete.push(p) - } - } - if (parasToDelete.length > 0) { - const deleteListByIndex = sortListBy(parasToDelete, ['lineIndex']) //NP API may give wrong results if lineIndexes are not in ASC order - destNote.removeParagraphs(deleteListByIndex) - } - return parasToDelete.map((p) => p.content.replace(timeBlockTag, '').replace(/^\d{2}:\d{2}-\d{2}:\d{2} /g, '')) -} - -/** - * Creates synced copies of the provided todo items according to the configuration. - * This is a helper function used within insertAndFinalizeTimeBlocks. - * - * @param {Array} todos - The list of todo items to create synced copies for. - * @param {AutoTimeBlockingConfig} config - The configuration object for auto time blocking. - * @returns {Promise} - */ -export async function createSyncedCopies(todos: Array, config: AutoTimeBlockingConfig): Promise { - // Assuming `writeSyncedCopies` is a utility function that handles the creation of synced copies. - await writeSyncedCopies(todos, { runSilently: true, ...config }) -} - -/** - * Fetches and prepares the todo items for today. It filters out completed items, - * appends necessary links based on the configuration, and removes date tags from - * todo items. - * - * @param {AutoTimeBlockingConfig} config - The configuratifetchon object for auto time blocking. - * @param {Array} completedItems - List of paragraphs/items that have been completed. - * @param {Object} pluginJson - Plugin metadata for logging purposes. - * @returns {Promise>} - The prepared list of todo items for today. - */ -export async function gatherAndPrepareTodos(config: AutoTimeBlockingConfig, completedItems: Array): Promise<$ReadOnlyArray> { - deleteParagraphsContainingString(Editor, config.timeBlockTag) - - // Fetch todo items for today - const todosParagraphs = await getTodaysFilteredTodos(config) - .filter((todo) => todo.type === 'open') - .filter((todo) => todo.filename !== Editor.filename || (todo.filename === Editor.filename && !completedItems.find((c) => c.lineIndex === todo.lineIndex))) - - logDebug(pluginJson, `Back from getTodaysFilteredTodos, ${todosParagraphs.length} potential items`) - - // Append links if necessary - const todosWithLinksMaybe = appendLinkIfNecessary(todosParagraphs, config) - logDebug(pluginJson, `After appendLinkIfNecessary, ${todosWithLinksMaybe.length} potential items`) - - // Remove date tags from todo items - const cleanTodayTodoParas = removeDateTagsFromArray(todosWithLinksMaybe) - logDebug(pluginJson, `After removeDateTagsFromArray, ${cleanTodayTodoParas.length} potential items`) - - return cleanTodayTodoParas -} - -/** - * Get the config for this plugin, from DataStore.settings or the defaults if settings are not valid - * Note: augments settings with current DataStore.preference('timeblockTextMustContainString') setting - * @returns {} config object - */ -export function getConfig(): AutoTimeBlockingConfig { - const config = DataStore.settings || {} - if (Object.keys(config).length) { - try { - // $FlowIgnore - // In real NotePlan, config.timeblockTextMustContainString won't be set, but in testing it will be, so this covers both test and prod - if (!config.timeblockTextMustContainString) config.timeblockTextMustContainString = DataStore.preference('timeblockTextMustContainString') || '' - validateAutoTimeBlockingConfig(config) - return config - } catch (error) { - showMessage(`Plugin Settings ${error.message}\nRunning with default settings. You should probably open the plugin configuration dialog and fix the problem(s) listed above.`) - logDebug(pluginJson, `Plugin Settings ${error.message} Running with default settings`) - } - } else { - logDebug(pluginJson, `config was empty. will use defaults`) - } - const defaultConfig = getTimeBlockingDefaults() - return defaultConfig -} - -/** - * Find all (unduplicated) todos: - * - todo items from references list (aka "backlinks") - * + items in the current note marked >today or with today's >date - * + open todos in the note (if that setting is on) - * + ...which include the include pattern (if specified in the config) - * - ...which do not include items the exclude pattern (if specified in the config) - * - items in the current note that are synced tasks to elsewhere (will be in references also) - * - * @param {*} config - * @param {Boolean} isSyncedCopyRun - true if we are just trying to get synced copies output (makes a difference in MANUAL_ORDERING mode) - * @returns - */ -export function getTodaysFilteredTodos(config: AutoTimeBlockingConfig, isSyncedCopyRun = false): Array { - const { includeTasksWithText, excludeTasksWithText, includeAllTodos, timeBlockTag } = config - // filter down to just the open todos - const backlinkParas = getTodaysReferences(Editor.note).filter((p) => p.type === 'open') - logDebug(pluginJson, `Found ${backlinkParas.length} backlink paras`) - clof(backlinkParas, `getTodaysFilteredTodos backlinkParas filtered to open`, ['filename', 'type', 'content'], true) - let todosInNote = Editor.note ? findOpenTodosInNote(Editor.note, includeAllTodos).filter((p) => p.type === 'open') : [] - if (todosInNote.length > 0) { - logDebug(pluginJson, ` getTodaysFilteredTodos: todosInNote Found ${todosInNote.length} items in today's note. Adding them to the possibilities.`) - clof(todosInNote, `getTodaysFilteredTodos todosInNote filtered to open`, ['filename', 'type', 'content'], true) - // we want to eliminate linked lines (for synced lines on the page) - // because these should be in the references from other pages - // but it's possible that this is a normal task in the note that is not in references, so for now, commenting this filter out - // because it should get deduped later in this function - const todayTasksWithSyncedLines = todosInNote.filter((todo) => /\^[a-zA-Z0-9]{6}/.test(todo.content)) - logDebug( - pluginJson, - ` getTodaysFilteredTodos: todosInNote had ${todayTasksWithSyncedLines.length} synced line items in today's note. If they are dupes in references, they should get deduped in the following steps.`, - ) - todosInNote = todosInNote.filter((todo) => !isTimeBlockLine(todo.content)) // if a user is using the todo character for timeblocks, eliminate those lines - todosInNote = todosInNote.filter((todo) => !new RegExp(timeBlockTag).test(todo.content)) // just to be extra safe, make sure we're not adding our own timeblocks - } - const backLinksAndNoteTodos = config.mode === 'MANUAL_ORDERING' && !isSyncedCopyRun ? todosInNote : [...backlinkParas, ...todosInNote] - - logDebug(pluginJson, `Found ${backLinksAndNoteTodos.length} backlinks+today-note items (may include completed items)`) - const undupedBackLinkParas = eliminateDuplicateSyncedParagraphs(backLinksAndNoteTodos, 'first', true) - logDebug(pluginJson, `Found ${undupedBackLinkParas.length} undupedBackLinkParas after duplicate elimination`) - // let todosParagraphs: Array = makeAllItemsTodos(undupedBackLinkParas) //some items may not be todos but we want to pretend they are and timeblock for them - // logDebug(pluginJson, `After makeAllItemsTodos, ${todosParagraphs.length} potential items`) - let todosParagraphs = - Array.isArray(includeTasksWithText) && includeTasksWithText?.length > 0 ? includeTasksWithPatterns(undupedBackLinkParas, includeTasksWithText) : undupedBackLinkParas - logDebug(pluginJson, `After includeTasksWithPatterns (${(includeTasksWithText ?? []).join(', ')}), ${todosParagraphs.length} potential items`) - todosParagraphs = Array.isArray(excludeTasksWithText) && excludeTasksWithText?.length > 0 ? excludeTasksWithPatterns(todosParagraphs, excludeTasksWithText) : todosParagraphs - logDebug(pluginJson, `After excludeTasksWithPatterns (${(excludeTasksWithText ?? []).join(', ')}), ${todosParagraphs.length} potential items`) - clof(todosParagraphs, `getTodaysFilteredTodos todosParagraphs`, ['filename', 'type', 'content'], true) - return todosParagraphs.filter((t) => t.content) -} - -/** - * Write synced copies of passed paragraphs to the Editor - * Assumes any deletions were done already - * @param {Array} todosParagraphs - the paragraphs to write - * @return {Promise, config: AutoTimeBlockingConfig): Promise { - if (!todosParagraphs.length && !config.runSilently) { - await showMessage(`No todos/references marked for this day!`, 'OK', 'Write Synced Copies') - } else { - clof(todosParagraphs, `writeSyncedCopies: todosParagraphs`, 'content', true) - const syncedList = getSyncedCopiesAsList(todosParagraphs) - clo(syncedList, `writeSyncedCopies: syncedList`) - logDebug(pluginJson, `Deleting previous synced list heading and content`) - if (!String(config.syncedCopiesTitle)?.length) { - await showMessage(`You need to set a synced copies title in the plugin settings`) - return - } - logDebug(pluginJson, `Inserting synced list content: ${syncedList.length} items`) - // $FlowIgnore - await insertItemsIntoNote(Editor, syncedList, config.syncedCopiesTitle, config.foldSyncedCopiesHeading, config) - } -} - -export async function insertItemsIntoNote( - note: CoreNoteFields, - list: Array | null = [], - heading: string = '', - shouldFold: boolean = false, - config: AutoTimeBlockingConfig = getConfig(), -) { - if (list && list.length > 0 && note) { - // $FlowIgnore - logDebug(pluginJson, `insertItemsIntoNote: items.length=${list.length}`) - clo(list, `insertItemsIntoNote: list`) - clof(list, `insertItemsIntoNote: list`, null, false) - // Note: could probably use API addParagraphBelowHeadingTitle to insert - insertContentUnderHeading(note, heading, list.join('\n')) - // Fold the heading to hide the list - if (shouldFold && heading !== '') { - const thePara = note.paragraphs.find((p) => p.type === 'title' && p.content.includes(heading)) - if (thePara) { - logDebug(pluginJson, `insertItemsIntoNote: folding "${heading}" - isFolded=${String(Editor.isFolded(thePara))}`) - // $FlowIgnore[method-unbinding] - the function is not being removed from the Editor object. - if (Editor.isFolded) { - // make sure this command exists - if (!Editor.isFolded(thePara)) { - Editor.toggleFolding(thePara) - logDebug(pluginJson, `insertItemsIntoNote: folded heading "${heading}"`) - } - } else { - thePara.content = `${String(heading)} …` // this was the old hack for folding - await note.updateParagraph(thePara) - note.content = note.content ?? '' - } - } else { - logDebug(pluginJson, `insertItemsIntoNote could not find heading: ${heading}`) - } - } - } else { - if (config && !config.passBackResults) { - // await showMessage('No items to insert or work hours left. Check config/presets. Also look for calendar events which may have blocked off the rest of the day.') - } - } -} diff --git a/dwertheimer.EventAutomations/src/triggers.js b/dwertheimer.EventAutomations/src/triggers.js deleted file mode 100644 index c04f82ee2..000000000 --- a/dwertheimer.EventAutomations/src/triggers.js +++ /dev/null @@ -1,67 +0,0 @@ -// @flow - -import pluginJson from '../plugin.json' -import { shouldRunCheckedItemChecksOriginal, getConfig, getTodaysFilteredTodos } from './timeblocking-shared' -import { cleanTimeBlockLine } from './timeblocking-helpers' -import { createTimeBlocksForTodaysTasks } from './NPTimeblocking' -import { isTriggerLoop } from '@helpers/NPFrontMatter' -import { log, logError, logDebug, timer, clo, clof, JSP } from '@helpers/dev' -import { getBlockUnderHeading } from '@helpers/NPParagraph' - -/** - * onEditorWillSave - look for timeblocks that were marked done and remove them - * Plugin entrypoint for command: "/onEditorWillSave" (trigger) - * @author @dwertheimer - * @param {*} incoming - */ -export async function onEditorWillSave(incoming: string | null = null) { - try { - if (Editor?.note && isTriggerLoop(Editor.note)) return - const completedTypes = ['done', 'scheduled', 'cancelled', 'checklistDone', 'checklistScheduled', 'checklistCancelled'] - logDebug(pluginJson, `onEditorWillSave running with incoming:${String(incoming)}`) - const config = await getConfig() - const { timeBlockHeading } = config - // check for today note? -- if (!editorIsOpenToToday()) - if (shouldRunCheckedItemChecksOriginal(config)) { - // get character block - const updatedParasInTodayNote = [] - const timeBlocks = getBlockUnderHeading(Editor, timeBlockHeading, false) - if (timeBlocks?.length) { - // only try to mark items that are completed and were created by this plugin - const checkedItems = timeBlocks.filter((f) => completedTypes.indexOf(f.type) > -1 && f.content.indexOf(config.timeBlockTag) > -1) - if (checkedItems?.length) { - clo(checkedItems, `onEditorWillSave found:${checkedItems?.length} checked items`) - const todayTodos = getTodaysFilteredTodos(config) - // clo(todayTodos, `onEditorWillSave ${todayTodos?.length} todayTodos`) - checkedItems.forEach((item, i) => { - const referenceID = item.content.match(/noteplan:\/\/.*(\%5E.*)\)/)?.[1].replace('%5E', '^') || null - logDebug(pluginJson, `onEditorWillSave: item[${i}] content="${item.content}" blockID="${referenceID}"`) - const todo = todayTodos.find((f) => (referenceID ? f.blockId === referenceID : cleanTimeBlockLine(item.content, config) === cleanTimeBlockLine(f.content, config))) - if (todo) { - clo(todo, `onEditorWillSave: found todo for item[${i}] blockID="${referenceID}" content=${todo.content} in file ${todo.filename || ''} | now updating`) - const isEditor = Editor.filename === todo.filename - const note = isEditor ? Editor : todo.note - todo.type = 'done' - note?.updateParagraph(todo) - logDebug(pluginJson, `onEditorWillSave: found todo for item[${i}] blockID="${referenceID}" content=${todo.content} in file ${todo.filename || ''} | now updating`) - if (!isEditor) { - DataStore.updateCache(note) - } else { - logDebug(pluginJson, `onEditorWillSave: checked off item: "${item.content}" but manual refresh of TimeBlocks will be required`) - updatedParasInTodayNote.push(Editor.paragraphs[todo.lineIndex]) - } - } else { - logDebug(pluginJson, `onEditorWillSave: no todo found for item[${i}] blockID="${referenceID}" cleanContent="${cleanTimeBlockLine(item.content, config)}"`) - } - }) - // re-run /atb - but is it safe within a hook? - await createTimeBlocksForTodaysTasks(config, updatedParasInTodayNote) - } else { - logDebug(pluginJson, `onEditorWillSave: no checked items found; nothing to do; exiting gracefully.`) - } - } - } - } catch (error) { - logError(pluginJson, JSP(error)) - } -} diff --git a/dwertheimer.Favorites/src/NPFavoritePresets.js b/dwertheimer.Favorites/src/NPFavoritePresets.js deleted file mode 100644 index 46eea6fb9..000000000 --- a/dwertheimer.Favorites/src/NPFavoritePresets.js +++ /dev/null @@ -1,152 +0,0 @@ -// @flow - -import pluginJson from '../plugin.json' -import { showMessage, chooseOption } from '@helpers/userInput' -import { log, logError, logDebug, timer, clo, JSP } from '@helpers/dev' -import { type PresetCommand, savePluginCommand, choosePreset, presetChosen } from '@helpers/NPPresets' -import { getPluginJson } from '@helpers/NPConfiguration' - -const COMMAND_NAME_TEMPLATE = 'Favorites: Set Preset ' - -// check whether valid URL or xcallback URL -const isValidURL = (url) => /^(https?|[a-z0-9\-]+):\/\/[a-z0-9\-]+/i.test(url) - -/** - * Get the URL either through the callback creator or hand-entered - */ -export async function getURL(commandName: string, defaultValue: string): Promise { - const options = [ - { label: 'Run Link Creator to get URL (e.g. a NotePlan X-Callback)', value: 'linkCreator' }, - { label: 'Enter/Paste URL (you know or can paste the URL)', value: 'enter' }, - ] - if (defaultValue) options.push({ label: `Keep Previous Value: "${defaultValue}"`, value: 'previous' }) - const choice = await chooseOption('How do you want to set the URL?', options, null) - let url = '' - if (choice) { - if (choice === 'linkCreator') { - url = await DataStore.invokePluginCommandByName('Get X-Callback-URL', 'np.CallbackURLs', ['', true]) - } else if (choice === 'enter') { - url = await CommandBar.textPrompt('Set Preset X-Callback', `What X-Callback or URL do you want to run when the command\n"${commandName}"\nis selected?`, defaultValue || '') - } else if (choice === 'previous') { - url = defaultValue - } - } else { - logDebug(pluginJson, `getURL no choice made. Returning empty string.`) - } - return url || '' -} - -/** - * Each of the preset commands calls this function, as does set/reset a command - * It is called indirectly, as a callback sent to presetChosen - * @param {PresetCommand} commandDetails - the full command object from the current plugin.json - * @param {boolean} overwrite - this is a set/reset call - */ -export async function favoritePresetChosen(commandDetails: PresetCommand | null = null, overwrite: boolean = false) { - if (!Editor) { - showMessage(`You must be in the Editor with a document open to run this command`) - return - } - clo(commandDetails, `favoritePresetChosen: overwrite:${String(overwrite)} commandDetails:`) - if (commandDetails) { - let commandName = commandDetails.name.startsWith(COMMAND_NAME_TEMPLATE) ? '' : commandDetails.name - commandName = DataStore.settings.charsToPrepend ? commandName.replace(DataStore.settings.charsToPrepend, '').trim() : commandName - logDebug(pluginJson, `favoritePresetChosen: command.name = "${commandDetails.name}" overwrite?:${String(overwrite)}`) - // Put the text of an unset command in the plugin.json here (how we tell if it's vanilla/unset) - const favoriteIsUnset = !commandDetails.name || commandDetails.name.match(/Set Preset/) - logDebug(pluginJson, `favoritePresetChosen: favoriteIsUnset=${String(favoriteIsUnset)}`) - if (favoriteIsUnset || overwrite) { - // SET THE PRESET COMMAND - let favoriteCommand = await CommandBar.textPrompt( - 'Set Preset Text', - 'What human-readable text do you want to use for the command? (this is the text you will see in the Command Bar when you type slash)\n\nLeave blank to unset the command', - commandName, - ) - logDebug(pluginJson, `favoritePresetChosen favoriteCommand entered=${String(favoriteCommand)}`) - if (favoriteCommand && favoriteCommand !== '') { - favoriteCommand = DataStore.settings.charsToPrepend ? `${DataStore.settings.charsToPrepend}${favoriteCommand}` : favoriteCommand - const text = await getURL(favoriteCommand, commandDetails.data || '') - if (text) { - // validate x-callback URL - if (!isValidURL(text)) { - await showMessage(`"${text}" is not a valid URL. Must be an X-Callback URL or full Web URL. Please try again.`) - return - } - await savePluginCommand(pluginJson, { ...commandDetails, name: favoriteCommand, data: text }) - await showMessage( - `Menu command set.\n\nYou will find it in the CommandBar immediately by typing:\n"/${String( - favoriteCommand, - )}"\nYou won't see it in the menu until you restart NotePlan.`, - ) - } else { - // User cancelled the x-callback prompt - await showMessage(`Command not set. You must enter an X-Callback URL to set this command.`) - } - } else { - if (favoriteCommand === '') { - clo(commandDetails, `favoritePresetChosen: Unsetting commandDetails:`) - const numString = commandDetails.jsFunction.replace(/runPreset/, '') - await savePluginCommand(pluginJson, { ...commandDetails, name: `${COMMAND_NAME_TEMPLATE} ${numString}`, data: '' }) - await showMessage(`Preset ${numString} has been deleted and can be reused.`) - } // otherwise someone canceled the command - } - } else { - // EXECUTE THE COMMAND CLICKED - if (commandDetails.data) { - NotePlan.openURL(commandDetails.data) - } else { - logError(pluginJson, `favoritePresetChosen No commandDetails.data for command: ${commandName}. Cannot move forward.`) - } - } - } else { - logError(pluginJson, `favoritePresetChosen: no command details object sent. Cannot continue.`) - } -} - -/* - * PLUGIN ENTRYPOINTS BELOW THIS LINE - */ - -/** - * Change a preset to another one - * Plugin entrypoint for command: "/Change favorite Preset" - * @param {*} incoming - */ -export async function changePreset(incoming: string) { - try { - logDebug(pluginJson, `changePreset running incoming:${incoming}`) - const livePluginJson = await getPluginJson(pluginJson['plugin.id']) - const chosen = await choosePreset(livePluginJson, 'Choose a preset to set/reset') - if (chosen) { - logDebug(pluginJson, `changePreset: ${chosen} -- calling presetChosen with favoritePresetChosen callback`) - await presetChosen(pluginJson, chosen, favoritePresetChosen, [true]) - } - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * PRESET ENTRYPOINTS BELOW - */ - -export const runPreset01 = async (): Promise => await presetChosen(pluginJson, `runPreset01`, favoritePresetChosen) -export const runPreset02 = async (): Promise => await presetChosen(pluginJson, `runPreset02`, favoritePresetChosen) -export const runPreset03 = async (): Promise => await presetChosen(pluginJson, `runPreset03`, favoritePresetChosen) -export const runPreset04 = async (): Promise => await presetChosen(pluginJson, `runPreset04`, favoritePresetChosen) -export const runPreset05 = async (): Promise => await presetChosen(pluginJson, `runPreset05`, favoritePresetChosen) -export const runPreset06 = async (): Promise => await presetChosen(pluginJson, `runPreset06`, favoritePresetChosen) -export const runPreset07 = async (): Promise => await presetChosen(pluginJson, `runPreset07`, favoritePresetChosen) -export const runPreset08 = async (): Promise => await presetChosen(pluginJson, `runPreset08`, favoritePresetChosen) -export const runPreset09 = async (): Promise => await presetChosen(pluginJson, `runPreset09`, favoritePresetChosen) -export const runPreset10 = async (): Promise => await presetChosen(pluginJson, `runPreset10`, favoritePresetChosen) -export const runPreset11 = async (): Promise => await presetChosen(pluginJson, `runPreset11`, favoritePresetChosen) -export const runPreset12 = async (): Promise => await presetChosen(pluginJson, `runPreset12`, favoritePresetChosen) -export const runPreset13 = async (): Promise => await presetChosen(pluginJson, `runPreset13`, favoritePresetChosen) -export const runPreset14 = async (): Promise => await presetChosen(pluginJson, `runPreset14`, favoritePresetChosen) -export const runPreset15 = async (): Promise => await presetChosen(pluginJson, `runPreset15`, favoritePresetChosen) -export const runPreset16 = async (): Promise => await presetChosen(pluginJson, `runPreset16`, favoritePresetChosen) -export const runPreset17 = async (): Promise => await presetChosen(pluginJson, `runPreset17`, favoritePresetChosen) -export const runPreset18 = async (): Promise => await presetChosen(pluginJson, `runPreset18`, favoritePresetChosen) -export const runPreset19 = async (): Promise => await presetChosen(pluginJson, `runPreset19`, favoritePresetChosen) -export const runPreset20 = async (): Promise => await presetChosen(pluginJson, `runPreset20`, favoritePresetChosen) diff --git a/dwertheimer.Favorites/src/favorites.js b/dwertheimer.Favorites/src/favorites.js deleted file mode 100644 index 5e924198c..000000000 --- a/dwertheimer.Favorites/src/favorites.js +++ /dev/null @@ -1,28 +0,0 @@ -// @flow - -export type FavoritesConfig = { - favoriteIcon: string, - position: 'prepend' | 'append', -} - -export const getFavoriteDefault = (): string => '⭐️' - -export const filterForFaves = (notes: $ReadOnlyArray, config: FavoritesConfig): Array => notes.filter((n) => hasFavoriteIcon(n.title || '', config.favoriteIcon)) - -export const getFaveOptionsArray = (notes: Array): Array<{ label: string, value: string }> => - notes - .filter((n) => Boolean(n.title && n.filename)) - .map((n) => { - // $FlowIgnore - return { label: n.title, value: n.filename } - }) - -export const hasFavoriteIcon = (title: string, icon: string): boolean => title.search(icon) !== -1 - -export const getFavoritedTitle = (title: string, position: string, icon: string): string => { - return position === 'prepend' ? `${icon} ${title}` : `${title} ${icon}` -} - -export const removeFavoriteFromTitle = (title: string, icon: string): string => { - return title.replace(icon, '').trim().replace(' ', ' ') -} diff --git a/dwertheimer.Favorites/src/index.js b/dwertheimer.Favorites/src/index.js deleted file mode 100644 index 2baa4b3ea..000000000 --- a/dwertheimer.Favorites/src/index.js +++ /dev/null @@ -1,57 +0,0 @@ -// @flow -import pluginJson from '../plugin.json' -import { log, logError, logDebug, timer, clo, JSP } from '@helpers/dev' -import { /* getPluginJson ,*/ updateSettingData, pluginUpdated } from '@helpers/NPConfiguration' -import { rememberPresetsAfterInstall } from '@helpers/NPPresets' - -export { editSettings } from '@helpers/NPSettings' - -export { setFavorite, openFavorite, removeFavorite } from './NPFavorites' - -export { - changePreset, - runPreset01, - runPreset02, - runPreset03, - runPreset04, - runPreset05, - runPreset06, - runPreset07, - runPreset08, - runPreset09, - runPreset10, - runPreset11, - runPreset12, - runPreset13, - runPreset14, - runPreset15, - runPreset16, - runPreset17, - runPreset18, - runPreset19, - runPreset20, -} from './NPFavoritePresets' - -/** - * NotePlan calls this function after the plugin is installed or updated. - * The `updateSettingData` function looks through the new plugin settings in plugin.json and updates - * the user preferences to include any new fields - */ -export async function onUpdateOrInstall(): Promise { - logDebug(pluginJson, 'dwertheimer.Favorites::onUpdateOrInstall running') - await updateSettingData(pluginJson) - await rememberPresetsAfterInstall(pluginJson) -} - -/** - * NotePlan calls this function every time the plugin is run (any command in this plugin) - * You should not need to edit this function. All work should be done in the commands themselves - */ -export function init(): void { - // clo(DataStore.settings, `${pluginJson['plugin.id']} Plugin Settings`) - DataStore.installOrUpdatePluginsByID([pluginJson['plugin.id']], true, false, false).then((r) => pluginUpdated(pluginJson, r)) -} - -export function onSettingsUpdated(): void { - logDebug(pluginJson, 'dwertheimer.Favorites::onSettingsUpdated called (but this plugin does not do anything after settings are updated)') -} diff --git a/dwertheimer.JestHelpers/src/NPPluginMain.js b/dwertheimer.JestHelpers/src/NPPluginMain.js deleted file mode 100644 index 5f1390065..000000000 --- a/dwertheimer.JestHelpers/src/NPPluginMain.js +++ /dev/null @@ -1,218 +0,0 @@ -// @flow -// Plugin code goes in files like this. Can be one per command, or several in a file. -// `export async function [name of jsFunction called by Noteplan]` -// then include that function name as an export in the index.js file also -// About Flow: https://flow.org/en/docs/usage/#toc-write-flow-code -// Getting started with Flow in NotePlan: https://github.com/NotePlan/plugins/blob/main/Flow_Guide.md - -// NOTE: This file is named NPPluginMain.js (you could change that name and change the reference to it in index.js) -// As a matter of convention, we use NP at the beginning of files which contain calls to NotePlan APIs (Editor, DataStore, etc.) -// Because you cannot easily write tests for code that calls NotePlan APIs, we try to keep the code in the NP files as lean as possible -// and put the majority of the work in the /support folder files which have Jest tests for each function -// support/helpers is an example of a testable file that is used by the plugin command -// REMINDER, to build this plugin as you work on it: -// From the command line: -// `noteplan-cli plugin:dev dwertheimer.JestHelpers --test --watch --coverage` - -import pluginJson from '../plugin.json' -// import * as helpers from './support/helpers' -import { log, logError, clo, JSP, getFilteredProps } from '@helpers/dev' -// import { createRunPluginCallbackUrl } from '@helpers/general' -import { getInput } from '@helpers/userInput' - -/** - * A convenience function for creating Jest __mocks__ stubs for a NP API function - * Outputs result to console where it can be pasted into a __mocks__ file and edited - * @param {*} object - * @param {*} name - */ -export function createMockOutput(object: any, name: string): void { - // log(`NPdev::createMockOutput object type is: `, `${typeof object}`) - const props = getFilteredProps(object).sort() - const output = props.map((prop) => { - let propType, - value = '' - if (typeof object[prop] === 'object') { - if (Array.isArray(object[prop])) { - propType = 'array' - let objdetail = ' SOMETHING ' - if (object[prop].length) { - objdetail = `{ return ${JSP(object[prop][0], `\t\t`)} }` - } - value = ` -\t/* ${prop}: [${objdetail}], */` - } else { - propType = 'object' - let objdetail = '{ SOME_OBJECT_DATA }' - if (object[prop] && !(object[prop] instanceof Date)) { - objdetail = `${JSP(object[prop], `\t\t`)} ` - } else { - objdetail = `${object[prop].toString()} // (Date object)` - } - value = ` -\t/* ${prop}: ${objdetail}, */` - } - } else if (typeof object[prop] === 'function') { - propType = 'function' - value = ` -\t// async ${prop}() { return null }, ` - } else { - propType = 'value' - value = ` -\t// ${prop}: VALUE,` - } - return `${value}` - }) - console.log( - `/*\n * ${name} mocks\n *\n * Note: nested object example data are there for reference only -- will need to be deleted or cleaned up before use (consider using a factory)\n * For functions: check whether async or not & add params & return value\n * \n */\n\nconst ${name} = {\n${output.join( - '\n', - )}\n}\n\nmodule.exports = ${name}`, - ) -} - -function getMockClassText(name: string, props: Array, methods: Array): string { - const template = ` -/* - * ${name} mock class - * - * Usage: const my${name} = new ${name}({ param changes here }) - * - */ - -export class ${name} { - - // Properties - ${props.sort().join('\n')} - - // Methods - ${methods.sort().join('\n')} - - constructor(data?: any = {}) { - this.__update(data) - } - - __update(data?: any = {}) { - Object.keys(data).forEach((key) => { - this[key] = data[key] - }) - return this - } -} -` - return template -} - -export function createMockClass(object: any, name: string): void { - log(`NPdev::createMockOutput object type is: `, `${typeof object}`) - const classProps = [], - classMethods = [] - const properties = getFilteredProps(object).sort() - properties.forEach((prop) => { - let propType, - value = '' - if (typeof object[prop] === 'object') { - if (Array.isArray(object[prop])) { - propType = 'array' - let objdetail = '' - if (object[prop].length) { - objdetail = `${JSP(object[prop][0], ' ')} ` - } - classProps.push(`${prop} = [] ${objdetail.length ? `/* sample: [${objdetail}] */` : ''}`) - } else { - propType = 'object' - let objdetail = '{ SOME_OBJECT_DATA }' - if (object[prop] && !(object[prop] instanceof Date)) { - objdetail = `${JSP(object[prop], `\t\t`)} ` - } else { - objdetail = `new Date("${object[prop].toString()}")` - } - classProps.push(`${prop} = {} /* ${objdetail}, */`) - } - } else if (typeof object[prop] === 'function') { - propType = 'function' - classMethods.push(`async ${prop}() { throw("${name} :: ${prop} Not implemented yet") } `) - } else { - propType = 'value' - classProps.push(`${prop} = 'PLACEHOLDER' // TODO: add value`) - } - return `${value}` - }) - console.log(getMockClassText(name, classProps, classMethods)) -} - -export async function generateMock(incoming: ?string = ''): Promise { - // every command/plugin entry point should always be wrapped in a try/catch block - try { - // MUST BE A CLASS YOU ARE SENDING, NOT AN ARRAY!!! - - // EXAMPLE to create a subitem class: - // const pl = await DataStore.installedPlugins() - // createMockClass(pl[0].commands[0], 'PluginCommandObjectMock') - - const name = await getInput('What is the name of the mock?') - // console.log(this[name]) - if (name && this[name]) createMockOutput(this[name], name) - else console.log(`No object for ${name || ''}`) - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -// Returns NP object to prove that the mock is working from inside NP calls -export function getCalendar(): any { - return Calendar -} - -// Returns NP object to prove that the mock is working from inside NP calls -export function getClipboard(): any { - return Clipboard -} - -// Returns NP object to prove that the mock is working from inside NP calls -export function getCommandBar(): any { - return CommandBar -} - -// Returns NP object to prove that the mock is working from inside NP calls -export function getDataStore(): any { - return DataStore -} - -// Returns NP object to prove that the mock is working from inside NP calls -export function getEditor(): any { - return Editor -} - -// Returns NP object to prove that the mock is working from inside NP calls -export function getNotePlan(): any { - return NotePlan -} - -/** - * outputEditorJson - * Plugin entrypoint for "/Output Editor Doc as JSON" - */ -export function outputEditorJson() { - try { - const e = Editor - const nObj = { title: e.title, filename: e.filename, type: e.type, paragraphs: [] } - nObj.paragraphs = e.paragraphs.map((p) => ({ - content: p.content, - rawContent: p.rawContent, - type: p.type, - heading: p.heading, - headingLevel: p.headingLevel, - lineIndex: p.lineIndex, - isRecurring: p.isRecurring, - indents: p.indents, - noteType: p.noteType, - })) - console.log(`--- Editor ---`) - console.log(JSON.stringify(nObj, null, 2)) - console.log(`--- /Editor ---`) - console.log(`--- For debugging paras ---`) - nObj.paragraphs.forEach((p) => console.log(`[${p.lineIndex}]: type=${p.type} content="${p.content}" heading:"${p.heading}"`)) - } catch (error) { - logError(pluginJson, JSON.stringify(error)) - } -} diff --git a/dwertheimer.JestHelpers/src/index.js b/dwertheimer.JestHelpers/src/index.js deleted file mode 100644 index e0920d2dc..000000000 --- a/dwertheimer.JestHelpers/src/index.js +++ /dev/null @@ -1,54 +0,0 @@ -// @flow -// Flow typing is important for reducing errors and improving the quality of the code. -// About Flow: https://flow.org/en/docs/usage/#toc-write-flow-code -// Getting started with Flow in NotePlan: https://github.com/NotePlan/plugins/blob/main/Flow_Guide.md -// Note: As you will see in this plugin folder, you can have multiple files -- e.g. one file per command or logical group of commands -// ...and separate files for helper/support functions that can be tested in isolation -// The `autowatch` packager combines them all into one script.js file for NotePlan to read -// From the command line: -// `noteplan-cli plugin:dev {{pluginId}} --test --watch --coverage` -// ...will watch for changes and will compile the Plugin script code -// and copy it to your plugins directory where NotePlan can find it -// Since NP reloads the Javascript every time you CMD-J to insert a plugin, -// you can immediately test the new code without restarting NotePlan -// This index.js file is where the packager starts looking for files to combine into one script.js file -// So you need to add a line below for each function that you want NP to have access to. -// Typically, listed below are only the top-level plug-in functions listed in plugin.json - -export { generateMock, outputEditorJson } from './NPPluginMain' // add one of these for every command specifified in plugin.json (the function could be in any file as long as it's exported) - -// Do not change this line. This is here so your plugin will get recompiled every time you change your plugin.json file -import pluginJson from '../plugin.json' -import { clo } from '@helpers/dev' - -/* - * NOTEPLAN HOOKS - * The rest of these functions are called by NotePlan automatically under certain conditions - * It is unlikely you will need to edit/add anything below this line - */ - -import { updateSettingData, pluginUpdated } from '@helpers/NPConfiguration' - -/** - * NotePlan calls this function after the plugin is installed or updated. - * The `updateSettingData` function looks through the new plugin settings in plugin.json and updates - * the user preferences to include any new fields - */ -export async function onUpdateOrInstall(): Promise { - await updateSettingData(pluginJson) -} - -/** - * NotePlan calls this function every time the plugin is run (any command in this plugin) - * You should not need to edit this function. All work should be done in the commands themselves - */ -export async function init(): Promise { - clo(DataStore.settings, `${pluginJson['plugin.id']} Plugin Settings`) - DataStore.installOrUpdatePluginsByID([pluginJson['plugin.id']], true, false, false).then((r) => pluginUpdated(pluginJson, r)) -} - -/** - * NotePlan calls this function settings are updated in the Preferences panel - * You should not need to edit this function - */ -export async function onSettingsUpdated(): Promise {} diff --git a/dwertheimer.MathSolver/__tests__/date-time-math.test.js b/dwertheimer.MathSolver/__tests__/date-time-math.test.js deleted file mode 100644 index 0e96bef93..000000000 --- a/dwertheimer.MathSolver/__tests__/date-time-math.test.js +++ /dev/null @@ -1,28 +0,0 @@ -// Jest testing docs: https://jestjs.io/docs/using-matchers -/* global describe, expect, test, beforeAll */ -/* eslint-disable */ - -import * as d from '../src/support/date-time-math' -import { copyObject } from '@helpers/dev' - -import { Calendar, Clipboard, CommandBar, DataStore, Editor, NotePlan, Note, Paragraph } from '@mocks/index' - -beforeAll(() => { - global.Calendar = Calendar - global.Clipboard = Clipboard - global.CommandBar = CommandBar - global.DataStore = DataStore - global.Editor = Editor - global.NotePlan = NotePlan -}) - -describe.skip('dwertheimer.MathSolver' /* pluginID */, () => { - describe('date-time-math' /* file */, () => { - describe('checkForTime' /* file */, () => { - test('should find time in a line and return it', () => { - const res = d.checkForTime("03:00-04:00 yo",{}) - expect(res.strToBeParsed).toEqual("") - }) - }) -}) -}) /* describe */ diff --git a/dwertheimer.MathSolver/src/NPMathBlocks.js b/dwertheimer.MathSolver/src/NPMathBlocks.js deleted file mode 100644 index 6b1a0f144..000000000 --- a/dwertheimer.MathSolver/src/NPMathBlocks.js +++ /dev/null @@ -1,487 +0,0 @@ -// @flow -/** - * TODO: - * why doesn't dollars work? $2 + $4 - * "round by default" and "show no-rounding button" - * what to do about commas - * as you calculate and recalculate, seems to cycle between totals and non-totals - * refactor to make more modular and eliminate relations and one of the each-line-loops - * maybe print zero in output on subtotal or total lines only - * time-date math per George: https://discord.com/channels/763107030223290449/1009525907075113053/1012085619658334351 and - * - https://documentation.soulver.app/syntax-reference/dates-and-times - * - https://documentation.soulver.app/whats-new - * 2) would be so cool if @Eduard could tweak autocomplete inside a math block to give you choices of variables without you having to type them in. - * - Allow for statements inside parens - * - make "at" and "per" work properly - * - in to cm etc. - * - implement format preferences (see hidden user prefs) - * - implement insertResultsAtCursor - * - add user pref for whether to include total or not - * - the second total prints at the bottom (need a cloneDeep to work) - * (done) add output columns https://www.npmjs.com/package/columnify (showColumns fa;se) - * (done) Nested frontmatter under mathPresets - * (done) Can you assign a subtotal line to a variable? @george65#1130 - * (done) save variables you use frequently in preferences and reference them without defining them every time - * (done) pricePerHour = 20 //= 20 (does not need to print this out) - * (done) ignore date on left - * (done) basic time math - * Reference: https://numpad.io/ - * Playground: https://mathnotepad.com/ - */ -// import cloneDeep from 'lodash/cloneDeep' // Should not Crash NP anymore -import columnify from 'columnify' -import pluginJson from '../plugin.json' -import { chooseOption, showMessage } from '../../helpers/userInput' -import type { CodeBlock } from '../../helpers/codeBlocks' -import { type LineInfo, parse, isLineType, checkIfUnit } from './support/solver' -import { getParagraphContainingPosition, getSelectedParagraphLineIndex } from '@helpers/NPParagraph' -import { log, logDebug, logError, logWarn, clo, JSP } from '@helpers/dev' -import { createRunPluginCallbackUrl, formatWithFields, CreateUUID } from '@helpers/general' -import { getCodeBlocksOfType } from '@helpers/codeBlocks' -import { getAttributes } from '@helpers/NPFrontMatter' - -/** - * Get the frontmatter variables for this document - * @param {string} noteContent - * @returns {object} frontmatter values - */ -export function getFrontmatterVariables(noteContent: string): any { - try { - const noteContentNoTabs = noteContent.replace('\t', ' ') //tabs in frontmatter break hierarchy - const fmVars = getAttributes(noteContentNoTabs) - return fmVars && fmVars.mathPresets ? fmVars.mathPresets : {} - } catch (error) { - logError(pluginJson, JSP(error)) - return {} - } -} - -/** - * Round a number to a certain number of significant digits in the decimal - * @param {*} n - the number - * @param {*} d - the number of digits to round to (zeros will be omitted) - * @returns - */ -const round = (n: number, d: number) => (n && d ? Math.round(n * Math.pow(10, d)) / Math.pow(10, d) : 0) - -/** - * Format the output according to user preferences - * @param {Array} results - the results of the solver's info property - * @param {string} formatTemplate - the template to use for formatting output (not currently implemented) - * @param {string} precisionSetting - the precision to round to (default "No Rounding") - * @returns {Array} formatted text - */ -export function formatOutput(results: Array, formatTemplate: string = '{{originalText}} {{value}}', precisionSetting: string = 'No Rounding'): Array { - const precision = precisionSetting === 'No Rounding' ? 14 : Number(precisionSetting) - const resultsWithStringValues = results.map((line) => { - const isPctOf = /(\d*[\.,])?(\d+\s?)(as|as a)?(\s*%)(\s+(of)\s+)(\d*[\.,])?(\d+\s?)/g.test(line.originalText) - const isZero = line.lineValue === 0 && isLineType(line, ['N', 'S', 'T']) // && !/total/i.test(line.originalText) - const isNotCalc = (String(line.lineValue) === line.expression && !isPctOf) || (Number(line.lineValue) === Number(line.expression) && !Number(line.expression) !== 0) - const isNumericalAssignment = line.typeOfResult === 'A' && !/(\+|\-|\*|\/)+/.test(line.originalText) - const isUndefined = line.lineValue === undefined - let val = line.lineValue - if (!isUndefined) { - // logDebug(pluginJson, `checking line.lineValue: ${String(line?.lineValue)}`) - if (checkIfUnit(line.lineValue)) { - const strValue = String(line.lineValue).split(' ') - val = `${round(Number(strValue[0]), precision)} ${strValue[1]}` - logDebug(pluginJson, `checking val.value: ${val}`) - } else { - val = val && precision && typeof val === 'number' ? round(val, precision) : val - } - } - // val = val.toFixed(precision).replace(/(?:\.\d*)(0+)$/,"") - line.value = !line.lineValue || isZero || isNotCalc || isNumericalAssignment || isUndefined ? '' : `//= ${String(val)}` // eslint-disable-line eqeqeq - if (line.error) line.value += ` //= ${line.error}` - // logDebug(pluginJson, `line.value: ${line.value} line.expression: ${line.expression}`) - return line - }) - const formatted = resultsWithStringValues.map((line) => formatWithFields(formatTemplate, typeof line === 'object' ? line : {})) - // logDebug(pluginJson, `Formatted data: ${JSON.stringify(resultsWithStringValues, null, 2)}`) - - return formatted -} - -/** - * Parse the code blocks in the current note - * @returns {Array} the results of the solver - */ -// export function parseCodeBlocks(): void { -// // was: $ReadOnlyArray<$ReadOnly> -// // $FlowIgnore -// const codeBlocks = getCodeBlocksOfType('math') -// if (codeBlocks.length) { -// codeBlocks.map((block) => parse(block.text)) -// } else { -// logDebug(pluginJson, `There were no code blocks to parse`) -// } -// } - -/** - * Show the results of the solver in the editor at cursor position - * @param {Array} results - the results of the solver - * @param {string} template - should probably be called with settings.documentTemplate - */ -// export function insertResultsAtCursor(results: Array,template:string): void { -// const formatted = formatOutput(results,template) -// Editor.insertTextAtCursor(formatted.join('\n')) -// } - -/** - * Remove annotations from a specific code block - * @param {*} note - * @param {*} blockData - */ -export function removeAnnotations(note: CoreNoteFields, blockData: $ReadOnly) { - const updates = [] - for (let i = 0; i < blockData.paragraphs.length; i++) { - const paragraph = blockData.paragraphs[i] - if (/(\/\/\=.*)/g.test(paragraph.content)) { - // const thisParaInNote = note.paragraphs[paragraph.lineIndex] - paragraph.content = paragraph.content.replace(/(\/\/\=.*)/g, '').trimEnd() - } - paragraph.content = paragraph.content.trimEnd() - updates.push(paragraph) - } - if (updates.length) note.updateParagraphs(updates) -} - -export function annotateResults(note: CoreNoteFields, blockData: $ReadOnly, results: Array, template: string, mode: string): void { - const { columnarOutput, precisionSetting } = DataStore.settings - logDebug(pluginJson, `annotateResults mode=${mode} results:${results.length} lines`) - // clo(results, `annotateResults: results obj`) - const totalsOnly = mode === 'totalsOnly' - const debug = mode === 'debug' - const precision = mode === 'noRounding' ? 'No Rounding' : precisionSetting - formatOutput(results, template, precision) // important: populates the .value field for output - // const updates = [] - let j = 0 - const debugOutput = [] - const outputObjects = [] - for (let i = 0; i < blockData.paragraphs.length; i++) { - // const li = blockData.paragraphs[i].lineIndex - const paragraph = blockData.paragraphs[i] - const solverData = results[j] - paragraph.content = paragraph.content.replace(/(\/\/\=.*)/g, '').trimEnd() //clean every line - let shouldPrint = !totalsOnly || (totalsOnly && (solverData.typeOfResult === 'T' || solverData.typeOfResult === 'S')) - if (debug) { - shouldPrint = true - // Probably don't need to output error, because it's in value field: ${solverData.error.length ? ` err:"${solverData.error}"` : ''} - solverData.value = ` //= R${i}(${solverData.typeOfResult}): expr:"${solverData.expression}" lineValue:${String(solverData.lineValue)} value:"${String(solverData.value)}"` - // debugOutput.push(`R${String(i).padStart(2,'0')}(${solverData.typeOfResult}): orig:"${solverData.originalText}" expr:"${solverData.expression}" lineValue:${solverData.lineValue} value:"${solverData.value }"`) - debugOutput.push({ - row: `R${String(i)}`, - typeOfResult: `${solverData.typeOfResult}`, - originalText: `${solverData.originalText}`, - expression: `${solverData.expression}`, - lineValue: `${String(solverData.lineValue)}`, - value: `"${String(solverData.value)}"`, - }) - } - if (solverData.value !== '' && shouldPrint) { - const comment = solverData.value ? ` ${solverData.value}` : '' - // clo(solverData, `annotateResults solverData`) - // logDebug(pluginJson, `$comment=${comment}`) - // const thisParaInNote = note.paragraphs[paragraph.lineIndex] - // thisParaInNote.content.replace(/ {2}(\/\/\=.*)/g,'') - if (columnarOutput && !debug) { - outputObjects.push({ content: paragraph.content.trimEnd(), comment }) - } else { - paragraph.content = paragraph.content.trimEnd() + comment - } - // ` logDebug(`annotateResults: paragraph.lineIndex: ${paragraph.lineIndex} content="${paragraph.content}" results[].value=${solverData.value || ''}`) - // logDebug(`${paragraph.content}${comment}`) - } else { - if (columnarOutput) { - outputObjects.push({ content: paragraph.content.trimEnd(), comment: '' }) - } - } - j++ - } - if (columnarOutput) { - // clo(blockData.paragraphs,`blockData.paragraphs`) - const formattedColumnarOutput = columnify(outputObjects) - formattedColumnarOutput - .split('\n') - .slice(1) - .forEach((line, i) => { - blockData.paragraphs[i].content = line - }) - // clo(formattedColumnarOutput, `annotateResults::formattedColumnarOutput\n`) - } - if (debugOutput.length && DataStore.settings._logLevel === 'DEBUG') { - const columns = columnify(debugOutput) //options is a 2nd param - console.log(`\n\n${columns}\nDebug Output:\n`) - } - // clo(updates, `annotateResults::updates:`) - // if (updates.length) note.updateParagraphs(blockData.paragraphs) - note.updateParagraphs(blockData.paragraphs) -} - -/** - * Show the results of the solver in popup - * @param {Array} results - the results of the solver - * @param {string} template - should probably be called with settings.documentTemplate - * @param {string} title - the title of the popup - */ -export async function showResultsInPopup(results: Array, template: string, title: string): Promise { - if (results.length) { - const formattedLines = formatOutput(results, template) - const options = formattedLines.map((line, i) => ({ label: line, value: String(results[i].lineValue) })) - logDebug(pluginJson, `Showing results in popup: ${String(options.map((o) => o.label))}`) - const selected = await chooseOption(`${title} Results (return to copy line value)`, options, options[0].value) - if (selected) { - logDebug(pluginJson, `Selected: ${selected}`) - Clipboard.string = String(selected) - } - } -} - -/** - * Remove all annotations previously added by this plugin - * (plugin entrypoint for command: /Remove Annotations from Active Document) - * @returns {void} - */ -export function removeAllAnnotations(buttonClickedIndex: number | null): void { - if (Editor) { - const note = Editor - if (!note) return - const codeBlocks = getCodeBlocksOfType(Editor, 'math') - // logDebug(pluginJson, `removeAllAnnotations ${codeBlocks.length} "${buttonClickedIndex}"="${typeof buttonClickedIndex}"`) - const blockToCalc = buttonClickedIndex != null ? findBlockToCalculate(codeBlocks, buttonClickedIndex) : null - const startIndex = blockToCalc !== null ? blockToCalc : 0 - const endIndex = blockToCalc !== null ? blockToCalc + 1 : codeBlocks.length - for (let i = startIndex; i < endIndex; i++) { - // logDebug(pluginJson, `removeAllAnnotations ${i}/${codeBlocks.length}`) - // clo(codeBlocks[i], `removeAllAnnotations::codeBlocks[${i}]`) - const blockData = codeBlocks[i] - removeAnnotations(note, blockData) - } - } -} - -/** - * Find the block index of the last block above the given line index - * @param {*} blocks - * @param {*} lineIndex - * @returns - */ -export function findBlockToCalculate(blocks: $ReadOnlyArray, lineIndex: number): number { - let blockToCalculate = -1 - for (let i = 0; i < blocks.length; i++) { - const block = blocks[i] - const startLine = block.paragraphs[0].lineIndex - const endLine = block.paragraphs[block.paragraphs.length - 1].lineIndex - if (startLine < lineIndex && endLine < lineIndex) { - blockToCalculate = i - } - } - return blockToCalculate -} - -/** - * Generic math block processing function (can be called by calculate or totalsOnly) - * @param {number|null} buttonClickedIndex - the index of the calculation button that was clicked (to look above it for the math block) - * @param {boolean} mode - if empty, calculate normally, 'totalsOnly', only calculate totals, 'debug' - calculate with verbose debug output (default: '') - */ -export async function calculateBlocks(buttonClickedIndex: number | null = null, mode: string = '', vars: any = {}): Promise { - try { - const { popUpTemplate, presetValues /*, precisionSetting */ } = DataStore.settings - // const precision = (precisionSetting === "No Rounding") ? null : Number(precisionSetting) // doing rounding inside parse does not work well. Will do it inside annotate - const precision = null - // get the code blocks in the editor - await removeAllAnnotations(buttonClickedIndex) - // let codeBlocks = incoming === '' || incoming === null ? getCodeBlocksOfType(Editor, `math`) : [{ type: 'unknown', code: incoming, startLineIndex: -1 }] - let codeBlocks = getCodeBlocksOfType(Editor, `math`) - const blockToCalc = buttonClickedIndex !== null ? findBlockToCalculate(codeBlocks, buttonClickedIndex) : null - const startIndex = blockToCalc !== null ? blockToCalc : 0 - const endIndex = blockToCalc !== null ? blockToCalc + 1 : codeBlocks.length - // clo(codeBlocks, `calculateBlocks::codeBlocks`) - logDebug(pluginJson, `calculateBlocks: codeBlocks.length:${codeBlocks.length} buttonClickedIndex:${buttonClickedIndex || ''} startIndex:${startIndex} endIndex:${endIndex}`) - if (codeBlocks.length && Editor) { - for (let b = startIndex; b < endIndex; b++) { - if (b > 0) { - // get the codeblocks again because the line indexes may have changed if the last round made edits - codeBlocks = getCodeBlocksOfType(Editor, `math`) - } - const block = codeBlocks[b] - // removeAnnotations(Editor, block) //FIXME: MAYBE put this back, especially for non-columnar output - // clo(block,`calculateEditorMathBlocks block=`) - let currentData = { info: [], variables: { ...presetValues, ...vars }, relations: [], expressions: [], rows: 0, precision } - block.code.split('\n').forEach((line, i) => { - currentData.rows = i + 1 - currentData = parse(line, i, currentData) - }) - if (mode === 'noRounding') { - currentData.precision = null - } - // const totalData = parse('TOTAL', currentData.rows, currentData) - // const t = totalData.info[currentData.rows] - // const totalLine = { - // lineValue: t.lineValue, - // originalText: t.originalText, - // expression: t.expression, - // row: -1, - // typeOfResult: t.typeOfResult, - // typeOfResultFormat: t.typeOfResultFormat, - // value: t.value, - // error: '', - // } - // logDebug(pluginJson,`Final data: ${JSON.stringify(currentData,null,2)}`) - // TODO: Maybe add a total if there isn't one? But maybe people are not adding? - // if (currentData.info[currentData.info.length-1].typeOfResult !== "T") { - // currentData = parse("total",i,currentData) - // } - // TODO: add user pref for whether to include total or not - // clo(currentData, `calculateEditorMathBlocks mathBlock[${b}] currentData:`) - await annotateResults(Editor, block, currentData.info, popUpTemplate, mode) - // await showResultsInPopup([totalLine,...currentData.info], popUpTemplate, `Block ${b+1}`) - } - } else { - const msg = `Did not find any 'math' code blocks in active editor` - logDebug(pluginJson, msg) - await showMessage(msg) - } - } catch (error) { - logError(pluginJson, `calculateBlocks error: ${error}`) - } -} - -/** - * Calculate all the math blocks on the current page - * (plugin entrypoint for command: /Calculate Math Code Blocks in Active Document) - * @param {string} incoming - string from xcallback entry - */ -export async function calculateEditorMathBlocks(incoming: string | null = null) { - try { - await calculateBlocks(null, '', getFrontmatterVariables(Editor.content || '')) - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * Calculate the math block preceeding the button that was clicked - * (plugin entrypoint for command: /Calculate Preceeding Math Block -- hidden, used only by xcallback) - */ -export async function calculatePreceedingMathBlock(id: string) { - try { - const paragraph = Editor.paragraphs.find((p) => p.content.includes(id)) - if (paragraph) { - // const buttonClickedIndex = await getSelectedParagraphLineIndex() - const buttonClickedIndex = paragraph.lineIndex - logDebug(pluginJson, `calculatePreceedingMathBlock calling calculateBlocks with buttonClickedIndex=${buttonClickedIndex} content:"${paragraph.content}"`) - await calculateBlocks(buttonClickedIndex, '', getFrontmatterVariables(Editor.content || '')) - } - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * Calculate the math block preceeding the button that was clicked - * (plugin entrypoint for command: /Calculate Preceeding Math Block -- hidden, used only by xcallback) - */ -export async function calculatePreceedingMathBlockTotal(id: string) { - try { - const paragraph = Editor.paragraphs.find((p) => p.content.includes(id)) - if (paragraph) { - // const buttonClickedIndex = await getSelectedParagraphLineIndex() - const buttonClickedIndex = paragraph.lineIndex - logDebug(pluginJson, `calculatePreceedingMathBlock calling calculateBlocks with buttonClickedIndex=${buttonClickedIndex} content:"${paragraph.content}"`) - await calculateBlocks(buttonClickedIndex, 'totalsOnly', getFrontmatterVariables(Editor.content || '')) - } - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * Calculate the math block preceeding the button that was clicked - * (plugin entrypoint for command: /Calculate Preceeding Math Block -- hidden, used only by xcallback) - */ -export function clearPreceedingMathBlock(id: string) { - try { - if (id) { - const paragraph = Editor.paragraphs.find((p) => p.content.includes(id)) - if (paragraph) { - removeAllAnnotations(paragraph.lineIndex) - } - } - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * Calculate all the math blocks on the current page but annotate the totals only - * (plugin entrypoint for command: /Calculate Totals Only) - * @param {string} incoming - string from xcallback entry - */ -export async function calculateEditorMathBlocksTotalsOnly(incoming: string | null = null) { - try { - await calculateBlocks(null, 'totalsOnly', getFrontmatterVariables(Editor.content || '')) - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * Calculate Math Blocks with no rounding - * Plugin entrypoint for "/Calculate Math Blocks (no rounding)" - */ -export async function calculateNoRounding(incoming: string) { - try { - await calculateBlocks(null, 'noRounding', getFrontmatterVariables(Editor.content || '')) - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * Insert a math block and a calculate button - * (plugin entrypoint for command: /Insert Math Block at Cursor) - */ -export async function insertMathBlock() { - try { - const { includeCalc, includeClear, includeTotals } = DataStore.settings - // NOTE: this relies on the calculate command being the first in the list in plugin.json - const guid = CreateUUID(8) - const calcLink = includeCalc ? `[Calculate](${createRunPluginCallbackUrl(pluginJson['plugin.id'], pluginJson['plugin.commands'][0].name, [guid])})` : '' - const clrLink = includeClear ? `[Clear](${createRunPluginCallbackUrl(pluginJson['plugin.id'], pluginJson['plugin.commands'][1].name, [guid])})` : '' - const totLink = includeTotals ? `[Totals](${createRunPluginCallbackUrl(pluginJson['plugin.id'], pluginJson['plugin.commands'][2].name, [guid])})` : '' - let buttonLine = includeClear ? clrLink : '' - buttonLine = includeCalc ? `${buttonLine + (includeClear ? ` ` : '')}${calcLink}` : buttonLine - buttonLine = includeTotals ? `${buttonLine + (includeClear || includeCalc ? ` ` : '')}${totLink}` : buttonLine - buttonLine = buttonLine.length ? `${buttonLine}\n` : '' - const onLine = Editor?.selection?.start && Editor.selection.start >= 0 ? getParagraphContainingPosition(Editor, Editor.selection.start) : null - const returnIfNeeded = onLine?.type !== 'empty' ? '\n' : '' - const block = `${returnIfNeeded}\`\`\`math\n\n\`\`\`\n${buttonLine}` - await Editor.insertTextAtCursor(block) - const sel = Editor.selection - if (sel) { - const para = Editor?.selection?.start && Editor.selection.start >= 0 ? getParagraphContainingPosition(Editor, Editor.selection.start) : null - if (para && para.lineIndex) { - const offset = buttonLine.length ? 3 : 2 - const range = Editor.paragraphs[para.lineIndex - offset].contentRange - if (range?.start) { - Editor.select(range.start, 0) - } - } - } - } catch (error) { - logError(pluginJson, error) - } -} - -/** - * Calculate Math Block with Verbose Debug Output - * Plugin entrypoint for "/Debug Math Calculations" - */ -export async function debugMath() { - try { - await calculateBlocks(null, 'debug', getFrontmatterVariables(Editor.content || '')) - } catch (error) { - logError(pluginJson, JSP(error)) - } -} diff --git a/dwertheimer.MathSolver/src/index.js b/dwertheimer.MathSolver/src/index.js deleted file mode 100644 index a77a384aa..000000000 --- a/dwertheimer.MathSolver/src/index.js +++ /dev/null @@ -1,56 +0,0 @@ -// @flow - -// Typically, listed below are only the top-level plug-in functions listed in plugin.json - -export { - calculateEditorMathBlocks, - removeAllAnnotations, - insertMathBlock, - calculateEditorMathBlocksTotalsOnly, - calculateNoRounding, - debugMath, - calculatePreceedingMathBlock, - clearPreceedingMathBlock, - calculatePreceedingMathBlockTotal, -} from './NPMathBlocks' - -import pluginJson from '../plugin.json' - -/* - * NOTEPLAN HOOKS - * The rest of these functions are called by NotePlan automatically under certain conditions - * It is unlikely you will need to edit/add anything below this line - */ - -// eslint-disable-next-line import/order -import { updateSettingData, pluginUpdated } from '@helpers/NPConfiguration' -import { logError, JSP, clo } from '@helpers/dev' -/** - * NotePlan calls this function after the plugin is installed or updated. - * The `updateSettingData` function looks through the new plugin settings in plugin.json and updates - * the user preferences to include any new fields - */ -export async function onUpdateOrInstall(): Promise { - await updateSettingData(pluginJson) -} - -/** - * NotePlan calls this function every time the plugin is run (any command in this plugin) - * You should not need to edit this function. All work should be done in the commands themselves - */ -// eslint-disable-next-line require-await -export async function init(): Promise { - try { - clo(DataStore.settings, `${pluginJson['plugin.id']} Plugin Settings`) - // Check for the latest version of this plugin, and if a minor update is available, install it and show a message - DataStore.installOrUpdatePluginsByID([pluginJson['plugin.id']], false, false, false).then((r) => pluginUpdated(pluginJson, r)) - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * NotePlan calls this function settings are updated in the Preferences panel - * You should not need to edit this function - */ -export async function onSettingsUpdated(): Promise {} diff --git a/dwertheimer.MathSolver/src/support/date-time-math.js b/dwertheimer.MathSolver/src/support/date-time-math.js deleted file mode 100644 index 36b3dccf7..000000000 --- a/dwertheimer.MathSolver/src/support/date-time-math.js +++ /dev/null @@ -1,12 +0,0 @@ - -// import {getTimeBlockString} from '@helpers/timeblocks' - -export function checkForTime(strToBeParsed, currentData) { - const reHasTime = /([0-2]\d:[0-5]\d(:\d{0,2})?)/ - if (reHasTime.test(strToBeParsed)) { - const pdt = Calendar.parseDateText(strToBeParsed) - strToBeParsed = strToBeParsed.replace(pdt.text).trim() - } - return ({currentData,strToBeParsed}) - } - diff --git a/dwertheimer.MathSolver/src/support/solver.js b/dwertheimer.MathSolver/src/support/solver.js deleted file mode 100644 index 036383e23..000000000 --- a/dwertheimer.MathSolver/src/support/solver.js +++ /dev/null @@ -1,522 +0,0 @@ -/* eslint-disable no-cond-assign */ -// @flow - -/** - * Text parsing code based on: https://github.com/LorenzoCorbella74/soulver-web (licensed under the ISC License) - */ - -/** - * TODO: - * actually call removeParentheticals() and ensure they get into expressions - * - Should multiple "total(s)" in a note restart the counting? Right now they work like subtotals - * Look at createUnit() function and checkIfUnit() which seems like it's not finished - * - Line 472+ (relations) doesn't seem to work right (looking at the tests, you seem get blank arrays, etc.) but maybe that doesn't matter for our purposes - we don't really use it that much I don't think - */ - -/** - * Parsed line metadata - * {string} typeOfResult: H for header/comment, N per normal number line, S per subtotal, T per total, A for assignment, E for Error - * {string} typeOfResultFormat: N for normal, % for percentage, B for assigned total (A=total) - */ - -// const pluginJson = 'dwertheimer.MathSolver/solver.js' - -export type LineInfo = { - lineValue: number | null | { mathjs: string, value: number, unit: string, fixPrefix: string }, - originalText: string, - expression: string, - row: number, - typeOfResult: string, //"H" (Heading/comment)|"N" (Number)|"S" (Subtotal)|"T" (Total)|"A" (Assignment) |"E" (Error)| "B" (Assignment of Total Line - A=total) - typeOfResultFormat: string, //"N"|"%" /* Does not appear the % is ever used */, - complete: boolean, - value?: string, - error?: string, -} - -/** - * Running data - */ -export type CurrentData = { - info: Array, - variables: { [string]: any }, - relations: Array | null>, - expressions: Array, - rows: number, - precision: ?number, -} - -import math from './math.min' -// import {create, all} from 'mathjs/lib/esm/number' -// const math = create(all) - -import { log, logDebug, logError, clo, JSP } from '@helpers/dev' - -// whitespace (not-colon maybe) = (not-colon not-space) chars -// const reAssignmentSides = /\s*([^:]*?)\s*=\s*([^:\s]*)/g //original version (only grabs first number after =) -const reAssignmentSides = /\s*([^:]*?)\s*=(.*)/g // dbw version: allows for math on right side - -const functionNames = ['sin', 'cos', 'tan', 'exp', 'sqrt', 'ceil', 'floor', 'abs', 'acos', 'asin', 'atan', 'log', 'round'] -const specialOperator = ['in\\b', 'k\\b', 'M\\b', 'mm\\b', 'cm\\b', 'm\\b', 'km\\b', 'mg\\b', 'g\\b', 'kg\\b', 'cm2\\b', 'm2\\b', 'km2\\b'] -const currencies = ['$'] - -// let rows = 0; -// let selectedRow = 0; -// const expressions = []; -// let variables = {}; -// let results = []; -// let relations = []; // indicates in which row the totals are to be reloaded -// let info = []; -// let importedFile = {}; -// const APP_VERSION = 'V 0.1.6'; -// let isDark = false; -// let statusListening = 'stop'; - -/** - * //TODO: Remove items enclosed in quotes or square brackets to be sent directly to mathjs - * @param {string} inString - * @returns {[string,string]} [stringFound, stringWithoutFoundText] - */ -export function removeParentheticals(inString: string): [string, Array] { - const reParens = /(?:\"|{)(.*?)(?:\"|})/g - // const matches = inString.match(reParens) - // matches[0] includes the delimiter, matches[1] is the inside string - // NEED TO DO A doWhile to get them all - // FIXME: I AM HERE - const quotedContent = [] - let match, - newStr = inString - while ((match = reParens.exec(inString))) { - newStr = newStr.replace(match[0], '').replace(/ {2,}/g, ' ').trim() - quotedContent.push(match[1]) - } - return [newStr, quotedContent.length ? quotedContent : []] -} - -/** - * Is line type included in the array? - * @param {line} - the info line to search in - * @param {string|Array} - the type to compare against - */ -export function isLineType(line: LineInfo, searchForType: string | Array): boolean { - const lineTypes = Array.isArray(searchForType) ? searchForType : [searchForType] - return lineTypes.indexOf(line.typeOfResult) > -1 -} - -export function checkIfUnit(obj: any): boolean { - return typeof obj === 'object' && obj !== null && obj.value -} - -// update each line according to the presence of the variables present in 'relations' -// function updateRelated(data) { -// for (let numRow = 0; numRow < data.rows; numRow++) { -// // for all the rows you see those that include the variables in the relationships -// const who = data.relations.map((e) => e && (e.includes(`R${numRow}`) || Object.keys(data.variables).findIndex((a) => a === e) > -1)) // FIXME: to be reviewed... -// if (who && who.length > 0) { -// who.forEach((element) => { -// if (element) { -// try { -// const results = math.evaluate(data.expressions, data.variables) -// results.map((e, i) => (data.variables[`R${i}`] = checkIfUnit(e) ? math.unit(e) : e)) // put the row results in the variables -// // updateResultInRow(results[index] ? formatResults(results[index]) : '', index) // the current row is updated -// } catch (error) { -// // updateResultInRow('', index) -// console.log('Completing expression', error) -// } -// } -// }) -// } -// } -// return data -// } - -// assegna ad ogni riga le variabili presenti -/* - [ - null, - [R0], - [R0,R1] - ] -*/ -// function setRelation(selectedRow, presences, relations) { -// relations[selectedRow] = presences -// return relations -// } - -export function removeTextPlusColon(strToBeParsed: string): string { - const isTotal = /(sub)?total:/i.test(strToBeParsed) // allow total: or subtotal: - return isTotal ? strToBeParsed : strToBeParsed.replace(/^.*:/gm, '').trim() -} - -function removeTextFromStr(strToBeParsed, variables) { - // remove all characters but not the substrings of variables, function names and units of measure - const varConcatenated = Object.keys(variables).concat(functionNames).concat(currencies).concat(specialOperator).join('|') - const re = varConcatenated ? `\\b(?!${varConcatenated})\\b([a-zA-Z:])+` : '[a-zA-Z:]+' - return strToBeParsed - .replace(new RegExp(re, 'g'), '') - .replace(/\ /g, '') - .replace(/\&;/g, '') -} - -function removeComments(incomingString: string, currentData: CurrentData, selectedRow: number): string { - let strToBeParsed = incomingString - if (currentData.info[selectedRow].complete !== true) { - // Remove comment+colon - strToBeParsed = removeTextPlusColon(strToBeParsed) - if (/(^|\s)#(.*)/g.test(strToBeParsed)) { - strToBeParsed = strToBeParsed - .replace(/(^|\s)#(.*)/g, '$1') - .replace('\t', '') - .trim() // remove anything beyond double slashes - } else if (/\/\/(.*)/g.test(strToBeParsed)) { - strToBeParsed = strToBeParsed.replace(/\/\/(.*)/g, '').trim() // remove anything beyond double slashes - } - if (strToBeParsed.trim() === '') { - currentData.info[selectedRow].typeOfResult = 'H' - currentData.info[selectedRow].complete = true - } - // logDebug(pluginJson,`str="${strToBeParsed}" = ${info[selectedRow].typeOfResult}`) - // edit outStr & set .complete if finished - } - return strToBeParsed -} - -export function parse(thisLineStr: string, lineIndex: number, cd: CurrentData): CurrentData { - const currentData = cd - const pluginJson = 'solver::parse' - let strToBeParsed = thisLineStr.trim() - const { info, variables, expressions, rows, precision } = currentData - // let relations = currentData.relations // we need to be able to write this one - let match - const selectedRow = lineIndex - currentData.info[selectedRow] = { - row: selectedRow, - typeOfResult: 'N', - typeOfResultFormat: 'N', - originalText: strToBeParsed, - expression: '', - lineValue: 0, - error: '', - complete: false, - } - - // Remove comments/headers $FlowIgnore - strToBeParsed = removeComments(strToBeParsed, currentData, selectedRow) - - // let preProcessedValue = null - try { - // logDebug(pluginJson, `---`) - // logDebug(pluginJson, `about to preproc str = "${strToBeParsed}"`) - // clo(currentData, `currentData before pre-process`) - // logDebug(pluginJson, `str = now will pre-proc "${strToBeParsed}"`) - // const results = math.evaluate([strToBeParsed], variables) - // clo(results, `solver::parse math.js pre-process success on: "${strToBeParsed}" Result is Array<${typeof results[0]}> =`) - // preProcessedValue = results[0] - } catch (error) { - // errors are to be expected since we are pre-processing - // error messages: "Undefined symbol total", "Unexpected part "4" (char 7)" - logDebug(pluginJson, `math.js pre-process FAILED on "${strToBeParsed}" with message: "${error.message}"`) - } - - // Look for passthroughs (quoted or square brackets) - // const [foundStr, strWithoutFound] = removeParentheticals(strToBeParsed) - // if (foundStr) { - // strToBeParsed = strWithoutFound - // const results = math.evaluate([foundStr], variables) - // info[selectedRow].typeOfResult = 'H' - // info[selectedRow].lineValue = results[0] - // clo(results, `passtrhough ${foundStr}`) - // } - - let out = '0' // used for subtotals and totals - - // Subtotals - if (!/((sub)?total\s*={1,})/.test(strToBeParsed)) { - // guard against someone using "total = a + b" - if (/(subtotal\b).*/gi.test(strToBeParsed)) { - info[selectedRow].typeOfResult = 'S' - for (let i = selectedRow - 1; i >= 0; i--) { - if (info[i].typeOfResult === 'N') { - out += `+ R${i}` - // H for header, S per subtotal, T per total - } else if (info[i].typeOfResult === 'S' || info[i].typeOfResult === 'T' || info[i].typeOfResult === 'B') { - break - } - } - // Totals - } else if (/(total\b).*/gi.test(strToBeParsed)) { - info[selectedRow].typeOfResult = 'T' - for (let i = 0; i <= rows - 2; i++) { - if (info[i].typeOfResult === 'N') { - out += `+ R${i}` - } - } - } - if (out !== '0') { - if (/[=]/.test(strToBeParsed)) { - while ((match = reAssignmentSides.exec(strToBeParsed))) { - if (match[1]?.trim() !== '' && match[2]?.trim() !== '') { - strToBeParsed = strToBeParsed.replace(match[0], `${match[1]} = ${out}`) - } - } - info[selectedRow].typeOfResult = 'B' // for Assign-Equal-(sub)Total - } else { - strToBeParsed = out // build the string for the totals - } - } - } - - // k - A number can have a little "k" behind it to denote 1,000 * the number (e.g. `4k`) - // if (/(?<=\d)([k])/g.test(strToBeParsed)) { // positive lookbehind was crashing NP plugin. trying without. i am not sure why it was necessary - if (/(\d+(?:\.\d+)?)(k)/g.test(strToBeParsed)) { - // strToBeParsed = strToBeParsed.replace(/(?<=\d)([k])/g, "*1000").trim() // see note above - strToBeParsed = strToBeParsed.replace(/(\d+(?:\.\d+)?)(k)/g, '$1*1000').trim() - } - // M - a number can have an uppercase "M" behind it to denote 1,000,000 * the number (e.g. `5M`) - if (/(\d+(?:\.\d+)?)(M)/g.test(strToBeParsed)) { - strToBeParsed = strToBeParsed.replace(/(\d+(?:\.\d+)?)(M)/g, '$1*1000000').trim() - } - - // variable assignment - if (/[=]/.test(strToBeParsed)) { - // TODO: expression management inside the DX of an assignment ... - let match - while ((match = reAssignmentSides.exec(strToBeParsed))) { - // logDebug(`solver::parse/assignment`,`strToBeParsed="${strToBeParsed}"; matches = ${match.toString()}`) - if (match[1]?.trim() !== '' && match[2]?.trim() !== '') { - if (info[selectedRow].typeOfResult !== 'B') { - info[selectedRow].typeOfResult = 'A' - } - variables[match[1]] = match[2] - } else { - // incomplete assigments (e.g. in progress) will be ignored - info[selectedRow].typeOfResult = 'H' - } - } - } - - // 10.5% of 100.5 TODO: 10.5% di (espressione) non funziona - // SOURCE: https://stackoverflow.com/questions/12812902/javascript-regular-expression-matching-cityname // how to take only specific parts - const reg = /(\d*[\.,])?(\d+)(\s?%)(\s+)(of)(\s+)(\d*[\.,])?(\d+\s?)/g - while ((match = reg.exec(strToBeParsed))) { - // console.log(match); - const num = match[1] ? match[1] + match[2] : match[2] - const dest = match[7] ? match[7] + match[8] : match[8] - const sostituzione = (Number(dest) * (Number(num) / 100)).toString() - strToBeParsed = strToBeParsed.replace(/(\d*[\.,])?(\d+)(\s?%)(\s+)(di|of)(\s+)(\d*[\.,])?(\d+\s?)/g, sostituzione) - } - - // +/- 10 % TODO: (2 + 22%)% non funziona! - const add = /\+\s?(\d*[\.,])?(\d+\s?)(%)/g - while ((match = add.exec(strToBeParsed))) { - const num = match[1] ? match[1] + match[2] : match[2] - const sostituzione = ((Number(num) + 100) / 100).toString() - strToBeParsed = strToBeParsed.replace(/\+\s?(\d*[\.,])?(\d+\s?)(%)/g, `*${sostituzione}`) - } - const sub = /\-\s?(\d*[\.,])?(\d+\s?)(%)/g - while ((match = sub.exec(strToBeParsed))) { - const num = match[1] ? match[1] + match[2] : match[2] - const sostituzione = ((100 - Number(num)) / 100).toString() - strToBeParsed = strToBeParsed.replace(/\-\s?(\d*[\.,])?(\d+\s?)(%)/g, `*${sostituzione}`) - } - - // 10.1 as % of 1000 - const as = /(\d*[\.,])?(\d+\s?)(as|as a)(\s+%)(\s+(of)\s+)(\d*[\.,])?(\d+\s?)/g - while ((match = as.exec(strToBeParsed))) { - const num1 = match[1] ? match[1] + match[2] : match[2] - const num2 = match[7] ? match[7] + match[8] : match[8] - const sostituzione = (Number(num1) / Number(num2)).toString() - strToBeParsed = strToBeParsed.replace(/(\d*[\.,])?(\d+\s?)(as|as a)(\s+%)(\s+(of)\s+)(\d*[\.,])?(\d+\s?)/g, `${sostituzione}`) - } - - // logDebug(`String before mathOnlyStr: ${strToBeParsed}`) - let mathOnlyStr = removeTextFromStr(strToBeParsed, variables).replace(/ +/g, ' ') - if (mathOnlyStr.trim() === '=') { - mathOnlyStr = '' - } - // logDebug(`String after mathOnlyStr for ${strToBeParsed}: ${mathOnlyStr}`) - - // if there are variables used, relations are defined so the proper lines can be updated later - const vars = Object.keys(variables) - const relRegStr = `\\b(${vars.join('|')})\\b` - const relReg = new RegExp(relRegStr, 'g') - const matches = mathOnlyStr.match(relReg) - //FIXME: dbw figure out what is going on in this line, because it seems to be a problem - let presences = matches ? (/\b(sub)?total(e)?\b/g.exec(strToBeParsed) ? matches.map((e) => e.replace(/\+/g, '')) : matches) : null - if (Array.isArray(presences) && presences.length > 0) presences = presences.filter((f) => f !== '') - expressions[selectedRow] = mathOnlyStr.replace(/^0\+/g, '').trim() || '0' // it removes the 0+ fix sums with units - // logDebug(`solver::parse Relations:`,relations) - // relations = setRelation(selectedRow, presences, relations) - - try { - const results = math.evaluate(expressions, variables) - // results.map((e, i) => variables[`R${i}`] = checkIfUnit(e) ? math.unit(e) : e) // you put the row results in the variables - results.map((e, i) => { - // clo(expressions[i], `parse:expressions[${i}]`) - const rounded = precision ? Number(math.format(e, { precision })) : e - variables[`R${i}`] = checkIfUnit(e) ? math.unit(e) : isNaN(rounded) ? e : rounded - info[i].lineValue = variables[`R${i}`] - if (info[i].typeOfResult === 'N' && mathOnlyStr.trim() === '' && info[i].expression === '0') { - logDebug(`solver::parse`, `R${i}: "${info[i].originalText}" is a number; info[i].typeOfResult=${info[i].typeOfResult} expressions[i]=${expressions[i]}`) - if (info[i].originalText.trim() !== '') { - info[i].error = `was not a number, equation, variable or comment` - info[i].typeOfResult === 'H' // remove it from calculations - } - info[i].expression = '' - } else { - info[i].expression = expressions[i] - } - }) // keep for NP output metadata - // let data = { info, variables, relations, expressions, rows } - // data = updateRelated(data) - // logDebug(`solver::parse`,`Returning (one-line):\n${JSON.stringify(data)}`) - // logDebug(`solver::parse`,`Returning (Pretty):\n${JSON.stringify(data,null,2)}`) - return currentData // all variables inside have been updated because the desctructuring just creates references - // createOrUpdateResult(results[selectedRow] ? formatResults(results[selectedRow]) : '') // the current row is updated - } catch (error) { - // createOrUpdateResult('') - logDebug(pluginJson, `Error completing expression in: ${String(expressions)} ${error}`) - clo(expressions && expressions.length ? expressions : {}, `parse--expressions`) - info[selectedRow].error = error.toString() - info[selectedRow].typeOfResult = 'E' - expressions[selectedRow] = '' - return currentData - } -} - -// function formatResults (result) { -// let output, check -// if (checkIfUnit(result)) { -// check = result.value -// } else { -// check = result -// } -// if (check % 1 != 0) { -// output = format(result, 2) -// } else { -// output = result -// } -// return output -// } - -// function initSpeechRecognition () { -// try { -// SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition -// recognition = null -// // mostra btn -// listenBtn.classList.add('show') -// listenBtn.classList.remove('hide') -// statusListening = 'stop' -// } catch (e) { -// console.error(e) -// } -// } - -// function formatDate (date) { -// let d = new Date(date), -// month = `${ d.getMonth() + 1}`, -// day = `${ d.getDate()}`, -// year = d.getFullYear() - -// if (month.length < 2) -// {month = `0${ month}`} -// if (day.length < 2) -// {day = `0${ day}`} - -// return [year, month, day].join('-') -// } - -// function getCurrencies () { -// const data = { -// "success": true, -// "timestamp": 1519296206, -// "base": "EUR", -// "date": "2021-03-17", -// "rates": { -// "AUD": 1.566015, -// "CAD": 1.560132, -// "CHF": 1.154727, -// "CNY": 7.827874, -// "GBP": 0.882047, -// "JPY": 132.360679, -// "USD": 1.23396, -// } -// } -// /* return fetch('https://api.exchangeratesapi.io/latest') -// .then(function (response) { -// return response.json() -// }) -// .then(function (data) { -// */ -// console.log('Currencies:', data) -// localStorage.setItem(`currencies-${data.date}`, JSON.stringify(data)) -// return createUnit(data) -// /* }); */ -// } - -// function createUnit (data) { -// math.createUnit(data.base, { aliases: ['€'] }) -// Object.keys(data.rates) -// .forEach(function (currency) { -// math.createUnit(currency, math.unit(1 / data.rates[currency], data.base)) -// }) -// // return an array with all available currencies -// return Object.keys(data.rates).concat(data.base) -// } - -// function format (value) { -// const precision = 4 -// return math.format(value, precision) -// } - -// function setAppVersion () { -// document.querySelector('.version').innerText = APP_VERSION -// } - -// function init () { -// currencies = localStorage.getItem(`currencies-${formatDate(new Date())}`) // only a call a day/browser!!! -// if (!currencies) { -// currencies = getCurrencies() -// } else { -// currencies = createUnit(JSON.parse(currencies)) -// } -// // Turn the theme of if the 'dark-theme' key exists in localStorage -// if (localStorage.getItem('dark-theme')) { -// document.body.classList.add('dark-theme') -// isDark = true -// } - -// setAppVersion() - -// initSpeechRecognition() -// // si crea la 1° riga -// createRowFromTemplate() -// } - -// init() -/* - TODO: - [] classe per la gestione del caret - [] classe per la gestione della view e classe per la gestione del parsing e calcoli (in file separati) - [] formattazione di numeri con separazione per migliaia e virgola - [] colori custom definiti nelle preferenze tramite modale - [] totale in fondo alla pagina - [] percentuali - NUM +/- 20% - 40 come % di 50 (N as a % of N) - 20 che % è di 50 (N is what % of N) - [] matematica per le date - Today + 3 weeks 2 days - 3:35 am + 9 hours 20 minutes - From March 12 to July 30 - [] json export / inport tramite modale - [] variabili globali - [] progressive web app ed electron - [] internalizzazione e formati numerici - - NOTES: - https://stackoverflow.com/questions/6249095/how-to-set-caretcursor-position-in-contenteditable-element-div - https://stackoverflow.com/questions/10778291/move-the-cursor-position-with-javascript - https://stackoverflow.com/questions/18884262/regular-expression-match-string-not-preceded-by-another-string-javascript - - -*/ diff --git a/dwertheimer.ReactSkeleton/src/index.js b/dwertheimer.ReactSkeleton/src/index.js deleted file mode 100644 index bc9c1e7d3..000000000 --- a/dwertheimer.ReactSkeleton/src/index.js +++ /dev/null @@ -1,36 +0,0 @@ -// @flow - -/** - * Imports - */ -import pluginJson from '../plugin.json' -import { clo } from '@helpers/dev' - -/** - * Command Exports - */ -export { editSettings } from '@helpers/NPSettings' - -export { testReactWindow, onMessageFromHTMLView } from './reactMain.js' - -/** - * Hooks - */ - -// updateSettingsData will execute whenever your plugin is installed or updated -import { updateSettingData, pluginUpdated } from '@helpers/NPConfiguration' - -export function init(): void { - // this runs every time the plugin starts up (any command in this plugin is run) - clo(DataStore.settings, `${pluginJson['plugin.id']} Plugin Settings`) - DataStore.installOrUpdatePluginsByID([pluginJson['plugin.id']], true, false, false).then((r) => pluginUpdated(pluginJson, r)) -} - -export async function onSettingsUpdated(): Promise { - // you probably won't need to use this...it's fired when the settings are updated in the Preferences panel -} - -export function onUpdateOrInstall(): void { - // this runs after the plugin is installed or updated. the following command updates the plugin's settings data - updateSettingData(pluginJson) -} diff --git a/dwertheimer.ReactSkeleton/src/react/components/WebView.jsx b/dwertheimer.ReactSkeleton/src/react/components/WebView.jsx deleted file mode 100644 index 40a79093e..000000000 --- a/dwertheimer.ReactSkeleton/src/react/components/WebView.jsx +++ /dev/null @@ -1,227 +0,0 @@ -/**************************************************************************************************************************** - * WEBVIEW COMPONENT - * This is your top-level React component. All other React components should be imported and included below - ****************************************************************************************************************************/ -// @flow - -/** - * IMPORTANT - * YOU MUST ROLL UP THESE FILES INTO A SINGLE FILE IN ORDER TO USE IT IN THE PLUGIN - * RUN FROM THE SHELL: node 'dwertheimer.ReactSkeleton/src/react/support/performRollup.node.js' --watch - */ - -type Props = { - data: any /* passed in from the plugin as globalSharedData */, - dispatch: Function, - reactSettings: any, - setReactSettings: Function, -} -/**************************************************************************************************************************** - * NOTES - * WebView should act as a "controlled component", as far as the data from the plugin is concerned. - * Plugin-related data is always passed in via props, and never stored in state in this component - * - * FYI, if you do use state, it is highly recommended when setting state with hooks to use the functional form of setState - * e.g. setTodos((prevTodos) => [...prevTodos, newTodo]) rather than setTodos([...todos, newTodo]) - * This has cost me a lot of time in debugging stale state issues - */ - -/**************************************************************************************************************************** - * IMPORTS - ****************************************************************************************************************************/ - -import React, { useEffect, type Node } from 'react' -import { clo, logDebug, timer } from '@helpers/react/reactDev' -import { type PassedData } from '../../reactMain.js' -import { AppProvider } from './AppContext.jsx' -import CompositeLineExample from './CompositeLineExample.jsx' -import Button from './Button.jsx' -/** - * Root element for the Plugin's React Tree - * @param {any} data - * @param {Function} dispatch - function to send data back to the Root Component and plugin - */ -export function WebView({ data, dispatch, reactSettings, setReactSettings }: Props): Node { - /**************************************************************************************************************************** - * HOOKS - ****************************************************************************************************************************/ - - // GENERALLY SPEAKING YOU DO NOT WANT TO USE STATE HOOKS IN THE WEBVIEW COMPONENT - // because the plugin may need to know what changes were made so when it updates data, it will be consistent - // otherwise when the plugin updates data, it will overwrite any changes made locally in the Webview - // instead of using hooks here, save updates to data using: - // dispatch('UPDATE_DATA', {...data,changesToData}) - // this will save the data at the Root React Component level, which will give the plugin access to this data also - // sending this dispatch will re-render the Webview component with the new data - - /**************************************************************************************************************************** - * VARIABLES - ****************************************************************************************************************************/ - - // destructure all the startup data we expect from the plugin - const { pluginData, debug } = data - const { tableRows } = pluginData - - /**************************************************************************************************************************** - * HANDLERS - ****************************************************************************************************************************/ - - /** - * Submit button on the page was clicked - * (sample handler for a button in the react window) - * @param {any} e - the event object - * @param {number} index - the index of the button that was clicked - */ - const onSubmitClick = (e: any, index: number) => { - logDebug(`Webview: onSubmitClick: ${e.type || ''} click on index: ${index}`) - sendActionToPlugin('onSubmitClick', { index: index }) - } - - // A sample function that does something interactive in the window using React - // you will delete this - const scrambleLines = () => { - logDebug(`Webview: scrambleLines: click`) - // in this example, we are not going to send any data back to the plugin, everything is local - // we are just randomly reordering the lines just for demonstration purposes - const newTableRows = [...tableRows] - newTableRows.sort(() => Math.random() - 0.5) - const newData = { ...data, pluginData: { ...data.pluginData, tableRows: newTableRows } } - dispatch('UPDATE_DATA', newData) // save the data at the Root React Component level, which will give the plugin access to this data also - // this will cause this component to re-render with the new data - // will never reach anything below this line because the component will re-render - dispatch('SHOW_BANNER', { - msg: 'FYI: Page automatically re-rendered locally after pluginData was changed at Root component level. Did not call the plugin.', - color: 'blue', - border: 'blue', - }) - } - - /**************************************************************************************************************************** - * EFFECTS - ****************************************************************************************************************************/ - - /** - * When the data changes, console.log it so we know and scroll the window - * Fires after components draw - */ - useEffect(() => { - logDebug(`Webview: useEffect: data changed. data: ${JSON.stringify(data)}`) - if (data?.passThroughVars?.lastWindowScrollTop !== undefined && data.passThroughVars.lastWindowScrollTop !== window.scrollY) { - debug && logDebug(`Webview: useEffect: data changed. Scrolling to ${String(data.lastWindowScrollTop)}`) - window.scrollTo(0, data.passThroughVars.lastWindowScrollTop) - } - }, [data]) - - /**************************************************************************************************************************** - * FUNCTIONS - ****************************************************************************************************************************/ - /** - * Helper function to remove HTML entities from a string. Not used in this example but leaving here because it's useful - * if you want to allow people to enter text in an HTML field - * @param {string} text - * @returns {string} cleaned text without HTML entities - */ - // eslint-disable-next-line no-unused-vars - function decodeHTMLEntities(text: string): string { - const textArea = document.createElement('textarea') - textArea.innerHTML = text - const decoded = textArea.value - return decoded - } - - /** - * Add the passthrough variables to the data object that will roundtrip to the plugin and come back in the data object - * Because any data change coming from the plugin will force a React re-render, we can use this to store data that we want to persist - * (e.g. lastWindowScrollTop) - * @param {*} data - * @returns - */ - const addPassthroughVars = (data: PassedData): PassedData => { - const newData = { ...data } - if (!newData.passThroughVars) newData.passThroughVars = { lastWindowScrollTop: 0 } - newData.passThroughVars.lastWindowScrollTop = window.scrollY - return newData - } - - /** - * Convenience function to send an action to the plugin and saving any passthrough data first in the Root data store - * This is useful if you want to save data that you want to persist when the plugin sends data back to the Webview - * For instance, saving where the scroll position was so that when data changes and the Webview re-renders, it can scroll back to where it was - * @param {string} command - * @param {any} dataToSend - */ - const sendActionToPlugin = (command: string, dataToSend: any, additionalDetails: string = '') => { - logDebug(`Webview: sendActionToPlugin: ${command} ${additionalDetails}`, dataToSend) - const newData = addPassthroughVars(data) // save scroll position and other data in data object at root level - dispatch('UPDATE_DATA', newData) // save the data at the Root React Component level, which will give the plugin access to this data also - sendToPlugin([command, dataToSend, additionalDetails]) // send action to plugin - } - - /** - * Send data back to the plugin to update the data in the plugin - * This could cause a refresh of the Webview if the plugin sends back new data, so we want to save any passthrough data first - * In that case, don't call this directly, use sendActionToPlugin() instead - * @param {[command:string,data:any,additionalDetails:string]} param0 - */ - const sendToPlugin = ([command: string, data: any, additionalDetails: string = '']) => { - if (!command) throw new Error('sendToPlugin: command must be called with a string') - logDebug(`Webview: sendToPlugin: ${JSON.stringify(command)} ${additionalDetails}`, command, data, additionalDetails) - if (!data) throw new Error('sendToPlugin: data must be called with an object') - dispatch('SEND_TO_PLUGIN', [command, data], `WebView: sendToPlugin: ${String(command)} ${additionalDetails}`) - } - - /** - * Updates the pluginData with the provided new data (must be the whole pluginData object) - * - * @param {Object} newData - The new data to update the plugin with, - * @param {string} messageForLog - An optional message to log with the update - * @throws {Error} Throws an error if newData is not provided or if it does not have more keys than the current pluginData. - * @return {void} - */ - const updatePluginData = (newData, messageForLog?: string) => { - if (!newData) { - throw new Error('updatePluginData: newData must be called with an object') - } - if (Object.keys(newData).length < Object.keys(pluginData).length) { - throw new Error('updatePluginData: newData must be called with an object that has more keys than the current pluginData. You must send a full pluginData object') - } - const newFullData = { ...data, pluginData: newData } - dispatch('UPDATE_DATA', newFullData, messageForLog) // save the data at the Root React Component level, which will give the plugin access to this data also - } - if (!pluginData.reactSettings) pluginData.reactSettings = {} - - /**************************************************************************************************************************** - * RENDER - ****************************************************************************************************************************/ - - return ( - -
- {/* replace all this code with your own component(s) */} -
- -
-
-
Text
-
Submit Change to Plugin
-
-
- {tableRows.map((row) => ( - - ))} -
- {/* end of replace */} -
-
- ) -} diff --git a/dwertheimer.ReactSkeleton/src/reactMain.js b/dwertheimer.ReactSkeleton/src/reactMain.js deleted file mode 100644 index ca33c9a17..000000000 --- a/dwertheimer.ReactSkeleton/src/reactMain.js +++ /dev/null @@ -1,195 +0,0 @@ -// @flow - -import pluginJson from '../plugin.json' -import { getGlobalSharedData, sendToHTMLWindow, sendBannerMessage } from '../../helpers/HTMLView' -import { log, logError, logDebug, timer, clo, JSP } from '@helpers/dev' -import { getWindowFromId } from '@helpers/NPWindows' -import { generateCSSFromTheme } from '@helpers/NPThemeToCSS' - -const WEBVIEW_WINDOW_ID = `${pluginJson['plugin.id']} React Window` // will be used as the customId for your window -// you can leave it like this or if you plan to open multiple windows, make it more specific per window -const REACT_WINDOW_TITLE = 'React View Skeleton Test' // change this to what you want window title to display - -export type PassedData = { - startTime?: Date /* used for timing/debugging */, - title?: string /* React Window Title */, - pluginData: any /* Your plugin's data to pass on first launch (or edited later) */, - ENV_MODE?: 'development' | 'production', - debug: boolean /* set based on ENV_MODE above */, - logProfilingMessage: boolean /* whether you want to see profiling messages on React redraws (not super interesting) */, - returnPluginCommand: { id: string, command: string } /* plugin jsFunction that will receive comms back from the React window */, - componentPath: string /* the path to the rolled up webview bundle. should be ../pluginID/react.c.WebView.bundle.* */, - passThroughVars?: { lastWindowScrollTop: number } /* any data you want to pass through to the React Window */, -} - -/** - * Gathers key data for the React Window, including the callback function that is used for comms back to the plugin - * @returns {PassedData} the React Data Window object - */ -export function getInitialDataForReactWindowObjectForReactView(): PassedData { - const startTime = new Date() - // get whatever pluginData you want the React window to start with and include it in the object below. This all gets passed to the React window - const pluginData = getInitialDataForReactWindow() - const ENV_MODE = 'development' /* helps during development. set to 'production' when ready to release */ - const dataToPass: PassedData = { - pluginData, - title: REACT_WINDOW_TITLE, - logProfilingMessage: false, - debug: ENV_MODE === 'development' ? true : false, - ENV_MODE, - returnPluginCommand: { id: pluginJson['plugin.id'], command: 'onMessageFromHTMLView' }, - /* change the ID below to your plugin ID */ - componentPath: `../dwertheimer.ReactSkeleton/react.c.WebView.bundle.${ENV_MODE === 'development' ? 'dev' : 'min'}.js`, - startTime, - } - return dataToPass -} - -/** - * Gather data you want passed to the React Window (e.g. what you you will use to display) - * You will likely use this function to pull together your starting window data - * Must return an object, with any number of properties, however you cannot use the following reserved - * properties: pluginData, title, debug, ENV_MODE, returnPluginCommand, componentPath, passThroughVars, startTime - * @returns {[string]: mixed} - the data that your React Window will start with - */ -export function getInitialDataForReactWindow(): { [string]: mixed } { - // for demonstration purposes will just fake some data for now, - // you would want to gather some data from your plugin - const data = Array.from(Array(10).keys()).map((i) => ({ textValue: `Item ${i}`, id: i, buttonText: `Submit ${i}` })) - return { - platform: NotePlan.environment.platform, // used in dialog positioning and CSS - tableRows: data, - } // this could be any object full of data you want to pass to the window - // we return tableRows just as an example, but there's nothing magic about that property name - // you could pass any object with any number of fields you want -} - -/** - * Router function that receives requests from the React Window and routes them to the appropriate function - * Typically based on a user interaction in the React Window - * (e.g. handleSubmitButtonClick example below) - * Here's where you will process any other commands+data that comes back from the React Window - * How it works: - * let reactWindowData...reaches out to the React window and get the most current pluginData that it's using to render. - * This is the data that you initially built and passed to the window in the initial call (with a few additions you don't need to worry about) - * Then in the case statements, we pass that data to a function which will act on the particular action type, - * and you edit the part of the data object that needs to be edited: typically `reactWindowData.pluginData.XXX` - * and that function IMPORTANTLY returns a modified reactWindowData object after acting on the action (this should be the full object used to render the React Window) - * That new updated reactWindowData object is sent back to the React window basically saying "hey, the data has changed, re-render as necessary!" - * and React will look through the data and find the parts that have changed and re-draw only those parts of the window - * @param {string} actionType - the reducer-type action to be dispatched - * @param {any} data - the relevant sent from the React Window (could be anything the plugin needs to act on the actionType) - * @author @dwertheimer - */ -export async function onMessageFromHTMLView(actionType: string, data: any = null): Promise { - try { - logDebug(pluginJson, `NP Plugin return path (onMessageFromHTMLView) received actionType="${actionType}" (typeof=${typeof actionType}) (typeof data=${typeof data})`) - clo(data, `Plugin onMessageFromHTMLView data=`) - let reactWindowData = await getGlobalSharedData(WEBVIEW_WINDOW_ID) // get the current data from the React Window - clo(reactWindowData, `Plugin onMessageFromHTMLView reactWindowData=`) - if (data.passThroughVars) reactWindowData.passThroughVars = { ...reactWindowData.passThroughVars, ...data.passThroughVars } - switch (actionType) { - /* best practice here is not to actually do the processing but to call a function based on what the actionType was sent by React */ - /* you would probably call a different function for each actionType */ - case 'onSubmitClick': - reactWindowData = await handleSubmitButtonClick(data, reactWindowData) //update the data to send it back to the React Window - break - default: - await sendBannerMessage(WEBVIEW_WINDOW_ID, `Plugin received an unknown actionType: "${actionType}" command with data:\n${JSON.stringify(data)}`) - break - } - if (reactWindowData) { - const updateText = `After ${actionType}, data was updated` /* this is just a string for debugging so you know what changed in the React Window */ - clo(reactWindowData, `Plugin onMessageFromHTMLView after updating window data,reactWindowData=`) - sendToHTMLWindow(WEBVIEW_WINDOW_ID, 'SET_DATA', reactWindowData, updateText) // note this will cause the React Window to re-render with the currentJSData - } - return {} // this return value is ignored but needs to exist or we get an error - } catch (error) { - logError(pluginJson, JSP(error)) - } -} - -/** - * Update the data in the React Window (and cause it to re-draw as necessary with the new data) - * This is likely most relevant when a trigger has been sent from a NotePlan window, but could be used anytime a plugin wants to update the data in the React Window - * This is exactly the same as onMessageFromHTMLView, but named updateReactWindowData to clarify that the plugin is updating the data in the React Window - * rather than a user interaction having triggered it (the result is the same) - * @param {string} actionType - the reducer-type action to be dispatched -- see onMessageFromHTMLView above - * @param {any} data - any data that the router (specified in onMessageFromHTMLView) needs -- may be nothing - * @returns {Promise} - does not return anything important - */ -export async function updateReactWindowData(actionType: string, data: any = null): Promise { - if (!getWindowFromId(WEBVIEW_WINDOW_ID)) { - logError(pluginJson, `updateReactWindowData('${actionType}'): Window with ID ${WEBVIEW_WINDOW_ID} not found. Could not update data.`) - return - } - return await onMessageFromHTMLView(actionType, data) -} - -/** - * An example handler function that is called when someone clicks a button in the React Window - * When someone clicks a "Submit" button in the React Window, it calls the router (onMessageFromHTMLView) - * which sees the actionType === "onSubmitClick" so it routes to this function for processing - * @param {any} data - the data sent from the React Window for the action 'onSubmitClick' - * @param {any} reactWindowData - the current data in the React Window - * @returns {any} - the updated data to send back to the React Window - */ -async function handleSubmitButtonClick(data: any, reactWindowData: PassedData): Promise { - const { index: clickedIndex } = data //in our example, the button click just sends the index of the row clicked - await sendBannerMessage( - WEBVIEW_WINDOW_ID, - `Plugin received an actionType: "onSubmitClick" command with data:
${JSON.stringify( - data, - )}.
Plugin then fired this message over the bridge to the React window and changed the data in the React window.`, - ) - clo(reactWindowData, `handleSubmitButtonClick: reactWindowData BEFORE update`) - // change the data in the React window for the row that was clicked (just an example) - // find the right row, even though rows could have been scrambled by the user inside the React Window - const index = reactWindowData.pluginData.tableRows.findIndex((row) => row.id === clickedIndex) - reactWindowData.pluginData.tableRows[index].textValue = `Item ${clickedIndex} was updated by the plugin (see changed data in the debug section below)` - return reactWindowData //updated data to send back to React Window -} - -/** - * Plugin Entry Point for "Test React Window" - * @author @dwertheimer - */ -export async function testReactWindow(): Promise { - try { - logDebug(pluginJson, `testReactWindow starting up`) - // make sure we have the np.Shared plugin which has the core react code and some basic CSS - await DataStore.installOrUpdatePluginsByID(['np.Shared'], false, false, true) // you must have np.Shared code in order to open up a React Window - logDebug(pluginJson, `testReactWindow: installOrUpdatePluginsByID ['np.Shared'] completed`) - // get initial data to pass to the React Window - const data = await getInitialDataForReactWindowObjectForReactView() - - // Note the first tag below uses the w3.css scaffolding for basic UI elements. You can delete that line if you don't want to use it - // w3.css reference: https://www.w3schools.com/w3css/defaulT.asp - // The second line needs to be updated to your pluginID in order to load any specific CSS you want to include for the React Window (in requiredFiles) - const cssTagsString = ` - - \n` - const windowOptions = { - savedFilename: `../../${pluginJson['plugin.id']}/saved-output.html` /* for saving a debug version of the html file */, - headerTags: cssTagsString, - windowTitle: data.title, - customId: WEBVIEW_WINDOW_ID, - reuseUsersWindowRect: true, - shouldFocus: true /* focus window every time (set to false if you want a bg refresh) */, - generalCSSIn: generateCSSFromTheme(), // either use dashboard-specific theme name, or get general CSS set automatically from current theme - postBodyScript: ` - - `, - } - logDebug(`===== testReactWindow Calling React after ${timer(data.startTime || new Date())} =====`) - logDebug(pluginJson, `testReactWindow invoking window. testReactWindow stopping here. It's all React from this point forward`) - clo(data, `testReactWindow data object passed`) - // now ask np.Shared to open the React Window with the data we just gathered - await DataStore.invokePluginCommandByName('openReactWindow', 'np.Shared', [data, windowOptions]) - } catch (error) { - logError(pluginJson, JSP(error)) - } -} diff --git a/dwertheimer.TaskAutomations/requiredFiles/react.c.WebView.bundle.dev.js b/dwertheimer.TaskAutomations/requiredFiles/react.c.WebView.bundle.dev.js deleted file mode 100644 index ff1522884..000000000 --- a/dwertheimer.TaskAutomations/requiredFiles/react.c.WebView.bundle.dev.js +++ /dev/null @@ -1,44552 +0,0 @@ -var WebViewBundle = (function (exports, React$1) { - 'use strict'; - - function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } - - function _interopNamespace(e) { - if (e && e.__esModule) return e; - var n = Object.create(null); - if (e) { - Object.keys(e).forEach(function (k) { - if (k !== 'default') { - var d = Object.getOwnPropertyDescriptor(e, k); - Object.defineProperty(n, k, d.get ? d : { - enumerable: true, - get: function () { return e[k]; } - }); - } - }); - } - n["default"] = e; - return Object.freeze(n); - } - - var React__default = /*#__PURE__*/_interopDefaultLegacy(React$1); - var React__namespace = /*#__PURE__*/_interopNamespace(React$1); - - var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; - - function getDefaultExportFromCjs (x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; - } - - function getAugmentedNamespace(n) { - if (n.__esModule) return n; - var a = Object.defineProperty({}, '__esModule', {value: true}); - Object.keys(n).forEach(function (k) { - var d = Object.getOwnPropertyDescriptor(n, k); - Object.defineProperty(a, k, d.get ? d : { - enumerable: true, - get: function () { - return n[k]; - } - }); - }); - return a; - } - - /** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); - * // => false - */ - - function isObject$2(value) { - var type = typeof value; - return value != null && (type == 'object' || type == 'function'); - } - - var isObject_1 = isObject$2; - - /** Detect free variable `global` from Node.js. */ - - var freeGlobal$1 = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; - - var _freeGlobal = freeGlobal$1; - - var freeGlobal = _freeGlobal; - - /** Detect free variable `self`. */ - var freeSelf = typeof self == 'object' && self && self.Object === Object && self; - - /** Used as a reference to the global object. */ - var root$2 = freeGlobal || freeSelf || Function('return this')(); - - var _root = root$2; - - var root$1 = _root; - - /** - * Gets the timestamp of the number of milliseconds that have elapsed since - * the Unix epoch (1 January 1970 00:00:00 UTC). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Date - * @returns {number} Returns the timestamp. - * @example - * - * _.defer(function(stamp) { - * console.log(_.now() - stamp); - * }, _.now()); - * // => Logs the number of milliseconds it took for the deferred invocation. - */ - var now$1 = function() { - return root$1.Date.now(); - }; - - var now_1 = now$1; - - /** Used to match a single whitespace character. */ - - var reWhitespace = /\s/; - - /** - * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace - * character of `string`. - * - * @private - * @param {string} string The string to inspect. - * @returns {number} Returns the index of the last non-whitespace character. - */ - function trimmedEndIndex$1(string) { - var index = string.length; - - while (index-- && reWhitespace.test(string.charAt(index))) {} - return index; - } - - var _trimmedEndIndex = trimmedEndIndex$1; - - var trimmedEndIndex = _trimmedEndIndex; - - /** Used to match leading whitespace. */ - var reTrimStart = /^\s+/; - - /** - * The base implementation of `_.trim`. - * - * @private - * @param {string} string The string to trim. - * @returns {string} Returns the trimmed string. - */ - function baseTrim$1(string) { - return string - ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '') - : string; - } - - var _baseTrim = baseTrim$1; - - var root = _root; - - /** Built-in value references. */ - var Symbol$3 = root.Symbol; - - var _Symbol = Symbol$3; - - var Symbol$2 = _Symbol; - - /** Used for built-in method references. */ - var objectProto$1 = Object.prototype; - - /** Used to check objects for own properties. */ - var hasOwnProperty$1 = objectProto$1.hasOwnProperty; - - /** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ - var nativeObjectToString$1 = objectProto$1.toString; - - /** Built-in value references. */ - var symToStringTag$1 = Symbol$2 ? Symbol$2.toStringTag : undefined; - - /** - * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the raw `toStringTag`. - */ - function getRawTag$1(value) { - var isOwn = hasOwnProperty$1.call(value, symToStringTag$1), - tag = value[symToStringTag$1]; - - try { - value[symToStringTag$1] = undefined; - var unmasked = true; - } catch (e) {} - - var result = nativeObjectToString$1.call(value); - if (unmasked) { - if (isOwn) { - value[symToStringTag$1] = tag; - } else { - delete value[symToStringTag$1]; - } - } - return result; - } - - var _getRawTag = getRawTag$1; - - /** Used for built-in method references. */ - - var objectProto = Object.prototype; - - /** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ - var nativeObjectToString = objectProto.toString; - - /** - * Converts `value` to a string using `Object.prototype.toString`. - * - * @private - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - */ - function objectToString$1(value) { - return nativeObjectToString.call(value); - } - - var _objectToString = objectToString$1; - - var Symbol$1 = _Symbol, - getRawTag = _getRawTag, - objectToString = _objectToString; - - /** `Object#toString` result references. */ - var nullTag = '[object Null]', - undefinedTag = '[object Undefined]'; - - /** Built-in value references. */ - var symToStringTag = Symbol$1 ? Symbol$1.toStringTag : undefined; - - /** - * The base implementation of `getTag` without fallbacks for buggy environments. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ - function baseGetTag$1(value) { - if (value == null) { - return value === undefined ? undefinedTag : nullTag; - } - return (symToStringTag && symToStringTag in Object(value)) - ? getRawTag(value) - : objectToString(value); - } - - var _baseGetTag = baseGetTag$1; - - /** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. - * @example - * - * _.isObjectLike({}); - * // => true - * - * _.isObjectLike([1, 2, 3]); - * // => true - * - * _.isObjectLike(_.noop); - * // => false - * - * _.isObjectLike(null); - * // => false - */ - - function isObjectLike$1(value) { - return value != null && typeof value == 'object'; - } - - var isObjectLike_1 = isObjectLike$1; - - var baseGetTag = _baseGetTag, - isObjectLike = isObjectLike_1; - - /** `Object#toString` result references. */ - var symbolTag = '[object Symbol]'; - - /** - * Checks if `value` is classified as a `Symbol` primitive or object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. - * @example - * - * _.isSymbol(Symbol.iterator); - * // => true - * - * _.isSymbol('abc'); - * // => false - */ - function isSymbol$1(value) { - return typeof value == 'symbol' || - (isObjectLike(value) && baseGetTag(value) == symbolTag); - } - - var isSymbol_1 = isSymbol$1; - - var baseTrim = _baseTrim, - isObject$1 = isObject_1, - isSymbol = isSymbol_1; - - /** Used as references for various `Number` constants. */ - var NAN = 0 / 0; - - /** Used to detect bad signed hexadecimal string values. */ - var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; - - /** Used to detect binary string values. */ - var reIsBinary = /^0b[01]+$/i; - - /** Used to detect octal string values. */ - var reIsOctal = /^0o[0-7]+$/i; - - /** Built-in method references without a dependency on `root`. */ - var freeParseInt = parseInt; - - /** - * Converts `value` to a number. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to process. - * @returns {number} Returns the number. - * @example - * - * _.toNumber(3.2); - * // => 3.2 - * - * _.toNumber(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toNumber(Infinity); - * // => Infinity - * - * _.toNumber('3.2'); - * // => 3.2 - */ - function toNumber$1(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - if (isObject$1(value)) { - var other = typeof value.valueOf == 'function' ? value.valueOf() : value; - value = isObject$1(other) ? (other + '') : other; - } - if (typeof value != 'string') { - return value === 0 ? value : +value; - } - value = baseTrim(value); - var isBinary = reIsBinary.test(value); - return (isBinary || reIsOctal.test(value)) - ? freeParseInt(value.slice(2), isBinary ? 2 : 8) - : (reIsBadHex.test(value) ? NAN : +value); - } - - var toNumber_1 = toNumber$1; - - var isObject = isObject_1, - now = now_1, - toNumber = toNumber_1; - - /** Error message constants. */ - var FUNC_ERROR_TEXT = 'Expected a function'; - - /* Built-in method references for those with the same name as other `lodash` methods. */ - var nativeMax = Math.max, - nativeMin = Math.min; - - /** - * Creates a debounced function that delays invoking `func` until after `wait` - * milliseconds have elapsed since the last time the debounced function was - * invoked. The debounced function comes with a `cancel` method to cancel - * delayed `func` invocations and a `flush` method to immediately invoke them. - * Provide `options` to indicate whether `func` should be invoked on the - * leading and/or trailing edge of the `wait` timeout. The `func` is invoked - * with the last arguments provided to the debounced function. Subsequent - * calls to the debounced function return the result of the last `func` - * invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the debounced function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.debounce` and `_.throttle`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to debounce. - * @param {number} [wait=0] The number of milliseconds to delay. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=false] - * Specify invoking on the leading edge of the timeout. - * @param {number} [options.maxWait] - * The maximum time `func` is allowed to be delayed before it's invoked. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new debounced function. - * @example - * - * // Avoid costly calculations while the window size is in flux. - * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); - * - * // Invoke `sendMail` when clicked, debouncing subsequent calls. - * jQuery(element).on('click', _.debounce(sendMail, 300, { - * 'leading': true, - * 'trailing': false - * })); - * - * // Ensure `batchLog` is invoked once after 1 second of debounced calls. - * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); - * var source = new EventSource('/stream'); - * jQuery(source).on('message', debounced); - * - * // Cancel the trailing debounced invocation. - * jQuery(window).on('popstate', debounced.cancel); - */ - function debounce(func, wait, options) { - var lastArgs, - lastThis, - maxWait, - result, - timerId, - lastCallTime, - lastInvokeTime = 0, - leading = false, - maxing = false, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - wait = toNumber(wait) || 0; - if (isObject(options)) { - leading = !!options.leading; - maxing = 'maxWait' in options; - maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - - function invokeFunc(time) { - var args = lastArgs, - thisArg = lastThis; - - lastArgs = lastThis = undefined; - lastInvokeTime = time; - result = func.apply(thisArg, args); - return result; - } - - function leadingEdge(time) { - // Reset any `maxWait` timer. - lastInvokeTime = time; - // Start the timer for the trailing edge. - timerId = setTimeout(timerExpired, wait); - // Invoke the leading edge. - return leading ? invokeFunc(time) : result; - } - - function remainingWait(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime, - timeWaiting = wait - timeSinceLastCall; - - return maxing - ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) - : timeWaiting; - } - - function shouldInvoke(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime; - - // Either this is the first call, activity has stopped and we're at the - // trailing edge, the system time has gone backwards and we're treating - // it as the trailing edge, or we've hit the `maxWait` limit. - return (lastCallTime === undefined || (timeSinceLastCall >= wait) || - (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); - } - - function timerExpired() { - var time = now(); - if (shouldInvoke(time)) { - return trailingEdge(time); - } - // Restart the timer. - timerId = setTimeout(timerExpired, remainingWait(time)); - } - - function trailingEdge(time) { - timerId = undefined; - - // Only invoke if we have `lastArgs` which means `func` has been - // debounced at least once. - if (trailing && lastArgs) { - return invokeFunc(time); - } - lastArgs = lastThis = undefined; - return result; - } - - function cancel() { - if (timerId !== undefined) { - clearTimeout(timerId); - } - lastInvokeTime = 0; - lastArgs = lastCallTime = lastThis = timerId = undefined; - } - - function flush() { - return timerId === undefined ? result : trailingEdge(now()); - } - - function debounced() { - var time = now(), - isInvoking = shouldInvoke(time); - - lastArgs = arguments; - lastThis = this; - lastCallTime = time; - - if (isInvoking) { - if (timerId === undefined) { - return leadingEdge(lastCallTime); - } - if (maxing) { - // Handle invocations in a tight loop. - clearTimeout(timerId); - timerId = setTimeout(timerExpired, wait); - return invokeFunc(lastCallTime); - } - } - if (timerId === undefined) { - timerId = setTimeout(timerExpired, wait); - } - return result; - } - debounced.cancel = cancel; - debounced.flush = flush; - return debounced; - } - - var debounce_1 = debounce; - - var index_cjs = {}; - - var reactIs$2 = {exports: {}}; - - var reactIs_development$1 = {}; - - /** - * @license React - * react-is.development.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - - { - (function() { - - // ATTENTION - // When adding new symbols to this file, - // Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols' - // The Symbol used to tag the ReactElement-like types. - var REACT_ELEMENT_TYPE = Symbol.for('react.element'); - var REACT_PORTAL_TYPE = Symbol.for('react.portal'); - var REACT_FRAGMENT_TYPE = Symbol.for('react.fragment'); - var REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode'); - var REACT_PROFILER_TYPE = Symbol.for('react.profiler'); - var REACT_PROVIDER_TYPE = Symbol.for('react.provider'); - var REACT_CONTEXT_TYPE = Symbol.for('react.context'); - var REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context'); - var REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref'); - var REACT_SUSPENSE_TYPE = Symbol.for('react.suspense'); - var REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list'); - var REACT_MEMO_TYPE = Symbol.for('react.memo'); - var REACT_LAZY_TYPE = Symbol.for('react.lazy'); - var REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen'); - - // ----------------------------------------------------------------------------- - - var enableScopeAPI = false; // Experimental Create Event Handle API. - var enableCacheElement = false; - var enableTransitionTracing = false; // No known bugs, but needs performance testing - - var enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber - // stuff. Intended to enable React core members to more easily debug scheduling - // issues in DEV builds. - - var enableDebugTracing = false; // Track which Fiber(s) schedule render work. - - var REACT_MODULE_REFERENCE; - - { - REACT_MODULE_REFERENCE = Symbol.for('react.module.reference'); - } - - function isValidElementType(type) { - if (typeof type === 'string' || typeof type === 'function') { - return true; - } // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill). - - - if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing ) { - return true; - } - - if (typeof type === 'object' && type !== null) { - if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object - // types supported by any Flight configuration anywhere since - // we don't know which Flight build this will end up being used - // with. - type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== undefined) { - return true; - } - } - - return false; - } - - function typeOf(object) { - if (typeof object === 'object' && object !== null) { - var $$typeof = object.$$typeof; - - switch ($$typeof) { - case REACT_ELEMENT_TYPE: - var type = object.type; - - switch (type) { - case REACT_FRAGMENT_TYPE: - case REACT_PROFILER_TYPE: - case REACT_STRICT_MODE_TYPE: - case REACT_SUSPENSE_TYPE: - case REACT_SUSPENSE_LIST_TYPE: - return type; - - default: - var $$typeofType = type && type.$$typeof; - - switch ($$typeofType) { - case REACT_SERVER_CONTEXT_TYPE: - case REACT_CONTEXT_TYPE: - case REACT_FORWARD_REF_TYPE: - case REACT_LAZY_TYPE: - case REACT_MEMO_TYPE: - case REACT_PROVIDER_TYPE: - return $$typeofType; - - default: - return $$typeof; - } - - } - - case REACT_PORTAL_TYPE: - return $$typeof; - } - } - - return undefined; - } - var ContextConsumer = REACT_CONTEXT_TYPE; - var ContextProvider = REACT_PROVIDER_TYPE; - var Element = REACT_ELEMENT_TYPE; - var ForwardRef = REACT_FORWARD_REF_TYPE; - var Fragment = REACT_FRAGMENT_TYPE; - var Lazy = REACT_LAZY_TYPE; - var Memo = REACT_MEMO_TYPE; - var Portal = REACT_PORTAL_TYPE; - var Profiler = REACT_PROFILER_TYPE; - var StrictMode = REACT_STRICT_MODE_TYPE; - var Suspense = REACT_SUSPENSE_TYPE; - var SuspenseList = REACT_SUSPENSE_LIST_TYPE; - var hasWarnedAboutDeprecatedIsAsyncMode = false; - var hasWarnedAboutDeprecatedIsConcurrentMode = false; // AsyncMode should be deprecated - - function isAsyncMode(object) { - { - if (!hasWarnedAboutDeprecatedIsAsyncMode) { - hasWarnedAboutDeprecatedIsAsyncMode = true; // Using console['warn'] to evade Babel and ESLint - - console['warn']('The ReactIs.isAsyncMode() alias has been deprecated, ' + 'and will be removed in React 18+.'); - } - } - - return false; - } - function isConcurrentMode(object) { - { - if (!hasWarnedAboutDeprecatedIsConcurrentMode) { - hasWarnedAboutDeprecatedIsConcurrentMode = true; // Using console['warn'] to evade Babel and ESLint - - console['warn']('The ReactIs.isConcurrentMode() alias has been deprecated, ' + 'and will be removed in React 18+.'); - } - } - - return false; - } - function isContextConsumer(object) { - return typeOf(object) === REACT_CONTEXT_TYPE; - } - function isContextProvider(object) { - return typeOf(object) === REACT_PROVIDER_TYPE; - } - function isElement(object) { - return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE; - } - function isForwardRef(object) { - return typeOf(object) === REACT_FORWARD_REF_TYPE; - } - function isFragment(object) { - return typeOf(object) === REACT_FRAGMENT_TYPE; - } - function isLazy(object) { - return typeOf(object) === REACT_LAZY_TYPE; - } - function isMemo(object) { - return typeOf(object) === REACT_MEMO_TYPE; - } - function isPortal(object) { - return typeOf(object) === REACT_PORTAL_TYPE; - } - function isProfiler(object) { - return typeOf(object) === REACT_PROFILER_TYPE; - } - function isStrictMode(object) { - return typeOf(object) === REACT_STRICT_MODE_TYPE; - } - function isSuspense(object) { - return typeOf(object) === REACT_SUSPENSE_TYPE; - } - function isSuspenseList(object) { - return typeOf(object) === REACT_SUSPENSE_LIST_TYPE; - } - - reactIs_development$1.ContextConsumer = ContextConsumer; - reactIs_development$1.ContextProvider = ContextProvider; - reactIs_development$1.Element = Element; - reactIs_development$1.ForwardRef = ForwardRef; - reactIs_development$1.Fragment = Fragment; - reactIs_development$1.Lazy = Lazy; - reactIs_development$1.Memo = Memo; - reactIs_development$1.Portal = Portal; - reactIs_development$1.Profiler = Profiler; - reactIs_development$1.StrictMode = StrictMode; - reactIs_development$1.Suspense = Suspense; - reactIs_development$1.SuspenseList = SuspenseList; - reactIs_development$1.isAsyncMode = isAsyncMode; - reactIs_development$1.isConcurrentMode = isConcurrentMode; - reactIs_development$1.isContextConsumer = isContextConsumer; - reactIs_development$1.isContextProvider = isContextProvider; - reactIs_development$1.isElement = isElement; - reactIs_development$1.isForwardRef = isForwardRef; - reactIs_development$1.isFragment = isFragment; - reactIs_development$1.isLazy = isLazy; - reactIs_development$1.isMemo = isMemo; - reactIs_development$1.isPortal = isPortal; - reactIs_development$1.isProfiler = isProfiler; - reactIs_development$1.isStrictMode = isStrictMode; - reactIs_development$1.isSuspense = isSuspense; - reactIs_development$1.isSuspenseList = isSuspenseList; - reactIs_development$1.isValidElementType = isValidElementType; - reactIs_development$1.typeOf = typeOf; - })(); - } - - { - reactIs$2.exports = reactIs_development$1; - } - - // - - var shallowequal = function shallowEqual(objA, objB, compare, compareContext) { - var ret = compare ? compare.call(compareContext, objA, objB) : void 0; - - if (ret !== void 0) { - return !!ret; - } - - if (objA === objB) { - return true; - } - - if (typeof objA !== "object" || !objA || typeof objB !== "object" || !objB) { - return false; - } - - var keysA = Object.keys(objA); - var keysB = Object.keys(objB); - - if (keysA.length !== keysB.length) { - return false; - } - - var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB); - - // Test for A's keys different from B. - for (var idx = 0; idx < keysA.length; idx++) { - var key = keysA[idx]; - - if (!bHasOwnProperty(key)) { - return false; - } - - var valueA = objA[key]; - var valueB = objB[key]; - - ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0; - - if (ret === false || (ret === void 0 && valueA !== valueB)) { - return false; - } - } - - return true; - }; - - function stylis_min (W) { - function M(d, c, e, h, a) { - for (var m = 0, b = 0, v = 0, n = 0, q, g, x = 0, K = 0, k, u = k = q = 0, l = 0, r = 0, I = 0, t = 0, B = e.length, J = B - 1, y, f = '', p = '', F = '', G = '', C; l < B;) { - g = e.charCodeAt(l); - l === J && 0 !== b + n + v + m && (0 !== b && (g = 47 === b ? 10 : 47), n = v = m = 0, B++, J++); - - if (0 === b + n + v + m) { - if (l === J && (0 < r && (f = f.replace(N, '')), 0 < f.trim().length)) { - switch (g) { - case 32: - case 9: - case 59: - case 13: - case 10: - break; - - default: - f += e.charAt(l); - } - - g = 59; - } - - switch (g) { - case 123: - f = f.trim(); - q = f.charCodeAt(0); - k = 1; - - for (t = ++l; l < B;) { - switch (g = e.charCodeAt(l)) { - case 123: - k++; - break; - - case 125: - k--; - break; - - case 47: - switch (g = e.charCodeAt(l + 1)) { - case 42: - case 47: - a: { - for (u = l + 1; u < J; ++u) { - switch (e.charCodeAt(u)) { - case 47: - if (42 === g && 42 === e.charCodeAt(u - 1) && l + 2 !== u) { - l = u + 1; - break a; - } - - break; - - case 10: - if (47 === g) { - l = u + 1; - break a; - } - - } - } - - l = u; - } - - } - - break; - - case 91: - g++; - - case 40: - g++; - - case 34: - case 39: - for (; l++ < J && e.charCodeAt(l) !== g;) { - } - - } - - if (0 === k) break; - l++; - } - - k = e.substring(t, l); - 0 === q && (q = (f = f.replace(ca, '').trim()).charCodeAt(0)); - - switch (q) { - case 64: - 0 < r && (f = f.replace(N, '')); - g = f.charCodeAt(1); - - switch (g) { - case 100: - case 109: - case 115: - case 45: - r = c; - break; - - default: - r = O; - } - - k = M(c, r, k, g, a + 1); - t = k.length; - 0 < A && (r = X(O, f, I), C = H(3, k, r, c, D, z, t, g, a, h), f = r.join(''), void 0 !== C && 0 === (t = (k = C.trim()).length) && (g = 0, k = '')); - if (0 < t) switch (g) { - case 115: - f = f.replace(da, ea); - - case 100: - case 109: - case 45: - k = f + '{' + k + '}'; - break; - - case 107: - f = f.replace(fa, '$1 $2'); - k = f + '{' + k + '}'; - k = 1 === w || 2 === w && L('@' + k, 3) ? '@-webkit-' + k + '@' + k : '@' + k; - break; - - default: - k = f + k, 112 === h && (k = (p += k, '')); - } else k = ''; - break; - - default: - k = M(c, X(c, f, I), k, h, a + 1); - } - - F += k; - k = I = r = u = q = 0; - f = ''; - g = e.charCodeAt(++l); - break; - - case 125: - case 59: - f = (0 < r ? f.replace(N, '') : f).trim(); - if (1 < (t = f.length)) switch (0 === u && (q = f.charCodeAt(0), 45 === q || 96 < q && 123 > q) && (t = (f = f.replace(' ', ':')).length), 0 < A && void 0 !== (C = H(1, f, c, d, D, z, p.length, h, a, h)) && 0 === (t = (f = C.trim()).length) && (f = '\x00\x00'), q = f.charCodeAt(0), g = f.charCodeAt(1), q) { - case 0: - break; - - case 64: - if (105 === g || 99 === g) { - G += f + e.charAt(l); - break; - } - - default: - 58 !== f.charCodeAt(t - 1) && (p += P(f, q, g, f.charCodeAt(2))); - } - I = r = u = q = 0; - f = ''; - g = e.charCodeAt(++l); - } - } - - switch (g) { - case 13: - case 10: - 47 === b ? b = 0 : 0 === 1 + q && 107 !== h && 0 < f.length && (r = 1, f += '\x00'); - 0 < A * Y && H(0, f, c, d, D, z, p.length, h, a, h); - z = 1; - D++; - break; - - case 59: - case 125: - if (0 === b + n + v + m) { - z++; - break; - } - - default: - z++; - y = e.charAt(l); - - switch (g) { - case 9: - case 32: - if (0 === n + m + b) switch (x) { - case 44: - case 58: - case 9: - case 32: - y = ''; - break; - - default: - 32 !== g && (y = ' '); - } - break; - - case 0: - y = '\\0'; - break; - - case 12: - y = '\\f'; - break; - - case 11: - y = '\\v'; - break; - - case 38: - 0 === n + b + m && (r = I = 1, y = '\f' + y); - break; - - case 108: - if (0 === n + b + m + E && 0 < u) switch (l - u) { - case 2: - 112 === x && 58 === e.charCodeAt(l - 3) && (E = x); - - case 8: - 111 === K && (E = K); - } - break; - - case 58: - 0 === n + b + m && (u = l); - break; - - case 44: - 0 === b + v + n + m && (r = 1, y += '\r'); - break; - - case 34: - case 39: - 0 === b && (n = n === g ? 0 : 0 === n ? g : n); - break; - - case 91: - 0 === n + b + v && m++; - break; - - case 93: - 0 === n + b + v && m--; - break; - - case 41: - 0 === n + b + m && v--; - break; - - case 40: - if (0 === n + b + m) { - if (0 === q) switch (2 * x + 3 * K) { - case 533: - break; - - default: - q = 1; - } - v++; - } - - break; - - case 64: - 0 === b + v + n + m + u + k && (k = 1); - break; - - case 42: - case 47: - if (!(0 < n + m + v)) switch (b) { - case 0: - switch (2 * g + 3 * e.charCodeAt(l + 1)) { - case 235: - b = 47; - break; - - case 220: - t = l, b = 42; - } - - break; - - case 42: - 47 === g && 42 === x && t + 2 !== l && (33 === e.charCodeAt(t + 2) && (p += e.substring(t, l + 1)), y = '', b = 0); - } - } - - 0 === b && (f += y); - } - - K = x; - x = g; - l++; - } - - t = p.length; - - if (0 < t) { - r = c; - if (0 < A && (C = H(2, p, r, d, D, z, t, h, a, h), void 0 !== C && 0 === (p = C).length)) return G + p + F; - p = r.join(',') + '{' + p + '}'; - - if (0 !== w * E) { - 2 !== w || L(p, 2) || (E = 0); - - switch (E) { - case 111: - p = p.replace(ha, ':-moz-$1') + p; - break; - - case 112: - p = p.replace(Q, '::-webkit-input-$1') + p.replace(Q, '::-moz-$1') + p.replace(Q, ':-ms-input-$1') + p; - } - - E = 0; - } - } - - return G + p + F; - } - - function X(d, c, e) { - var h = c.trim().split(ia); - c = h; - var a = h.length, - m = d.length; - - switch (m) { - case 0: - case 1: - var b = 0; - - for (d = 0 === m ? '' : d[0] + ' '; b < a; ++b) { - c[b] = Z(d, c[b], e).trim(); - } - - break; - - default: - var v = b = 0; - - for (c = []; b < a; ++b) { - for (var n = 0; n < m; ++n) { - c[v++] = Z(d[n] + ' ', h[b], e).trim(); - } - } - - } - - return c; - } - - function Z(d, c, e) { - var h = c.charCodeAt(0); - 33 > h && (h = (c = c.trim()).charCodeAt(0)); - - switch (h) { - case 38: - return c.replace(F, '$1' + d.trim()); - - case 58: - return d.trim() + c.replace(F, '$1' + d.trim()); - - default: - if (0 < 1 * e && 0 < c.indexOf('\f')) return c.replace(F, (58 === d.charCodeAt(0) ? '' : '$1') + d.trim()); - } - - return d + c; - } - - function P(d, c, e, h) { - var a = d + ';', - m = 2 * c + 3 * e + 4 * h; - - if (944 === m) { - d = a.indexOf(':', 9) + 1; - var b = a.substring(d, a.length - 1).trim(); - b = a.substring(0, d).trim() + b + ';'; - return 1 === w || 2 === w && L(b, 1) ? '-webkit-' + b + b : b; - } - - if (0 === w || 2 === w && !L(a, 1)) return a; - - switch (m) { - case 1015: - return 97 === a.charCodeAt(10) ? '-webkit-' + a + a : a; - - case 951: - return 116 === a.charCodeAt(3) ? '-webkit-' + a + a : a; - - case 963: - return 110 === a.charCodeAt(5) ? '-webkit-' + a + a : a; - - case 1009: - if (100 !== a.charCodeAt(4)) break; - - case 969: - case 942: - return '-webkit-' + a + a; - - case 978: - return '-webkit-' + a + '-moz-' + a + a; - - case 1019: - case 983: - return '-webkit-' + a + '-moz-' + a + '-ms-' + a + a; - - case 883: - if (45 === a.charCodeAt(8)) return '-webkit-' + a + a; - if (0 < a.indexOf('image-set(', 11)) return a.replace(ja, '$1-webkit-$2') + a; - break; - - case 932: - if (45 === a.charCodeAt(4)) switch (a.charCodeAt(5)) { - case 103: - return '-webkit-box-' + a.replace('-grow', '') + '-webkit-' + a + '-ms-' + a.replace('grow', 'positive') + a; - - case 115: - return '-webkit-' + a + '-ms-' + a.replace('shrink', 'negative') + a; - - case 98: - return '-webkit-' + a + '-ms-' + a.replace('basis', 'preferred-size') + a; - } - return '-webkit-' + a + '-ms-' + a + a; - - case 964: - return '-webkit-' + a + '-ms-flex-' + a + a; - - case 1023: - if (99 !== a.charCodeAt(8)) break; - b = a.substring(a.indexOf(':', 15)).replace('flex-', '').replace('space-between', 'justify'); - return '-webkit-box-pack' + b + '-webkit-' + a + '-ms-flex-pack' + b + a; - - case 1005: - return ka.test(a) ? a.replace(aa, ':-webkit-') + a.replace(aa, ':-moz-') + a : a; - - case 1e3: - b = a.substring(13).trim(); - c = b.indexOf('-') + 1; - - switch (b.charCodeAt(0) + b.charCodeAt(c)) { - case 226: - b = a.replace(G, 'tb'); - break; - - case 232: - b = a.replace(G, 'tb-rl'); - break; - - case 220: - b = a.replace(G, 'lr'); - break; - - default: - return a; - } - - return '-webkit-' + a + '-ms-' + b + a; - - case 1017: - if (-1 === a.indexOf('sticky', 9)) break; - - case 975: - c = (a = d).length - 10; - b = (33 === a.charCodeAt(c) ? a.substring(0, c) : a).substring(d.indexOf(':', 7) + 1).trim(); - - switch (m = b.charCodeAt(0) + (b.charCodeAt(7) | 0)) { - case 203: - if (111 > b.charCodeAt(8)) break; - - case 115: - a = a.replace(b, '-webkit-' + b) + ';' + a; - break; - - case 207: - case 102: - a = a.replace(b, '-webkit-' + (102 < m ? 'inline-' : '') + 'box') + ';' + a.replace(b, '-webkit-' + b) + ';' + a.replace(b, '-ms-' + b + 'box') + ';' + a; - } - - return a + ';'; - - case 938: - if (45 === a.charCodeAt(5)) switch (a.charCodeAt(6)) { - case 105: - return b = a.replace('-items', ''), '-webkit-' + a + '-webkit-box-' + b + '-ms-flex-' + b + a; - - case 115: - return '-webkit-' + a + '-ms-flex-item-' + a.replace(ba, '') + a; - - default: - return '-webkit-' + a + '-ms-flex-line-pack' + a.replace('align-content', '').replace(ba, '') + a; - } - break; - - case 973: - case 989: - if (45 !== a.charCodeAt(3) || 122 === a.charCodeAt(4)) break; - - case 931: - case 953: - if (!0 === la.test(d)) return 115 === (b = d.substring(d.indexOf(':') + 1)).charCodeAt(0) ? P(d.replace('stretch', 'fill-available'), c, e, h).replace(':fill-available', ':stretch') : a.replace(b, '-webkit-' + b) + a.replace(b, '-moz-' + b.replace('fill-', '')) + a; - break; - - case 962: - if (a = '-webkit-' + a + (102 === a.charCodeAt(5) ? '-ms-' + a : '') + a, 211 === e + h && 105 === a.charCodeAt(13) && 0 < a.indexOf('transform', 10)) return a.substring(0, a.indexOf(';', 27) + 1).replace(ma, '$1-webkit-$2') + a; - } - - return a; - } - - function L(d, c) { - var e = d.indexOf(1 === c ? ':' : '{'), - h = d.substring(0, 3 !== c ? e : 10); - e = d.substring(e + 1, d.length - 1); - return R(2 !== c ? h : h.replace(na, '$1'), e, c); - } - - function ea(d, c) { - var e = P(c, c.charCodeAt(0), c.charCodeAt(1), c.charCodeAt(2)); - return e !== c + ';' ? e.replace(oa, ' or ($1)').substring(4) : '(' + c + ')'; - } - - function H(d, c, e, h, a, m, b, v, n, q) { - for (var g = 0, x = c, w; g < A; ++g) { - switch (w = S[g].call(B, d, x, e, h, a, m, b, v, n, q)) { - case void 0: - case !1: - case !0: - case null: - break; - - default: - x = w; - } - } - - if (x !== c) return x; - } - - function T(d) { - switch (d) { - case void 0: - case null: - A = S.length = 0; - break; - - default: - if ('function' === typeof d) S[A++] = d;else if ('object' === typeof d) for (var c = 0, e = d.length; c < e; ++c) { - T(d[c]); - } else Y = !!d | 0; - } - - return T; - } - - function U(d) { - d = d.prefix; - void 0 !== d && (R = null, d ? 'function' !== typeof d ? w = 1 : (w = 2, R = d) : w = 0); - return U; - } - - function B(d, c) { - var e = d; - 33 > e.charCodeAt(0) && (e = e.trim()); - V = e; - e = [V]; - - if (0 < A) { - var h = H(-1, c, e, e, D, z, 0, 0, 0, 0); - void 0 !== h && 'string' === typeof h && (c = h); - } - - var a = M(O, e, c, 0, 0); - 0 < A && (h = H(-2, a, e, e, D, z, a.length, 0, 0, 0), void 0 !== h && (a = h)); - V = ''; - E = 0; - z = D = 1; - return a; - } - - var ca = /^\0+/g, - N = /[\0\r\f]/g, - aa = /: */g, - ka = /zoo|gra/, - ma = /([,: ])(transform)/g, - ia = /,\r+?/g, - F = /([\t\r\n ])*\f?&/g, - fa = /@(k\w+)\s*(\S*)\s*/, - Q = /::(place)/g, - ha = /:(read-only)/g, - G = /[svh]\w+-[tblr]{2}/, - da = /\(\s*(.*)\s*\)/g, - oa = /([\s\S]*?);/g, - ba = /-self|flex-/g, - na = /[^]*?(:[rp][el]a[\w-]+)[^]*/, - la = /stretch|:\s*\w+\-(?:conte|avail)/, - ja = /([^-])(image-set\()/, - z = 1, - D = 1, - E = 0, - w = 1, - O = [], - S = [], - A = 0, - R = null, - Y = 0, - V = ''; - B.use = T; - B.set = U; - void 0 !== W && U(W); - return B; - } - - var unitlessKeys$1 = { - animationIterationCount: 1, - borderImageOutset: 1, - borderImageSlice: 1, - borderImageWidth: 1, - boxFlex: 1, - boxFlexGroup: 1, - boxOrdinalGroup: 1, - columnCount: 1, - columns: 1, - flex: 1, - flexGrow: 1, - flexPositive: 1, - flexShrink: 1, - flexNegative: 1, - flexOrder: 1, - gridRow: 1, - gridRowEnd: 1, - gridRowSpan: 1, - gridRowStart: 1, - gridColumn: 1, - gridColumnEnd: 1, - gridColumnSpan: 1, - gridColumnStart: 1, - msGridRow: 1, - msGridRowSpan: 1, - msGridColumn: 1, - msGridColumnSpan: 1, - fontWeight: 1, - lineHeight: 1, - opacity: 1, - order: 1, - orphans: 1, - tabSize: 1, - widows: 1, - zIndex: 1, - zoom: 1, - WebkitLineClamp: 1, - // SVG-related properties - fillOpacity: 1, - floodOpacity: 1, - stopOpacity: 1, - strokeDasharray: 1, - strokeDashoffset: 1, - strokeMiterlimit: 1, - strokeOpacity: 1, - strokeWidth: 1 - }; - - function memoize(fn) { - var cache = Object.create(null); - return function (arg) { - if (cache[arg] === undefined) cache[arg] = fn(arg); - return cache[arg]; - }; - } - - var reactPropsRegex = /^((children|dangerouslySetInnerHTML|key|ref|autoFocus|defaultValue|defaultChecked|innerHTML|suppressContentEditableWarning|suppressHydrationWarning|valueLink|abbr|accept|acceptCharset|accessKey|action|allow|allowUserMedia|allowPaymentRequest|allowFullScreen|allowTransparency|alt|async|autoComplete|autoPlay|capture|cellPadding|cellSpacing|challenge|charSet|checked|cite|classID|className|cols|colSpan|content|contentEditable|contextMenu|controls|controlsList|coords|crossOrigin|data|dateTime|decoding|default|defer|dir|disabled|disablePictureInPicture|download|draggable|encType|enterKeyHint|form|formAction|formEncType|formMethod|formNoValidate|formTarget|frameBorder|headers|height|hidden|high|href|hrefLang|htmlFor|httpEquiv|id|inputMode|integrity|is|keyParams|keyType|kind|label|lang|list|loading|loop|low|marginHeight|marginWidth|max|maxLength|media|mediaGroup|method|min|minLength|multiple|muted|name|nonce|noValidate|open|optimum|pattern|placeholder|playsInline|poster|preload|profile|radioGroup|readOnly|referrerPolicy|rel|required|reversed|role|rows|rowSpan|sandbox|scope|scoped|scrolling|seamless|selected|shape|size|sizes|slot|span|spellCheck|src|srcDoc|srcLang|srcSet|start|step|style|summary|tabIndex|target|title|translate|type|useMap|value|width|wmode|wrap|about|datatype|inlist|prefix|property|resource|typeof|vocab|autoCapitalize|autoCorrect|autoSave|color|incremental|fallback|inert|itemProp|itemScope|itemType|itemID|itemRef|on|option|results|security|unselectable|accentHeight|accumulate|additive|alignmentBaseline|allowReorder|alphabetic|amplitude|arabicForm|ascent|attributeName|attributeType|autoReverse|azimuth|baseFrequency|baselineShift|baseProfile|bbox|begin|bias|by|calcMode|capHeight|clip|clipPathUnits|clipPath|clipRule|colorInterpolation|colorInterpolationFilters|colorProfile|colorRendering|contentScriptType|contentStyleType|cursor|cx|cy|d|decelerate|descent|diffuseConstant|direction|display|divisor|dominantBaseline|dur|dx|dy|edgeMode|elevation|enableBackground|end|exponent|externalResourcesRequired|fill|fillOpacity|fillRule|filter|filterRes|filterUnits|floodColor|floodOpacity|focusable|fontFamily|fontSize|fontSizeAdjust|fontStretch|fontStyle|fontVariant|fontWeight|format|from|fr|fx|fy|g1|g2|glyphName|glyphOrientationHorizontal|glyphOrientationVertical|glyphRef|gradientTransform|gradientUnits|hanging|horizAdvX|horizOriginX|ideographic|imageRendering|in|in2|intercept|k|k1|k2|k3|k4|kernelMatrix|kernelUnitLength|kerning|keyPoints|keySplines|keyTimes|lengthAdjust|letterSpacing|lightingColor|limitingConeAngle|local|markerEnd|markerMid|markerStart|markerHeight|markerUnits|markerWidth|mask|maskContentUnits|maskUnits|mathematical|mode|numOctaves|offset|opacity|operator|order|orient|orientation|origin|overflow|overlinePosition|overlineThickness|panose1|paintOrder|pathLength|patternContentUnits|patternTransform|patternUnits|pointerEvents|points|pointsAtX|pointsAtY|pointsAtZ|preserveAlpha|preserveAspectRatio|primitiveUnits|r|radius|refX|refY|renderingIntent|repeatCount|repeatDur|requiredExtensions|requiredFeatures|restart|result|rotate|rx|ry|scale|seed|shapeRendering|slope|spacing|specularConstant|specularExponent|speed|spreadMethod|startOffset|stdDeviation|stemh|stemv|stitchTiles|stopColor|stopOpacity|strikethroughPosition|strikethroughThickness|string|stroke|strokeDasharray|strokeDashoffset|strokeLinecap|strokeLinejoin|strokeMiterlimit|strokeOpacity|strokeWidth|surfaceScale|systemLanguage|tableValues|targetX|targetY|textAnchor|textDecoration|textRendering|textLength|to|transform|u1|u2|underlinePosition|underlineThickness|unicode|unicodeBidi|unicodeRange|unitsPerEm|vAlphabetic|vHanging|vIdeographic|vMathematical|values|vectorEffect|version|vertAdvY|vertOriginX|vertOriginY|viewBox|viewTarget|visibility|widths|wordSpacing|writingMode|x|xHeight|x1|x2|xChannelSelector|xlinkActuate|xlinkArcrole|xlinkHref|xlinkRole|xlinkShow|xlinkTitle|xlinkType|xmlBase|xmlns|xmlnsXlink|xmlLang|xmlSpace|y|y1|y2|yChannelSelector|z|zoomAndPan|for|class|autofocus)|(([Dd][Aa][Tt][Aa]|[Aa][Rr][Ii][Aa]|x)-.*))$/; // https://esbench.com/bench/5bfee68a4cd7e6009ef61d23 - - var isPropValid = /* #__PURE__ */memoize(function (prop) { - return reactPropsRegex.test(prop) || prop.charCodeAt(0) === 111 - /* o */ - && prop.charCodeAt(1) === 110 - /* n */ - && prop.charCodeAt(2) < 91; - } - /* Z+1 */ - ); - - var reactIs$1 = {exports: {}}; - - var reactIs_development = {}; - - /** @license React v16.13.1 - * react-is.development.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - - - - { - (function() { - - // The Symbol used to tag the ReactElement-like types. If there is no native Symbol - // nor polyfill, then a plain number is used for performance. - var hasSymbol = typeof Symbol === 'function' && Symbol.for; - var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7; - var REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca; - var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb; - var REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc; - var REACT_PROFILER_TYPE = hasSymbol ? Symbol.for('react.profiler') : 0xead2; - var REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') : 0xeacd; - var REACT_CONTEXT_TYPE = hasSymbol ? Symbol.for('react.context') : 0xeace; // TODO: We don't use AsyncMode or ConcurrentMode anymore. They were temporary - // (unstable) APIs that have been removed. Can we remove the symbols? - - var REACT_ASYNC_MODE_TYPE = hasSymbol ? Symbol.for('react.async_mode') : 0xeacf; - var REACT_CONCURRENT_MODE_TYPE = hasSymbol ? Symbol.for('react.concurrent_mode') : 0xeacf; - var REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0; - var REACT_SUSPENSE_TYPE = hasSymbol ? Symbol.for('react.suspense') : 0xead1; - var REACT_SUSPENSE_LIST_TYPE = hasSymbol ? Symbol.for('react.suspense_list') : 0xead8; - var REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3; - var REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4; - var REACT_BLOCK_TYPE = hasSymbol ? Symbol.for('react.block') : 0xead9; - var REACT_FUNDAMENTAL_TYPE = hasSymbol ? Symbol.for('react.fundamental') : 0xead5; - var REACT_RESPONDER_TYPE = hasSymbol ? Symbol.for('react.responder') : 0xead6; - var REACT_SCOPE_TYPE = hasSymbol ? Symbol.for('react.scope') : 0xead7; - - function isValidElementType(type) { - return typeof type === 'string' || typeof type === 'function' || // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill. - type === REACT_FRAGMENT_TYPE || type === REACT_CONCURRENT_MODE_TYPE || type === REACT_PROFILER_TYPE || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || typeof type === 'object' && type !== null && (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || type.$$typeof === REACT_FUNDAMENTAL_TYPE || type.$$typeof === REACT_RESPONDER_TYPE || type.$$typeof === REACT_SCOPE_TYPE || type.$$typeof === REACT_BLOCK_TYPE); - } - - function typeOf(object) { - if (typeof object === 'object' && object !== null) { - var $$typeof = object.$$typeof; - - switch ($$typeof) { - case REACT_ELEMENT_TYPE: - var type = object.type; - - switch (type) { - case REACT_ASYNC_MODE_TYPE: - case REACT_CONCURRENT_MODE_TYPE: - case REACT_FRAGMENT_TYPE: - case REACT_PROFILER_TYPE: - case REACT_STRICT_MODE_TYPE: - case REACT_SUSPENSE_TYPE: - return type; - - default: - var $$typeofType = type && type.$$typeof; - - switch ($$typeofType) { - case REACT_CONTEXT_TYPE: - case REACT_FORWARD_REF_TYPE: - case REACT_LAZY_TYPE: - case REACT_MEMO_TYPE: - case REACT_PROVIDER_TYPE: - return $$typeofType; - - default: - return $$typeof; - } - - } - - case REACT_PORTAL_TYPE: - return $$typeof; - } - } - - return undefined; - } // AsyncMode is deprecated along with isAsyncMode - - var AsyncMode = REACT_ASYNC_MODE_TYPE; - var ConcurrentMode = REACT_CONCURRENT_MODE_TYPE; - var ContextConsumer = REACT_CONTEXT_TYPE; - var ContextProvider = REACT_PROVIDER_TYPE; - var Element = REACT_ELEMENT_TYPE; - var ForwardRef = REACT_FORWARD_REF_TYPE; - var Fragment = REACT_FRAGMENT_TYPE; - var Lazy = REACT_LAZY_TYPE; - var Memo = REACT_MEMO_TYPE; - var Portal = REACT_PORTAL_TYPE; - var Profiler = REACT_PROFILER_TYPE; - var StrictMode = REACT_STRICT_MODE_TYPE; - var Suspense = REACT_SUSPENSE_TYPE; - var hasWarnedAboutDeprecatedIsAsyncMode = false; // AsyncMode should be deprecated - - function isAsyncMode(object) { - { - if (!hasWarnedAboutDeprecatedIsAsyncMode) { - hasWarnedAboutDeprecatedIsAsyncMode = true; // Using console['warn'] to evade Babel and ESLint - - console['warn']('The ReactIs.isAsyncMode() alias has been deprecated, ' + 'and will be removed in React 17+. Update your code to use ' + 'ReactIs.isConcurrentMode() instead. It has the exact same API.'); - } - } - - return isConcurrentMode(object) || typeOf(object) === REACT_ASYNC_MODE_TYPE; - } - function isConcurrentMode(object) { - return typeOf(object) === REACT_CONCURRENT_MODE_TYPE; - } - function isContextConsumer(object) { - return typeOf(object) === REACT_CONTEXT_TYPE; - } - function isContextProvider(object) { - return typeOf(object) === REACT_PROVIDER_TYPE; - } - function isElement(object) { - return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE; - } - function isForwardRef(object) { - return typeOf(object) === REACT_FORWARD_REF_TYPE; - } - function isFragment(object) { - return typeOf(object) === REACT_FRAGMENT_TYPE; - } - function isLazy(object) { - return typeOf(object) === REACT_LAZY_TYPE; - } - function isMemo(object) { - return typeOf(object) === REACT_MEMO_TYPE; - } - function isPortal(object) { - return typeOf(object) === REACT_PORTAL_TYPE; - } - function isProfiler(object) { - return typeOf(object) === REACT_PROFILER_TYPE; - } - function isStrictMode(object) { - return typeOf(object) === REACT_STRICT_MODE_TYPE; - } - function isSuspense(object) { - return typeOf(object) === REACT_SUSPENSE_TYPE; - } - - reactIs_development.AsyncMode = AsyncMode; - reactIs_development.ConcurrentMode = ConcurrentMode; - reactIs_development.ContextConsumer = ContextConsumer; - reactIs_development.ContextProvider = ContextProvider; - reactIs_development.Element = Element; - reactIs_development.ForwardRef = ForwardRef; - reactIs_development.Fragment = Fragment; - reactIs_development.Lazy = Lazy; - reactIs_development.Memo = Memo; - reactIs_development.Portal = Portal; - reactIs_development.Profiler = Profiler; - reactIs_development.StrictMode = StrictMode; - reactIs_development.Suspense = Suspense; - reactIs_development.isAsyncMode = isAsyncMode; - reactIs_development.isConcurrentMode = isConcurrentMode; - reactIs_development.isContextConsumer = isContextConsumer; - reactIs_development.isContextProvider = isContextProvider; - reactIs_development.isElement = isElement; - reactIs_development.isForwardRef = isForwardRef; - reactIs_development.isFragment = isFragment; - reactIs_development.isLazy = isLazy; - reactIs_development.isMemo = isMemo; - reactIs_development.isPortal = isPortal; - reactIs_development.isProfiler = isProfiler; - reactIs_development.isStrictMode = isStrictMode; - reactIs_development.isSuspense = isSuspense; - reactIs_development.isValidElementType = isValidElementType; - reactIs_development.typeOf = typeOf; - })(); - } - - { - reactIs$1.exports = reactIs_development; - } - - var reactIs = reactIs$1.exports; - - /** - * Copyright 2015, Yahoo! Inc. - * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. - */ - var REACT_STATICS = { - childContextTypes: true, - contextType: true, - contextTypes: true, - defaultProps: true, - displayName: true, - getDefaultProps: true, - getDerivedStateFromError: true, - getDerivedStateFromProps: true, - mixins: true, - propTypes: true, - type: true - }; - var KNOWN_STATICS = { - name: true, - length: true, - prototype: true, - caller: true, - callee: true, - arguments: true, - arity: true - }; - var FORWARD_REF_STATICS = { - '$$typeof': true, - render: true, - defaultProps: true, - displayName: true, - propTypes: true - }; - var MEMO_STATICS = { - '$$typeof': true, - compare: true, - defaultProps: true, - displayName: true, - propTypes: true, - type: true - }; - var TYPE_STATICS = {}; - TYPE_STATICS[reactIs.ForwardRef] = FORWARD_REF_STATICS; - TYPE_STATICS[reactIs.Memo] = MEMO_STATICS; - - function getStatics(component) { - // React v16.11 and below - if (reactIs.isMemo(component)) { - return MEMO_STATICS; - } // React v16.12 and above - - - return TYPE_STATICS[component['$$typeof']] || REACT_STATICS; - } - - var defineProperty = Object.defineProperty; - var getOwnPropertyNames = Object.getOwnPropertyNames; - var getOwnPropertySymbols = Object.getOwnPropertySymbols; - var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; - var getPrototypeOf = Object.getPrototypeOf; - var objectPrototype = Object.prototype; - function hoistNonReactStatics(targetComponent, sourceComponent, blacklist) { - if (typeof sourceComponent !== 'string') { - // don't hoist over string (html) components - if (objectPrototype) { - var inheritedComponent = getPrototypeOf(sourceComponent); - - if (inheritedComponent && inheritedComponent !== objectPrototype) { - hoistNonReactStatics(targetComponent, inheritedComponent, blacklist); - } - } - - var keys = getOwnPropertyNames(sourceComponent); - - if (getOwnPropertySymbols) { - keys = keys.concat(getOwnPropertySymbols(sourceComponent)); - } - - var targetStatics = getStatics(targetComponent); - var sourceStatics = getStatics(sourceComponent); - - for (var i = 0; i < keys.length; ++i) { - var key = keys[i]; - - if (!KNOWN_STATICS[key] && !(blacklist && blacklist[key]) && !(sourceStatics && sourceStatics[key]) && !(targetStatics && targetStatics[key])) { - var descriptor = getOwnPropertyDescriptor(sourceComponent, key); - - try { - // Avoid failures from read-only properties - defineProperty(targetComponent, key, descriptor); - } catch (e) {} - } - } - } - - return targetComponent; - } - - var hoistNonReactStatics_cjs = hoistNonReactStatics; - - function v$1(){return (v$1=Object.assign||function(e){for(var t=1;t ({})}\n```\n\n',8:'ThemeProvider: Please make your "theme" prop an object.\n\n',9:"Missing document ``\n\n",10:"Cannot find a StyleSheet instance. Usually this happens if there are multiple copies of styled-components loaded at once. Check out this issue for how to troubleshoot and fix the common cases where this situation can happen: https://github.com/styled-components/styled-components/issues/1941#issuecomment-417862021\n\n",11:"_This error was replaced with a dev-time warning, it will be deleted for v4 final._ [createGlobalStyle] received children which will not be rendered. Please use the component without passing children elements.\n\n",12:"It seems you are interpolating a keyframe declaration (%s) into an untagged string. This was supported in styled-components v3, but is not longer supported in v4 as keyframes are now injected on-demand. Please wrap your string in the css\\`\\` helper which ensures the styles are injected correctly. See https://www.styled-components.com/docs/api#css\n\n",13:"%s is not a styled component and cannot be referred to via component selector. See https://www.styled-components.com/docs/advanced#referring-to-other-components for more details.\n\n",14:'ThemeProvider: "theme" prop is required.\n\n',15:"A stylis plugin has been supplied that is not named. We need a name for each plugin to be able to prevent styling collisions between different stylis configurations within the same app. Before you pass your plugin to ``, please make sure each plugin is uniquely-named, e.g.\n\n```js\nObject.defineProperty(importedPlugin, 'name', { value: 'some-unique-name' });\n```\n\n",16:"Reached the limit of how many styled components may be created at group %s.\nYou may only create up to 1,073,741,824 components. If you're creating components dynamically,\nas for instance in your render method then you may be running into this limitation.\n\n",17:"CSSStyleSheet could not be found on HTMLStyleElement.\nHas styled-components' style tag been unmounted or altered by another script?\n"};function D$1(){for(var e=arguments.length<=0?void 0:arguments[0],t=[],n=1,r=arguments.length;n1?t-1:0),r=1;r=this.groupSizes.length){for(var n=this.groupSizes,r=n.length,o=r;e>=o;)(o<<=1)<0&&j$1(16,""+e);this.groupSizes=new Uint32Array(o),this.groupSizes.set(n),this.length=o;for(var s=r;s=this.length||0===this.groupSizes[e])return t;for(var n=this.groupSizes[e],r=this.indexOfGroup(e),o=r+n,s=r;s1<<30)&&j$1(16,""+t),x$1.set(e,t),k.set(t,e),t},z=function(e){return k.get(e)},M=function(e,t){t>=V&&(V=t+1),x$1.set(e,t),k.set(t,e);},G="style["+A+'][data-styled-version="5.3.9"]',L$1=new RegExp("^"+A+'\\.g(\\d+)\\[id="([\\w\\d-]+)"\\].*?"([^"]*)'),F$1=function(e,t,n){for(var r,o=n.split(","),s=0,i=o.length;s=0;n--){var r=t[n];if(r&&1===r.nodeType&&r.hasAttribute(A))return r}}(n),s=void 0!==o?o.nextSibling:null;r.setAttribute(A,"active"),r.setAttribute("data-styled-version","5.3.9");var i=q();return i&&r.setAttribute("nonce",i),n.insertBefore(r,s),r},$=function(){function e(e){var t=this.element=H(e);t.appendChild(document.createTextNode("")),this.sheet=function(e){if(e.sheet)return e.sheet;for(var t=document.styleSheets,n=0,r=t.length;n=0){var n=document.createTextNode(t),r=this.nodes[e];return this.element.insertBefore(n,r||null),this.length++,!0}return !1},t.deleteRule=function(e){this.element.removeChild(this.nodes[e]),this.length--;},t.getRule=function(e){return e0&&(u+=e+",");})),r+=""+a+c+'{content:"'+u+'"}/*!sc*/\n';}}}return r}(this)},e}(),K=/(a)(d)/gi,Q=function(e){return String.fromCharCode(e+(e>25?39:97))};function ee(e){var t,n="";for(t=Math.abs(e);t>52;t=t/52|0)n=Q(t%52)+n;return (Q(t%52)+n).replace(K,"$1-$2")}var te=function(e,t){for(var n=t.length;n;)e=33*e^t.charCodeAt(--n);return e},ne=function(e){return te(5381,e)};function re(e){for(var t=0;t>>0);if(!t.hasNameForId(r,i)){var a=n(s,"."+i,void 0,r);t.insertRules(r,i,a);}o.push(i),this.staticRulesId=i;}else {for(var c=this.rules.length,u=te(this.baseHash,n.hash),l="",d=0;d>>0);if(!t.hasNameForId(r,m)){var y=n(l,"."+m,void 0,r);t.insertRules(r,m,y);}o.push(m);}}return o.join(" ")},e}(),ie=/^\s*\/\/.*$/gm,ae=[":","[",".","#"];function ce(e){var t,n,r,o,s=void 0===e?E$1:e,i=s.options,a=void 0===i?E$1:i,c=s.plugins,u=void 0===c?w$2:c,l=new stylis_min(a),d=[],h=function(e){function t(t){if(t)try{e(t+"}");}catch(e){}}return function(n,r,o,s,i,a,c,u,l,d){switch(n){case 1:if(0===l&&64===r.charCodeAt(0))return e(r+";"),"";break;case 2:if(0===u)return r+"/*|*/";break;case 3:switch(u){case 102:case 112:return e(o[0]+r),"";default:return r+(0===d?"/*|*/":"")}case-2:r.split("/*|*/}").forEach(t);}}}((function(e){d.push(e);})),f=function(e,r,s){return 0===r&&-1!==ae.indexOf(s[n.length])||s.match(o)?e:"."+t};function m(e,s,i,a){void 0===a&&(a="&");var c=e.replace(ie,""),u=s&&i?i+" "+s+" { "+c+" }":c;return t=a,n=s,r=new RegExp("\\"+n+"\\b","g"),o=new RegExp("(\\"+n+"\\b){2,}"),l(i||!s?"":s,u)}return l.use([].concat(u,[function(e,t,o){2===e&&o.length&&o[0].lastIndexOf(n)>0&&(o[0]=o[0].replace(r,f));},h,function(e){if(-2===e){var t=d;return d=[],t}}])),m.hash=u.length?u.reduce((function(e,t){return t.name||j$1(15),te(e,t.name)}),5381).toString():"",m}var ue=React__default["default"].createContext(),le=ue.Consumer,de=React__default["default"].createContext(),he=(de.Consumer,new Z),pe=ce();function fe(){return React$1.useContext(ue)||he}function me(){return React$1.useContext(de)||pe}function ye(e){var t=React$1.useState(e.stylisPlugins),n=t[0],s=t[1],c=fe(),u=React$1.useMemo((function(){var t=c;return e.sheet?t=e.sheet:e.target&&(t=t.reconstructWithOptions({target:e.target},!1)),e.disableCSSOMInjection&&(t=t.reconstructWithOptions({useCSSOMInjection:!1})),t}),[e.disableCSSOMInjection,e.sheet,e.target]),l=React$1.useMemo((function(){return ce({options:{prefix:!e.disableVendorPrefixes},plugins:n})}),[e.disableVendorPrefixes,n]);return React$1.useEffect((function(){shallowequal(n,e.stylisPlugins)||s(e.stylisPlugins);}),[e.stylisPlugins]),React__default["default"].createElement(ue.Provider,{value:u},React__default["default"].createElement(de.Provider,{value:l},React__default["default"].Children.only(e.children)))}var ve=function(){function e(e,t){var n=this;this.inject=function(e,t){void 0===t&&(t=pe);var r=n.name+t.hash;e.hasNameForId(n.id,r)||e.insertRules(n.id,r,t(n.rules,r,"@keyframes"));},this.toString=function(){return j$1(12,String(n.name))},this.name=e,this.id="sc-keyframes-"+e,this.rules=t;}return e.prototype.getName=function(e){return void 0===e&&(e=pe),this.name+e.hash},e}(),ge=/([A-Z])/,Se=/([A-Z])/g,we=/^ms-/,Ee=function(e){return "-"+e.toLowerCase()};function be(e){return ge.test(e)?e.replace(Se,Ee).replace(we,"-ms-"):e}var _e=function(e){return null==e||!1===e||""===e};function Ne(e,n,r,o){if(Array.isArray(e)){for(var s,i=[],a=0,c=e.length;a1?t-1:0),r=1;r1?t-1:0),i=1;i?@[\\\]^`{|}~-]+/g,je=/(^-|-$)/g;function Te(e){return e.replace(De,"-").replace(je,"")}var xe=function(e){return ee(ne(e)>>>0)};function ke(e){return "string"==typeof e&&(e.charAt(0)===e.charAt(0).toLowerCase())}var Ve=function(e){return "function"==typeof e||"object"==typeof e&&null!==e&&!Array.isArray(e)},Be=function(e){return "__proto__"!==e&&"constructor"!==e&&"prototype"!==e};function ze(e,t,n){var r=e[n];Ve(t)&&Ve(r)?Me(r,t):e[n]=t;}function Me(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r=0||(o[n]=e[n]);return o}(t,["componentId"]),s=r&&r+"-"+(ke(e)?e:Te(_(e)));return qe(e,v$1({},o,{attrs:S,componentId:s}),n)},Object.defineProperty(C,"defaultProps",{get:function(){return this._foldedDefaultProps},set:function(t){this._foldedDefaultProps=o?Me({},e.defaultProps,t):t;}}),(Oe(f,g),C.warnTooManyClasses=function(e,t){var n={},r=!1;return function(o){if(!r&&(n[o]=!0,Object.keys(n).length>=200)){var s=t?' with the id of "'+t+'"':"";console.warn("Over 200 classes were generated for component "+e+s+".\nConsider using the attrs method, together with a style object for frequently changed styles.\nExample:\n const Component = styled.div.attrs(props => ({\n style: {\n background: props.background,\n },\n }))`width: 100%;`\n\n "),r=!0,n={};}}}(f,g)),Object.defineProperty(C,"toString",{value:function(){return "."+C.styledComponentId}}),i&&hoistNonReactStatics_cjs(C,e,{attrs:!0,componentStyle:!0,displayName:!0,foldedComponentIds:!0,shouldForwardProp:!0,styledComponentId:!0,target:!0,withComponent:!0}),C}var He=function(e){return function e(t,r,o){if(void 0===o&&(o=E$1),!reactIs$2.exports.isValidElementType(r))return j$1(1,String(r));var s=function(){return t(r,o,Ce.apply(void 0,arguments))};return s.withConfig=function(n){return e(t,r,v$1({},o,{},n))},s.attrs=function(n){return e(t,r,v$1({},o,{attrs:Array.prototype.concat(o.attrs,n).filter(Boolean)}))},s}(qe,e)};["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","marquee","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","clipPath","defs","ellipse","foreignObject","g","image","line","linearGradient","marker","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","textPath","tspan"].forEach((function(e){He[e]=He(e);}));var $e=function(){function e(e,t){this.rules=e,this.componentId=t,this.isStatic=re(e),Z.registerId(this.componentId+1);}var t=e.prototype;return t.createStyles=function(e,t,n,r){var o=r(Ne(this.rules,t,n,r).join(""),""),s=this.componentId+e;n.insertRules(s,s,o);},t.removeStyles=function(e,t){t.clearRules(this.componentId+e);},t.renderStyles=function(e,t,n,r){e>2&&Z.registerId(this.componentId+e),this.removeStyles(e,n),this.createStyles(e,t,n,r);},e}();function We(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o meta tag to the stylesheet, or simply embedding it manually in your index.html section for a simpler app."),t.server&&h(l,e,t,o,n),React$1.useLayoutEffect((function(){if(!t.server)return h(l,e,t,o,n),function(){return u.removeStyles(l,t)}}),[l,e,t,o,n]),null}function h(e,t,n,r,o){if(u.isStatic)u.renderStyles(e,O,n,o);else {var s=v$1({},t,{theme:Re(t,r,l.defaultProps)});u.renderStyles(e,s,n,o);}}return Oe(a),React__default["default"].memo(l)}function Ue(e){"undefined"!=typeof navigator&&"ReactNative"===navigator.product&&console.warn("`keyframes` cannot be used on ReactNative, only on the web. To do animation in ReactNative please use Animated.");for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r"+t+""},this.getStyleTags=function(){return e.sealed?j$1(2):e._emitSheetCSS()},this.getStyleElement=function(){var t;if(e.sealed)return j$1(2);var n=((t={})[A]="",t["data-styled-version"]="5.3.9",t.dangerouslySetInnerHTML={__html:e.instance.toString()},t),o=q();return o&&(n.nonce=o),[React__default["default"].createElement("style",v$1({},n,{key:"sc-0-0"}))]},this.seal=function(){e.sealed=!0;},this.instance=new Z({isServer:!0}),this.sealed=!1;}var t=e.prototype;return t.collectStyles=function(e){return this.sealed?j$1(2):React__default["default"].createElement(ye,{sheet:this.instance},e)},t.interleaveWithNodeStream=function(e){return j$1(3)},e}(),Xe=function(e){var t=React__default["default"].forwardRef((function(t,n){var o=React$1.useContext(Ge),i=e.defaultProps,a=Re(t,o,i);return void 0===a&&console.warn('[withTheme] You are not using a ThemeProvider nor passing a theme prop or a theme in defaultProps in component class "'+_(e)+'"'),React__default["default"].createElement(e,v$1({},t,{theme:a,ref:n}))}));return hoistNonReactStatics_cjs(t,e),t.displayName="WithTheme("+_(e)+")",t},Ze=function(){return React$1.useContext(Ge)},Ke={StyleSheet:Z,masterSheet:he};"undefined"!=typeof navigator&&"ReactNative"===navigator.product&&console.warn("It looks like you've imported 'styled-components' on React Native.\nPerhaps you're looking to import 'styled-components/native'?\nRead more about this at https://www.styled-components.com/docs/basics#react-native"),"undefined"!=typeof window&&(window["__styled-components-init__"]=window["__styled-components-init__"]||0,1===window["__styled-components-init__"]&&console.warn("It looks like there are several instances of 'styled-components' initialized in this application. This may cause dynamic styles to not render properly, errors during the rehydration process, a missing theme prop, and makes your application bigger without good reason.\n\nSee https://s-c.sh/2BAXzed for more info."),window["__styled-components-init__"]+=1); - - var styledComponents_browser_esm = /*#__PURE__*/Object.freeze({ - __proto__: null, - 'default': He, - ServerStyleSheet: Je, - StyleSheetConsumer: le, - StyleSheetContext: ue, - StyleSheetManager: ye, - ThemeConsumer: Le, - ThemeContext: Ge, - ThemeProvider: Fe, - __PRIVATE__: Ke, - createGlobalStyle: We, - css: Ce, - isStyledComponent: N, - keyframes: Ue, - useTheme: Ze, - version: C, - withTheme: Xe - }); - - var require$$1 = /*@__PURE__*/getAugmentedNamespace(styledComponents_browser_esm); - - (function (exports) { - Object.defineProperty(exports,"__esModule",{value:!0});var e=React__default["default"],t=require$$1;function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}function o(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(n){if("default"!==n){var o=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,o.get?o:{enumerable:!0,get:function(){return e[n]}});}})),t.default=e,Object.freeze(t)}var a,l=o(e),r=n(e),i=n(t);function s(e,t){return e[t]}function d(e,t){return t.split(".").reduce(((e,t)=>{const n=t.match(/[^\]\\[.]+/g);if(n&&n.length>1)for(let t=0;ts(e,n)===a)),1):o.splice(o.findIndex((e=>e===t)),1),o}function u(e){return e.map(((e,t)=>{const n=Object.assign(Object.assign({},e),{sortable:e.sortable||!!e.sortFunction||void 0});return e.id||(n.id=t+1),n}))}function p(e,t){return Math.ceil(e/t)}function b(e,t){return Math.min(e,t)}!function(e){e.ASC="asc",e.DESC="desc";}(a||(a={}));const m=()=>null;function f(e,t=[],n=[]){let o={},a=[...n];return t.length&&t.forEach((t=>{if(!t.when||"function"!=typeof t.when)throw new Error('"when" must be defined in the conditional style object and must be function');t.when(e)&&(o=t.style||{},t.classNames&&(a=[...a,...t.classNames]),"function"==typeof t.style&&(o=t.style(e)||{}));})),{style:o,classNames:a.join(" ")}}function h(e,t=[],n="id"){const o=s(e,n);return o?t.some((e=>s(e,n)===o)):t.some((t=>t===e))}function w(e,t){return t?e.findIndex((e=>x(e.id,t))):-1}function x(e,t){return e==t}function C(e,t){const n=!e.toggleOnSelectedRowsChange;switch(t.type){case"SELECT_ALL_ROWS":{const{keyField:n,rows:o,rowCount:a,mergeSelections:l}=t,r=!e.allSelected,i=!e.toggleOnSelectedRowsChange;if(l){const t=r?[...e.selectedRows,...o.filter((t=>!h(t,e.selectedRows,n)))]:e.selectedRows.filter((e=>!h(e,o,n)));return Object.assign(Object.assign({},e),{allSelected:r,selectedCount:t.length,selectedRows:t,toggleOnSelectedRowsChange:i})}return Object.assign(Object.assign({},e),{allSelected:r,selectedCount:r?a:0,selectedRows:r?o:[],toggleOnSelectedRowsChange:i})}case"SELECT_SINGLE_ROW":{const{keyField:o,row:a,isSelected:l,rowCount:r,singleSelect:i}=t;return i?l?Object.assign(Object.assign({},e),{selectedCount:0,allSelected:!1,selectedRows:[],toggleOnSelectedRowsChange:n}):Object.assign(Object.assign({},e),{selectedCount:1,allSelected:!1,selectedRows:[a],toggleOnSelectedRowsChange:n}):l?Object.assign(Object.assign({},e),{selectedCount:e.selectedRows.length>0?e.selectedRows.length-1:0,allSelected:!1,selectedRows:g(e.selectedRows,a,o),toggleOnSelectedRowsChange:n}):Object.assign(Object.assign({},e),{selectedCount:e.selectedRows.length+1,allSelected:e.selectedRows.length+1===r,selectedRows:c(e.selectedRows,a),toggleOnSelectedRowsChange:n})}case"SELECT_MULTIPLE_ROWS":{const{keyField:o,selectedRows:a,totalRows:l,mergeSelections:r}=t;if(r){const t=[...e.selectedRows,...a.filter((t=>!h(t,e.selectedRows,o)))];return Object.assign(Object.assign({},e),{selectedCount:t.length,allSelected:!1,selectedRows:t,toggleOnSelectedRowsChange:n})}return Object.assign(Object.assign({},e),{selectedCount:a.length,allSelected:a.length===l,selectedRows:a,toggleOnSelectedRowsChange:n})}case"CLEAR_SELECTED_ROWS":{const{selectedRowsFlag:n}=t;return Object.assign(Object.assign({},e),{allSelected:!1,selectedCount:0,selectedRows:[],selectedRowsFlag:n})}case"SORT_CHANGE":{const{sortDirection:o,selectedColumn:a,clearSelectedOnSort:l}=t;return Object.assign(Object.assign(Object.assign({},e),{selectedColumn:a,sortDirection:o,currentPage:1}),l&&{allSelected:!1,selectedCount:0,selectedRows:[],toggleOnSelectedRowsChange:n})}case"CHANGE_PAGE":{const{page:o,paginationServer:a,visibleOnly:l,persistSelectedOnPageChange:r}=t,i=a&&r,s=a&&!r||l;return Object.assign(Object.assign(Object.assign(Object.assign({},e),{currentPage:o}),i&&{allSelected:!1}),s&&{allSelected:!1,selectedCount:0,selectedRows:[],toggleOnSelectedRowsChange:n})}case"CHANGE_ROWS_PER_PAGE":{const{rowsPerPage:n,page:o}=t;return Object.assign(Object.assign({},e),{currentPage:o,rowsPerPage:n})}}}const y=t.css` - pointer-events: none; - opacity: 0.4; -`,v=i.default.div` - position: relative; - box-sizing: border-box; - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - max-width: 100%; - ${({disabled:e})=>e&&y}; - ${({theme:e})=>e.table.style}; -`,R=t.css` - position: sticky; - position: -webkit-sticky; /* Safari */ - top: 0; - z-index: 1; -`,S=i.default.div` - display: flex; - width: 100%; - ${({fixedHeader:e})=>e&&R}; - ${({theme:e})=>e.head.style}; -`,E=i.default.div` - display: flex; - align-items: stretch; - width: 100%; - ${({theme:e})=>e.headRow.style}; - ${({dense:e,theme:t})=>e&&t.headRow.denseStyle}; -`,O=(e,...n)=>t.css` - @media screen and (max-width: ${599}px) { - ${t.css(e,...n)} - } - `,P=(e,...n)=>t.css` - @media screen and (max-width: ${959}px) { - ${t.css(e,...n)} - } - `,k=(e,...n)=>t.css` - @media screen and (max-width: ${1280}px) { - ${t.css(e,...n)} - } - `,D=e=>(n,...o)=>t.css` - @media screen and (max-width: ${e}px) { - ${t.css(n,...o)} - } - `,H=i.default.div` - position: relative; - display: flex; - align-items: center; - box-sizing: border-box; - line-height: normal; - ${({theme:e,headCell:t})=>e[t?"headCells":"cells"].style}; - ${({noPadding:e})=>e&&"padding: 0"}; -`,$=i.default(H)` - flex-grow: ${({button:e,grow:t})=>0===t||e?0:t||1}; - flex-shrink: 0; - flex-basis: 0; - max-width: ${({maxWidth:e})=>e||"100%"}; - min-width: ${({minWidth:e})=>e||"100px"}; - ${({width:e})=>e&&t.css` - min-width: ${e}; - max-width: ${e}; - `}; - ${({right:e})=>e&&"justify-content: flex-end"}; - ${({button:e,center:t})=>(t||e)&&"justify-content: center"}; - ${({compact:e,button:t})=>(e||t)&&"padding: 0"}; - - /* handle hiding cells */ - ${({hide:e})=>e&&"sm"===e&&O` - display: none; - `}; - ${({hide:e})=>e&&"md"===e&&P` - display: none; - `}; - ${({hide:e})=>e&&"lg"===e&&k` - display: none; - `}; - ${({hide:e})=>e&&Number.isInteger(e)&&D(e)` - display: none; - `}; -`,j=t.css` - div:first-child { - white-space: ${({wrapCell:e})=>e?"normal":"nowrap"}; - overflow: ${({allowOverflow:e})=>e?"visible":"hidden"}; - text-overflow: ellipsis; - } -`,F=i.default($).attrs((e=>({style:e.style})))` - ${({renderAsCell:e})=>!e&&j}; - ${({theme:e,isDragging:t})=>t&&e.cells.draggingStyle}; - ${({cellStyle:e})=>e}; -`;var T=l.memo((function({id:e,column:t,row:n,rowIndex:o,dataTag:a,isDragging:r,onDragStart:i,onDragOver:s,onDragEnd:c,onDragEnter:g,onDragLeave:u}){const{style:p,classNames:b}=f(n,t.conditionalCellStyles,["rdt_TableCell"]);return l.createElement(F,{id:e,"data-column-id":t.id,role:"cell",className:b,"data-tag":a,cellStyle:t.style,renderAsCell:!!t.cell,allowOverflow:t.allowOverflow,button:t.button,center:t.center,compact:t.compact,grow:t.grow,hide:t.hide,maxWidth:t.maxWidth,minWidth:t.minWidth,right:t.right,width:t.width,wrapCell:t.wrap,style:p,isDragging:r,onDragStart:i,onDragOver:s,onDragEnd:c,onDragEnter:g,onDragLeave:u},!t.cell&&l.createElement("div",{"data-tag":a},function(e,t,n,o){if(!t)return null;if("string"!=typeof t&&"function"!=typeof t)throw new Error("selector must be a . delimited string eg (my.property) or function (e.g. row => row.field");return n&&"function"==typeof n?n(e,o):t&&"function"==typeof t?t(e,o):d(e,t)}(n,t.selector,t.format,o)),t.cell&&t.cell(n,o,t,e))}));var I=l.memo((function({name:e,component:t="input",componentOptions:n={style:{}},indeterminate:o=!1,checked:a=!1,disabled:r=!1,onClick:i=m}){const s=t,d="input"!==s?n.style:(e=>Object.assign(Object.assign({fontSize:"18px"},!e&&{cursor:"pointer"}),{padding:0,marginTop:"1px",verticalAlign:"middle",position:"relative"}))(r),c=l.useMemo((()=>function(e,...t){let n;return Object.keys(e).map((t=>e[t])).forEach(((o,a)=>{const l=e;"function"==typeof o&&(n=Object.assign(Object.assign({},l),{[Object.keys(e)[a]]:o(...t)}));})),n||e}(n,o)),[n,o]);return l.createElement(s,Object.assign({type:"checkbox",ref:e=>{e&&(e.indeterminate=o);},style:d,onClick:r?m:i,name:e,"aria-label":e,checked:a,disabled:r},c,{onChange:m}))}));const M=i.default(H)` - flex: 0 0 48px; - min-width: 48px; - justify-content: center; - align-items: center; - user-select: none; - white-space: nowrap; -`;function A({name:e,keyField:t,row:n,rowCount:o,selected:a,selectableRowsComponent:r,selectableRowsComponentProps:i,selectableRowsSingle:s,selectableRowDisabled:d,onSelectedRow:c}){const g=!(!d||!d(n));return l.createElement(M,{onClick:e=>e.stopPropagation(),className:"rdt_TableCell",noPadding:!0},l.createElement(I,{name:e,component:r,componentOptions:i,checked:a,"aria-checked":a,onClick:()=>{c({type:"SELECT_SINGLE_ROW",row:n,isSelected:a,keyField:t,rowCount:o,singleSelect:s});},disabled:g}))}const L=i.default.button` - display: inline-flex; - align-items: center; - user-select: none; - white-space: nowrap; - border: none; - background-color: transparent; - ${({theme:e})=>e.expanderButton.style}; -`;function _({disabled:e=!1,expanded:t=!1,expandableIcon:n,id:o,row:a,onToggled:r}){const i=t?n.expanded:n.collapsed;return l.createElement(L,{"aria-disabled":e,onClick:()=>r&&r(a),"data-testid":`expander-button-${o}`,disabled:e,"aria-label":t?"Collapse Row":"Expand Row",role:"button",type:"button"},i)}const z=i.default(H)` - white-space: nowrap; - font-weight: 400; - min-width: 48px; - ${({theme:e})=>e.expanderCell.style}; -`;function N({row:e,expanded:t=!1,expandableIcon:n,id:o,onToggled:a,disabled:r=!1}){return l.createElement(z,{onClick:e=>e.stopPropagation(),noPadding:!0},l.createElement(_,{id:o,row:e,expanded:t,expandableIcon:n,disabled:r,onToggled:a}))}const W=i.default.div` - width: 100%; - box-sizing: border-box; - ${({theme:e})=>e.expanderRow.style}; - ${({extendedRowStyle:e})=>e}; -`;var B=l.memo((function({data:e,ExpanderComponent:t,expanderComponentProps:n,extendedRowStyle:o,extendedClassNames:a}){const r=["rdt_ExpanderRow",...a.split(" ").filter((e=>"rdt_TableRow"!==e))].join(" ");return l.createElement(W,{className:r,extendedRowStyle:o},l.createElement(t,Object.assign({data:e},n)))}));var G,V,U;exports.Direction=void 0,(G=exports.Direction||(exports.Direction={})).LTR="ltr",G.RTL="rtl",G.AUTO="auto",exports.Alignment=void 0,(V=exports.Alignment||(exports.Alignment={})).LEFT="left",V.RIGHT="right",V.CENTER="center",exports.Media=void 0,(U=exports.Media||(exports.Media={})).SM="sm",U.MD="md",U.LG="lg";const q=t.css` - &:hover { - ${({highlightOnHover:e,theme:t})=>e&&t.rows.highlightOnHoverStyle}; - } -`,Y=t.css` - &:hover { - cursor: pointer; - } -`,K=i.default.div.attrs((e=>({style:e.style})))` - display: flex; - align-items: stretch; - align-content: stretch; - width: 100%; - box-sizing: border-box; - ${({theme:e})=>e.rows.style}; - ${({dense:e,theme:t})=>e&&t.rows.denseStyle}; - ${({striped:e,theme:t})=>e&&t.rows.stripedStyle}; - ${({highlightOnHover:e})=>e&&q}; - ${({pointerOnHover:e})=>e&&Y}; - ${({selected:e,theme:t})=>e&&t.rows.selectedHighlightStyle}; -`;function J({columns:e=[],conditionalRowStyles:t=[],defaultExpanded:n=!1,defaultExpanderDisabled:o=!1,dense:a=!1,expandableIcon:r,expandableRows:i=!1,expandableRowsComponent:d,expandableRowsComponentProps:c,expandableRowsHideExpander:g,expandOnRowClicked:u=!1,expandOnRowDoubleClicked:p=!1,highlightOnHover:b=!1,id:h,expandableInheritConditionalStyles:w,keyField:C,onRowClicked:y=m,onRowDoubleClicked:v=m,onRowMouseEnter:R=m,onRowMouseLeave:S=m,onRowExpandToggled:E=m,onSelectedRow:O=m,pointerOnHover:P=!1,row:k,rowCount:D,rowIndex:H,selectableRowDisabled:$=null,selectableRows:j=!1,selectableRowsComponent:F,selectableRowsComponentProps:I,selectableRowsHighlight:M=!1,selectableRowsSingle:L=!1,selected:_,striped:z=!1,draggingColumnId:W,onDragStart:G,onDragOver:V,onDragEnd:U,onDragEnter:q,onDragLeave:Y}){const[J,Q]=l.useState(n);l.useEffect((()=>{Q(n);}),[n]);const X=l.useCallback((()=>{Q(!J),E(!J,k);}),[J,E,k]),Z=P||i&&(u||p),ee=l.useCallback((e=>{e.target&&"allowRowEvents"===e.target.getAttribute("data-tag")&&(y(k,e),!o&&i&&u&&X());}),[o,u,i,X,y,k]),te=l.useCallback((e=>{e.target&&"allowRowEvents"===e.target.getAttribute("data-tag")&&(v(k,e),!o&&i&&p&&X());}),[o,p,i,X,v,k]),ne=l.useCallback((e=>{R(k,e);}),[R,k]),oe=l.useCallback((e=>{S(k,e);}),[S,k]),ae=s(k,C),{style:le,classNames:re}=f(k,t,["rdt_TableRow"]),ie=M&&_,se=w?le:{},de=z&&H%2==0;return l.createElement(l.Fragment,null,l.createElement(K,{id:`row-${h}`,role:"row",striped:de,highlightOnHover:b,pointerOnHover:!o&&Z,dense:a,onClick:ee,onDoubleClick:te,onMouseEnter:ne,onMouseLeave:oe,className:re,selected:ie,style:le},j&&l.createElement(A,{name:`select-row-${ae}`,keyField:C,row:k,rowCount:D,selected:_,selectableRowsComponent:F,selectableRowsComponentProps:I,selectableRowDisabled:$,selectableRowsSingle:L,onSelectedRow:O}),i&&!g&&l.createElement(N,{id:ae,expandableIcon:r,expanded:J,row:k,onToggled:X,disabled:o}),e.map((e=>e.omit?null:l.createElement(T,{id:`cell-${e.id}-${ae}`,key:`cell-${e.id}-${ae}`,dataTag:e.ignoreRowClick||e.button?null:"allowRowEvents",column:e,row:k,rowIndex:H,isDragging:x(W,e.id),onDragStart:G,onDragOver:V,onDragEnd:U,onDragEnter:q,onDragLeave:Y})))),i&&J&&l.createElement(B,{key:`expander-${ae}`,data:k,extendedRowStyle:se,extendedClassNames:re,ExpanderComponent:d,expanderComponentProps:c}))}const Q=i.default.span` - padding: 2px; - color: inherit; - flex-grow: 0; - flex-shrink: 0; - ${({sortActive:e})=>e?"opacity: 1":"opacity: 0"}; - ${({sortDirection:e})=>"desc"===e&&"transform: rotate(180deg)"}; -`,X=({sortActive:e,sortDirection:t})=>r.default.createElement(Q,{sortActive:e,sortDirection:t},"▲"),Z=i.default($)` - ${({button:e})=>e&&"text-align: center"}; - ${({theme:e,isDragging:t})=>t&&e.headCells.draggingStyle}; -`,ee=t.css` - cursor: pointer; - span.__rdt_custom_sort_icon__ { - i, - svg { - transform: 'translate3d(0, 0, 0)'; - ${({sortActive:e})=>e?"opacity: 1":"opacity: 0"}; - color: inherit; - font-size: 18px; - height: 18px; - width: 18px; - backface-visibility: hidden; - transform-style: preserve-3d; - transition-duration: 95ms; - transition-property: transform; - } - - &.asc i, - &.asc svg { - transform: rotate(180deg); - } - } - - ${({sortActive:e})=>!e&&t.css` - &:hover, - &:focus { - opacity: 0.7; - - span, - span.__rdt_custom_sort_icon__ * { - opacity: 0.7; - } - } - `}; -`,te=i.default.div` - display: inline-flex; - align-items: center; - justify-content: inherit; - height: 100%; - width: 100%; - outline: none; - user-select: none; - overflow: hidden; - ${({disabled:e})=>!e&&ee}; -`,ne=i.default.div` - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -`;var oe=l.memo((function({column:e,disabled:t,draggingColumnId:n,selectedColumn:o={},sortDirection:r,sortIcon:i,sortServer:s,pagination:d,paginationServer:c,persistSelectedOnSort:g,selectableRowsVisibleOnly:u,onSort:p,onDragStart:b,onDragOver:m,onDragEnd:f,onDragEnter:h,onDragLeave:w}){l.useEffect((()=>{"string"==typeof e.selector&&console.error(`Warning: ${e.selector} is a string based column selector which has been deprecated as of v7 and will be removed in v8. Instead, use a selector function e.g. row => row[field]...`);}),[]);const[C,y]=l.useState(!1),v=l.useRef(null);if(l.useEffect((()=>{v.current&&y(v.current.scrollWidth>v.current.clientWidth);}),[C]),e.omit)return null;const R=()=>{if(!e.sortable&&!e.selector)return;let t=r;x(o.id,e.id)&&(t=r===a.ASC?a.DESC:a.ASC),p({type:"SORT_CHANGE",sortDirection:t,selectedColumn:e,clearSelectedOnSort:d&&c&&!g||s||u});},S=e=>l.createElement(X,{sortActive:e,sortDirection:r}),E=()=>l.createElement("span",{className:[r,"__rdt_custom_sort_icon__"].join(" ")},i),O=!(!e.sortable||!x(o.id,e.id)),P=!e.sortable||t,k=e.sortable&&!i&&!e.right,D=e.sortable&&!i&&e.right,H=e.sortable&&i&&!e.right,$=e.sortable&&i&&e.right;return l.createElement(Z,{"data-column-id":e.id,className:"rdt_TableCol",headCell:!0,allowOverflow:e.allowOverflow,button:e.button,compact:e.compact,grow:e.grow,hide:e.hide,maxWidth:e.maxWidth,minWidth:e.minWidth,right:e.right,center:e.center,width:e.width,draggable:e.reorder,isDragging:x(e.id,n),onDragStart:b,onDragOver:m,onDragEnd:f,onDragEnter:h,onDragLeave:w},e.name&&l.createElement(te,{"data-column-id":e.id,"data-sort-id":e.id,role:"columnheader",tabIndex:0,className:"rdt_TableCol_Sortable",onClick:P?void 0:R,onKeyPress:P?void 0:e=>{"Enter"===e.key&&R();},sortActive:!P&&O,disabled:P},!P&&$&&E(),!P&&D&&S(O),"string"==typeof e.name?l.createElement(ne,{title:C?e.name:void 0,ref:v,"data-column-id":e.id},e.name):e.name,!P&&H&&E(),!P&&k&&S(O)))}));const ae=i.default(H)` - flex: 0 0 48px; - justify-content: center; - align-items: center; - user-select: none; - white-space: nowrap; - font-size: unset; -`;function le({headCell:e=!0,rowData:t,keyField:n,allSelected:o,mergeSelections:a,selectedRows:r,selectableRowsComponent:i,selectableRowsComponentProps:s,selectableRowDisabled:d,onSelectAllRows:c}){const g=r.length>0&&!o,u=d?t.filter((e=>!d(e))):t,p=0===u.length,b=Math.min(t.length,u.length);return l.createElement(ae,{className:"rdt_TableCol",headCell:e,noPadding:!0},l.createElement(I,{name:"select-all-rows",component:i,componentOptions:s,onClick:()=>{c({type:"SELECT_ALL_ROWS",rows:u,rowCount:b,mergeSelections:a,keyField:n});},checked:o,indeterminate:g,disabled:p}))}function re(e=exports.Direction.AUTO){const t="object"==typeof window,[n,o]=l.useState(!1);return l.useEffect((()=>{if(t)if("auto"!==e)o("rtl"===e);else {const e=!(!window.document||!window.document.createElement),t=document.getElementsByTagName("BODY")[0],n=document.getElementsByTagName("HTML")[0],a="rtl"===t.dir||"rtl"===n.dir;o(e&&a);}}),[e,t]),n}const ie=i.default.div` - display: flex; - align-items: center; - flex: 1 0 auto; - height: 100%; - color: ${({theme:e})=>e.contextMenu.fontColor}; - font-size: ${({theme:e})=>e.contextMenu.fontSize}; - font-weight: 400; -`,se=i.default.div` - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; -`,de=i.default.div` - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - box-sizing: inherit; - z-index: 1; - align-items: center; - justify-content: space-between; - display: flex; - ${({rtl:e})=>e&&"direction: rtl"}; - ${({theme:e})=>e.contextMenu.style}; - ${({theme:e,visible:t})=>t&&e.contextMenu.activeStyle}; -`;function ce({contextMessage:e,contextActions:t,contextComponent:n,selectedCount:o,direction:a}){const r=re(a),i=o>0;return n?l.createElement(de,{visible:i},l.cloneElement(n,{selectedCount:o})):l.createElement(de,{visible:i,rtl:r},l.createElement(ie,null,((e,t,n)=>{if(0===t)return null;const o=1===t?e.singular:e.plural;return n?`${t} ${e.message||""} ${o}`:`${t} ${o} ${e.message||""}`})(e,o,r)),l.createElement(se,null,t))}const ge=i.default.div` - position: relative; - box-sizing: border-box; - overflow: hidden; - display: flex; - flex: 1 1 auto; - align-items: center; - justify-content: space-between; - width: 100%; - flex-wrap: wrap; - ${({theme:e})=>e.header.style} -`,ue=i.default.div` - flex: 1 0 auto; - color: ${({theme:e})=>e.header.fontColor}; - font-size: ${({theme:e})=>e.header.fontSize}; - font-weight: 400; -`,pe=i.default.div` - flex: 1 0 auto; - display: flex; - align-items: center; - justify-content: flex-end; - - > * { - margin-left: 5px; - } -`,be=({title:e,actions:t=null,contextMessage:n,contextActions:o,contextComponent:a,selectedCount:r,direction:i,showMenu:s=!0})=>l.createElement(ge,{className:"rdt_TableHeader",role:"heading","aria-level":1},l.createElement(ue,null,e),t&&l.createElement(pe,null,t),s&&l.createElement(ce,{contextMessage:n,contextActions:o,contextComponent:a,direction:i,selectedCount:r})) - /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */;function me(e,t){var n={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&t.indexOf(o)<0&&(n[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var a=0;for(o=Object.getOwnPropertySymbols(e);afe[e]}; - flex-wrap: ${({wrapContent:e})=>e?"wrap":"nowrap"}; - ${({theme:e})=>e.subHeader.style} -`,we=e=>{var{align:t="right",wrapContent:n=!0}=e,o=me(e,["align","wrapContent"]);return l.createElement(he,Object.assign({align:t,wrapContent:n},o))},xe=i.default.div` - display: flex; - flex-direction: column; -`,Ce=i.default.div` - position: relative; - width: 100%; - border-radius: inherit; - ${({responsive:e,fixedHeader:n})=>e&&t.css` - overflow-x: auto; - - // hidden prevents vertical scrolling in firefox when fixedHeader is disabled - overflow-y: ${n?"auto":"hidden"}; - min-height: 0; - `}; - - ${({fixedHeader:e=!1,fixedHeaderScrollHeight:n="100vh"})=>e&&t.css` - max-height: ${n}; - -webkit-overflow-scrolling: touch; - `}; - - ${({theme:e})=>e.responsiveWrapper.style}; -`,ye=i.default.div` - position: relative; - box-sizing: border-box; - width: 100%; - height: 100%; - ${e=>e.theme.progress.style}; -`,ve=i.default.div` - position: relative; - width: 100%; - ${({theme:e})=>e.tableWrapper.style}; -`,Re=i.default(H)` - white-space: nowrap; - ${({theme:e})=>e.expanderCell.style}; -`,Se=i.default.div` - box-sizing: border-box; - width: 100%; - height: 100%; - ${({theme:e})=>e.noData.style}; -`,Ee=()=>r.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24"},r.default.createElement("path",{d:"M7 10l5 5 5-5z"}),r.default.createElement("path",{d:"M0 0h24v24H0z",fill:"none"})),Oe=i.default.select` - cursor: pointer; - height: 24px; - max-width: 100%; - user-select: none; - padding-left: 8px; - padding-right: 24px; - box-sizing: content-box; - font-size: inherit; - color: inherit; - border: none; - background-color: transparent; - appearance: none; - direction: ltr; - flex-shrink: 0; - - &::-ms-expand { - display: none; - } - - &:disabled::-ms-expand { - background: #f60; - } - - option { - color: initial; - } -`,Pe=i.default.div` - position: relative; - flex-shrink: 0; - font-size: inherit; - color: inherit; - margin-top: 1px; - - svg { - top: 0; - right: 0; - color: inherit; - position: absolute; - fill: currentColor; - width: 24px; - height: 24px; - display: inline-block; - user-select: none; - pointer-events: none; - } -`,ke=e=>{var{defaultValue:t,onChange:n}=e,o=me(e,["defaultValue","onChange"]);return l.createElement(Pe,null,l.createElement(Oe,Object.assign({onChange:n,defaultValue:t},o)),l.createElement(Ee,null))},De={columns:[],data:[],title:"",keyField:"id",selectableRows:!1,selectableRowsHighlight:!1,selectableRowsNoSelectAll:!1,selectableRowSelected:null,selectableRowDisabled:null,selectableRowsComponent:"input",selectableRowsComponentProps:{},selectableRowsVisibleOnly:!1,selectableRowsSingle:!1,clearSelectedRows:!1,expandableRows:!1,expandableRowDisabled:null,expandableRowExpanded:null,expandOnRowClicked:!1,expandableRowsHideExpander:!1,expandOnRowDoubleClicked:!1,expandableInheritConditionalStyles:!1,expandableRowsComponent:function(){return r.default.createElement("div",null,"To add an expander pass in a component instance via ",r.default.createElement("strong",null,"expandableRowsComponent"),". You can then access props.data from this component.")},expandableIcon:{collapsed:r.default.createElement((()=>r.default.createElement("svg",{fill:"currentColor",height:"24",viewBox:"0 0 24 24",width:"24",xmlns:"http://www.w3.org/2000/svg"},r.default.createElement("path",{d:"M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"}),r.default.createElement("path",{d:"M0-.25h24v24H0z",fill:"none"}))),null),expanded:r.default.createElement((()=>r.default.createElement("svg",{fill:"currentColor",height:"24",viewBox:"0 0 24 24",width:"24",xmlns:"http://www.w3.org/2000/svg"},r.default.createElement("path",{d:"M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z"}),r.default.createElement("path",{d:"M0-.75h24v24H0z",fill:"none"}))),null)},expandableRowsComponentProps:{},progressPending:!1,progressComponent:r.default.createElement("div",{style:{fontSize:"24px",fontWeight:700,padding:"24px"}},"Loading..."),persistTableHead:!1,sortIcon:null,sortFunction:null,sortServer:!1,striped:!1,highlightOnHover:!1,pointerOnHover:!1,noContextMenu:!1,contextMessage:{singular:"item",plural:"items",message:"selected"},actions:null,contextActions:null,contextComponent:null,defaultSortFieldId:null,defaultSortAsc:!0,responsive:!0,noDataComponent:r.default.createElement("div",{style:{padding:"24px"}},"There are no records to display"),disabled:!1,noTableHead:!1,noHeader:!1,subHeader:!1,subHeaderAlign:exports.Alignment.RIGHT,subHeaderWrap:!0,subHeaderComponent:null,fixedHeader:!1,fixedHeaderScrollHeight:"100vh",pagination:!1,paginationServer:!1,paginationServerOptions:{persistSelectedOnSort:!1,persistSelectedOnPageChange:!1},paginationDefaultPage:1,paginationResetDefaultPage:!1,paginationTotalRows:0,paginationPerPage:10,paginationRowsPerPageOptions:[10,15,20,25,30],paginationComponent:null,paginationComponentOptions:{},paginationIconFirstPage:r.default.createElement((()=>r.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24","aria-hidden":"true",role:"presentation"},r.default.createElement("path",{d:"M18.41 16.59L13.82 12l4.59-4.59L17 6l-6 6 6 6zM6 6h2v12H6z"}),r.default.createElement("path",{fill:"none",d:"M24 24H0V0h24v24z"}))),null),paginationIconLastPage:r.default.createElement((()=>r.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24","aria-hidden":"true",role:"presentation"},r.default.createElement("path",{d:"M5.59 7.41L10.18 12l-4.59 4.59L7 18l6-6-6-6zM16 6h2v12h-2z"}),r.default.createElement("path",{fill:"none",d:"M0 0h24v24H0V0z"}))),null),paginationIconNext:r.default.createElement((()=>r.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24","aria-hidden":"true",role:"presentation"},r.default.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"}),r.default.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}))),null),paginationIconPrevious:r.default.createElement((()=>r.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24","aria-hidden":"true",role:"presentation"},r.default.createElement("path",{d:"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"}),r.default.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}))),null),dense:!1,conditionalRowStyles:[],theme:"default",customStyles:{},direction:exports.Direction.AUTO,onChangePage:m,onChangeRowsPerPage:m,onRowClicked:m,onRowDoubleClicked:m,onRowMouseEnter:m,onRowMouseLeave:m,onRowExpandToggled:m,onSelectedRowsChange:m,onSort:m,onColumnOrderChange:m},He={rowsPerPageText:"Rows per page:",rangeSeparatorText:"of",noRowsPerPage:!1,selectAllRowsItem:!1,selectAllRowsItemText:"All"},$e=i.default.nav` - display: flex; - flex: 1 1 auto; - justify-content: flex-end; - align-items: center; - box-sizing: border-box; - padding-right: 8px; - padding-left: 8px; - width: 100%; - ${({theme:e})=>e.pagination.style}; -`,je=i.default.button` - position: relative; - display: block; - user-select: none; - border: none; - ${({theme:e})=>e.pagination.pageButtonsStyle}; - ${({isRTL:e})=>e&&"transform: scale(-1, -1)"}; -`,Fe=i.default.div` - display: flex; - align-items: center; - border-radius: 4px; - white-space: nowrap; - ${O` - width: 100%; - justify-content: space-around; - `}; -`,Te=i.default.span` - flex-shrink: 1; - user-select: none; -`,Ie=i.default(Te)` - margin: 0 24px; -`,Me=i.default(Te)` - margin: 0 4px; -`;var Ae=l.memo((function({rowsPerPage:e,rowCount:t,currentPage:n,direction:o=De.direction,paginationRowsPerPageOptions:a=De.paginationRowsPerPageOptions,paginationIconLastPage:r=De.paginationIconLastPage,paginationIconFirstPage:i=De.paginationIconFirstPage,paginationIconNext:s=De.paginationIconNext,paginationIconPrevious:d=De.paginationIconPrevious,paginationComponentOptions:c=De.paginationComponentOptions,onChangeRowsPerPage:g=De.onChangeRowsPerPage,onChangePage:u=De.onChangePage}){const b=(()=>{const e="object"==typeof window;function t(){return {width:e?window.innerWidth:void 0,height:e?window.innerHeight:void 0}}const[n,o]=l.useState(t);return l.useEffect((()=>{if(!e)return ()=>null;function n(){o(t());}return window.addEventListener("resize",n),()=>window.removeEventListener("resize",n)}),[]),n})(),m=re(o),f=b.width&&b.width>599,h=p(t,e),w=n*e,x=w-e+1,C=1===n,y=n===h,v=Object.assign(Object.assign({},He),c),R=n===h?`${x}-${t} ${v.rangeSeparatorText} ${t}`:`${x}-${w} ${v.rangeSeparatorText} ${t}`,S=l.useCallback((()=>u(n-1)),[n,u]),E=l.useCallback((()=>u(n+1)),[n,u]),O=l.useCallback((()=>u(1)),[u]),P=l.useCallback((()=>u(p(t,e))),[u,t,e]),k=l.useCallback((e=>g(Number(e.target.value),n)),[n,g]),D=a.map((e=>l.createElement("option",{key:e,value:e},e)));v.selectAllRowsItem&&D.push(l.createElement("option",{key:-1,value:t},v.selectAllRowsItemText));const H=l.createElement(ke,{onChange:k,defaultValue:e,"aria-label":v.rowsPerPageText},D);return l.createElement($e,{className:"rdt_Pagination"},!v.noRowsPerPage&&f&&l.createElement(l.Fragment,null,l.createElement(Me,null,v.rowsPerPageText),H),f&&l.createElement(Ie,null,R),l.createElement(Fe,null,l.createElement(je,{id:"pagination-first-page",type:"button","aria-label":"First Page","aria-disabled":C,onClick:O,disabled:C,isRTL:m},i),l.createElement(je,{id:"pagination-previous-page",type:"button","aria-label":"Previous Page","aria-disabled":C,onClick:S,disabled:C,isRTL:m},d),!f&&H,l.createElement(je,{id:"pagination-next-page",type:"button","aria-label":"Next Page","aria-disabled":y,onClick:E,disabled:y,isRTL:m},s),l.createElement(je,{id:"pagination-last-page",type:"button","aria-label":"Last Page","aria-disabled":y,onClick:P,disabled:y,isRTL:m},r)))}));const Le=(e,t)=>{const n=l.useRef(!0);l.useEffect((()=>{n.current?n.current=!1:e();}),t);};var _e=function(e){return function(e){return !!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return "[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===ze}(e)}(e)};var ze="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function Ne(e,t){return !1!==t.clone&&t.isMergeableObject(e)?Ue((n=e,Array.isArray(n)?[]:{}),e,t):e;var n;}function We(e,t,n){return e.concat(t).map((function(e){return Ne(e,n)}))}function Be(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter((function(t){return e.propertyIsEnumerable(t)})):[]}(e))}function Ge(e,t){try{return t in e}catch(e){return !1}}function Ve(e,t,n){var o={};return n.isMergeableObject(e)&&Be(e).forEach((function(t){o[t]=Ne(e[t],n);})),Be(t).forEach((function(a){(function(e,t){return Ge(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))})(e,a)||(Ge(e,a)&&n.isMergeableObject(t[a])?o[a]=function(e,t){if(!t.customMerge)return Ue;var n=t.customMerge(e);return "function"==typeof n?n:Ue}(a,n)(e[a],t[a],n):o[a]=Ne(t[a],n));})),o}function Ue(e,t,n){(n=n||{}).arrayMerge=n.arrayMerge||We,n.isMergeableObject=n.isMergeableObject||_e,n.cloneUnlessOtherwiseSpecified=Ne;var o=Array.isArray(t);return o===Array.isArray(e)?o?n.arrayMerge(e,t,n):Ve(e,t,n):Ne(t,n)}Ue.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce((function(e,n){return Ue(e,n,t)}),{})};var qe=Ue;const Ye={text:{primary:"rgba(0, 0, 0, 0.87)",secondary:"rgba(0, 0, 0, 0.54)",disabled:"rgba(0, 0, 0, 0.38)"},background:{default:"#FFFFFF"},context:{background:"#e3f2fd",text:"rgba(0, 0, 0, 0.87)"},divider:{default:"rgba(0,0,0,.12)"},button:{default:"rgba(0,0,0,.54)",focus:"rgba(0,0,0,.12)",hover:"rgba(0,0,0,.12)",disabled:"rgba(0, 0, 0, .18)"},selected:{default:"#e3f2fd",text:"rgba(0, 0, 0, 0.87)"},highlightOnHover:{default:"#EEEEEE",text:"rgba(0, 0, 0, 0.87)"},striped:{default:"#FAFAFA",text:"rgba(0, 0, 0, 0.87)"}},Ke={default:Ye,light:Ye,dark:{text:{primary:"#FFFFFF",secondary:"rgba(255, 255, 255, 0.7)",disabled:"rgba(0,0,0,.12)"},background:{default:"#424242"},context:{background:"#E91E63",text:"#FFFFFF"},divider:{default:"rgba(81, 81, 81, 1)"},button:{default:"#FFFFFF",focus:"rgba(255, 255, 255, .54)",hover:"rgba(255, 255, 255, .12)",disabled:"rgba(255, 255, 255, .18)"},selected:{default:"rgba(0, 0, 0, .7)",text:"#FFFFFF"},highlightOnHover:{default:"rgba(0, 0, 0, .7)",text:"#FFFFFF"},striped:{default:"rgba(0, 0, 0, .87)",text:"#FFFFFF"}}};function Je(e,t,n,o){const[r,i]=l.useState((()=>u(e))),[s,d]=l.useState(""),c=l.useRef("");Le((()=>{i(u(e));}),[e]);const g=l.useCallback((e=>{var t,n,o;const{attributes:a}=e.target,l=null===(t=a.getNamedItem("data-column-id"))||void 0===t?void 0:t.value;l&&(c.current=(null===(o=null===(n=r[w(r,l)])||void 0===n?void 0:n.id)||void 0===o?void 0:o.toString())||"",d(c.current));}),[r]),p=l.useCallback((e=>{var n;const{attributes:o}=e.target,a=null===(n=o.getNamedItem("data-column-id"))||void 0===n?void 0:n.value;if(a&&c.current&&a!==c.current){const e=w(r,c.current),n=w(r,a),o=[...r];o[e]=r[n],o[n]=r[e],i(o),t(o);}}),[t,r]),b=l.useCallback((e=>{e.preventDefault();}),[]),m=l.useCallback((e=>{e.preventDefault();}),[]),f=l.useCallback((e=>{e.preventDefault(),c.current="",d("");}),[]),h=function(e=!1){return e?a.ASC:a.DESC}(o),x=l.useMemo((()=>r[w(r,null==n?void 0:n.toString())]||{}),[n,r]);return {tableColumns:r,draggingColumnId:s,handleDragStart:g,handleDragEnter:p,handleDragOver:b,handleDragLeave:m,handleDragEnd:f,defaultSortDirection:h,defaultSortColumn:x}}var Qe=l.memo((function(e){const{data:n=De.data,columns:o=De.columns,title:r=De.title,actions:i=De.actions,keyField:c=De.keyField,striped:g=De.striped,highlightOnHover:u=De.highlightOnHover,pointerOnHover:m=De.pointerOnHover,dense:f=De.dense,selectableRows:w=De.selectableRows,selectableRowsSingle:x=De.selectableRowsSingle,selectableRowsHighlight:y=De.selectableRowsHighlight,selectableRowsNoSelectAll:R=De.selectableRowsNoSelectAll,selectableRowsVisibleOnly:O=De.selectableRowsVisibleOnly,selectableRowSelected:P=De.selectableRowSelected,selectableRowDisabled:k=De.selectableRowDisabled,selectableRowsComponent:D=De.selectableRowsComponent,selectableRowsComponentProps:$=De.selectableRowsComponentProps,onRowExpandToggled:j=De.onRowExpandToggled,onSelectedRowsChange:F=De.onSelectedRowsChange,expandableIcon:T=De.expandableIcon,onChangeRowsPerPage:I=De.onChangeRowsPerPage,onChangePage:M=De.onChangePage,paginationServer:A=De.paginationServer,paginationServerOptions:L=De.paginationServerOptions,paginationTotalRows:_=De.paginationTotalRows,paginationDefaultPage:z=De.paginationDefaultPage,paginationResetDefaultPage:N=De.paginationResetDefaultPage,paginationPerPage:W=De.paginationPerPage,paginationRowsPerPageOptions:B=De.paginationRowsPerPageOptions,paginationIconLastPage:G=De.paginationIconLastPage,paginationIconFirstPage:V=De.paginationIconFirstPage,paginationIconNext:U=De.paginationIconNext,paginationIconPrevious:q=De.paginationIconPrevious,paginationComponent:Y=De.paginationComponent,paginationComponentOptions:K=De.paginationComponentOptions,responsive:Q=De.responsive,progressPending:X=De.progressPending,progressComponent:Z=De.progressComponent,persistTableHead:ee=De.persistTableHead,noDataComponent:te=De.noDataComponent,disabled:ne=De.disabled,noTableHead:ae=De.noTableHead,noHeader:re=De.noHeader,fixedHeader:ie=De.fixedHeader,fixedHeaderScrollHeight:se=De.fixedHeaderScrollHeight,pagination:de=De.pagination,subHeader:ce=De.subHeader,subHeaderAlign:ge=De.subHeaderAlign,subHeaderWrap:ue=De.subHeaderWrap,subHeaderComponent:pe=De.subHeaderComponent,noContextMenu:me=De.noContextMenu,contextMessage:fe=De.contextMessage,contextActions:he=De.contextActions,contextComponent:Ee=De.contextComponent,expandableRows:Oe=De.expandableRows,onRowClicked:Pe=De.onRowClicked,onRowDoubleClicked:ke=De.onRowDoubleClicked,onRowMouseEnter:He=De.onRowMouseEnter,onRowMouseLeave:$e=De.onRowMouseLeave,sortIcon:je=De.sortIcon,onSort:Fe=De.onSort,sortFunction:Te=De.sortFunction,sortServer:Ie=De.sortServer,expandableRowsComponent:Me=De.expandableRowsComponent,expandableRowsComponentProps:_e=De.expandableRowsComponentProps,expandableRowDisabled:ze=De.expandableRowDisabled,expandableRowsHideExpander:Ne=De.expandableRowsHideExpander,expandOnRowClicked:We=De.expandOnRowClicked,expandOnRowDoubleClicked:Be=De.expandOnRowDoubleClicked,expandableRowExpanded:Ge=De.expandableRowExpanded,expandableInheritConditionalStyles:Ve=De.expandableInheritConditionalStyles,defaultSortFieldId:Ue=De.defaultSortFieldId,defaultSortAsc:Ye=De.defaultSortAsc,clearSelectedRows:Qe=De.clearSelectedRows,conditionalRowStyles:Xe=De.conditionalRowStyles,theme:Ze=De.theme,customStyles:et=De.customStyles,direction:tt=De.direction,onColumnOrderChange:nt=De.onColumnOrderChange,className:ot}=e,{tableColumns:at,draggingColumnId:lt,handleDragStart:rt,handleDragEnter:it,handleDragOver:st,handleDragLeave:dt,handleDragEnd:ct,defaultSortDirection:gt,defaultSortColumn:ut}=Je(o,nt,Ue,Ye),[{rowsPerPage:pt,currentPage:bt,selectedRows:mt,allSelected:ft,selectedCount:ht,selectedColumn:wt,sortDirection:xt,toggleOnSelectedRowsChange:Ct},yt]=l.useReducer(C,{allSelected:!1,selectedCount:0,selectedRows:[],selectedColumn:ut,toggleOnSelectedRowsChange:!1,sortDirection:gt,currentPage:z,rowsPerPage:W,selectedRowsFlag:!1,contextMessage:De.contextMessage}),{persistSelectedOnSort:vt=!1,persistSelectedOnPageChange:Rt=!1}=L,St=!(!A||!Rt&&!vt),Et=de&&!X&&n.length>0,Ot=Y||Ae,Pt=l.useMemo((()=>((e={},t="default",n="default")=>{const o=Ke[t]?t:n;return qe({table:{style:{color:(a=Ke[o]).text.primary,backgroundColor:a.background.default}},tableWrapper:{style:{display:"table"}},responsiveWrapper:{style:{}},header:{style:{fontSize:"22px",color:a.text.primary,backgroundColor:a.background.default,minHeight:"56px",paddingLeft:"16px",paddingRight:"8px"}},subHeader:{style:{backgroundColor:a.background.default,minHeight:"52px"}},head:{style:{color:a.text.primary,fontSize:"12px",fontWeight:500}},headRow:{style:{backgroundColor:a.background.default,minHeight:"52px",borderBottomWidth:"1px",borderBottomColor:a.divider.default,borderBottomStyle:"solid"},denseStyle:{minHeight:"32px"}},headCells:{style:{paddingLeft:"16px",paddingRight:"16px"},draggingStyle:{cursor:"move"}},contextMenu:{style:{backgroundColor:a.context.background,fontSize:"18px",fontWeight:400,color:a.context.text,paddingLeft:"16px",paddingRight:"8px",transform:"translate3d(0, -100%, 0)",transitionDuration:"125ms",transitionTimingFunction:"cubic-bezier(0, 0, 0.2, 1)",willChange:"transform"},activeStyle:{transform:"translate3d(0, 0, 0)"}},cells:{style:{paddingLeft:"16px",paddingRight:"16px",wordBreak:"break-word"},draggingStyle:{}},rows:{style:{fontSize:"13px",fontWeight:400,color:a.text.primary,backgroundColor:a.background.default,minHeight:"48px","&:not(:last-of-type)":{borderBottomStyle:"solid",borderBottomWidth:"1px",borderBottomColor:a.divider.default}},denseStyle:{minHeight:"32px"},selectedHighlightStyle:{"&:nth-of-type(n)":{color:a.selected.text,backgroundColor:a.selected.default,borderBottomColor:a.background.default}},highlightOnHoverStyle:{color:a.highlightOnHover.text,backgroundColor:a.highlightOnHover.default,transitionDuration:"0.15s",transitionProperty:"background-color",borderBottomColor:a.background.default,outlineStyle:"solid",outlineWidth:"1px",outlineColor:a.background.default},stripedStyle:{color:a.striped.text,backgroundColor:a.striped.default}},expanderRow:{style:{color:a.text.primary,backgroundColor:a.background.default}},expanderCell:{style:{flex:"0 0 48px"}},expanderButton:{style:{color:a.button.default,fill:a.button.default,backgroundColor:"transparent",borderRadius:"2px",transition:"0.25s",height:"100%",width:"100%","&:hover:enabled":{cursor:"pointer"},"&:disabled":{color:a.button.disabled},"&:hover:not(:disabled)":{cursor:"pointer",backgroundColor:a.button.hover},"&:focus":{outline:"none",backgroundColor:a.button.focus},svg:{margin:"auto"}}},pagination:{style:{color:a.text.secondary,fontSize:"13px",minHeight:"56px",backgroundColor:a.background.default,borderTopStyle:"solid",borderTopWidth:"1px",borderTopColor:a.divider.default},pageButtonsStyle:{borderRadius:"50%",height:"40px",width:"40px",padding:"8px",margin:"px",cursor:"pointer",transition:"0.4s",color:a.button.default,fill:a.button.default,backgroundColor:"transparent","&:disabled":{cursor:"unset",color:a.button.disabled,fill:a.button.disabled},"&:hover:not(:disabled)":{backgroundColor:a.button.hover},"&:focus":{outline:"none",backgroundColor:a.button.focus}}},noData:{style:{display:"flex",alignItems:"center",justifyContent:"center",color:a.text.primary,backgroundColor:a.background.default}},progress:{style:{display:"flex",alignItems:"center",justifyContent:"center",color:a.text.primary,backgroundColor:a.background.default}}},e);var a;})(et,Ze)),[et,Ze]),kt=l.useMemo((()=>Object.assign({},"auto"!==tt&&{dir:tt})),[tt]),Dt=l.useMemo((()=>{if(Ie)return n;if((null==wt?void 0:wt.sortFunction)&&"function"==typeof wt.sortFunction){const e=wt.sortFunction,t=xt===a.ASC?e:(t,n)=>-1*e(t,n);return [...n].sort(t)}return function(e,t,n,o){return t?o&&"function"==typeof o?o(e.slice(0),t,n):e.slice(0).sort(((e,o)=>{let a,l;if("string"==typeof t?(a=d(e,t),l=d(o,t)):(a=t(e),l=t(o)),"asc"===n){if(al)return 1}if("desc"===n){if(a>l)return -1;if(a{if(de&&!A){const e=bt*pt,t=e-pt;return Dt.slice(t,e)}return Dt}),[bt,de,A,pt,Dt]),$t=l.useCallback((e=>{yt(e);}),[]),jt=l.useCallback((e=>{yt(e);}),[]),Ft=l.useCallback((e=>{yt(e);}),[]),Tt=l.useCallback(((e,t)=>Pe(e,t)),[Pe]),It=l.useCallback(((e,t)=>ke(e,t)),[ke]),Mt=l.useCallback(((e,t)=>He(e,t)),[He]),At=l.useCallback(((e,t)=>$e(e,t)),[$e]),Lt=l.useCallback((e=>yt({type:"CHANGE_PAGE",page:e,paginationServer:A,visibleOnly:O,persistSelectedOnPageChange:Rt})),[A,Rt,O]),_t=l.useCallback((e=>{const t=p(_||Ht.length,e),n=b(bt,t);A||Lt(n),yt({type:"CHANGE_ROWS_PER_PAGE",page:n,rowsPerPage:e});}),[bt,Lt,A,_,Ht.length]);if(de&&!A&&Dt.length>0&&0===Ht.length){const e=p(Dt.length,pt),t=b(bt,e);Lt(t);}Le((()=>{F({allSelected:ft,selectedCount:ht,selectedRows:mt.slice(0)});}),[Ct]),Le((()=>{Fe(wt,xt,Dt.slice(0));}),[wt,xt]),Le((()=>{M(bt,_||Dt.length);}),[bt]),Le((()=>{I(pt,bt);}),[pt]),Le((()=>{Lt(z);}),[z,N]),Le((()=>{if(de&&A&&_>0){const e=p(_,pt),t=b(bt,e);bt!==t&&Lt(t);}}),[_]),l.useEffect((()=>{yt({type:"CLEAR_SELECTED_ROWS",selectedRowsFlag:Qe});}),[x,Qe]),l.useEffect((()=>{if(!P)return;const e=Dt.filter((e=>P(e))),t=x?e.slice(0,1):e;yt({type:"SELECT_MULTIPLE_ROWS",keyField:c,selectedRows:t,totalRows:Dt.length,mergeSelections:St});}),[n,P]);const zt=O?Ht:Dt,Nt=Rt||x||R;return l.createElement(t.ThemeProvider,{theme:Pt},!re&&(!!r||!!i)&&l.createElement(be,{title:r,actions:i,showMenu:!me,selectedCount:ht,direction:tt,contextActions:he,contextComponent:Ee,contextMessage:fe}),ce&&l.createElement(we,{align:ge,wrapContent:ue},pe),l.createElement(Ce,Object.assign({responsive:Q,fixedHeader:ie,fixedHeaderScrollHeight:se,className:ot},kt),l.createElement(ve,null,X&&!ee&&l.createElement(ye,null,Z),l.createElement(v,{disabled:ne,className:"rdt_Table",role:"table"},!ae&&(!!ee||Dt.length>0&&!X)&&l.createElement(S,{className:"rdt_TableHead",role:"rowgroup",fixedHeader:ie},l.createElement(E,{className:"rdt_TableHeadRow",role:"row",dense:f},w&&(Nt?l.createElement(H,{style:{flex:"0 0 48px"}}):l.createElement(le,{allSelected:ft,selectedRows:mt,selectableRowsComponent:D,selectableRowsComponentProps:$,selectableRowDisabled:k,rowData:zt,keyField:c,mergeSelections:St,onSelectAllRows:jt})),Oe&&!Ne&&l.createElement(Re,null),at.map((e=>l.createElement(oe,{key:e.id,column:e,selectedColumn:wt,disabled:X||0===Dt.length,pagination:de,paginationServer:A,persistSelectedOnSort:vt,selectableRowsVisibleOnly:O,sortDirection:xt,sortIcon:je,sortServer:Ie,onSort:$t,onDragStart:rt,onDragOver:st,onDragEnd:ct,onDragEnter:it,onDragLeave:dt,draggingColumnId:lt}))))),!Dt.length&&!X&&l.createElement(Se,null,te),X&&ee&&l.createElement(ye,null,Z),!X&&Dt.length>0&&l.createElement(xe,{className:"rdt_TableBody",role:"rowgroup"},Ht.map(((e,t)=>{const n=s(e,c),o=function(e=""){return "number"!=typeof e&&(!e||0===e.length)}(n)?t:n,a=h(e,mt,c),r=!!(Oe&&Ge&&Ge(e)),i=!!(Oe&&ze&&ze(e));return l.createElement(J,{id:o,key:o,keyField:c,"data-row-id":o,columns:at,row:e,rowCount:Dt.length,rowIndex:t,selectableRows:w,expandableRows:Oe,expandableIcon:T,highlightOnHover:u,pointerOnHover:m,dense:f,expandOnRowClicked:We,expandOnRowDoubleClicked:Be,expandableRowsComponent:Me,expandableRowsComponentProps:_e,expandableRowsHideExpander:Ne,defaultExpanderDisabled:i,defaultExpanded:r,expandableInheritConditionalStyles:Ve,conditionalRowStyles:Xe,selected:a,selectableRowsHighlight:y,selectableRowsComponent:D,selectableRowsComponentProps:$,selectableRowDisabled:k,selectableRowsSingle:x,striped:g,onRowExpandToggled:j,onRowClicked:Tt,onRowDoubleClicked:It,onRowMouseEnter:Mt,onRowMouseLeave:At,onSelectedRow:Ft,draggingColumnId:lt,onDragStart:rt,onDragOver:st,onDragEnd:ct,onDragEnter:it,onDragLeave:dt})})))))),Et&&l.createElement("div",null,l.createElement(Ot,{onChangePage:Lt,onChangeRowsPerPage:_t,rowCount:_||Dt.length,currentPage:bt,rowsPerPage:pt,direction:tt,paginationRowsPerPageOptions:B,paginationIconLastPage:G,paginationIconFirstPage:V,paginationIconNext:U,paginationIconPrevious:q,paginationComponentOptions:K})))}));exports.STOP_PROP_TAG="allowRowEvents",exports.createTheme=function(e="default",t,n="default"){return Ke[e]||(Ke[e]=qe(Ke[n],t||{})),Ke[e]=qe(Ke[e],t||{}),Ke[e]},exports.default=Qe,exports.defaultThemes=Ke; - - }(index_cjs)); - - var DataTable = /*@__PURE__*/getDefaultExportFromCjs(index_cjs); - - function _typeof(obj) { - "@babel/helpers - typeof"; - - return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }, _typeof(obj); - } - - function _toPrimitive(input, hint) { - if (_typeof(input) !== "object" || input === null) return input; - var prim = input[Symbol.toPrimitive]; - if (prim !== undefined) { - var res = prim.call(input, hint || "default"); - if (_typeof(res) !== "object") return res; - throw new TypeError("@@toPrimitive must return a primitive value."); - } - return (hint === "string" ? String : Number)(input); - } - - function _toPropertyKey(arg) { - var key = _toPrimitive(arg, "string"); - return _typeof(key) === "symbol" ? key : String(key); - } - - function _defineProperty(obj, key, value) { - key = _toPropertyKey(key); - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } else { - obj[key] = value; - } - return obj; - } - - function ownKeys(object, enumerableOnly) { - var keys = Object.keys(object); - if (Object.getOwnPropertySymbols) { - var symbols = Object.getOwnPropertySymbols(object); - enumerableOnly && (symbols = symbols.filter(function (sym) { - return Object.getOwnPropertyDescriptor(object, sym).enumerable; - })), keys.push.apply(keys, symbols); - } - return keys; - } - function _objectSpread2(target) { - for (var i = 1; i < arguments.length; i++) { - var source = null != arguments[i] ? arguments[i] : {}; - i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { - _defineProperty(target, key, source[key]); - }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { - Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); - }); - } - return target; - } - - function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; - } - - function _iterableToArrayLimit(arr, i) { - var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; - if (null != _i) { - var _s, - _e, - _x, - _r, - _arr = [], - _n = !0, - _d = !1; - try { - if (_x = (_i = _i.call(arr)).next, 0 === i) { - if (Object(_i) !== _i) return; - _n = !1; - } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); - } catch (err) { - _d = !0, _e = err; - } finally { - try { - if (!_n && null != _i["return"] && (_r = _i["return"](), Object(_r) !== _r)) return; - } finally { - if (_d) throw _e; - } - } - return _arr; - } - } - - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - return arr2; - } - - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); - } - - function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); - } - - function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); - } - - function _objectWithoutPropertiesLoose(source, excluded) { - if (source == null) return {}; - var target = {}; - var sourceKeys = Object.keys(source); - var key, i; - for (i = 0; i < sourceKeys.length; i++) { - key = sourceKeys[i]; - if (excluded.indexOf(key) >= 0) continue; - target[key] = source[key]; - } - return target; - } - - function _objectWithoutProperties(source, excluded) { - if (source == null) return {}; - var target = _objectWithoutPropertiesLoose(source, excluded); - var key, i; - if (Object.getOwnPropertySymbols) { - var sourceSymbolKeys = Object.getOwnPropertySymbols(source); - for (i = 0; i < sourceSymbolKeys.length; i++) { - key = sourceSymbolKeys[i]; - if (excluded.indexOf(key) >= 0) continue; - if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; - target[key] = source[key]; - } - } - return target; - } - - var _excluded$5 = ["defaultInputValue", "defaultMenuIsOpen", "defaultValue", "inputValue", "menuIsOpen", "onChange", "onInputChange", "onMenuClose", "onMenuOpen", "value"]; - function useStateManager(_ref) { - var _ref$defaultInputValu = _ref.defaultInputValue, - defaultInputValue = _ref$defaultInputValu === void 0 ? '' : _ref$defaultInputValu, - _ref$defaultMenuIsOpe = _ref.defaultMenuIsOpen, - defaultMenuIsOpen = _ref$defaultMenuIsOpe === void 0 ? false : _ref$defaultMenuIsOpe, - _ref$defaultValue = _ref.defaultValue, - defaultValue = _ref$defaultValue === void 0 ? null : _ref$defaultValue, - propsInputValue = _ref.inputValue, - propsMenuIsOpen = _ref.menuIsOpen, - propsOnChange = _ref.onChange, - propsOnInputChange = _ref.onInputChange, - propsOnMenuClose = _ref.onMenuClose, - propsOnMenuOpen = _ref.onMenuOpen, - propsValue = _ref.value, - restSelectProps = _objectWithoutProperties(_ref, _excluded$5); - var _useState = React$1.useState(propsInputValue !== undefined ? propsInputValue : defaultInputValue), - _useState2 = _slicedToArray(_useState, 2), - stateInputValue = _useState2[0], - setStateInputValue = _useState2[1]; - var _useState3 = React$1.useState(propsMenuIsOpen !== undefined ? propsMenuIsOpen : defaultMenuIsOpen), - _useState4 = _slicedToArray(_useState3, 2), - stateMenuIsOpen = _useState4[0], - setStateMenuIsOpen = _useState4[1]; - var _useState5 = React$1.useState(propsValue !== undefined ? propsValue : defaultValue), - _useState6 = _slicedToArray(_useState5, 2), - stateValue = _useState6[0], - setStateValue = _useState6[1]; - var onChange = React$1.useCallback(function (value, actionMeta) { - if (typeof propsOnChange === 'function') { - propsOnChange(value, actionMeta); - } - setStateValue(value); - }, [propsOnChange]); - var onInputChange = React$1.useCallback(function (value, actionMeta) { - var newValue; - if (typeof propsOnInputChange === 'function') { - newValue = propsOnInputChange(value, actionMeta); - } - setStateInputValue(newValue !== undefined ? newValue : value); - }, [propsOnInputChange]); - var onMenuOpen = React$1.useCallback(function () { - if (typeof propsOnMenuOpen === 'function') { - propsOnMenuOpen(); - } - setStateMenuIsOpen(true); - }, [propsOnMenuOpen]); - var onMenuClose = React$1.useCallback(function () { - if (typeof propsOnMenuClose === 'function') { - propsOnMenuClose(); - } - setStateMenuIsOpen(false); - }, [propsOnMenuClose]); - var inputValue = propsInputValue !== undefined ? propsInputValue : stateInputValue; - var menuIsOpen = propsMenuIsOpen !== undefined ? propsMenuIsOpen : stateMenuIsOpen; - var value = propsValue !== undefined ? propsValue : stateValue; - return _objectSpread2(_objectSpread2({}, restSelectProps), {}, { - inputValue: inputValue, - menuIsOpen: menuIsOpen, - onChange: onChange, - onInputChange: onInputChange, - onMenuClose: onMenuClose, - onMenuOpen: onMenuOpen, - value: value - }); - } - - function _extends$1() { - _extends$1 = Object.assign ? Object.assign.bind() : function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - return target; - }; - return _extends$1.apply(this, arguments); - } - - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); - } - } - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - Object.defineProperty(Constructor, "prototype", { - writable: false - }); - return Constructor; - } - - function _setPrototypeOf(o, p) { - _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { - o.__proto__ = p; - return o; - }; - return _setPrototypeOf(o, p); - } - - function _inherits(subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function"); - } - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - writable: true, - configurable: true - } - }); - Object.defineProperty(subClass, "prototype", { - writable: false - }); - if (superClass) _setPrototypeOf(subClass, superClass); - } - - function _getPrototypeOf(o) { - _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { - return o.__proto__ || Object.getPrototypeOf(o); - }; - return _getPrototypeOf(o); - } - - function _isNativeReflectConstruct() { - if (typeof Reflect === "undefined" || !Reflect.construct) return false; - if (Reflect.construct.sham) return false; - if (typeof Proxy === "function") return true; - try { - Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); - return true; - } catch (e) { - return false; - } - } - - function _assertThisInitialized(self) { - if (self === void 0) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - return self; - } - - function _possibleConstructorReturn(self, call) { - if (call && (_typeof(call) === "object" || typeof call === "function")) { - return call; - } else if (call !== void 0) { - throw new TypeError("Derived constructors may only return object or undefined"); - } - return _assertThisInitialized(self); - } - - function _createSuper(Derived) { - var hasNativeReflectConstruct = _isNativeReflectConstruct(); - return function _createSuperInternal() { - var Super = _getPrototypeOf(Derived), - result; - if (hasNativeReflectConstruct) { - var NewTarget = _getPrototypeOf(this).constructor; - result = Reflect.construct(Super, arguments, NewTarget); - } else { - result = Super.apply(this, arguments); - } - return _possibleConstructorReturn(this, result); - }; - } - - function _arrayWithoutHoles(arr) { - if (Array.isArray(arr)) return _arrayLikeToArray(arr); - } - - function _iterableToArray(iter) { - if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); - } - - function _nonIterableSpread() { - throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); - } - - function _toConsumableArray(arr) { - return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); - } - - /* - - Based off glamor's StyleSheet, thanks Sunil ❤️ - - high performance StyleSheet for css-in-js systems - - - uses multiple style tags behind the scenes for millions of rules - - uses `insertRule` for appending in production for *much* faster performance - - // usage - - import { StyleSheet } from '@emotion/sheet' - - let styleSheet = new StyleSheet({ key: '', container: document.head }) - - styleSheet.insert('#box { border: 1px solid red; }') - - appends a css rule into the stylesheet - - styleSheet.flush() - - empties the stylesheet of all its contents - - */ - // $FlowFixMe - function sheetForTag(tag) { - if (tag.sheet) { - // $FlowFixMe - return tag.sheet; - } // this weirdness brought to you by firefox - - /* istanbul ignore next */ - - - for (var i = 0; i < document.styleSheets.length; i++) { - if (document.styleSheets[i].ownerNode === tag) { - // $FlowFixMe - return document.styleSheets[i]; - } - } - } - - function createStyleElement(options) { - var tag = document.createElement('style'); - tag.setAttribute('data-emotion', options.key); - - if (options.nonce !== undefined) { - tag.setAttribute('nonce', options.nonce); - } - - tag.appendChild(document.createTextNode('')); - tag.setAttribute('data-s', ''); - return tag; - } - - var StyleSheet = /*#__PURE__*/function () { - // Using Node instead of HTMLElement since container may be a ShadowRoot - function StyleSheet(options) { - var _this = this; - - this._insertTag = function (tag) { - var before; - - if (_this.tags.length === 0) { - if (_this.insertionPoint) { - before = _this.insertionPoint.nextSibling; - } else if (_this.prepend) { - before = _this.container.firstChild; - } else { - before = _this.before; - } - } else { - before = _this.tags[_this.tags.length - 1].nextSibling; - } - - _this.container.insertBefore(tag, before); - - _this.tags.push(tag); - }; - - this.isSpeedy = options.speedy === undefined ? "development" === 'production' : options.speedy; - this.tags = []; - this.ctr = 0; - this.nonce = options.nonce; // key is the value of the data-emotion attribute, it's used to identify different sheets - - this.key = options.key; - this.container = options.container; - this.prepend = options.prepend; - this.insertionPoint = options.insertionPoint; - this.before = null; - } - - var _proto = StyleSheet.prototype; - - _proto.hydrate = function hydrate(nodes) { - nodes.forEach(this._insertTag); - }; - - _proto.insert = function insert(rule) { - // the max length is how many rules we have per style tag, it's 65000 in speedy mode - // it's 1 in dev because we insert source maps that map a single rule to a location - // and you can only have one source map per style tag - if (this.ctr % (this.isSpeedy ? 65000 : 1) === 0) { - this._insertTag(createStyleElement(this)); - } - - var tag = this.tags[this.tags.length - 1]; - - { - var isImportRule = rule.charCodeAt(0) === 64 && rule.charCodeAt(1) === 105; - - if (isImportRule && this._alreadyInsertedOrderInsensitiveRule) { - // this would only cause problem in speedy mode - // but we don't want enabling speedy to affect the observable behavior - // so we report this error at all times - console.error("You're attempting to insert the following rule:\n" + rule + '\n\n`@import` rules must be before all other types of rules in a stylesheet but other rules have already been inserted. Please ensure that `@import` rules are before all other rules.'); - } - this._alreadyInsertedOrderInsensitiveRule = this._alreadyInsertedOrderInsensitiveRule || !isImportRule; - } - - if (this.isSpeedy) { - var sheet = sheetForTag(tag); - - try { - // this is the ultrafast version, works across browsers - // the big drawback is that the css won't be editable in devtools - sheet.insertRule(rule, sheet.cssRules.length); - } catch (e) { - if (!/:(-moz-placeholder|-moz-focus-inner|-moz-focusring|-ms-input-placeholder|-moz-read-write|-moz-read-only|-ms-clear|-ms-expand|-ms-reveal){/.test(rule)) { - console.error("There was a problem inserting the following rule: \"" + rule + "\"", e); - } - } - } else { - tag.appendChild(document.createTextNode(rule)); - } - - this.ctr++; - }; - - _proto.flush = function flush() { - // $FlowFixMe - this.tags.forEach(function (tag) { - return tag.parentNode && tag.parentNode.removeChild(tag); - }); - this.tags = []; - this.ctr = 0; - - { - this._alreadyInsertedOrderInsensitiveRule = false; - } - }; - - return StyleSheet; - }(); - - var MS = '-ms-'; - var MOZ = '-moz-'; - var WEBKIT = '-webkit-'; - - var COMMENT = 'comm'; - var RULESET = 'rule'; - var DECLARATION = 'decl'; - var IMPORT = '@import'; - var KEYFRAMES = '@keyframes'; - - /** - * @param {number} - * @return {number} - */ - var abs = Math.abs; - - /** - * @param {number} - * @return {string} - */ - var from = String.fromCharCode; - - /** - * @param {object} - * @return {object} - */ - var assign = Object.assign; - - /** - * @param {string} value - * @param {number} length - * @return {number} - */ - function hash (value, length) { - return charat(value, 0) ^ 45 ? (((((((length << 2) ^ charat(value, 0)) << 2) ^ charat(value, 1)) << 2) ^ charat(value, 2)) << 2) ^ charat(value, 3) : 0 - } - - /** - * @param {string} value - * @return {string} - */ - function trim (value) { - return value.trim() - } - - /** - * @param {string} value - * @param {RegExp} pattern - * @return {string?} - */ - function match (value, pattern) { - return (value = pattern.exec(value)) ? value[0] : value - } - - /** - * @param {string} value - * @param {(string|RegExp)} pattern - * @param {string} replacement - * @return {string} - */ - function replace (value, pattern, replacement) { - return value.replace(pattern, replacement) - } - - /** - * @param {string} value - * @param {string} search - * @return {number} - */ - function indexof (value, search) { - return value.indexOf(search) - } - - /** - * @param {string} value - * @param {number} index - * @return {number} - */ - function charat (value, index) { - return value.charCodeAt(index) | 0 - } - - /** - * @param {string} value - * @param {number} begin - * @param {number} end - * @return {string} - */ - function substr (value, begin, end) { - return value.slice(begin, end) - } - - /** - * @param {string} value - * @return {number} - */ - function strlen (value) { - return value.length - } - - /** - * @param {any[]} value - * @return {number} - */ - function sizeof (value) { - return value.length - } - - /** - * @param {any} value - * @param {any[]} array - * @return {any} - */ - function append (value, array) { - return array.push(value), value - } - - /** - * @param {string[]} array - * @param {function} callback - * @return {string} - */ - function combine (array, callback) { - return array.map(callback).join('') - } - - var line = 1; - var column = 1; - var length = 0; - var position = 0; - var character = 0; - var characters = ''; - - /** - * @param {string} value - * @param {object | null} root - * @param {object | null} parent - * @param {string} type - * @param {string[] | string} props - * @param {object[] | string} children - * @param {number} length - */ - function node (value, root, parent, type, props, children, length) { - return {value: value, root: root, parent: parent, type: type, props: props, children: children, line: line, column: column, length: length, return: ''} - } - - /** - * @param {object} root - * @param {object} props - * @return {object} - */ - function copy (root, props) { - return assign(node('', null, null, '', null, null, 0), root, {length: -root.length}, props) - } - - /** - * @return {number} - */ - function char () { - return character - } - - /** - * @return {number} - */ - function prev () { - character = position > 0 ? charat(characters, --position) : 0; - - if (column--, character === 10) - column = 1, line--; - - return character - } - - /** - * @return {number} - */ - function next () { - character = position < length ? charat(characters, position++) : 0; - - if (column++, character === 10) - column = 1, line++; - - return character - } - - /** - * @return {number} - */ - function peek () { - return charat(characters, position) - } - - /** - * @return {number} - */ - function caret () { - return position - } - - /** - * @param {number} begin - * @param {number} end - * @return {string} - */ - function slice (begin, end) { - return substr(characters, begin, end) - } - - /** - * @param {number} type - * @return {number} - */ - function token (type) { - switch (type) { - // \0 \t \n \r \s whitespace token - case 0: case 9: case 10: case 13: case 32: - return 5 - // ! + , / > @ ~ isolate token - case 33: case 43: case 44: case 47: case 62: case 64: case 126: - // ; { } breakpoint token - case 59: case 123: case 125: - return 4 - // : accompanied token - case 58: - return 3 - // " ' ( [ opening delimit token - case 34: case 39: case 40: case 91: - return 2 - // ) ] closing delimit token - case 41: case 93: - return 1 - } - - return 0 - } - - /** - * @param {string} value - * @return {any[]} - */ - function alloc (value) { - return line = column = 1, length = strlen(characters = value), position = 0, [] - } - - /** - * @param {any} value - * @return {any} - */ - function dealloc (value) { - return characters = '', value - } - - /** - * @param {number} type - * @return {string} - */ - function delimit (type) { - return trim(slice(position - 1, delimiter(type === 91 ? type + 2 : type === 40 ? type + 1 : type))) - } - - /** - * @param {number} type - * @return {string} - */ - function whitespace (type) { - while (character = peek()) - if (character < 33) - next(); - else - break - - return token(type) > 2 || token(character) > 3 ? '' : ' ' - } - - /** - * @param {number} index - * @param {number} count - * @return {string} - */ - function escaping (index, count) { - while (--count && next()) - // not 0-9 A-F a-f - if (character < 48 || character > 102 || (character > 57 && character < 65) || (character > 70 && character < 97)) - break - - return slice(index, caret() + (count < 6 && peek() == 32 && next() == 32)) - } - - /** - * @param {number} type - * @return {number} - */ - function delimiter (type) { - while (next()) - switch (character) { - // ] ) " ' - case type: - return position - // " ' - case 34: case 39: - if (type !== 34 && type !== 39) - delimiter(character); - break - // ( - case 40: - if (type === 41) - delimiter(type); - break - // \ - case 92: - next(); - break - } - - return position - } - - /** - * @param {number} type - * @param {number} index - * @return {number} - */ - function commenter (type, index) { - while (next()) - // // - if (type + character === 47 + 10) - break - // /* - else if (type + character === 42 + 42 && peek() === 47) - break - - return '/*' + slice(index, position - 1) + '*' + from(type === 47 ? type : next()) - } - - /** - * @param {number} index - * @return {string} - */ - function identifier (index) { - while (!token(peek())) - next(); - - return slice(index, position) - } - - /** - * @param {string} value - * @return {object[]} - */ - function compile (value) { - return dealloc(parse('', null, null, null, [''], value = alloc(value), 0, [0], value)) - } - - /** - * @param {string} value - * @param {object} root - * @param {object?} parent - * @param {string[]} rule - * @param {string[]} rules - * @param {string[]} rulesets - * @param {number[]} pseudo - * @param {number[]} points - * @param {string[]} declarations - * @return {object} - */ - function parse (value, root, parent, rule, rules, rulesets, pseudo, points, declarations) { - var index = 0; - var offset = 0; - var length = pseudo; - var atrule = 0; - var property = 0; - var previous = 0; - var variable = 1; - var scanning = 1; - var ampersand = 1; - var character = 0; - var type = ''; - var props = rules; - var children = rulesets; - var reference = rule; - var characters = type; - - while (scanning) - switch (previous = character, character = next()) { - // ( - case 40: - if (previous != 108 && charat(characters, length - 1) == 58) { - if (indexof(characters += replace(delimit(character), '&', '&\f'), '&\f') != -1) - ampersand = -1; - break - } - // " ' [ - case 34: case 39: case 91: - characters += delimit(character); - break - // \t \n \r \s - case 9: case 10: case 13: case 32: - characters += whitespace(previous); - break - // \ - case 92: - characters += escaping(caret() - 1, 7); - continue - // / - case 47: - switch (peek()) { - case 42: case 47: - append(comment(commenter(next(), caret()), root, parent), declarations); - break - default: - characters += '/'; - } - break - // { - case 123 * variable: - points[index++] = strlen(characters) * ampersand; - // } ; \0 - case 125 * variable: case 59: case 0: - switch (character) { - // \0 } - case 0: case 125: scanning = 0; - // ; - case 59 + offset: - if (property > 0 && (strlen(characters) - length)) - append(property > 32 ? declaration(characters + ';', rule, parent, length - 1) : declaration(replace(characters, ' ', '') + ';', rule, parent, length - 2), declarations); - break - // @ ; - case 59: characters += ';'; - // { rule/at-rule - default: - append(reference = ruleset(characters, root, parent, index, offset, rules, points, type, props = [], children = [], length), rulesets); - - if (character === 123) - if (offset === 0) - parse(characters, root, reference, reference, props, rulesets, length, points, children); - else - switch (atrule === 99 && charat(characters, 3) === 110 ? 100 : atrule) { - // d m s - case 100: case 109: case 115: - parse(value, reference, reference, rule && append(ruleset(value, reference, reference, 0, 0, rules, points, type, rules, props = [], length), children), rules, children, length, points, rule ? props : children); - break - default: - parse(characters, reference, reference, reference, [''], children, 0, points, children); - } - } - - index = offset = property = 0, variable = ampersand = 1, type = characters = '', length = pseudo; - break - // : - case 58: - length = 1 + strlen(characters), property = previous; - default: - if (variable < 1) - if (character == 123) - --variable; - else if (character == 125 && variable++ == 0 && prev() == 125) - continue - - switch (characters += from(character), character * variable) { - // & - case 38: - ampersand = offset > 0 ? 1 : (characters += '\f', -1); - break - // , - case 44: - points[index++] = (strlen(characters) - 1) * ampersand, ampersand = 1; - break - // @ - case 64: - // - - if (peek() === 45) - characters += delimit(next()); - - atrule = peek(), offset = length = strlen(type = characters += identifier(caret())), character++; - break - // - - case 45: - if (previous === 45 && strlen(characters) == 2) - variable = 0; - } - } - - return rulesets - } - - /** - * @param {string} value - * @param {object} root - * @param {object?} parent - * @param {number} index - * @param {number} offset - * @param {string[]} rules - * @param {number[]} points - * @param {string} type - * @param {string[]} props - * @param {string[]} children - * @param {number} length - * @return {object} - */ - function ruleset (value, root, parent, index, offset, rules, points, type, props, children, length) { - var post = offset - 1; - var rule = offset === 0 ? rules : ['']; - var size = sizeof(rule); - - for (var i = 0, j = 0, k = 0; i < index; ++i) - for (var x = 0, y = substr(value, post + 1, post = abs(j = points[i])), z = value; x < size; ++x) - if (z = trim(j > 0 ? rule[x] + ' ' + y : replace(y, /&\f/g, rule[x]))) - props[k++] = z; - - return node(value, root, parent, offset === 0 ? RULESET : type, props, children, length) - } - - /** - * @param {number} value - * @param {object} root - * @param {object?} parent - * @return {object} - */ - function comment (value, root, parent) { - return node(value, root, parent, COMMENT, from(char()), substr(value, 2, -2), 0) - } - - /** - * @param {string} value - * @param {object} root - * @param {object?} parent - * @param {number} length - * @return {object} - */ - function declaration (value, root, parent, length) { - return node(value, root, parent, DECLARATION, substr(value, 0, length), substr(value, length + 1, -1), length) - } - - /** - * @param {object[]} children - * @param {function} callback - * @return {string} - */ - function serialize (children, callback) { - var output = ''; - var length = sizeof(children); - - for (var i = 0; i < length; i++) - output += callback(children[i], i, children, callback) || ''; - - return output - } - - /** - * @param {object} element - * @param {number} index - * @param {object[]} children - * @param {function} callback - * @return {string} - */ - function stringify (element, index, children, callback) { - switch (element.type) { - case IMPORT: case DECLARATION: return element.return = element.return || element.value - case COMMENT: return '' - case KEYFRAMES: return element.return = element.value + '{' + serialize(element.children, callback) + '}' - case RULESET: element.value = element.props.join(','); - } - - return strlen(children = serialize(element.children, callback)) ? element.return = element.value + '{' + children + '}' : '' - } - - /** - * @param {function[]} collection - * @return {function} - */ - function middleware (collection) { - var length = sizeof(collection); - - return function (element, index, children, callback) { - var output = ''; - - for (var i = 0; i < length; i++) - output += collection[i](element, index, children, callback) || ''; - - return output - } - } - - var identifierWithPointTracking = function identifierWithPointTracking(begin, points, index) { - var previous = 0; - var character = 0; - - while (true) { - previous = character; - character = peek(); // &\f - - if (previous === 38 && character === 12) { - points[index] = 1; - } - - if (token(character)) { - break; - } - - next(); - } - - return slice(begin, position); - }; - - var toRules = function toRules(parsed, points) { - // pretend we've started with a comma - var index = -1; - var character = 44; - - do { - switch (token(character)) { - case 0: - // &\f - if (character === 38 && peek() === 12) { - // this is not 100% correct, we don't account for literal sequences here - like for example quoted strings - // stylis inserts \f after & to know when & where it should replace this sequence with the context selector - // and when it should just concatenate the outer and inner selectors - // it's very unlikely for this sequence to actually appear in a different context, so we just leverage this fact here - points[index] = 1; - } - - parsed[index] += identifierWithPointTracking(position - 1, points, index); - break; - - case 2: - parsed[index] += delimit(character); - break; - - case 4: - // comma - if (character === 44) { - // colon - parsed[++index] = peek() === 58 ? '&\f' : ''; - points[index] = parsed[index].length; - break; - } - - // fallthrough - - default: - parsed[index] += from(character); - } - } while (character = next()); - - return parsed; - }; - - var getRules = function getRules(value, points) { - return dealloc(toRules(alloc(value), points)); - }; // WeakSet would be more appropriate, but only WeakMap is supported in IE11 - - - var fixedElements = /* #__PURE__ */new WeakMap(); - var compat = function compat(element) { - if (element.type !== 'rule' || !element.parent || // positive .length indicates that this rule contains pseudo - // negative .length indicates that this rule has been already prefixed - element.length < 1) { - return; - } - - var value = element.value, - parent = element.parent; - var isImplicitRule = element.column === parent.column && element.line === parent.line; - - while (parent.type !== 'rule') { - parent = parent.parent; - if (!parent) return; - } // short-circuit for the simplest case - - - if (element.props.length === 1 && value.charCodeAt(0) !== 58 - /* colon */ - && !fixedElements.get(parent)) { - return; - } // if this is an implicitly inserted rule (the one eagerly inserted at the each new nested level) - // then the props has already been manipulated beforehand as they that array is shared between it and its "rule parent" - - - if (isImplicitRule) { - return; - } - - fixedElements.set(element, true); - var points = []; - var rules = getRules(value, points); - var parentRules = parent.props; - - for (var i = 0, k = 0; i < rules.length; i++) { - for (var j = 0; j < parentRules.length; j++, k++) { - element.props[k] = points[i] ? rules[i].replace(/&\f/g, parentRules[j]) : parentRules[j] + " " + rules[i]; - } - } - }; - var removeLabel = function removeLabel(element) { - if (element.type === 'decl') { - var value = element.value; - - if ( // charcode for l - value.charCodeAt(0) === 108 && // charcode for b - value.charCodeAt(2) === 98) { - // this ignores label - element["return"] = ''; - element.value = ''; - } - } - }; - var ignoreFlag = 'emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason'; - - var isIgnoringComment = function isIgnoringComment(element) { - return element.type === 'comm' && element.children.indexOf(ignoreFlag) > -1; - }; - - var createUnsafeSelectorsAlarm = function createUnsafeSelectorsAlarm(cache) { - return function (element, index, children) { - if (element.type !== 'rule' || cache.compat) return; - var unsafePseudoClasses = element.value.match(/(:first|:nth|:nth-last)-child/g); - - if (unsafePseudoClasses) { - var isNested = element.parent === children[0]; // in nested rules comments become children of the "auto-inserted" rule - // - // considering this input: - // .a { - // .b /* comm */ {} - // color: hotpink; - // } - // we get output corresponding to this: - // .a { - // & { - // /* comm */ - // color: hotpink; - // } - // .b {} - // } - - var commentContainer = isNested ? children[0].children : // global rule at the root level - children; - - for (var i = commentContainer.length - 1; i >= 0; i--) { - var node = commentContainer[i]; - - if (node.line < element.line) { - break; - } // it is quite weird but comments are *usually* put at `column: element.column - 1` - // so we seek *from the end* for the node that is earlier than the rule's `element` and check that - // this will also match inputs like this: - // .a { - // /* comm */ - // .b {} - // } - // - // but that is fine - // - // it would be the easiest to change the placement of the comment to be the first child of the rule: - // .a { - // .b { /* comm */ } - // } - // with such inputs we wouldn't have to search for the comment at all - // TODO: consider changing this comment placement in the next major version - - - if (node.column < element.column) { - if (isIgnoringComment(node)) { - return; - } - - break; - } - } - - unsafePseudoClasses.forEach(function (unsafePseudoClass) { - console.error("The pseudo class \"" + unsafePseudoClass + "\" is potentially unsafe when doing server-side rendering. Try changing it to \"" + unsafePseudoClass.split('-child')[0] + "-of-type\"."); - }); - } - }; - }; - - var isImportRule = function isImportRule(element) { - return element.type.charCodeAt(1) === 105 && element.type.charCodeAt(0) === 64; - }; - - var isPrependedWithRegularRules = function isPrependedWithRegularRules(index, children) { - for (var i = index - 1; i >= 0; i--) { - if (!isImportRule(children[i])) { - return true; - } - } - - return false; - }; // use this to remove incorrect elements from further processing - // so they don't get handed to the `sheet` (or anything else) - // as that could potentially lead to additional logs which in turn could be overhelming to the user - - - var nullifyElement = function nullifyElement(element) { - element.type = ''; - element.value = ''; - element["return"] = ''; - element.children = ''; - element.props = ''; - }; - - var incorrectImportAlarm = function incorrectImportAlarm(element, index, children) { - if (!isImportRule(element)) { - return; - } - - if (element.parent) { - console.error("`@import` rules can't be nested inside other rules. Please move it to the top level and put it before regular rules. Keep in mind that they can only be used within global styles."); - nullifyElement(element); - } else if (isPrependedWithRegularRules(index, children)) { - console.error("`@import` rules can't be after other rules. Please put your `@import` rules before your other rules."); - nullifyElement(element); - } - }; - - /* eslint-disable no-fallthrough */ - - function prefix(value, length) { - switch (hash(value, length)) { - // color-adjust - case 5103: - return WEBKIT + 'print-' + value + value; - // animation, animation-(delay|direction|duration|fill-mode|iteration-count|name|play-state|timing-function) - - case 5737: - case 4201: - case 3177: - case 3433: - case 1641: - case 4457: - case 2921: // text-decoration, filter, clip-path, backface-visibility, column, box-decoration-break - - case 5572: - case 6356: - case 5844: - case 3191: - case 6645: - case 3005: // mask, mask-image, mask-(mode|clip|size), mask-(repeat|origin), mask-position, mask-composite, - - case 6391: - case 5879: - case 5623: - case 6135: - case 4599: - case 4855: // background-clip, columns, column-(count|fill|gap|rule|rule-color|rule-style|rule-width|span|width) - - case 4215: - case 6389: - case 5109: - case 5365: - case 5621: - case 3829: - return WEBKIT + value + value; - // appearance, user-select, transform, hyphens, text-size-adjust - - case 5349: - case 4246: - case 4810: - case 6968: - case 2756: - return WEBKIT + value + MOZ + value + MS + value + value; - // flex, flex-direction - - case 6828: - case 4268: - return WEBKIT + value + MS + value + value; - // order - - case 6165: - return WEBKIT + value + MS + 'flex-' + value + value; - // align-items - - case 5187: - return WEBKIT + value + replace(value, /(\w+).+(:[^]+)/, WEBKIT + 'box-$1$2' + MS + 'flex-$1$2') + value; - // align-self - - case 5443: - return WEBKIT + value + MS + 'flex-item-' + replace(value, /flex-|-self/, '') + value; - // align-content - - case 4675: - return WEBKIT + value + MS + 'flex-line-pack' + replace(value, /align-content|flex-|-self/, '') + value; - // flex-shrink - - case 5548: - return WEBKIT + value + MS + replace(value, 'shrink', 'negative') + value; - // flex-basis - - case 5292: - return WEBKIT + value + MS + replace(value, 'basis', 'preferred-size') + value; - // flex-grow - - case 6060: - return WEBKIT + 'box-' + replace(value, '-grow', '') + WEBKIT + value + MS + replace(value, 'grow', 'positive') + value; - // transition - - case 4554: - return WEBKIT + replace(value, /([^-])(transform)/g, '$1' + WEBKIT + '$2') + value; - // cursor - - case 6187: - return replace(replace(replace(value, /(zoom-|grab)/, WEBKIT + '$1'), /(image-set)/, WEBKIT + '$1'), value, '') + value; - // background, background-image - - case 5495: - case 3959: - return replace(value, /(image-set\([^]*)/, WEBKIT + '$1' + '$`$1'); - // justify-content - - case 4968: - return replace(replace(value, /(.+:)(flex-)?(.*)/, WEBKIT + 'box-pack:$3' + MS + 'flex-pack:$3'), /s.+-b[^;]+/, 'justify') + WEBKIT + value + value; - // (margin|padding)-inline-(start|end) - - case 4095: - case 3583: - case 4068: - case 2532: - return replace(value, /(.+)-inline(.+)/, WEBKIT + '$1$2') + value; - // (min|max)?(width|height|inline-size|block-size) - - case 8116: - case 7059: - case 5753: - case 5535: - case 5445: - case 5701: - case 4933: - case 4677: - case 5533: - case 5789: - case 5021: - case 4765: - // stretch, max-content, min-content, fill-available - if (strlen(value) - 1 - length > 6) switch (charat(value, length + 1)) { - // (m)ax-content, (m)in-content - case 109: - // - - if (charat(value, length + 4) !== 45) break; - // (f)ill-available, (f)it-content - - case 102: - return replace(value, /(.+:)(.+)-([^]+)/, '$1' + WEBKIT + '$2-$3' + '$1' + MOZ + (charat(value, length + 3) == 108 ? '$3' : '$2-$3')) + value; - // (s)tretch - - case 115: - return ~indexof(value, 'stretch') ? prefix(replace(value, 'stretch', 'fill-available'), length) + value : value; - } - break; - // position: sticky - - case 4949: - // (s)ticky? - if (charat(value, length + 1) !== 115) break; - // display: (flex|inline-flex) - - case 6444: - switch (charat(value, strlen(value) - 3 - (~indexof(value, '!important') && 10))) { - // stic(k)y - case 107: - return replace(value, ':', ':' + WEBKIT) + value; - // (inline-)?fl(e)x - - case 101: - return replace(value, /(.+:)([^;!]+)(;|!.+)?/, '$1' + WEBKIT + (charat(value, 14) === 45 ? 'inline-' : '') + 'box$3' + '$1' + WEBKIT + '$2$3' + '$1' + MS + '$2box$3') + value; - } - - break; - // writing-mode - - case 5936: - switch (charat(value, length + 11)) { - // vertical-l(r) - case 114: - return WEBKIT + value + MS + replace(value, /[svh]\w+-[tblr]{2}/, 'tb') + value; - // vertical-r(l) - - case 108: - return WEBKIT + value + MS + replace(value, /[svh]\w+-[tblr]{2}/, 'tb-rl') + value; - // horizontal(-)tb - - case 45: - return WEBKIT + value + MS + replace(value, /[svh]\w+-[tblr]{2}/, 'lr') + value; - } - - return WEBKIT + value + MS + value + value; - } - - return value; - } - - var prefixer = function prefixer(element, index, children, callback) { - if (element.length > -1) if (!element["return"]) switch (element.type) { - case DECLARATION: - element["return"] = prefix(element.value, element.length); - break; - - case KEYFRAMES: - return serialize([copy(element, { - value: replace(element.value, '@', '@' + WEBKIT) - })], callback); - - case RULESET: - if (element.length) return combine(element.props, function (value) { - switch (match(value, /(::plac\w+|:read-\w+)/)) { - // :read-(only|write) - case ':read-only': - case ':read-write': - return serialize([copy(element, { - props: [replace(value, /:(read-\w+)/, ':' + MOZ + '$1')] - })], callback); - // :placeholder - - case '::placeholder': - return serialize([copy(element, { - props: [replace(value, /:(plac\w+)/, ':' + WEBKIT + 'input-$1')] - }), copy(element, { - props: [replace(value, /:(plac\w+)/, ':' + MOZ + '$1')] - }), copy(element, { - props: [replace(value, /:(plac\w+)/, MS + 'input-$1')] - })], callback); - } - - return ''; - }); - } - }; - - var defaultStylisPlugins = [prefixer]; - - var createCache = function createCache(options) { - var key = options.key; - - if (!key) { - throw new Error("You have to configure `key` for your cache. Please make sure it's unique (and not equal to 'css') as it's used for linking styles to your cache.\n" + "If multiple caches share the same key they might \"fight\" for each other's style elements."); - } - - if ( key === 'css') { - var ssrStyles = document.querySelectorAll("style[data-emotion]:not([data-s])"); // get SSRed styles out of the way of React's hydration - // document.head is a safe place to move them to(though note document.head is not necessarily the last place they will be) - // note this very very intentionally targets all style elements regardless of the key to ensure - // that creating a cache works inside of render of a React component - - Array.prototype.forEach.call(ssrStyles, function (node) { - // we want to only move elements which have a space in the data-emotion attribute value - // because that indicates that it is an Emotion 11 server-side rendered style elements - // while we will already ignore Emotion 11 client-side inserted styles because of the :not([data-s]) part in the selector - // Emotion 10 client-side inserted styles did not have data-s (but importantly did not have a space in their data-emotion attributes) - // so checking for the space ensures that loading Emotion 11 after Emotion 10 has inserted some styles - // will not result in the Emotion 10 styles being destroyed - var dataEmotionAttribute = node.getAttribute('data-emotion'); - - if (dataEmotionAttribute.indexOf(' ') === -1) { - return; - } - document.head.appendChild(node); - node.setAttribute('data-s', ''); - }); - } - - var stylisPlugins = options.stylisPlugins || defaultStylisPlugins; - - { - // $FlowFixMe - if (/[^a-z-]/.test(key)) { - throw new Error("Emotion key must only contain lower case alphabetical characters and - but \"" + key + "\" was passed"); - } - } - - var inserted = {}; - var container; - var nodesToHydrate = []; - - { - container = options.container || document.head; - Array.prototype.forEach.call( // this means we will ignore elements which don't have a space in them which - // means that the style elements we're looking at are only Emotion 11 server-rendered style elements - document.querySelectorAll("style[data-emotion^=\"" + key + " \"]"), function (node) { - var attrib = node.getAttribute("data-emotion").split(' '); // $FlowFixMe - - for (var i = 1; i < attrib.length; i++) { - inserted[attrib[i]] = true; - } - - nodesToHydrate.push(node); - }); - } - - var _insert; - - var omnipresentPlugins = [compat, removeLabel]; - - { - omnipresentPlugins.push(createUnsafeSelectorsAlarm({ - get compat() { - return cache.compat; - } - - }), incorrectImportAlarm); - } - - { - var currentSheet; - var finalizingPlugins = [stringify, function (element) { - if (!element.root) { - if (element["return"]) { - currentSheet.insert(element["return"]); - } else if (element.value && element.type !== COMMENT) { - // insert empty rule in non-production environments - // so @emotion/jest can grab `key` from the (JS)DOM for caches without any rules inserted yet - currentSheet.insert(element.value + "{}"); - } - } - } ]; - var serializer = middleware(omnipresentPlugins.concat(stylisPlugins, finalizingPlugins)); - - var stylis = function stylis(styles) { - return serialize(compile(styles), serializer); - }; - - _insert = function insert(selector, serialized, sheet, shouldCache) { - currentSheet = sheet; - - if (serialized.map !== undefined) { - currentSheet = { - insert: function insert(rule) { - sheet.insert(rule + serialized.map); - } - }; - } - - stylis(selector ? selector + "{" + serialized.styles + "}" : serialized.styles); - - if (shouldCache) { - cache.inserted[serialized.name] = true; - } - }; - } - - var cache = { - key: key, - sheet: new StyleSheet({ - key: key, - container: container, - nonce: options.nonce, - speedy: options.speedy, - prepend: options.prepend, - insertionPoint: options.insertionPoint - }), - nonce: options.nonce, - inserted: inserted, - registered: {}, - insert: _insert - }; - cache.sheet.hydrate(nodesToHydrate); - return cache; - }; - - var isBrowser$1 = "object" !== 'undefined'; - function getRegisteredStyles(registered, registeredStyles, classNames) { - var rawClassName = ''; - classNames.split(' ').forEach(function (className) { - if (registered[className] !== undefined) { - registeredStyles.push(registered[className] + ";"); - } else { - rawClassName += className + " "; - } - }); - return rawClassName; - } - var registerStyles = function registerStyles(cache, serialized, isStringTag) { - var className = cache.key + "-" + serialized.name; - - if ( // we only need to add the styles to the registered cache if the - // class name could be used further down - // the tree but if it's a string tag, we know it won't - // so we don't have to add it to registered cache. - // this improves memory usage since we can avoid storing the whole style string - (isStringTag === false || // we need to always store it if we're in compat mode and - // in node since emotion-server relies on whether a style is in - // the registered cache to know whether a style is global or not - // also, note that this check will be dead code eliminated in the browser - isBrowser$1 === false ) && cache.registered[className] === undefined) { - cache.registered[className] = serialized.styles; - } - }; - var insertStyles = function insertStyles(cache, serialized, isStringTag) { - registerStyles(cache, serialized, isStringTag); - var className = cache.key + "-" + serialized.name; - - if (cache.inserted[serialized.name] === undefined) { - var current = serialized; - - do { - cache.insert(serialized === current ? "." + className : '', current, cache.sheet, true); - - current = current.next; - } while (current !== undefined); - } - }; - - /* eslint-disable */ - // Inspired by https://github.com/garycourt/murmurhash-js - // Ported from https://github.com/aappleby/smhasher/blob/61a0530f28277f2e850bfc39600ce61d02b518de/src/MurmurHash2.cpp#L37-L86 - function murmur2(str) { - // 'm' and 'r' are mixing constants generated offline. - // They're not really 'magic', they just happen to work well. - // const m = 0x5bd1e995; - // const r = 24; - // Initialize the hash - var h = 0; // Mix 4 bytes at a time into the hash - - var k, - i = 0, - len = str.length; - - for (; len >= 4; ++i, len -= 4) { - k = str.charCodeAt(i) & 0xff | (str.charCodeAt(++i) & 0xff) << 8 | (str.charCodeAt(++i) & 0xff) << 16 | (str.charCodeAt(++i) & 0xff) << 24; - k = - /* Math.imul(k, m): */ - (k & 0xffff) * 0x5bd1e995 + ((k >>> 16) * 0xe995 << 16); - k ^= - /* k >>> r: */ - k >>> 24; - h = - /* Math.imul(k, m): */ - (k & 0xffff) * 0x5bd1e995 + ((k >>> 16) * 0xe995 << 16) ^ - /* Math.imul(h, m): */ - (h & 0xffff) * 0x5bd1e995 + ((h >>> 16) * 0xe995 << 16); - } // Handle the last few bytes of the input array - - - switch (len) { - case 3: - h ^= (str.charCodeAt(i + 2) & 0xff) << 16; - - case 2: - h ^= (str.charCodeAt(i + 1) & 0xff) << 8; - - case 1: - h ^= str.charCodeAt(i) & 0xff; - h = - /* Math.imul(h, m): */ - (h & 0xffff) * 0x5bd1e995 + ((h >>> 16) * 0xe995 << 16); - } // Do a few final mixes of the hash to ensure the last few - // bytes are well-incorporated. - - - h ^= h >>> 13; - h = - /* Math.imul(h, m): */ - (h & 0xffff) * 0x5bd1e995 + ((h >>> 16) * 0xe995 << 16); - return ((h ^ h >>> 15) >>> 0).toString(36); - } - - var unitlessKeys = { - animationIterationCount: 1, - borderImageOutset: 1, - borderImageSlice: 1, - borderImageWidth: 1, - boxFlex: 1, - boxFlexGroup: 1, - boxOrdinalGroup: 1, - columnCount: 1, - columns: 1, - flex: 1, - flexGrow: 1, - flexPositive: 1, - flexShrink: 1, - flexNegative: 1, - flexOrder: 1, - gridRow: 1, - gridRowEnd: 1, - gridRowSpan: 1, - gridRowStart: 1, - gridColumn: 1, - gridColumnEnd: 1, - gridColumnSpan: 1, - gridColumnStart: 1, - msGridRow: 1, - msGridRowSpan: 1, - msGridColumn: 1, - msGridColumnSpan: 1, - fontWeight: 1, - lineHeight: 1, - opacity: 1, - order: 1, - orphans: 1, - tabSize: 1, - widows: 1, - zIndex: 1, - zoom: 1, - WebkitLineClamp: 1, - // SVG-related properties - fillOpacity: 1, - floodOpacity: 1, - stopOpacity: 1, - strokeDasharray: 1, - strokeDashoffset: 1, - strokeMiterlimit: 1, - strokeOpacity: 1, - strokeWidth: 1 - }; - - var ILLEGAL_ESCAPE_SEQUENCE_ERROR = "You have illegal escape sequence in your template literal, most likely inside content's property value.\nBecause you write your CSS inside a JavaScript string you actually have to do double escaping, so for example \"content: '\\00d7';\" should become \"content: '\\\\00d7';\".\nYou can read more about this here:\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#ES2018_revision_of_illegal_escape_sequences"; - var UNDEFINED_AS_OBJECT_KEY_ERROR = "You have passed in falsy value as style object's key (can happen when in example you pass unexported component as computed key)."; - var hyphenateRegex = /[A-Z]|^ms/g; - var animationRegex = /_EMO_([^_]+?)_([^]*?)_EMO_/g; - - var isCustomProperty = function isCustomProperty(property) { - return property.charCodeAt(1) === 45; - }; - - var isProcessableValue = function isProcessableValue(value) { - return value != null && typeof value !== 'boolean'; - }; - - var processStyleName = /* #__PURE__ */memoize(function (styleName) { - return isCustomProperty(styleName) ? styleName : styleName.replace(hyphenateRegex, '-$&').toLowerCase(); - }); - - var processStyleValue = function processStyleValue(key, value) { - switch (key) { - case 'animation': - case 'animationName': - { - if (typeof value === 'string') { - return value.replace(animationRegex, function (match, p1, p2) { - cursor = { - name: p1, - styles: p2, - next: cursor - }; - return p1; - }); - } - } - } - - if (unitlessKeys[key] !== 1 && !isCustomProperty(key) && typeof value === 'number' && value !== 0) { - return value + 'px'; - } - - return value; - }; - - { - var contentValuePattern = /(var|attr|counters?|url|element|(((repeating-)?(linear|radial))|conic)-gradient)\(|(no-)?(open|close)-quote/; - var contentValues = ['normal', 'none', 'initial', 'inherit', 'unset']; - var oldProcessStyleValue = processStyleValue; - var msPattern = /^-ms-/; - var hyphenPattern = /-(.)/g; - var hyphenatedCache = {}; - - processStyleValue = function processStyleValue(key, value) { - if (key === 'content') { - if (typeof value !== 'string' || contentValues.indexOf(value) === -1 && !contentValuePattern.test(value) && (value.charAt(0) !== value.charAt(value.length - 1) || value.charAt(0) !== '"' && value.charAt(0) !== "'")) { - throw new Error("You seem to be using a value for 'content' without quotes, try replacing it with `content: '\"" + value + "\"'`"); - } - } - - var processed = oldProcessStyleValue(key, value); - - if (processed !== '' && !isCustomProperty(key) && key.indexOf('-') !== -1 && hyphenatedCache[key] === undefined) { - hyphenatedCache[key] = true; - console.error("Using kebab-case for css properties in objects is not supported. Did you mean " + key.replace(msPattern, 'ms-').replace(hyphenPattern, function (str, _char) { - return _char.toUpperCase(); - }) + "?"); - } - - return processed; - }; - } - - var noComponentSelectorMessage = 'Component selectors can only be used in conjunction with ' + '@emotion/babel-plugin, the swc Emotion plugin, or another Emotion-aware ' + 'compiler transform.'; - - function handleInterpolation(mergedProps, registered, interpolation) { - if (interpolation == null) { - return ''; - } - - if (interpolation.__emotion_styles !== undefined) { - if (interpolation.toString() === 'NO_COMPONENT_SELECTOR') { - throw new Error(noComponentSelectorMessage); - } - - return interpolation; - } - - switch (typeof interpolation) { - case 'boolean': - { - return ''; - } - - case 'object': - { - if (interpolation.anim === 1) { - cursor = { - name: interpolation.name, - styles: interpolation.styles, - next: cursor - }; - return interpolation.name; - } - - if (interpolation.styles !== undefined) { - var next = interpolation.next; - - if (next !== undefined) { - // not the most efficient thing ever but this is a pretty rare case - // and there will be very few iterations of this generally - while (next !== undefined) { - cursor = { - name: next.name, - styles: next.styles, - next: cursor - }; - next = next.next; - } - } - - var styles = interpolation.styles + ";"; - - if (interpolation.map !== undefined) { - styles += interpolation.map; - } - - return styles; - } - - return createStringFromObject(mergedProps, registered, interpolation); - } - - case 'function': - { - if (mergedProps !== undefined) { - var previousCursor = cursor; - var result = interpolation(mergedProps); - cursor = previousCursor; - return handleInterpolation(mergedProps, registered, result); - } else { - console.error('Functions that are interpolated in css calls will be stringified.\n' + 'If you want to have a css call based on props, create a function that returns a css call like this\n' + 'let dynamicStyle = (props) => css`color: ${props.color}`\n' + 'It can be called directly with props or interpolated in a styled call like this\n' + "let SomeComponent = styled('div')`${dynamicStyle}`"); - } - - break; - } - - case 'string': - { - var matched = []; - var replaced = interpolation.replace(animationRegex, function (match, p1, p2) { - var fakeVarName = "animation" + matched.length; - matched.push("const " + fakeVarName + " = keyframes`" + p2.replace(/^@keyframes animation-\w+/, '') + "`"); - return "${" + fakeVarName + "}"; - }); - - if (matched.length) { - console.error('`keyframes` output got interpolated into plain string, please wrap it with `css`.\n\n' + 'Instead of doing this:\n\n' + [].concat(matched, ["`" + replaced + "`"]).join('\n') + '\n\nYou should wrap it with `css` like this:\n\n' + ("css`" + replaced + "`")); - } - } - - break; - } // finalize string values (regular strings and functions interpolated into css calls) - - - if (registered == null) { - return interpolation; - } - - var cached = registered[interpolation]; - return cached !== undefined ? cached : interpolation; - } - - function createStringFromObject(mergedProps, registered, obj) { - var string = ''; - - if (Array.isArray(obj)) { - for (var i = 0; i < obj.length; i++) { - string += handleInterpolation(mergedProps, registered, obj[i]) + ";"; - } - } else { - for (var _key in obj) { - var value = obj[_key]; - - if (typeof value !== 'object') { - if (registered != null && registered[value] !== undefined) { - string += _key + "{" + registered[value] + "}"; - } else if (isProcessableValue(value)) { - string += processStyleName(_key) + ":" + processStyleValue(_key, value) + ";"; - } - } else { - if (_key === 'NO_COMPONENT_SELECTOR' && "development" !== 'production') { - throw new Error(noComponentSelectorMessage); - } - - if (Array.isArray(value) && typeof value[0] === 'string' && (registered == null || registered[value[0]] === undefined)) { - for (var _i = 0; _i < value.length; _i++) { - if (isProcessableValue(value[_i])) { - string += processStyleName(_key) + ":" + processStyleValue(_key, value[_i]) + ";"; - } - } - } else { - var interpolated = handleInterpolation(mergedProps, registered, value); - - switch (_key) { - case 'animation': - case 'animationName': - { - string += processStyleName(_key) + ":" + interpolated + ";"; - break; - } - - default: - { - if (_key === 'undefined') { - console.error(UNDEFINED_AS_OBJECT_KEY_ERROR); - } - - string += _key + "{" + interpolated + "}"; - } - } - } - } - } - } - - return string; - } - - var labelPattern = /label:\s*([^\s;\n{]+)\s*(;|$)/g; - var sourceMapPattern; - - { - sourceMapPattern = /\/\*#\ssourceMappingURL=data:application\/json;\S+\s+\*\//g; - } // this is the cursor for keyframes - // keyframes are stored on the SerializedStyles object as a linked list - - - var cursor; - var serializeStyles = function serializeStyles(args, registered, mergedProps) { - if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && args[0].styles !== undefined) { - return args[0]; - } - - var stringMode = true; - var styles = ''; - cursor = undefined; - var strings = args[0]; - - if (strings == null || strings.raw === undefined) { - stringMode = false; - styles += handleInterpolation(mergedProps, registered, strings); - } else { - if (strings[0] === undefined) { - console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR); - } - - styles += strings[0]; - } // we start at 1 since we've already handled the first arg - - - for (var i = 1; i < args.length; i++) { - styles += handleInterpolation(mergedProps, registered, args[i]); - - if (stringMode) { - if (strings[i] === undefined) { - console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR); - } - - styles += strings[i]; - } - } - - var sourceMap; - - { - styles = styles.replace(sourceMapPattern, function (match) { - sourceMap = match; - return ''; - }); - } // using a global regex with .exec is stateful so lastIndex has to be reset each time - - - labelPattern.lastIndex = 0; - var identifierName = ''; - var match; // https://esbench.com/bench/5b809c2cf2949800a0f61fb5 - - while ((match = labelPattern.exec(styles)) !== null) { - identifierName += '-' + // $FlowFixMe we know it's not null - match[1]; - } - - var name = murmur2(styles) + identifierName; - - { - // $FlowFixMe SerializedStyles type doesn't have toString property (and we don't want to add it) - return { - name: name, - styles: styles, - map: sourceMap, - next: cursor, - toString: function toString() { - return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; - } - }; - } - }; - - var syncFallback = function syncFallback(create) { - return create(); - }; - - var useInsertionEffect = React__namespace['useInsertion' + 'Effect'] ? React__namespace['useInsertion' + 'Effect'] : false; - var useInsertionEffectAlwaysWithSyncFallback = useInsertionEffect || syncFallback; - var useInsertionEffectWithLayoutFallback = useInsertionEffect || React$1.useLayoutEffect; - - var hasOwnProperty = {}.hasOwnProperty; - - var EmotionCacheContext = /* #__PURE__ */React$1.createContext( // we're doing this to avoid preconstruct's dead code elimination in this one case - // because this module is primarily intended for the browser and node - // but it's also required in react native and similar environments sometimes - // and we could have a special build just for that - // but this is much easier and the native packages - // might use a different theme context in the future anyway - typeof HTMLElement !== 'undefined' ? /* #__PURE__ */createCache({ - key: 'css' - }) : null); - - { - EmotionCacheContext.displayName = 'EmotionCacheContext'; - } - - EmotionCacheContext.Provider; - - var withEmotionCache = function withEmotionCache(func) { - // $FlowFixMe - return /*#__PURE__*/React$1.forwardRef(function (props, ref) { - // the cache will never be null in the browser - var cache = React$1.useContext(EmotionCacheContext); - return func(props, cache, ref); - }); - }; - - var ThemeContext = /* #__PURE__ */React$1.createContext({}); - - { - ThemeContext.displayName = 'EmotionThemeContext'; - } - - var getLastPart = function getLastPart(functionName) { - // The match may be something like 'Object.createEmotionProps' or - // 'Loader.prototype.render' - var parts = functionName.split('.'); - return parts[parts.length - 1]; - }; - - var getFunctionNameFromStackTraceLine = function getFunctionNameFromStackTraceLine(line) { - // V8 - var match = /^\s+at\s+([A-Za-z0-9$.]+)\s/.exec(line); - if (match) return getLastPart(match[1]); // Safari / Firefox - - match = /^([A-Za-z0-9$.]+)@/.exec(line); - if (match) return getLastPart(match[1]); - return undefined; - }; - - var internalReactFunctionNames = /* #__PURE__ */new Set(['renderWithHooks', 'processChild', 'finishClassComponent', 'renderToString']); // These identifiers come from error stacks, so they have to be valid JS - // identifiers, thus we only need to replace what is a valid character for JS, - // but not for CSS. - - var sanitizeIdentifier = function sanitizeIdentifier(identifier) { - return identifier.replace(/\$/g, '-'); - }; - - var getLabelFromStackTrace = function getLabelFromStackTrace(stackTrace) { - if (!stackTrace) return undefined; - var lines = stackTrace.split('\n'); - - for (var i = 0; i < lines.length; i++) { - var functionName = getFunctionNameFromStackTraceLine(lines[i]); // The first line of V8 stack traces is just "Error" - - if (!functionName) continue; // If we reach one of these, we have gone too far and should quit - - if (internalReactFunctionNames.has(functionName)) break; // The component name is the first function in the stack that starts with an - // uppercase letter - - if (/^[A-Z]/.test(functionName)) return sanitizeIdentifier(functionName); - } - - return undefined; - }; - - var typePropName = '__EMOTION_TYPE_PLEASE_DO_NOT_USE__'; - var labelPropName = '__EMOTION_LABEL_PLEASE_DO_NOT_USE__'; - var createEmotionProps = function createEmotionProps(type, props) { - if (typeof props.css === 'string' && // check if there is a css declaration - props.css.indexOf(':') !== -1) { - throw new Error("Strings are not allowed as css prop values, please wrap it in a css template literal from '@emotion/react' like this: css`" + props.css + "`"); - } - - var newProps = {}; - - for (var key in props) { - if (hasOwnProperty.call(props, key)) { - newProps[key] = props[key]; - } - } - - newProps[typePropName] = type; // For performance, only call getLabelFromStackTrace in development and when - // the label hasn't already been computed - - if (!!props.css && (typeof props.css !== 'object' || typeof props.css.name !== 'string' || props.css.name.indexOf('-') === -1)) { - var label = getLabelFromStackTrace(new Error().stack); - if (label) newProps[labelPropName] = label; - } - - return newProps; - }; - - var Insertion$1 = function Insertion(_ref) { - var cache = _ref.cache, - serialized = _ref.serialized, - isStringTag = _ref.isStringTag; - registerStyles(cache, serialized, isStringTag); - useInsertionEffectAlwaysWithSyncFallback(function () { - return insertStyles(cache, serialized, isStringTag); - }); - - return null; - }; - - var Emotion = /* #__PURE__ */withEmotionCache(function (props, cache, ref) { - var cssProp = props.css; // so that using `css` from `emotion` and passing the result to the css prop works - // not passing the registered cache to serializeStyles because it would - // make certain babel optimisations not possible - - if (typeof cssProp === 'string' && cache.registered[cssProp] !== undefined) { - cssProp = cache.registered[cssProp]; - } - - var WrappedComponent = props[typePropName]; - var registeredStyles = [cssProp]; - var className = ''; - - if (typeof props.className === 'string') { - className = getRegisteredStyles(cache.registered, registeredStyles, props.className); - } else if (props.className != null) { - className = props.className + " "; - } - - var serialized = serializeStyles(registeredStyles, undefined, React$1.useContext(ThemeContext)); - - if (serialized.name.indexOf('-') === -1) { - var labelFromStack = props[labelPropName]; - - if (labelFromStack) { - serialized = serializeStyles([serialized, 'label:' + labelFromStack + ';']); - } - } - - className += cache.key + "-" + serialized.name; - var newProps = {}; - - for (var key in props) { - if (hasOwnProperty.call(props, key) && key !== 'css' && key !== typePropName && (key !== labelPropName)) { - newProps[key] = props[key]; - } - } - - newProps.ref = ref; - newProps.className = className; - return /*#__PURE__*/React$1.createElement(React$1.Fragment, null, /*#__PURE__*/React$1.createElement(Insertion$1, { - cache: cache, - serialized: serialized, - isStringTag: typeof WrappedComponent === 'string' - }), /*#__PURE__*/React$1.createElement(WrappedComponent, newProps)); - }); - - { - Emotion.displayName = 'EmotionCssPropInternal'; - } - - var pkg = { - name: "@emotion/react", - version: "11.10.6", - main: "dist/emotion-react.cjs.js", - module: "dist/emotion-react.esm.js", - browser: { - "./dist/emotion-react.esm.js": "./dist/emotion-react.browser.esm.js" - }, - exports: { - ".": { - module: { - worker: "./dist/emotion-react.worker.esm.js", - browser: "./dist/emotion-react.browser.esm.js", - "default": "./dist/emotion-react.esm.js" - }, - "default": "./dist/emotion-react.cjs.js" - }, - "./jsx-runtime": { - module: { - worker: "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.esm.js", - browser: "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.esm.js", - "default": "./jsx-runtime/dist/emotion-react-jsx-runtime.esm.js" - }, - "default": "./jsx-runtime/dist/emotion-react-jsx-runtime.cjs.js" - }, - "./_isolated-hnrs": { - module: { - worker: "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.esm.js", - browser: "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.esm.js", - "default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.esm.js" - }, - "default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.cjs.js" - }, - "./jsx-dev-runtime": { - module: { - worker: "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.esm.js", - browser: "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.esm.js", - "default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.esm.js" - }, - "default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.cjs.js" - }, - "./package.json": "./package.json", - "./types/css-prop": "./types/css-prop.d.ts", - "./macro": "./macro.js" - }, - types: "types/index.d.ts", - files: [ - "src", - "dist", - "jsx-runtime", - "jsx-dev-runtime", - "_isolated-hnrs", - "types/*.d.ts", - "macro.js", - "macro.d.ts", - "macro.js.flow" - ], - sideEffects: false, - author: "Emotion Contributors", - license: "MIT", - scripts: { - "test:typescript": "dtslint types" - }, - dependencies: { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "hoist-non-react-statics": "^3.3.1" - }, - peerDependencies: { - react: ">=16.8.0" - }, - peerDependenciesMeta: { - "@types/react": { - optional: true - } - }, - devDependencies: { - "@definitelytyped/dtslint": "0.0.112", - "@emotion/css": "11.10.6", - "@emotion/css-prettifier": "1.1.1", - "@emotion/server": "11.10.0", - "@emotion/styled": "11.10.6", - "html-tag-names": "^1.1.2", - react: "16.14.0", - "svg-tag-names": "^1.1.1", - typescript: "^4.5.5" - }, - repository: "https://github.com/emotion-js/emotion/tree/main/packages/react", - publishConfig: { - access: "public" - }, - "umd:main": "dist/emotion-react.umd.min.js", - preconstruct: { - entrypoints: [ - "./index.js", - "./jsx-runtime.js", - "./jsx-dev-runtime.js", - "./_isolated-hnrs.js" - ], - umdName: "emotionReact", - exports: { - envConditions: [ - "browser", - "worker" - ], - extra: { - "./types/css-prop": "./types/css-prop.d.ts", - "./macro": "./macro.js" - } - } - } - }; - - var jsx = function jsx(type, props) { - var args = arguments; - - if (props == null || !hasOwnProperty.call(props, 'css')) { - // $FlowFixMe - return React$1.createElement.apply(undefined, args); - } - - var argsLength = args.length; - var createElementArgArray = new Array(argsLength); - createElementArgArray[0] = Emotion; - createElementArgArray[1] = createEmotionProps(type, props); - - for (var i = 2; i < argsLength; i++) { - createElementArgArray[i] = args[i]; - } // $FlowFixMe - - - return React$1.createElement.apply(null, createElementArgArray); - }; - - var warnedAboutCssPropForGlobal = false; // maintain place over rerenders. - // initial render from browser, insertBefore context.sheet.tags[0] or if a style hasn't been inserted there yet, appendChild - // initial client-side render from SSR, use place of hydrating tag - - var Global = /* #__PURE__ */withEmotionCache(function (props, cache) { - if (!warnedAboutCssPropForGlobal && ( // check for className as well since the user is - // probably using the custom createElement which - // means it will be turned into a className prop - // $FlowFixMe I don't really want to add it to the type since it shouldn't be used - props.className || props.css)) { - console.error("It looks like you're using the css prop on Global, did you mean to use the styles prop instead?"); - warnedAboutCssPropForGlobal = true; - } - - var styles = props.styles; - var serialized = serializeStyles([styles], undefined, React$1.useContext(ThemeContext)); - // but it is based on a constant that will never change at runtime - // it's effectively like having two implementations and switching them out - // so it's not actually breaking anything - - - var sheetRef = React$1.useRef(); - useInsertionEffectWithLayoutFallback(function () { - var key = cache.key + "-global"; // use case of https://github.com/emotion-js/emotion/issues/2675 - - var sheet = new cache.sheet.constructor({ - key: key, - nonce: cache.sheet.nonce, - container: cache.sheet.container, - speedy: cache.sheet.isSpeedy - }); - var rehydrating = false; // $FlowFixMe - - var node = document.querySelector("style[data-emotion=\"" + key + " " + serialized.name + "\"]"); - - if (cache.sheet.tags.length) { - sheet.before = cache.sheet.tags[0]; - } - - if (node !== null) { - rehydrating = true; // clear the hash so this node won't be recognizable as rehydratable by other s - - node.setAttribute('data-emotion', key); - sheet.hydrate([node]); - } - - sheetRef.current = [sheet, rehydrating]; - return function () { - sheet.flush(); - }; - }, [cache]); - useInsertionEffectWithLayoutFallback(function () { - var sheetRefCurrent = sheetRef.current; - var sheet = sheetRefCurrent[0], - rehydrating = sheetRefCurrent[1]; - - if (rehydrating) { - sheetRefCurrent[1] = false; - return; - } - - if (serialized.next !== undefined) { - // insert keyframes - insertStyles(cache, serialized.next, true); - } - - if (sheet.tags.length) { - // if this doesn't exist then it will be null so the style element will be appended - var element = sheet.tags[sheet.tags.length - 1].nextElementSibling; - sheet.before = element; - sheet.flush(); - } - - cache.insert("", serialized, sheet, false); - }, [cache, serialized.name]); - return null; - }); - - { - Global.displayName = 'EmotionGlobal'; - } - - function css$2() { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - return serializeStyles(args); - } - - var keyframes = function keyframes() { - var insertable = css$2.apply(void 0, arguments); - var name = "animation-" + insertable.name; // $FlowFixMe - - return { - name: name, - styles: "@keyframes " + name + "{" + insertable.styles + "}", - anim: 1, - toString: function toString() { - return "_EMO_" + this.name + "_" + this.styles + "_EMO_"; - } - }; - }; - - var classnames = function classnames(args) { - var len = args.length; - var i = 0; - var cls = ''; - - for (; i < len; i++) { - var arg = args[i]; - if (arg == null) continue; - var toAdd = void 0; - - switch (typeof arg) { - case 'boolean': - break; - - case 'object': - { - if (Array.isArray(arg)) { - toAdd = classnames(arg); - } else { - if (arg.styles !== undefined && arg.name !== undefined) { - console.error('You have passed styles created with `css` from `@emotion/react` package to the `cx`.\n' + '`cx` is meant to compose class names (strings) so you should convert those styles to a class name by passing them to the `css` received from component.'); - } - - toAdd = ''; - - for (var k in arg) { - if (arg[k] && k) { - toAdd && (toAdd += ' '); - toAdd += k; - } - } - } - - break; - } - - default: - { - toAdd = arg; - } - } - - if (toAdd) { - cls && (cls += ' '); - cls += toAdd; - } - } - - return cls; - }; - - function merge(registered, css, className) { - var registeredStyles = []; - var rawClassName = getRegisteredStyles(registered, registeredStyles, className); - - if (registeredStyles.length < 2) { - return className; - } - - return rawClassName + css(registeredStyles); - } - - var Insertion = function Insertion(_ref) { - var cache = _ref.cache, - serializedArr = _ref.serializedArr; - useInsertionEffectAlwaysWithSyncFallback(function () { - - for (var i = 0; i < serializedArr.length; i++) { - insertStyles(cache, serializedArr[i], false); - } - }); - - return null; - }; - - var ClassNames = /* #__PURE__ */withEmotionCache(function (props, cache) { - var hasRendered = false; - var serializedArr = []; - - var css = function css() { - if (hasRendered && "development" !== 'production') { - throw new Error('css can only be used during render'); - } - - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - var serialized = serializeStyles(args, cache.registered); - serializedArr.push(serialized); // registration has to happen here as the result of this might get consumed by `cx` - - registerStyles(cache, serialized, false); - return cache.key + "-" + serialized.name; - }; - - var cx = function cx() { - if (hasRendered && "development" !== 'production') { - throw new Error('cx can only be used during render'); - } - - for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; - } - - return merge(cache.registered, css, classnames(args)); - }; - - var content = { - css: css, - cx: cx, - theme: React$1.useContext(ThemeContext) - }; - var ele = props.children(content); - hasRendered = true; - return /*#__PURE__*/React$1.createElement(React$1.Fragment, null, /*#__PURE__*/React$1.createElement(Insertion, { - cache: cache, - serializedArr: serializedArr - }), ele); - }); - - { - ClassNames.displayName = 'EmotionClassNames'; - } - - { - var isBrowser = "object" !== 'undefined'; // #1727, #2905 for some reason Jest and Vitest evaluate modules twice if some consuming module gets mocked - - var isTestEnv = typeof jest !== 'undefined' || typeof vi !== 'undefined'; - - if (isBrowser && !isTestEnv) { - // globalThis has wide browser support - https://caniuse.com/?search=globalThis, Node.js 12 and later - var globalContext = // $FlowIgnore - typeof globalThis !== 'undefined' ? globalThis // eslint-disable-line no-undef - : isBrowser ? window : global; - var globalKey = "__EMOTION_REACT_" + pkg.version.split('.')[0] + "__"; - - if (globalContext[globalKey]) { - console.warn('You are loading @emotion/react when it is already loaded. Running ' + 'multiple instances may cause problems. This can happen if multiple ' + 'versions are used, or if multiple builds of the same version are ' + 'used.'); - } - - globalContext[globalKey] = true; - } - } - - function _taggedTemplateLiteral(strings, raw) { - if (!raw) { - raw = strings.slice(0); - } - return Object.freeze(Object.defineProperties(strings, { - raw: { - value: Object.freeze(raw) - } - })); - } - - var reactDom = {exports: {}}; - - var reactDom_development = {}; - - var scheduler = {exports: {}}; - - var scheduler_development = {}; - - /** - * @license React - * scheduler.development.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - - (function (exports) { - - { - (function() { - - /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ - if ( - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === - 'function' - ) { - __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); - } - var enableSchedulerDebugging = false; - var enableProfiling = false; - var frameYieldMs = 5; - - function push(heap, node) { - var index = heap.length; - heap.push(node); - siftUp(heap, node, index); - } - function peek(heap) { - return heap.length === 0 ? null : heap[0]; - } - function pop(heap) { - if (heap.length === 0) { - return null; - } - - var first = heap[0]; - var last = heap.pop(); - - if (last !== first) { - heap[0] = last; - siftDown(heap, last, 0); - } - - return first; - } - - function siftUp(heap, node, i) { - var index = i; - - while (index > 0) { - var parentIndex = index - 1 >>> 1; - var parent = heap[parentIndex]; - - if (compare(parent, node) > 0) { - // The parent is larger. Swap positions. - heap[parentIndex] = node; - heap[index] = parent; - index = parentIndex; - } else { - // The parent is smaller. Exit. - return; - } - } - } - - function siftDown(heap, node, i) { - var index = i; - var length = heap.length; - var halfLength = length >>> 1; - - while (index < halfLength) { - var leftIndex = (index + 1) * 2 - 1; - var left = heap[leftIndex]; - var rightIndex = leftIndex + 1; - var right = heap[rightIndex]; // If the left or right node is smaller, swap with the smaller of those. - - if (compare(left, node) < 0) { - if (rightIndex < length && compare(right, left) < 0) { - heap[index] = right; - heap[rightIndex] = node; - index = rightIndex; - } else { - heap[index] = left; - heap[leftIndex] = node; - index = leftIndex; - } - } else if (rightIndex < length && compare(right, node) < 0) { - heap[index] = right; - heap[rightIndex] = node; - index = rightIndex; - } else { - // Neither child is smaller. Exit. - return; - } - } - } - - function compare(a, b) { - // Compare sort index first, then task id. - var diff = a.sortIndex - b.sortIndex; - return diff !== 0 ? diff : a.id - b.id; - } - - // TODO: Use symbols? - var ImmediatePriority = 1; - var UserBlockingPriority = 2; - var NormalPriority = 3; - var LowPriority = 4; - var IdlePriority = 5; - - function markTaskErrored(task, ms) { - } - - /* eslint-disable no-var */ - - var hasPerformanceNow = typeof performance === 'object' && typeof performance.now === 'function'; - - if (hasPerformanceNow) { - var localPerformance = performance; - - exports.unstable_now = function () { - return localPerformance.now(); - }; - } else { - var localDate = Date; - var initialTime = localDate.now(); - - exports.unstable_now = function () { - return localDate.now() - initialTime; - }; - } // Max 31 bit integer. The max integer size in V8 for 32-bit systems. - // Math.pow(2, 30) - 1 - // 0b111111111111111111111111111111 - - - var maxSigned31BitInt = 1073741823; // Times out immediately - - var IMMEDIATE_PRIORITY_TIMEOUT = -1; // Eventually times out - - var USER_BLOCKING_PRIORITY_TIMEOUT = 250; - var NORMAL_PRIORITY_TIMEOUT = 5000; - var LOW_PRIORITY_TIMEOUT = 10000; // Never times out - - var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; // Tasks are stored on a min heap - - var taskQueue = []; - var timerQueue = []; // Incrementing id counter. Used to maintain insertion order. - - var taskIdCounter = 1; // Pausing the scheduler is useful for debugging. - var currentTask = null; - var currentPriorityLevel = NormalPriority; // This is set while performing work, to prevent re-entrance. - - var isPerformingWork = false; - var isHostCallbackScheduled = false; - var isHostTimeoutScheduled = false; // Capture local references to native APIs, in case a polyfill overrides them. - - var localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null; - var localClearTimeout = typeof clearTimeout === 'function' ? clearTimeout : null; - var localSetImmediate = typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom - - typeof navigator !== 'undefined' && navigator.scheduling !== undefined && navigator.scheduling.isInputPending !== undefined ? navigator.scheduling.isInputPending.bind(navigator.scheduling) : null; - - function advanceTimers(currentTime) { - // Check for tasks that are no longer delayed and add them to the queue. - var timer = peek(timerQueue); - - while (timer !== null) { - if (timer.callback === null) { - // Timer was cancelled. - pop(timerQueue); - } else if (timer.startTime <= currentTime) { - // Timer fired. Transfer to the task queue. - pop(timerQueue); - timer.sortIndex = timer.expirationTime; - push(taskQueue, timer); - } else { - // Remaining timers are pending. - return; - } - - timer = peek(timerQueue); - } - } - - function handleTimeout(currentTime) { - isHostTimeoutScheduled = false; - advanceTimers(currentTime); - - if (!isHostCallbackScheduled) { - if (peek(taskQueue) !== null) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } else { - var firstTimer = peek(timerQueue); - - if (firstTimer !== null) { - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); - } - } - } - } - - function flushWork(hasTimeRemaining, initialTime) { - - - isHostCallbackScheduled = false; - - if (isHostTimeoutScheduled) { - // We scheduled a timeout but it's no longer needed. Cancel it. - isHostTimeoutScheduled = false; - cancelHostTimeout(); - } - - isPerformingWork = true; - var previousPriorityLevel = currentPriorityLevel; - - try { - var currentTime; if (enableProfiling) ; else { - // No catch in prod code path. - return workLoop(hasTimeRemaining, initialTime); - } - } finally { - currentTask = null; - currentPriorityLevel = previousPriorityLevel; - isPerformingWork = false; - } - } - - function workLoop(hasTimeRemaining, initialTime) { - var currentTime = initialTime; - advanceTimers(currentTime); - currentTask = peek(taskQueue); - - while (currentTask !== null && !(enableSchedulerDebugging )) { - if (currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost())) { - // This currentTask hasn't expired, and we've reached the deadline. - break; - } - - var callback = currentTask.callback; - - if (typeof callback === 'function') { - currentTask.callback = null; - currentPriorityLevel = currentTask.priorityLevel; - var didUserCallbackTimeout = currentTask.expirationTime <= currentTime; - - var continuationCallback = callback(didUserCallbackTimeout); - currentTime = exports.unstable_now(); - - if (typeof continuationCallback === 'function') { - currentTask.callback = continuationCallback; - } else { - - if (currentTask === peek(taskQueue)) { - pop(taskQueue); - } - } - - advanceTimers(currentTime); - } else { - pop(taskQueue); - } - - currentTask = peek(taskQueue); - } // Return whether there's additional work - - - if (currentTask !== null) { - return true; - } else { - var firstTimer = peek(timerQueue); - - if (firstTimer !== null) { - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); - } - - return false; - } - } - - function unstable_runWithPriority(priorityLevel, eventHandler) { - switch (priorityLevel) { - case ImmediatePriority: - case UserBlockingPriority: - case NormalPriority: - case LowPriority: - case IdlePriority: - break; - - default: - priorityLevel = NormalPriority; - } - - var previousPriorityLevel = currentPriorityLevel; - currentPriorityLevel = priorityLevel; - - try { - return eventHandler(); - } finally { - currentPriorityLevel = previousPriorityLevel; - } - } - - function unstable_next(eventHandler) { - var priorityLevel; - - switch (currentPriorityLevel) { - case ImmediatePriority: - case UserBlockingPriority: - case NormalPriority: - // Shift down to normal priority - priorityLevel = NormalPriority; - break; - - default: - // Anything lower than normal priority should remain at the current level. - priorityLevel = currentPriorityLevel; - break; - } - - var previousPriorityLevel = currentPriorityLevel; - currentPriorityLevel = priorityLevel; - - try { - return eventHandler(); - } finally { - currentPriorityLevel = previousPriorityLevel; - } - } - - function unstable_wrapCallback(callback) { - var parentPriorityLevel = currentPriorityLevel; - return function () { - // This is a fork of runWithPriority, inlined for performance. - var previousPriorityLevel = currentPriorityLevel; - currentPriorityLevel = parentPriorityLevel; - - try { - return callback.apply(this, arguments); - } finally { - currentPriorityLevel = previousPriorityLevel; - } - }; - } - - function unstable_scheduleCallback(priorityLevel, callback, options) { - var currentTime = exports.unstable_now(); - var startTime; - - if (typeof options === 'object' && options !== null) { - var delay = options.delay; - - if (typeof delay === 'number' && delay > 0) { - startTime = currentTime + delay; - } else { - startTime = currentTime; - } - } else { - startTime = currentTime; - } - - var timeout; - - switch (priorityLevel) { - case ImmediatePriority: - timeout = IMMEDIATE_PRIORITY_TIMEOUT; - break; - - case UserBlockingPriority: - timeout = USER_BLOCKING_PRIORITY_TIMEOUT; - break; - - case IdlePriority: - timeout = IDLE_PRIORITY_TIMEOUT; - break; - - case LowPriority: - timeout = LOW_PRIORITY_TIMEOUT; - break; - - case NormalPriority: - default: - timeout = NORMAL_PRIORITY_TIMEOUT; - break; - } - - var expirationTime = startTime + timeout; - var newTask = { - id: taskIdCounter++, - callback: callback, - priorityLevel: priorityLevel, - startTime: startTime, - expirationTime: expirationTime, - sortIndex: -1 - }; - - if (startTime > currentTime) { - // This is a delayed task. - newTask.sortIndex = startTime; - push(timerQueue, newTask); - - if (peek(taskQueue) === null && newTask === peek(timerQueue)) { - // All tasks are delayed, and this is the task with the earliest delay. - if (isHostTimeoutScheduled) { - // Cancel an existing timeout. - cancelHostTimeout(); - } else { - isHostTimeoutScheduled = true; - } // Schedule a timeout. - - - requestHostTimeout(handleTimeout, startTime - currentTime); - } - } else { - newTask.sortIndex = expirationTime; - push(taskQueue, newTask); - // wait until the next time we yield. - - - if (!isHostCallbackScheduled && !isPerformingWork) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } - } - - return newTask; - } - - function unstable_pauseExecution() { - } - - function unstable_continueExecution() { - - if (!isHostCallbackScheduled && !isPerformingWork) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } - } - - function unstable_getFirstCallbackNode() { - return peek(taskQueue); - } - - function unstable_cancelCallback(task) { - // remove from the queue because you can't remove arbitrary nodes from an - // array based heap, only the first one.) - - - task.callback = null; - } - - function unstable_getCurrentPriorityLevel() { - return currentPriorityLevel; - } - - var isMessageLoopRunning = false; - var scheduledHostCallback = null; - var taskTimeoutID = -1; // Scheduler periodically yields in case there is other work on the main - // thread, like user events. By default, it yields multiple times per frame. - // It does not attempt to align with frame boundaries, since most tasks don't - // need to be frame aligned; for those that do, use requestAnimationFrame. - - var frameInterval = frameYieldMs; - var startTime = -1; - - function shouldYieldToHost() { - var timeElapsed = exports.unstable_now() - startTime; - - if (timeElapsed < frameInterval) { - // The main thread has only been blocked for a really short amount of time; - // smaller than a single frame. Don't yield yet. - return false; - } // The main thread has been blocked for a non-negligible amount of time. We - - - return true; - } - - function requestPaint() { - - } - - function forceFrameRate(fps) { - if (fps < 0 || fps > 125) { - // Using console['error'] to evade Babel and ESLint - console['error']('forceFrameRate takes a positive int between 0 and 125, ' + 'forcing frame rates higher than 125 fps is not supported'); - return; - } - - if (fps > 0) { - frameInterval = Math.floor(1000 / fps); - } else { - // reset the framerate - frameInterval = frameYieldMs; - } - } - - var performWorkUntilDeadline = function () { - if (scheduledHostCallback !== null) { - var currentTime = exports.unstable_now(); // Keep track of the start time so we can measure how long the main thread - // has been blocked. - - startTime = currentTime; - var hasTimeRemaining = true; // If a scheduler task throws, exit the current browser task so the - // error can be observed. - // - // Intentionally not using a try-catch, since that makes some debugging - // techniques harder. Instead, if `scheduledHostCallback` errors, then - // `hasMoreWork` will remain true, and we'll continue the work loop. - - var hasMoreWork = true; - - try { - hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime); - } finally { - if (hasMoreWork) { - // If there's more work, schedule the next message event at the end - // of the preceding one. - schedulePerformWorkUntilDeadline(); - } else { - isMessageLoopRunning = false; - scheduledHostCallback = null; - } - } - } else { - isMessageLoopRunning = false; - } // Yielding to the browser will give it a chance to paint, so we can - }; - - var schedulePerformWorkUntilDeadline; - - if (typeof localSetImmediate === 'function') { - // Node.js and old IE. - // There's a few reasons for why we prefer setImmediate. - // - // Unlike MessageChannel, it doesn't prevent a Node.js process from exiting. - // (Even though this is a DOM fork of the Scheduler, you could get here - // with a mix of Node.js 15+, which has a MessageChannel, and jsdom.) - // https://github.com/facebook/react/issues/20756 - // - // But also, it runs earlier which is the semantic we want. - // If other browsers ever implement it, it's better to use it. - // Although both of these would be inferior to native scheduling. - schedulePerformWorkUntilDeadline = function () { - localSetImmediate(performWorkUntilDeadline); - }; - } else if (typeof MessageChannel !== 'undefined') { - // DOM and Worker environments. - // We prefer MessageChannel because of the 4ms setTimeout clamping. - var channel = new MessageChannel(); - var port = channel.port2; - channel.port1.onmessage = performWorkUntilDeadline; - - schedulePerformWorkUntilDeadline = function () { - port.postMessage(null); - }; - } else { - // We should only fallback here in non-browser environments. - schedulePerformWorkUntilDeadline = function () { - localSetTimeout(performWorkUntilDeadline, 0); - }; - } - - function requestHostCallback(callback) { - scheduledHostCallback = callback; - - if (!isMessageLoopRunning) { - isMessageLoopRunning = true; - schedulePerformWorkUntilDeadline(); - } - } - - function requestHostTimeout(callback, ms) { - taskTimeoutID = localSetTimeout(function () { - callback(exports.unstable_now()); - }, ms); - } - - function cancelHostTimeout() { - localClearTimeout(taskTimeoutID); - taskTimeoutID = -1; - } - - var unstable_requestPaint = requestPaint; - var unstable_Profiling = null; - - exports.unstable_IdlePriority = IdlePriority; - exports.unstable_ImmediatePriority = ImmediatePriority; - exports.unstable_LowPriority = LowPriority; - exports.unstable_NormalPriority = NormalPriority; - exports.unstable_Profiling = unstable_Profiling; - exports.unstable_UserBlockingPriority = UserBlockingPriority; - exports.unstable_cancelCallback = unstable_cancelCallback; - exports.unstable_continueExecution = unstable_continueExecution; - exports.unstable_forceFrameRate = forceFrameRate; - exports.unstable_getCurrentPriorityLevel = unstable_getCurrentPriorityLevel; - exports.unstable_getFirstCallbackNode = unstable_getFirstCallbackNode; - exports.unstable_next = unstable_next; - exports.unstable_pauseExecution = unstable_pauseExecution; - exports.unstable_requestPaint = unstable_requestPaint; - exports.unstable_runWithPriority = unstable_runWithPriority; - exports.unstable_scheduleCallback = unstable_scheduleCallback; - exports.unstable_shouldYield = shouldYieldToHost; - exports.unstable_wrapCallback = unstable_wrapCallback; - /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ - if ( - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === - 'function' - ) { - __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); - } - - })(); - } - }(scheduler_development)); - - { - scheduler.exports = scheduler_development; - } - - /** - * @license React - * react-dom.development.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - - { - (function() { - - /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ - if ( - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === - 'function' - ) { - __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); - } - var React = React__default["default"]; - var Scheduler = scheduler.exports; - - var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; - - var suppressWarning = false; - function setSuppressWarning(newSuppressWarning) { - { - suppressWarning = newSuppressWarning; - } - } // In DEV, calls to console.warn and console.error get replaced - // by calls to these methods by a Babel plugin. - // - // In PROD (or in packages without access to React internals), - // they are left as they are instead. - - function warn(format) { - { - if (!suppressWarning) { - for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key]; - } - - printWarning('warn', format, args); - } - } - } - function error(format) { - { - if (!suppressWarning) { - for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - args[_key2 - 1] = arguments[_key2]; - } - - printWarning('error', format, args); - } - } - } - - function printWarning(level, format, args) { - // When changing this logic, you might want to also - // update consoleWithStackDev.www.js as well. - { - var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; - var stack = ReactDebugCurrentFrame.getStackAddendum(); - - if (stack !== '') { - format += '%s'; - args = args.concat([stack]); - } // eslint-disable-next-line react-internal/safe-string-coercion - - - var argsWithFormat = args.map(function (item) { - return String(item); - }); // Careful: RN currently depends on this prefix - - argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it - // breaks IE9: https://github.com/facebook/react/issues/13610 - // eslint-disable-next-line react-internal/no-production-logging - - Function.prototype.apply.call(console[level], console, argsWithFormat); - } - } - - var FunctionComponent = 0; - var ClassComponent = 1; - var IndeterminateComponent = 2; // Before we know whether it is function or class - - var HostRoot = 3; // Root of a host tree. Could be nested inside another node. - - var HostPortal = 4; // A subtree. Could be an entry point to a different renderer. - - var HostComponent = 5; - var HostText = 6; - var Fragment = 7; - var Mode = 8; - var ContextConsumer = 9; - var ContextProvider = 10; - var ForwardRef = 11; - var Profiler = 12; - var SuspenseComponent = 13; - var MemoComponent = 14; - var SimpleMemoComponent = 15; - var LazyComponent = 16; - var IncompleteClassComponent = 17; - var DehydratedFragment = 18; - var SuspenseListComponent = 19; - var ScopeComponent = 21; - var OffscreenComponent = 22; - var LegacyHiddenComponent = 23; - var CacheComponent = 24; - var TracingMarkerComponent = 25; - - // ----------------------------------------------------------------------------- - - var enableClientRenderFallbackOnTextMismatch = true; // TODO: Need to review this code one more time before landing - // the react-reconciler package. - - var enableNewReconciler = false; // Support legacy Primer support on internal FB www - - var enableLazyContextPropagation = false; // FB-only usage. The new API has different semantics. - - var enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber - - var enableSuspenseAvoidThisFallback = false; // Enables unstable_avoidThisFallback feature in Fizz - // React DOM Chopping Block - // - // Similar to main Chopping Block but only flags related to React DOM. These are - // grouped because we will likely batch all of them into a single major release. - // ----------------------------------------------------------------------------- - // Disable support for comment nodes as React DOM containers. Already disabled - // in open source, but www codebase still relies on it. Need to remove. - - var disableCommentsAsDOMContainers = true; // Disable javascript: URL strings in href for XSS protection. - // and client rendering, mostly to allow JSX attributes to apply to the custom - // element's object properties instead of only HTML attributes. - // https://github.com/facebook/react/issues/11347 - - var enableCustomElementPropertySupport = false; // Disables children for