diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a2f8d9a..39f0ecbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### ✨ New features +- [#731](https://github.com/estruyf/vscode-front-matter/issues/731): Added the ability to map/unmap taxonomy to multiple pages at once + ### 🎨 Enhancements - [#727](https://github.com/estruyf/vscode-front-matter/pull/727): Updated Japanese translations thanks to [mayumihara](https://github.com/mayumih387) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 9d35445c..d3dfda6f 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -3,6 +3,7 @@ "common.edit": "Edit", "common.delete": "Delete", "common.cancel": "Cancel", + "common.apply": "Apply", "common.clear": "Clear", "common.clear.value": "Clear value", "common.search": "Search", @@ -20,6 +21,7 @@ "common.slug": "Slug", "common.support": "Support", "common.remove.value": "Remove {0}", + "common.filter": "Back", "common.filter.value": "Filter by {0}", "common.error.message": "Sorry, something went wrong.", "common.openOnWebsite": "Open on website", @@ -32,6 +34,7 @@ "common.yes": "yes", "common.no": "no", "common.openSettings": "Open settings", + "common.back": "Back", "notifications.outputChannel.link": "output window", "notifications.outputChannel.description": "Check the {0} for more details.", @@ -296,6 +299,9 @@ "dashboard.taxonomyView.taxonomyManager.table.unmapped.title": "Missing in your settings", "dashboard.taxonomyView.taxonomyManager.filterInput.placeholder": "Filter", + "dashboard.taxonomyView.taxonomyTagging.pageTitle": "Map your content with: {0}", + "dashboard.taxonomyView.taxonomyTagging.checkbox": "Tag page with {0}", + "dashboard.taxonomyView.taxonomyView.navigationBar.title": "Select the taxonomy", "dashboard.taxonomyView.taxonomyView.button.import": "Import taxonomy", "dashboard.taxonomyView.taxonomyView.navigationItem.tags": "Tags", @@ -662,7 +668,7 @@ "helpers.taxonomyHelper.createNew.input.placeholder": "Enter the value you want to add", "helpers.taxonomyHelper.createNew.input.validate.noValue": "A value must be provided.", "helpers.taxonomyHelper.createNew.input.validate.exists": "The value already exists.", - "helpers.taxonomyHelper.process.insert": "{0}: Renaming \"{1}\" from {2} to {3}.", + "helpers.taxonomyHelper.process.insert": "{0}: Inserting \"{1}\" to your selected pages.", "helpers.taxonomyHelper.process.edit": "{0}: Renaming \"{1}\" from {2} to {3}.", "helpers.taxonomyHelper.process.merge": "{0}: Merging \"{1}\" from {2} to {3}.", "helpers.taxonomyHelper.process.delete": "{0}: Deleting \"{1}\" from {2}.", diff --git a/package.json b/package.json index b56558ba..8957161e 100644 --- a/package.json +++ b/package.json @@ -2180,6 +2180,12 @@ } ], "menus": { + "webview/context": [ + { + "command": "workbench.action.webview.openDeveloperTools", + "when": "frontMatter:isDevelopment" + } + ], "editor/title": [ { "command": "frontMatter.markup.heading", diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index a1286500..7b5001bb 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -7,7 +7,7 @@ import { } from '../constants'; import { join } from 'path'; import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from 'vscode'; -import { Logger, Settings as SettingsHelper } from '../helpers'; +import { DashboardSettings, Logger, Settings as SettingsHelper } from '../helpers'; import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; import { Extension } from '../helpers/Extension'; import { WebviewHelper } from '@estruyf/vscode'; @@ -160,6 +160,7 @@ export class Dashboard { Dashboard.isDisposed = true; Dashboard._viewData = undefined; PanelMediaListener.getMediaSelection(); + DashboardSettings.updateAfterClose(); await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false); }); diff --git a/src/constants/context.ts b/src/constants/context.ts index 3fc67cda..9b68b2aa 100644 --- a/src/constants/context.ts +++ b/src/constants/context.ts @@ -6,6 +6,7 @@ export const CONTEXT = { wysiwyg: 'frontMatter:markdown:wysiwyg', backer: 'frontMatter:backers:supporter', isValidFile: 'frontMatter:file:isValid', + isDevelopment: 'frontMatter:isDevelopment', hasViewModes: 'frontMatter:has:modes', @@ -14,5 +15,5 @@ export const CONTEXT = { isGitEnabled: 'frontMatter:git:enabled', - projectSwitchEnabled: 'frontMatter:project:switch:enabled', + projectSwitchEnabled: 'frontMatter:project:switch:enabled' }; diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index 335160bc..ab53e96d 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -70,6 +70,7 @@ export enum DashboardMessage { getTheme = 'getTheme', updateSetting = 'updateSetting', setState = 'setState', + getState = 'getState', runCustomScript = 'runCustomScript', sendTelemetry = 'sendTelemetry', logError = 'logError', diff --git a/src/dashboardWebView/components/Configuration/Astro/AstroContentTypes.tsx b/src/dashboardWebView/components/Configuration/Astro/AstroContentTypes.tsx index a40c4652..75bf52a2 100644 --- a/src/dashboardWebView/components/Configuration/Astro/AstroContentTypes.tsx +++ b/src/dashboardWebView/components/Configuration/Astro/AstroContentTypes.tsx @@ -32,7 +32,6 @@ export const AstroContentTypes: React.FunctionComponent if (collections.length > 0 && settings?.contentTypes?.length > 0) { // Find created content types from the collections const astroCollection = collections.find(c => settings.contentTypes.find((ct) => ct.name === c.name)); - console.log(`astroCollection`, astroCollection) if (astroCollection) { setStatus(Status.Completed); } else { diff --git a/src/dashboardWebView/components/Header/Sorting.tsx b/src/dashboardWebView/components/Header/Sorting.tsx index b1eb0df6..9249fe13 100644 --- a/src/dashboardWebView/components/Header/Sorting.tsx +++ b/src/dashboardWebView/components/Header/Sorting.tsx @@ -1,4 +1,4 @@ -import { Messenger } from '@estruyf/vscode/dist/client'; +import { Messenger, messageHandler } from '@estruyf/vscode/dist/client'; import { Menu } from '@headlessui/react'; import * as React from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; @@ -26,6 +26,7 @@ export const Sorting: React.FunctionComponent = ({ const [crntSorting, setCrntSorting] = useRecoilState(SortingAtom); const searchValue = useRecoilValue(SearchSelector); const settings = useRecoilValue(SettingsSelector); + const [crntSort, setCrntSort] = React.useState(null); const sortOptions: SortingOption[] = [ { @@ -143,29 +144,37 @@ export const Sorting: React.FunctionComponent = ({ ]; } - let crntSortingOption = crntSorting; - if (!crntSortingOption) { - if (view === NavigationType.Contents) { - crntSortingOption = settings?.dashboardState?.contents?.sorting || null; - } else if (view === NavigationType.Media) { - crntSortingOption = settings?.dashboardState?.media?.sorting || null; - } + React.useEffect(() => { + const getSorting = async () => { + let crntSortingOption = crntSorting; + if (!crntSortingOption) { + const sortingState = await messageHandler.request<{ key: string; value: SortingOption; }>(DashboardMessage.getState, { key: view === NavigationType.Media ? ExtensionState.Dashboard.Media.Sorting : ExtensionState.Dashboard.Contents.Sorting }); + crntSortingOption = sortingState?.value || null; - if (crntSortingOption === null) { - if (view === NavigationType.Contents && settings?.dashboardState.contents.defaultSorting) { - crntSortingOption = - allOptions.find((f) => f.id === settings?.dashboardState.contents.defaultSorting) || null; - } else if ( - view === NavigationType.Media && - settings?.dashboardState.contents.defaultSorting - ) { - crntSortingOption = - allOptions.find((f) => f.id === settings?.dashboardState.contents.defaultSorting) || null; + if (crntSortingOption === null) { + if (view === NavigationType.Contents && settings?.dashboardState.contents.defaultSorting) { + crntSortingOption = + allOptions.find((f) => f.id === settings?.dashboardState.contents.defaultSorting) || null; + } else if ( + view === NavigationType.Media && + settings?.dashboardState.media.defaultSorting + ) { + crntSortingOption = + allOptions.find((f) => f.id === settings?.dashboardState.media.defaultSorting) || null; + } + } } - } - } - let crntSort = allOptions.find((x) => x.id === crntSortingOption?.id) || sortOptions[0]; + let sort = allOptions.find((x) => x.id === crntSortingOption?.id) || sortOptions[0]; + setCrntSort(sort); + }; + + getSorting(); + }, [crntSorting]); + + if (crntSort === null) { + return null; + } return (
diff --git a/src/dashboardWebView/components/TaxonomyView/TaxonomyActions.tsx b/src/dashboardWebView/components/TaxonomyView/TaxonomyActions.tsx index 261c05f5..fe8e4897 100644 --- a/src/dashboardWebView/components/TaxonomyView/TaxonomyActions.tsx +++ b/src/dashboardWebView/components/TaxonomyView/TaxonomyActions.tsx @@ -3,7 +3,7 @@ import { ArrowUpCircleIcon, PencilIcon, PlusIcon, - SquaresPlusIcon, + TagIcon, TrashIcon, } from '@heroicons/react/24/outline'; import * as React from 'react'; @@ -83,7 +83,7 @@ export const TaxonomyActions: React.FunctionComponent = ( - + {l10n.t(LocalizationKey.commonEdit)} diff --git a/src/dashboardWebView/components/TaxonomyView/TaxonomyTagging.tsx b/src/dashboardWebView/components/TaxonomyView/TaxonomyTagging.tsx index 459eec2f..7fff6ee1 100644 --- a/src/dashboardWebView/components/TaxonomyView/TaxonomyTagging.tsx +++ b/src/dashboardWebView/components/TaxonomyView/TaxonomyTagging.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import { Page } from '../../models'; +import { Page, PageMappings, SortingOption } from '../../models'; import { useRecoilValue } from 'recoil'; -import { SettingsSelector } from '../../state'; +import { SettingsSelector, SortingAtom } from '../../state'; import { getTaxonomyField } from '../../../helpers/getTaxonomyField'; import { Sorting } from '../../../helpers/Sorting'; import { ArrowLeftIcon } from '@heroicons/react/24/outline'; @@ -9,12 +9,18 @@ import { Button } from '../Common/Button'; import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react'; import { FilterInput } from './FilterInput'; import { useDebounce } from '../../../hooks/useDebounce'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../../../localization'; +import { sortPages } from '../../../utils/sortPages'; +import { messageHandler } from '@estruyf/vscode/dist/client'; +import { DashboardMessage } from '../../DashboardMessage'; +import { ExtensionState } from '../../../constants'; export interface ITaxonomyTaggingProps { taxonomy: string | null; value: string; pages: Page[]; - onContentMapping: (value: string, pages: Page[]) => void; + onContentMapping: (value: string, pageMappings: PageMappings) => void; onDismiss: () => void; } @@ -26,33 +32,25 @@ export const TaxonomyTagging: React.FunctionComponent = ( onDismiss, }: React.PropsWithChildren) => { const settings = useRecoilValue(SettingsSelector); - const [selectedPages, setSelectedPages] = React.useState([]); + const [sortedPages, setSortedPages] = React.useState([]); + const [pageMappings, setPageMappings] = React.useState<{ + tagged: Page[]; + untagged: Page[]; + }>({ + tagged: [], + untagged: [] + }); const [filterValue, setFilterValue] = React.useState(''); const debounceFilterValue = useDebounce(filterValue, 500); - const onCheckboxClick = React.useCallback((page: Page) => { - const pageIdx = selectedPages.findIndex((p: Page) => p.fmFilePath === page.fmFilePath); - if (pageIdx === -1) { - setSelectedPages([...selectedPages, page]); - } else { - const newSelectedPages = [...selectedPages]; - newSelectedPages.splice(pageIdx, 1); - setSelectedPages(newSelectedPages); - } - }, [selectedPages]); - - const checkIfChecked = React.useCallback((page: Page) => { - return selectedPages.find((p: Page) => p.fmFilePath === page.fmFilePath); - }, [selectedPages]); - const untaggedPages = React.useMemo(() => { let untagged: Page[] = []; - if (!pages || !settings?.contentTypes || !taxonomy) { + if (!sortedPages || !settings?.contentTypes || !taxonomy) { return untagged; } - for (const page of pages) { + for (const page of sortedPages) { if (taxonomy === 'tags') { if (!page.fmTags || page.fmTags.indexOf(value) === -1) { untagged.push(page); @@ -85,25 +83,74 @@ export const TaxonomyTagging: React.FunctionComponent = ( } return untagged; - }, [pages, taxonomy, value, debounceFilterValue]); + }, [sortedPages, taxonomy, value, debounceFilterValue]); - console.log(`Selected pages`, selectedPages) + const onCheckboxClick = React.useCallback((page: Page) => { + const untaggedPage = untaggedPages.find((p: Page) => p.fmFilePath === page.fmFilePath); + + const clonedPageMappings = Object.assign({}, pageMappings); + const taggedIdx = pageMappings.tagged.findIndex((p: Page) => p.fmFilePath === page.fmFilePath); + const untaggedIdx = pageMappings.untagged.findIndex((p: Page) => p.fmFilePath === page.fmFilePath); + + if (untaggedPage) { + // Page was not yet tagged + if (taggedIdx === -1) { + clonedPageMappings.tagged.push(page); + + if (untaggedIdx !== -1) { + clonedPageMappings.untagged.splice(untaggedIdx, 1); + } + } else { + clonedPageMappings.tagged.splice(taggedIdx, 1); + + if (untaggedIdx === -1) { + clonedPageMappings.untagged.push(page); + } + } + } else { + // Page was already tagged, so only the untagged array needs to be updated + if (untaggedIdx === -1) { + clonedPageMappings.untagged.push(page); + } else { + clonedPageMappings.untagged.splice(taggedIdx, 1); + } + } + + setPageMappings(clonedPageMappings); + }, [pageMappings, untaggedPages]); + + const checkIfChecked = React.useCallback((page: Page) => { + return (!untaggedPages.find((p: Page) => p.fmFilePath === page.fmFilePath) && !pageMappings.untagged.find((p: Page) => p.fmFilePath === page.fmFilePath)) || pageMappings.tagged.find((p: Page) => p.fmFilePath === page.fmFilePath); + }, [untaggedPages, pageMappings.tagged]); + + React.useEffect(() => { + messageHandler.request<{ key: string; value: SortingOption; }>(DashboardMessage.getState, { + key: ExtensionState.Dashboard.Contents.Sorting + }).then(({ key, value }) => { + if (key === ExtensionState.Dashboard.Contents.Sorting && value) { + const sorted = sortPages(pages, value) + setSortedPages(sorted); + } else { + setSortedPages(pages); + } + }); + }, [pages]); return (
-

- Map your content with: {value} + {l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingPageTitle, value)}

setFilterValue(value)} onReset={() => setFilterValue('')} /> @@ -124,23 +171,27 @@ export const TaxonomyTagging: React.FunctionComponent = ( scope="col" className={`pr-6 py-3 text-left text-xs font-medium uppercase text-[var(--frontmatter-secondary-text)]`} > - Name + {l10n.t(LocalizationKey.commonTitle)} - {untaggedPages.map((page) => ( + {untaggedPages && sortedPages && sortedPages.map((page) => ( onCheckboxClick(page)} checked={checkIfChecked(page)}> - Tag page + + {l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingCheckbox, value)} + @@ -152,8 +203,8 @@ export const TaxonomyTagging: React.FunctionComponent = (
- - + +
); diff --git a/src/dashboardWebView/components/TaxonomyView/TaxonomyView.tsx b/src/dashboardWebView/components/TaxonomyView/TaxonomyView.tsx index 2ec6bb4d..b92671a4 100644 --- a/src/dashboardWebView/components/TaxonomyView/TaxonomyView.tsx +++ b/src/dashboardWebView/components/TaxonomyView/TaxonomyView.tsx @@ -6,7 +6,7 @@ import { useRecoilValue } from 'recoil'; import { TelemetryEvent } from '../../../constants'; import { TaxonomyData } from '../../../models'; import { DashboardMessage } from '../../DashboardMessage'; -import { Page } from '../../models'; +import { Page, PageMappings } from '../../models'; import { SettingsSelector } from '../../state'; import { NavigationBar, NavigationItem } from '../Layout'; import { PageLayout } from '../Layout/PageLayout'; @@ -32,13 +32,15 @@ export const TaxonomyView: React.FunctionComponent = ({ Messenger.send(DashboardMessage.importTaxonomy); }; - const onContentMapping = React.useCallback((value: string, pages: Page[]) => { + const onContentMapping = React.useCallback((value: string, pageMappings: PageMappings) => { messageHandler.request(DashboardMessage.mapTaxonomy, { taxonomy: selectedTaxonomy, value, - pages + pageMappings }).then(() => { setContentTagging(null); + }).catch(() => { + setContentTagging(null); }); }, [selectedTaxonomy]); @@ -117,7 +119,7 @@ export const TaxonomyView: React.FunctionComponent = ({ value={contentTagging} taxonomy={selectedTaxonomy} pages={pages} - onContentMapping={(value: string, pages: Page[]) => onContentMapping(value, pages)} + onContentMapping={(value: string, pageMappings: PageMappings) => onContentMapping(value, pageMappings)} onDismiss={() => setContentTagging(null)} /> ) : ( { + (searchedPages: Page[]) => { const framework = settings?.crntFramework; // Filter the pages @@ -67,35 +68,7 @@ export default function usePages(pages: Page[]) { // Sort the pages let pagesSorted: Page[] = Object.assign([], pagesToShow); if (!search) { - if (sorting && sorting.id === SortOption.FileNameAsc) { - pagesSorted = pagesSorted.sort(Sorting.alphabetically('fmFileName')); - } else if (sorting && sorting.id === SortOption.FileNameDesc) { - pagesSorted = pagesSorted.sort(Sorting.alphabetically('fmFileName')).reverse(); - } else if (sorting && sorting.id === SortOption.PublishedAsc) { - pagesSorted = pagesSorted.sort(Sorting.number('fmPublished')); - } else if (sorting && sorting.id === SortOption.LastModifiedAsc) { - pagesSorted = pagesSorted.sort(Sorting.number('fmModified')); - } else if (sorting && sorting.id === SortOption.PublishedDesc) { - pagesSorted = pagesSorted.sort(Sorting.number('fmPublished')).reverse(); - } else if (sorting && sorting.id === SortOption.LastModifiedDesc) { - pagesSorted = pagesSorted.sort(Sorting.number('fmModified')).reverse(); - } else if (sorting && sorting.id && sorting.name) { - const { order, name, type } = sorting; - - if (type === SortType.string) { - pagesSorted = pagesSorted.sort(Sorting.alphabetically(name)); - } else if (type === SortType.date) { - pagesSorted = pagesSorted.sort(Sorting.date(name)); - } else if (type === SortType.number) { - pagesSorted = pagesSorted.sort(Sorting.number(name)); - } - - if (order === SortOrder.desc) { - pagesSorted = pagesSorted.reverse(); - } - } else { - pagesSorted = pagesSorted.sort(Sorting.number('fmModified')).reverse(); - } + pagesSorted = sortPages(pagesSorted, sorting); } if (folder) { @@ -208,20 +181,27 @@ export default function usePages(pages: Page[]) { useEffect(() => { let usedSorting = sorting; - if (!usedSorting) { - const lastSort = settings?.dashboardState.contents.sorting; - if (lastSort) { - setSorting(lastSort); - return; + const startPageProcessing = () => { + // Check if search needs to be performed + let searchedPages = pages; + if (search) { + Messenger.send(DashboardMessage.searchPages, { query: search }); + } else { + processPages(searchedPages); } } - // Check if search needs to be performed - let searchedPages = pages; - if (search) { - Messenger.send(DashboardMessage.searchPages, { query: search }); + if (!usedSorting) { + messageHandler.request<{ key: string; value: SortingOption; }>(DashboardMessage.getState, { + key: ExtensionState.Dashboard.Contents.Sorting + }).then(({ key, value }) => { + if (key === ExtensionState.Dashboard.Contents.Sorting && value) { + setSorting(value); + return; + } + }); } else { - processPages(searchedPages); + startPageProcessing(); } }, [settings?.draftField, pages, sorting, search, tag, category, folder]); diff --git a/src/dashboardWebView/models/PageMappings.ts b/src/dashboardWebView/models/PageMappings.ts new file mode 100644 index 00000000..8c15023a --- /dev/null +++ b/src/dashboardWebView/models/PageMappings.ts @@ -0,0 +1,6 @@ +import { Page } from './Page'; + +export interface PageMappings { + tagged: Page[]; + untagged: Page[]; +} diff --git a/src/dashboardWebView/models/index.ts b/src/dashboardWebView/models/index.ts index 06a24ac9..d64691fa 100644 --- a/src/dashboardWebView/models/index.ts +++ b/src/dashboardWebView/models/index.ts @@ -1,6 +1,7 @@ export * from './DashboardViewType'; export * from './NavigationType'; export * from './Page'; +export * from './PageMappings'; export * from './Settings'; export * from './SortingOption'; export * from './Status'; diff --git a/src/extension.ts b/src/extension.ts index f88b54d7..80c3b6a2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,6 @@ import { GitListener } from './listeners/general/GitListener'; import * as vscode from 'vscode'; -import { COMMAND_NAME, EXTENSION_NAME, TelemetryEvent } from './constants'; +import { COMMAND_NAME, CONTEXT, EXTENSION_NAME, TelemetryEvent } from './constants'; import { MarkdownFoldingProvider } from './providers/MarkdownFoldingProvider'; import { TagType } from './panelWebView/TagType'; import { PanelProvider } from './panelWebView/PanelProvider'; @@ -46,6 +46,11 @@ export async function activate(context: vscode.ExtensionContext) { const { subscriptions, extensionUri, extensionPath } = context; const extension = Extension.getInstance(context); + // Set development context + if (!Extension.getInstance().isProductionMode) { + vscode.commands.executeCommand('setContext', CONTEXT.isDevelopment, true); + } + Backers.init(context).then(() => {}); // Make sure the EN language file is loaded diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 64e1d617..fc277e2c 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -59,6 +59,22 @@ export class DashboardSettings { return this.cachedSettings; } + public static async updateAfterClose() { + if (this.cachedSettings) { + this.cachedSettings = await this.getSettings(); + + const ext = Extension.getInstance(); + + // Update states + this.cachedSettings.dashboardState.contents.sorting = await ext.getState< + SortingOption | undefined + >(ExtensionState.Dashboard.Contents.Sorting, 'workspace'); + this.cachedSettings.dashboardState.media.sorting = await ext.getState< + SortingOption | undefined + >(ExtensionState.Dashboard.Media.Sorting, 'workspace'); + } + } + public static async getSettings() { const ext = Extension.getInstance(); const wsFolder = Folders.getWorkspaceFolder(); diff --git a/src/helpers/TaxonomyHelper.ts b/src/helpers/TaxonomyHelper.ts index 28a97b7f..b8dc5ad9 100644 --- a/src/helpers/TaxonomyHelper.ts +++ b/src/helpers/TaxonomyHelper.ts @@ -25,6 +25,7 @@ import { SettingsListener as DashboardSettingsListener } from '../listeners/dash import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; import { Page } from '../dashboardWebView/models'; +import { Logger } from './Logger'; export class TaxonomyHelper { private static db: JsonDB; @@ -255,22 +256,26 @@ export class TaxonomyHelper { } /** - * Process the taxonomy changes - * @param type - * @param taxonomyType - * @param oldValue - * @param newValue - * @returns + * Processes the taxonomy changes based on the specified type. + * @param type - The type of taxonomy change ('insert', 'edit', 'merge', 'delete'). + * @param taxonomyType - The type of taxonomy ('Tag', 'Category', or custom taxonomy name). + * @param oldValue - The old value of the taxonomy. + * @param newValue - The new value of the taxonomy (optional). + * @param pages - The array of Page objects (optional). + * @param needsSettingsUpdate - Indicates whether the settings need to be updated (default: true). */ public static async process( type: 'insert' | 'edit' | 'merge' | 'delete', taxonomyType: TaxonomyType | string, oldValue: string, newValue?: string, - pages?: Page[] + pages?: Page[], + needsSettingsUpdate: boolean = true ) { // Retrieve all the markdown files - const allFiles = type === 'insert' ? pages : await FilesHelper.getAllFiles(); + const allFiles = pages + ? pages?.map((p) => ({ fsPath: p.fmFilePath })) + : await FilesHelper.getAllFiles(); if (!allFiles) { return; } @@ -290,7 +295,6 @@ export class TaxonomyHelper { progressText = l10n.t( LocalizationKey.helpersTaxonomyHelperProcessInsert, EXTENSION_NAME, - taxonomyName, newValue || '' ); } else if (type === 'edit') { @@ -333,8 +337,7 @@ export class TaxonomyHelper { for (const file of allFiles) { progress.report({ increment: ++i / progressNr }); - const filePath = type === 'insert' ? (file as Page).fmFilePath : file.fsPath; - const mdFile = await readFileAsync(parseWinPath(filePath), { + const mdFile = await readFileAsync(parseWinPath(file.fsPath), { encoding: 'utf8' }); @@ -352,8 +355,7 @@ export class TaxonomyHelper { taxonomies = taxonomies.split(`,`); } - const spaces = window.activeTextEditor?.options?.tabSize; - + let needsFileUpdate = false; if (type === 'insert' && newValue) { if (taxonomies && taxonomies.length > 0) { taxonomies.push(newValue); @@ -363,15 +365,7 @@ export class TaxonomyHelper { const newTaxValue = [...new Set(taxonomies)].sort(); ContentType.setFieldValue(data, fieldNames, newTaxValue); - - // Update the file - await writeFileAsync( - parseWinPath(filePath), - FrontMatterParser.toFile(article.content, article.data, mdFile, { - indent: spaces || 2 - } as DumpOptions as any), - { encoding: 'utf8' } - ); + needsFileUpdate = true; } else if (type !== 'insert' && taxonomies && taxonomies.length > 0) { const idx = taxonomies.findIndex((o) => o === oldValue); @@ -384,25 +378,32 @@ export class TaxonomyHelper { const newTaxValue = [...new Set(taxonomies)].sort(); ContentType.setFieldValue(data, fieldNames, newTaxValue); - - // Update the file - await writeFileAsync( - parseWinPath(file.fsPath), - FrontMatterParser.toFile(article.content, article.data, mdFile, { - indent: spaces || 2 - } as DumpOptions as any), - { encoding: 'utf8' } - ); + needsFileUpdate = true; } } + + // Update the file when needed + if (needsFileUpdate) { + const spaces = window.activeTextEditor?.options?.tabSize; + + await writeFileAsync( + parseWinPath(file.fsPath), + FrontMatterParser.toFile(article.content, article.data, mdFile, { + indent: spaces || 2 + } as DumpOptions as any), + { encoding: 'utf8' } + ); + } } } catch (e) { - // Continue with the next file + Logger.error(`Failed to ${type} taxonomy value in ${file.fsPath}`); } } } - await this.addToSettings(taxonomyType, oldValue, newValue); + if (needsSettingsUpdate) { + await this.addToSettings(taxonomyType, oldValue, newValue); + } if (type === 'insert') { Notifications.info(l10n.t(LocalizationKey.helpersTaxonomyHelperProcessInsertSuccess)); diff --git a/src/listeners/dashboard/ExtensionListener.ts b/src/listeners/dashboard/ExtensionListener.ts index af7b5eb5..9a594d1a 100644 --- a/src/listeners/dashboard/ExtensionListener.ts +++ b/src/listeners/dashboard/ExtensionListener.ts @@ -27,6 +27,9 @@ export class ExtensionListener extends BaseListener { case DashboardMessage.setState: this.setState(msg?.payload); break; + case DashboardMessage.getState: + this.getState(msg.command, msg?.payload, msg.requestId); + break; } } @@ -36,4 +39,17 @@ export class ExtensionListener extends BaseListener { Extension.getInstance().setState(key, value, 'workspace'); } } + + private static async getState(command: string, data: any, requestId?: string) { + if (!command || !requestId || !data) { + return; + } + + const { key } = data; + if (key) { + const value = await Extension.getInstance().getState(key, 'workspace'); + + this.sendRequest(command as any, requestId, { key, value }); + } + } } diff --git a/src/listeners/dashboard/TaxonomyListener.ts b/src/listeners/dashboard/TaxonomyListener.ts index ca59e365..e566293e 100644 --- a/src/listeners/dashboard/TaxonomyListener.ts +++ b/src/listeners/dashboard/TaxonomyListener.ts @@ -5,7 +5,7 @@ import { DashboardMessage } from '../../dashboardWebView/DashboardMessage'; import { TaxonomyHelper } from '../../helpers'; import { PostMessageData } from '../../models'; import { BaseListener } from './BaseListener'; -import { Page } from '../../dashboardWebView/models'; +import { Page, PageMappings } from '../../dashboardWebView/models'; export class TaxonomyListener extends BaseListener { /** @@ -49,25 +49,43 @@ export class TaxonomyListener extends BaseListener { private static async mapTaxonomy({ command, requestId, - payload: { taxonomy, value, pages } + payload: { taxonomy, value, pageMappings } }: { command: string; requestId?: string; - payload: { taxonomy: string; value: string; pages: Page[] }; + payload: { taxonomy: string; value: string; pageMappings: PageMappings }; }) { - if (!command || !requestId || !taxonomy || !value || !pages) { + if (!command || !requestId || !taxonomy || !value || !pageMappings) { return; } - await TaxonomyHelper.process( - 'insert', - TaxonomyHelper.getTypeFromString(taxonomy), - '', - value, - pages - ); + try { + if (pageMappings.tagged.length > 0) { + await TaxonomyHelper.process( + 'insert', + TaxonomyHelper.getTypeFromString(taxonomy), + '', + value, + pageMappings.tagged, + false + ); + } - this.sendRequest(command as any, requestId, {}); + if (pageMappings.untagged.length > 0) { + await TaxonomyHelper.process( + 'delete', + TaxonomyHelper.getTypeFromString(taxonomy), + value, + '', + pageMappings.untagged, + false + ); + } + + this.sendRequest(command as any, requestId, {}); + } catch (e) { + this.sendError(command as any, requestId, e); + } } private static getData() { diff --git a/src/localization/localization.enum.ts b/src/localization/localization.enum.ts index 2d4fc0d8..c8062bd1 100644 --- a/src/localization/localization.enum.ts +++ b/src/localization/localization.enum.ts @@ -15,6 +15,10 @@ export enum LocalizationKey { * Cancel */ commonCancel = 'common.cancel', + /** + * Apply + */ + commonApply = 'common.apply', /** * Clear */ @@ -83,6 +87,10 @@ export enum LocalizationKey { * Remove {0} */ commonRemoveValue = 'common.remove.value', + /** + * Back + */ + commonFilter = 'common.filter', /** * Filter by {0} */ @@ -131,6 +139,10 @@ export enum LocalizationKey { * Open settings */ commonOpenSettings = 'common.openSettings', + /** + * Back + */ + commonBack = 'common.back', /** * output window */ @@ -979,6 +991,14 @@ export enum LocalizationKey { * Filter */ dashboardTaxonomyViewTaxonomyManagerFilterInputPlaceholder = 'dashboard.taxonomyView.taxonomyManager.filterInput.placeholder', + /** + * Map your content with: {0} + */ + dashboardTaxonomyViewTaxonomyTaggingPageTitle = 'dashboard.taxonomyView.taxonomyTagging.pageTitle', + /** + * Tag page with {0} + */ + dashboardTaxonomyViewTaxonomyTaggingCheckbox = 'dashboard.taxonomyView.taxonomyTagging.checkbox', /** * Select the taxonomy */ @@ -2177,7 +2197,7 @@ export enum LocalizationKey { */ helpersTaxonomyHelperCreateNewInputValidateExists = 'helpers.taxonomyHelper.createNew.input.validate.exists', /** - * {0}: Renaming "{1}" from {2} to {3}. + * {0}: Inserting "{1}" to your selected pages. */ helpersTaxonomyHelperProcessInsert = 'helpers.taxonomyHelper.process.insert', /** diff --git a/src/utils/sortPages.ts b/src/utils/sortPages.ts new file mode 100644 index 00000000..81d1a2b3 --- /dev/null +++ b/src/utils/sortPages.ts @@ -0,0 +1,38 @@ +import { SortOption } from '../dashboardWebView/constants/SortOption'; +import { Page, SortingOption } from '../dashboardWebView/models'; +import { Sorting } from '../helpers/Sorting'; +import { SortOrder, SortType } from '../models'; + +export const sortPages = (pages: Page[], sorting: SortingOption | null) => { + if (sorting && sorting.id === SortOption.FileNameAsc) { + pages = pages.sort(Sorting.alphabetically('fmFileName')); + } else if (sorting && sorting.id === SortOption.FileNameDesc) { + pages = pages.sort(Sorting.alphabetically('fmFileName')).reverse(); + } else if (sorting && sorting.id === SortOption.PublishedAsc) { + pages = pages.sort(Sorting.number('fmPublished')); + } else if (sorting && sorting.id === SortOption.LastModifiedAsc) { + pages = pages.sort(Sorting.number('fmModified')); + } else if (sorting && sorting.id === SortOption.PublishedDesc) { + pages = pages.sort(Sorting.number('fmPublished')).reverse(); + } else if (sorting && sorting.id === SortOption.LastModifiedDesc) { + pages = pages.sort(Sorting.number('fmModified')).reverse(); + } else if (sorting && sorting.id && sorting.name) { + const { order, name, type } = sorting; + + if (type === SortType.string) { + pages = pages.sort(Sorting.alphabetically(name)); + } else if (type === SortType.date) { + pages = pages.sort(Sorting.date(name)); + } else if (type === SortType.number) { + pages = pages.sort(Sorting.number(name)); + } + + if (order === SortOrder.desc) { + pages = pages.reverse(); + } + } else { + pages = pages.sort(Sorting.number('fmModified')).reverse(); + } + + return pages; +};