Multiple taxonomy management #731

This commit is contained in:
Elio Struyf
2024-01-16 14:22:17 +01:00
parent 033baea418
commit 60c8dd6185
21 changed files with 333 additions and 154 deletions
+2
View File
@@ -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)
+7 -1
View File
@@ -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}.",
+6
View File
@@ -2180,6 +2180,12 @@
}
],
"menus": {
"webview/context": [
{
"command": "workbench.action.webview.openDeveloperTools",
"when": "frontMatter:isDevelopment"
}
],
"editor/title": [
{
"command": "frontMatter.markup.heading",
+2 -1
View File
@@ -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);
});
+2 -1
View File
@@ -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'
};
+1
View File
@@ -70,6 +70,7 @@ export enum DashboardMessage {
getTheme = 'getTheme',
updateSetting = 'updateSetting',
setState = 'setState',
getState = 'getState',
runCustomScript = 'runCustomScript',
sendTelemetry = 'sendTelemetry',
logError = 'logError',
@@ -32,7 +32,6 @@ export const AstroContentTypes: React.FunctionComponent<IAstroContentTypesProps>
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 {
@@ -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<ISortingProps> = ({
const [crntSorting, setCrntSorting] = useRecoilState(SortingAtom);
const searchValue = useRecoilValue(SearchSelector);
const settings = useRecoilValue(SettingsSelector);
const [crntSort, setCrntSort] = React.useState<SortingOption | null>(null);
const sortOptions: SortingOption[] = [
{
@@ -143,29 +144,37 @@ export const Sorting: React.FunctionComponent<ISortingProps> = ({
];
}
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 (
<div className="flex items-center">
@@ -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<ITaxonomyActionsProps> = (
<LinkButton
title={`Tag content`}
onClick={onTagging}>
<SquaresPlusIcon className={`w-4 h-4`} aria-hidden={true} />
<TagIcon className={`w-4 h-4`} aria-hidden={true} />
<span className="sr-only">{l10n.t(LocalizationKey.commonEdit)}</span>
</LinkButton>
@@ -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<ITaxonomyTaggingProps> = (
onDismiss,
}: React.PropsWithChildren<ITaxonomyTaggingProps>) => {
const settings = useRecoilValue(SettingsSelector);
const [selectedPages, setSelectedPages] = React.useState<Page[]>([]);
const [sortedPages, setSortedPages] = React.useState<Page[]>([]);
const [pageMappings, setPageMappings] = React.useState<{
tagged: Page[];
untagged: Page[];
}>({
tagged: [],
untagged: []
});
const [filterValue, setFilterValue] = React.useState('');
const debounceFilterValue = useDebounce<string>(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<ITaxonomyTaggingProps> = (
}
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 (
<div className={`py-6 px-4 flex flex-col h-full overflow-hidden`}>
<div className={`flex w-full justify-between flex-shrink-0`}>
<div className={`flex gap-2 items-center`}>
<button onClick={onDismiss} title='Back'>
<span className='sr-only'>Back</span>
<button onClick={onDismiss} title={l10n.t(LocalizationKey.commonBack)}>
<span className='sr-only'>{l10n.t(LocalizationKey.commonBack)}</span>
<ArrowLeftIcon className='w-5 h-5 text-[var(--frontmatter-text)]' />
</button>
<h2 className={`text-lg first-letter:uppercase text-[var(--frontmatter-text)]`}>
Map your content with: {value} <span className='ml-2'></span>
{l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingPageTitle, value)}
</h2>
</div>
<div className='flex gap-4 justify-center items-center'>
<FilterInput
placeholder='Filter'
placeholder={l10n.t(LocalizationKey.commonFilter)}
value={filterValue}
onChange={(value) => setFilterValue(value)}
onReset={() => setFilterValue('')} />
@@ -124,23 +171,27 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
scope="col"
className={`pr-6 py-3 text-left text-xs font-medium uppercase text-[var(--frontmatter-secondary-text)]`}
>
Name
{l10n.t(LocalizationKey.commonTitle)}
</th>
</tr>
</thead>
<tbody className={`divide-y divide-[var(--frontmatter-border)]`}>
{untaggedPages.map((page) => (
{untaggedPages && sortedPages && sortedPages.map((page) => (
<tr key={page.fmFilePath} className='py-2'>
<td className={`pl-6 w-[25px]`}>
<VSCodeCheckbox
title={l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingCheckbox, value)}
onClick={() => onCheckboxClick(page)}
checked={checkIfChecked(page)}>
<span className='sr-only'>Tag page</span>
<span className='sr-only'>
{l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingCheckbox, value)}
</span>
</VSCodeCheckbox>
</td>
<td className={`pr-6 whitespace-nowrap text-sm font-medium text-[var(--frontmatter-text)]`}>
<button
title={page.title}
title={l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingCheckbox, value)}
className='hover:text-[var(--vscode-textLink-activeForeground)]'
onClick={() => onCheckboxClick(page)}>
{page.title}
</button>
@@ -152,8 +203,8 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
</div>
<div className='flex justify-end space-x-2'>
<Button onClick={onDismiss} secondary>Cancel</Button>
<Button onClick={() => onContentMapping(value, selectedPages)}>Apply</Button>
<Button onClick={onDismiss} secondary>{l10n.t(LocalizationKey.commonCancel)}</Button>
<Button onClick={() => onContentMapping(value, pageMappings)}>{l10n.t(LocalizationKey.commonApply)}</Button>
</div>
</div>
);
@@ -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<ITaxonomyViewProps> = ({
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<ITaxonomyViewProps> = ({
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)} />
) : (
<TaxonomyManager
+23 -43
View File
@@ -14,12 +14,13 @@ import {
TabSelector,
TagSelector
} from '../state';
import { SortOrder, SortType } from '../../models';
import { Sorting } from '../../helpers/Sorting';
import { Messenger } from '@estruyf/vscode/dist/client';
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../DashboardMessage';
import { EventData } from '@estruyf/vscode/dist/models';
import { parseWinPath } from '../../helpers/parseWinPath';
import { sortPages } from '../../utils/sortPages';
import { ExtensionState } from '../../constants';
import { SortingOption } from '../models';
export default function usePages(pages: Page[]) {
const [pageItems, setPageItems] = useRecoilState(AllPagesAtom);
@@ -37,7 +38,7 @@ export default function usePages(pages: Page[]) {
* Process all the pages by applying the sorting, filtering and searching.
*/
const processPages = useCallback(
(searchedPages: Page[], fullProcess: boolean = true) => {
(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]);
@@ -0,0 +1,6 @@
import { Page } from './Page';
export interface PageMappings {
tagged: Page[];
untagged: Page[];
}
+1
View File
@@ -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';
+6 -1
View File
@@ -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
+16
View File
@@ -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();
+34 -33
View File
@@ -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));
@@ -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<any | undefined>(key, 'workspace');
this.sendRequest(command as any, requestId, { key, value });
}
}
}
+30 -12
View File
@@ -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() {
+21 -1
View File
@@ -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',
/**
+38
View File
@@ -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;
};