mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-07 09:23:00 +02:00
Content tagging
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { Fragment } from 'react';
|
||||
import useThemeColors from '../../hooks/useThemeColors';
|
||||
|
||||
export interface IInfoDialogProps {
|
||||
icon?: JSX.Element;
|
||||
@@ -17,7 +16,6 @@ export const InfoDialog: React.FunctionComponent<IInfoDialogProps> = ({
|
||||
description,
|
||||
children
|
||||
}: React.PropsWithChildren<IInfoDialogProps>) => {
|
||||
const { getColors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<Transition.Root show={true} as={Fragment}>
|
||||
@@ -32,11 +30,7 @@ export const InfoDialog: React.FunctionComponent<IInfoDialogProps> = ({
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Overlay className={`fixed inset-0 transition-opacity ${getColors(
|
||||
`bg-vulcan-500 bg-opacity-75`,
|
||||
`bg-[var(--vscode-editor-background)] opacity-75`
|
||||
)
|
||||
}`} />
|
||||
<Dialog.Overlay className={`fixed inset-0 transition-opacity bg-[var(--vscode-editor-background)] opacity-75`} />
|
||||
</Transition.Child>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
@@ -53,31 +47,22 @@ export const InfoDialog: React.FunctionComponent<IInfoDialogProps> = ({
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div className={`inline-block align-bottom rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6 border-2 ${getColors(
|
||||
`bg-white dark:bg-vulcan-500 border-whisper-900`,
|
||||
`bg-[var(--vscode-editor-background)] border-[var(--frontmatter-border)]`
|
||||
)
|
||||
}`}>
|
||||
<div className={`inline-block align-bottom rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6 border-2 bg-[var(--vscode-editor-background)] border-[var(--frontmatter-border)]`}>
|
||||
<div className="sm:flex sm:items-start">
|
||||
{icon && (
|
||||
<div className={`mt-3 sm:mr-4 mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full sm:mx-0 sm:h-10 sm:w-10 ${getColors(
|
||||
`bg-gray-50 dark:bg-vulcan-400`,
|
||||
`bg-[var(--vscode-sidebar-background)]'`
|
||||
)
|
||||
}`}>
|
||||
<div className={`mt-3 sm:mr-4 mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full sm:mx-0 sm:h-10 sm:w-10 bg-[var(--vscode-sidebar-background)]`}>
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-3 text-center sm:mt-0 sm:text-left">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className={`text-lg leading-6 font-medium ${getColors(`text-vulcan-300 dark:text-whisper-900`, `text-[var(--vscode-editor-foreground)]`)
|
||||
}`}
|
||||
className={`text-lg leading-6 font-medium text-[var(--vscode-editor-foreground)]`}
|
||||
>
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className={`text-sm ${getColors(`text-vulcan-500 dark:text-whisper-500`, `text-[var(--vscode-editor-foreground)]`)}`}>{description}</p>
|
||||
<p className={`text-sm text-[var(--vscode-editor-foreground)]`}>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,12 +18,14 @@ export interface ITaxonomyActionsProps {
|
||||
field: string | null;
|
||||
value: string;
|
||||
unmapped?: boolean;
|
||||
onContentTagging: (value: string) => void;
|
||||
}
|
||||
|
||||
export const TaxonomyActions: React.FunctionComponent<ITaxonomyActionsProps> = ({
|
||||
field,
|
||||
value,
|
||||
unmapped
|
||||
unmapped,
|
||||
onContentTagging
|
||||
}: React.PropsWithChildren<ITaxonomyActionsProps>) => {
|
||||
const onEdit = useCallback(() => {
|
||||
Messenger.send(DashboardMessage.editTaxonomy, {
|
||||
@@ -60,59 +62,65 @@ export const TaxonomyActions: React.FunctionComponent<ITaxonomyActionsProps> = (
|
||||
});
|
||||
}, [field, value]);
|
||||
|
||||
const onTagging = useCallback(() => {
|
||||
onContentTagging(value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className={`space-x-2`}>
|
||||
{unmapped && (
|
||||
<>
|
||||
<div className={`space-x-2`}>
|
||||
{unmapped && (
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonAddTitle, value)}
|
||||
onClick={onAdd}>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewButtonAddTitle, value)}
|
||||
</span>
|
||||
</LinkButton>
|
||||
)}
|
||||
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonAddTitle, value)}
|
||||
onClick={onAdd}>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
title={`Tag content`}
|
||||
onClick={onTagging}>
|
||||
<SquaresPlusIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonEdit)}</span>
|
||||
</LinkButton>
|
||||
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonEditTitle, value)}
|
||||
onClick={onEdit}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonEdit)}</span>
|
||||
</LinkButton>
|
||||
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonMergeTitle, value)}
|
||||
onClick={onMerge}>
|
||||
<MergeIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewButtonAddTitle, value)}
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewButtonMergeTitle, value)}
|
||||
</span>
|
||||
</LinkButton>
|
||||
)}
|
||||
|
||||
<LinkButton
|
||||
title={`Tag content`}
|
||||
onClick={onEdit}>
|
||||
<SquaresPlusIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonEdit)}</span>
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonMoveTitle)}
|
||||
onClick={onMove}>
|
||||
<ArrowUpCircleIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewButtonMoveTitle)}
|
||||
</span>
|
||||
</LinkButton>
|
||||
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonEditTitle, value)}
|
||||
onClick={onEdit}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonEdit)}</span>
|
||||
</LinkButton>
|
||||
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonMergeTitle, value)}
|
||||
onClick={onMerge}>
|
||||
<MergeIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewButtonMergeTitle, value)}
|
||||
</span>
|
||||
</LinkButton>
|
||||
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonMoveTitle)}
|
||||
onClick={onMove}>
|
||||
<ArrowUpCircleIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewButtonMoveTitle)}
|
||||
</span>
|
||||
</LinkButton>
|
||||
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonDeleteTitle, value)}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewButtonDeleteTitle, value)}
|
||||
</span>
|
||||
</LinkButton>
|
||||
</div>
|
||||
<LinkButton
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonDeleteTitle, value)}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<span className="sr-only">
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewButtonDeleteTitle, value)}
|
||||
</span>
|
||||
</LinkButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,12 +21,14 @@ export interface ITaxonomyManagerProps {
|
||||
data: TaxonomyData | undefined;
|
||||
taxonomy: string | null;
|
||||
pages: Page[];
|
||||
onContentTagging: (value: string) => void;
|
||||
}
|
||||
|
||||
export const TaxonomyManager: React.FunctionComponent<ITaxonomyManagerProps> = ({
|
||||
data,
|
||||
taxonomy,
|
||||
pages
|
||||
pages,
|
||||
onContentTagging
|
||||
}: React.PropsWithChildren<ITaxonomyManagerProps>) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const { getColors } = useThemeColors();
|
||||
@@ -216,7 +218,7 @@ export const TaxonomyManager: React.FunctionComponent<ITaxonomyManagerProps> = (
|
||||
<TaxonomyLookup taxonomy={taxonomy} value={item} pages={pages} />
|
||||
</td>
|
||||
<td className={`px-6 py-4 whitespace-nowrap text-right text-sm font-medium`}>
|
||||
<TaxonomyActions field={taxonomy} value={item} />
|
||||
<TaxonomyActions field={taxonomy} value={item} onContentTagging={onContentTagging} />
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
@@ -247,7 +249,7 @@ export const TaxonomyManager: React.FunctionComponent<ITaxonomyManagerProps> = (
|
||||
<TaxonomyLookup taxonomy={taxonomy} value={item} pages={pages} />
|
||||
</td>
|
||||
<td className={`px-6 py-4 whitespace-nowrap text-right text-sm font-medium`}>
|
||||
<TaxonomyActions field={taxonomy} value={item} unmapped />
|
||||
<TaxonomyActions field={taxonomy} value={item} onContentTagging={onContentTagging} unmapped />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import * as React from 'react';
|
||||
import { Page } from '../../models';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { getTaxonomyField } from '../../../helpers/getTaxonomyField';
|
||||
import { Sorting } from '../../../helpers/Sorting';
|
||||
import { ArrowLeftIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
export interface ITaxonomyTaggingProps {
|
||||
taxonomy: string | null;
|
||||
value: string;
|
||||
pages: Page[];
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = ({
|
||||
taxonomy,
|
||||
value,
|
||||
pages,
|
||||
onDismiss
|
||||
}: React.PropsWithChildren<ITaxonomyTaggingProps>) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const untaggedPages = React.useMemo(() => {
|
||||
let untagged: Page[] = [];
|
||||
|
||||
if (!pages || !settings?.contentTypes || !taxonomy) {
|
||||
return untagged;
|
||||
}
|
||||
|
||||
for (const page of pages) {
|
||||
if (taxonomy === 'tags') {
|
||||
if (!page.fmTags || page.fmTags.indexOf(value) === -1) {
|
||||
untagged.push(page);
|
||||
}
|
||||
continue;
|
||||
} else if (taxonomy === 'categories') {
|
||||
if (!page.fmCategories || page.fmCategories.indexOf(value) === -1) {
|
||||
untagged.push(page);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
const contentType = settings.contentTypes.find((ct) => ct.name === page.fmContentType);
|
||||
|
||||
if (!contentType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
|
||||
if (fieldName && (!page[fieldName] || page[fieldName].indexOf(value) === -1)) {
|
||||
untagged.push(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
untagged = untagged.sort(Sorting.number('fmPublished')).reverse();
|
||||
|
||||
return untagged;
|
||||
}, [pages, taxonomy, value]);
|
||||
|
||||
return (
|
||||
<div className={`py-6 px-4 flex flex-col h-full overflow-hidden`}>
|
||||
<div className={`flex w-full justify-between flex-shrink-0`}>
|
||||
<h2 className='text-lg first-letter:uppercase flex items-center'>
|
||||
|
||||
<button onClick={onDismiss} title='Back' className='mr-2'>
|
||||
<span className='sr-only'>Back</span>
|
||||
<ArrowLeftIcon className='w-5 h-5 text-[var(--frontmatter-text)]' />
|
||||
</button>
|
||||
|
||||
{taxonomy}: {value}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 -mr-4 pr-4 flex flex-col flex-grow overflow-auto'>
|
||||
<ul>
|
||||
{untaggedPages.map((page) => (
|
||||
<li key={page.fmFilePath}>
|
||||
<input type='checkbox' />
|
||||
<span>{page.title}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -14,6 +14,7 @@ import { SponsorMsg } from '../Layout/SponsorMsg';
|
||||
import { TaxonomyManager } from './TaxonomyManager';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { TaxonomyTagging } from './TaxonomyTagging';
|
||||
|
||||
export interface ITaxonomyViewProps {
|
||||
pages: Page[];
|
||||
@@ -25,6 +26,7 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const [taxonomySettings, setTaxonomySettings] = useState<TaxonomyData>();
|
||||
const [selectedTaxonomy, setSelectedTaxonomy] = useState<string | null>(`tags`);
|
||||
const [contentTagging, setContentTagging] = useState<string | null>(null);
|
||||
|
||||
const onImport = () => {
|
||||
Messenger.send(DashboardMessage.importTaxonomy);
|
||||
@@ -62,7 +64,10 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
|
||||
>
|
||||
<NavigationItem
|
||||
isSelected={selectedTaxonomy === 'tags'}
|
||||
onClick={() => setSelectedTaxonomy(`tags`)}
|
||||
onClick={() => {
|
||||
setSelectedTaxonomy(`tags`);
|
||||
setContentTagging(null);
|
||||
}}
|
||||
>
|
||||
<ChevronRightIcon className="-ml-1 w-5 mr-2" />
|
||||
<span>{l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyViewNavigationItemTags)}</span>
|
||||
@@ -70,7 +75,10 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
|
||||
|
||||
<NavigationItem
|
||||
isSelected={selectedTaxonomy === 'categories'}
|
||||
onClick={() => setSelectedTaxonomy(`categories`)}
|
||||
onClick={() => {
|
||||
setSelectedTaxonomy(`categories`);
|
||||
setContentTagging(null);
|
||||
}}
|
||||
>
|
||||
<ChevronRightIcon className="-ml-1 w-5 mr-2" />
|
||||
<span>{l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyViewNavigationItemCategories)}</span>
|
||||
@@ -81,7 +89,10 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
|
||||
<NavigationItem
|
||||
key={`${taxonomy.id}-${index}`}
|
||||
isSelected={selectedTaxonomy === taxonomy.id}
|
||||
onClick={() => setSelectedTaxonomy(taxonomy.id)}
|
||||
onClick={() => {
|
||||
setSelectedTaxonomy(taxonomy.id);
|
||||
setContentTagging(null);
|
||||
}}
|
||||
>
|
||||
<ChevronRightIcon className="-ml-1 w-5 mr-2" />
|
||||
<span className={`first-letter:uppercase`}>{taxonomy.id}</span>
|
||||
@@ -90,10 +101,21 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
|
||||
</NavigationBar>
|
||||
|
||||
<div className={`w-10/12 h-full overflow-hidden`}>
|
||||
<TaxonomyManager
|
||||
data={taxonomySettings}
|
||||
taxonomy={selectedTaxonomy}
|
||||
pages={pages} />
|
||||
{
|
||||
contentTagging ? (
|
||||
<TaxonomyTagging
|
||||
value={contentTagging}
|
||||
taxonomy={selectedTaxonomy}
|
||||
pages={pages}
|
||||
onDismiss={() => setContentTagging(null)} />
|
||||
) : (
|
||||
<TaxonomyManager
|
||||
data={taxonomySettings}
|
||||
taxonomy={selectedTaxonomy}
|
||||
pages={pages}
|
||||
onContentTagging={(value: string) => setContentTagging(value)} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user