#675 - Pin content

This commit is contained in:
Elio Struyf
2023-09-23 14:11:00 +02:00
parent 2a763ab384
commit 2c8316288d
21 changed files with 475 additions and 69 deletions

View File

@@ -8,6 +8,7 @@
- [#553](https://github.com/estruyf/vscode-front-matter/issues/553): New `frontMatter.config.dynamicFilePath` setting which allows you to dynamically update the settings from a custom JS file
- [#563](https://github.com/estruyf/vscode-front-matter/issues/563): New `fieldCollection` to inherit/reuse fields in multiple content-types
- [#653](https://github.com/estruyf/vscode-front-matter/issues/653): Retrieve the Astro Content Collections to allow content type generation
- [#675](https://github.com/estruyf/vscode-front-matter/issues/675): Pinning content to the top of the content dashboard
### 🎨 Enhancements

View File

@@ -24,6 +24,8 @@
"common.error.message": "Sorry, something went wrong.",
"common.openOnWebsite": "Open on website",
"common.settings": "Settings",
"common.pin": "Pin",
"common.unpin": "Unpin",
"settings.contentTypes": "Content types",
"settings.contentFolders": "Content folders",
@@ -68,6 +70,7 @@
"dashboard.contents.overview.noMarkdown": "No Markdown to show",
"dashboard.contents.overview.noFolders": "Make sure you registered a content folder in your project to let Front Matter find the contents.",
"dashboard.contents.overview.pinned": "Pinned",
"dashboard.contents.status.draft": "Draft",
"dashboard.contents.status.published": "Published",

View File

@@ -204,6 +204,7 @@ export class Dashboard {
command: DashboardCommand;
requestId?: string;
payload?: unknown;
error?: unknown;
}) {
if (Dashboard.isDisposed) {
return;

View File

@@ -4,5 +4,6 @@ export const LocalStore = {
databaseFolder: 'database',
templatesFolder: 'templates',
mediaDatabaseFile: 'mediaDb.json',
taxonomyDatabaseFile: 'taxonomyDb.json'
taxonomyDatabaseFile: 'taxonomyDb.json',
pinnedItemsDatabaseFile: 'pinnedItemsDb.json'
};

View File

@@ -26,6 +26,9 @@ export enum DashboardMessage {
searchPages = 'searchPages',
openFile = 'openFile',
deleteFile = 'deleteFile',
getPinnedItems = 'getPinnedItems',
pinItem = 'pinItem',
unpinItem = 'unpinItem',
// Media Dashboard
getMedia = 'getMedia',

View File

@@ -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 { EyeIcon, GlobeIcon, TerminalIcon, TrashIcon } from '@heroicons/react/outline';
import * as React from 'react';
@@ -11,13 +11,16 @@ import { useState } from 'react';
import useThemeColors from '../../hooks/useThemeColors';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { SettingsSelector } from '../../state';
import { GeneralCommands } from '../../../constants';
import { PinIcon } from '../Icons/PinIcon';
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
export interface IContentActionsProps {
title: string;
path: string;
relPath: string;
scripts: CustomScript[] | undefined;
listView?: boolean;
onOpen: () => void;
@@ -26,10 +29,12 @@ export interface IContentActionsProps {
export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
title,
path,
relPath,
scripts,
onOpen,
listView
}: React.PropsWithChildren<IContentActionsProps>) => {
const [pinnedItems, setPinnedItems] = useRecoilState(PinnedItemsAtom);
const [showDeletionAlert, setShowDeletionAlert] = React.useState(false);
const { getColors } = useThemeColors();
const settings = useRecoilValue(SettingsSelector);
@@ -68,6 +73,20 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
}
}, [settings?.websiteUrl, path]);
const pinItem = React.useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
messageHandler.request<string[]>(DashboardMessage.pinItem, path).then((result) => {
setPinnedItems(result || []);
})
}, [path]);
const unpinItem = React.useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
messageHandler.request<string[]>(DashboardMessage.unpinItem, path).then((result) => {
setPinnedItems(result || []);
})
}, [path]);
const runCustomScript = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement>, script: CustomScript) => {
e.stopPropagation();
@@ -76,6 +95,10 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
[path]
);
const isPinned = React.useMemo(() => {
return pinnedItems.includes(relPath);
}, [pinnedItems, relPath]);
const customScriptActions = React.useMemo(() => {
return (scripts || [])
.filter(
@@ -148,6 +171,15 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
widthClass="w-44"
marginTopClass={listView ? '' : ''}
>
<MenuItem
title={
<div className="flex items-center">
<PinIcon className={`mr-2 h-5 w-5 flex-shrink-0 ${isPinned ? "" : "-rotate-90"}`} aria-hidden={true} />{' '}
<span>{isPinned ? l10n.t(LocalizationKey.commonUnpin) : l10n.t(LocalizationKey.commonPin)}</span>
</div>
}
onClick={(_, e) => isPinned ? unpinItem(e) : pinItem(e)}
/>
<MenuItem
title={
<div className="flex items-center">

View File

@@ -15,76 +15,32 @@ import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { useNavigate } from 'react-router-dom';
import { routePaths } from '../..';
import useCard from '../../hooks/useCard';
export interface IItemProps extends Page { }
const PREVIEW_IMAGE_FIELD = 'fmPreviewImage';
export const Item: React.FunctionComponent<IItemProps> = ({
fmFilePath,
fmDateFormat,
date,
title,
description,
type,
...pageData
}: React.PropsWithChildren<IItemProps>) => {
const view = useRecoilValue(ViewSelector);
const settings = useRecoilValue(SettingsSelector);
const draftField = useMemo(() => settings?.draftField, [settings]);
const cardFields = useMemo(() => settings?.dashboardState?.contents?.cardFields, [settings?.dashboardState?.contents?.cardFields]);
const { escapedTitle, escapedDescription } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
const navigate = useNavigate();
const { titleHtml, descriptionHtml, dateHtml, statusHtml, tagsHtml, imageHtml, footerHtml } = useExtensibility({
fmFilePath,
date,
title,
description,
type,
fmFilePath: pageData.fmFileData,
date: pageData.date,
title: pageData.title,
description: pageData.description,
type: pageData.type,
pageData
});
const escapedTitle = useMemo(() => {
let value = title;
if (cardFields?.title) {
if (cardFields.title === "description") {
value = description;
} else if (cardFields?.title !== "title") {
value = pageData[cardFields?.title] || title;
}
} else if (cardFields?.title === null) {
return null;
}
if (value && typeof value !== 'string') {
return l10n.t(LocalizationKey.dashboardContentsItemInvalidTitle);
}
return value;
}, [title, description, cardFields?.title, pageData]);
const escapedDescription = useMemo(() => {
let value = description;
if (cardFields?.description) {
if (cardFields.description === "title") {
value = title;
} else if (cardFields?.description !== "description") {
value = pageData[cardFields?.description] || description;
}
} else if (cardFields?.description === null) {
return null;
}
if (value && typeof value !== 'string') {
return l10n.t(LocalizationKey.dashboardContentsItemInvalidDescription);
}
return value;
}, [description, title, cardFields?.description, pageData]);
const openFile = () => {
Messenger.send(DashboardMessage.openFile, fmFilePath);
Messenger.send(DashboardMessage.openFile, pageData.fmFilePath);
};
const tags: string[] | undefined = useMemo(() => {
@@ -161,14 +117,15 @@ export const Item: React.FunctionComponent<IItemProps> = ({
dateHtml ? (
<div className='mr-4' dangerouslySetInnerHTML={{ __html: dateHtml }} />
) : (
cardFields?.date && <DateField className={`mr-4`} value={date} format={fmDateFormat} />
cardFields?.date && <DateField className={`mr-4`} value={pageData.date} format={pageData.fmDateFormat} />
)
}
</div>
<ContentActions
title={title}
path={fmFilePath}
title={pageData.title}
path={pageData.fmFilePath}
relPath={pageData.fmRelFileWsPath}
scripts={settings?.scripts}
onOpen={openFile}
/>
@@ -245,14 +202,15 @@ export const Item: React.FunctionComponent<IItemProps> = ({
<ContentActions
title={escapedTitle || ""}
path={fmFilePath}
path={pageData.fmFilePath}
relPath={pageData.fmRelFileWsPath}
scripts={settings?.scripts}
onOpen={openFile}
listView
/>
</div>
<div className="col-span-2">
<DateField value={date} />
<DateField value={pageData.date} />
</div>
<div className="col-span-2">
{draftField && draftField.name && <Status draft={pageData[draftField.name]} />}

View File

@@ -2,19 +2,25 @@ import { Disclosure } from '@headlessui/react';
import { ChevronRightIcon } from '@heroicons/react/solid';
import * as React from 'react';
import { useCallback, useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { groupBy } from '../../../helpers/GroupBy';
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
import { GroupOption } from '../../constants/GroupOption';
import { Page } from '../../models/Page';
import { Settings } from '../../models/Settings';
import { GroupingSelector, PageAtom } from '../../state';
import { GroupingSelector, PageAtom, ViewSelector } from '../../state';
import { Item } from './Item';
import { List } from './List';
import usePagination from '../../hooks/usePagination';
import useThemeColors from '../../hooks/useThemeColors';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
import { messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { PinIcon } from '../Icons/PinIcon';
import { PinnedItem } from './PinnedItem';
import { DashboardViewType } from '../../models';
export interface IOverviewProps {
pages: Page[];
@@ -25,10 +31,13 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
pages,
settings
}: React.PropsWithChildren<IOverviewProps>) => {
const [isReady, setIsReady] = React.useState<boolean>(false);
const [pinnedItems, setPinnedItems] = useRecoilState(PinnedItemsAtom);
const grouping = useRecoilValue(GroupingSelector);
const page = useRecoilValue(PageAtom);
const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination);
const { getColors } = useThemeColors();
const view = useRecoilValue(ViewSelector);
const pagedPages = useMemo(() => {
if (pageSetNr) {
@@ -36,7 +45,15 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
}
return pages;
}, [pages, page, pageSetNr]);
}, [pages, page, pageSetNr, pinnedItems, grouping]);
const pinnedPages = useMemo(() => {
if (grouping === GroupOption.none) {
return pages.filter((page) => pinnedItems.includes(page.fmRelFileWsPath));
}
return [];
}, [pages, pinnedItems, grouping]);
const groupName = useCallback(
(groupId, groupedPages) => {
@@ -49,6 +66,20 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
[grouping]
);
React.useEffect(() => {
messageHandler.request<string[]>(DashboardMessage.getPinnedItems).then((items) => {
setIsReady(true);
setPinnedItems(items || []);
}).catch(() => {
setIsReady(true);
setPinnedItems([]);
});
}, []);
if (!isReady) {
return null;
}
if (!pages || !pages.length) {
return (
<div className={`flex items-center justify-center h-full`}>
@@ -108,10 +139,34 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
}
return (
<List>
{pagedPages.map((page, idx) => (
<Item key={`${page.slug}-${idx}`} {...page} />
))}
</List>
<div className='divide-y divide-[var(--frontmatter-border)]'>
{
pinnedPages.length > 0 && (
<div className='mb-8'>
<h1 className='text-xl flex space-x-2 items-center mb-4'>
<PinIcon className={`-rotate-45`} />
<span>{l10n.t(LocalizationKey.dashboardContentsOverviewPinned)}</span>
</h1>
<List>
{pinnedPages.map((page, idx) => (
view === DashboardViewType.List ? (
<Item key={`${page.slug}-${idx}`} {...page} />
) : (
<PinnedItem key={`${page.slug}-${idx}`} {...page} />
)
))}
</List>
</div>
)
}
<div className={pinnedItems.length > 0 ? "pt-8" : ""}>
<List>
{pagedPages.map((page, idx) => (
<Item key={`${page.slug}-${idx}`} {...page} />
))}
</List>
</div>
</div>
);
};

View File

@@ -0,0 +1,57 @@
import * as React from 'react';
import { Page } from '../../models';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
import { ContentActions } from './ContentActions';
import { DashboardMessage } from '../../DashboardMessage';
import { messageHandler } from '@estruyf/vscode/dist/client';
import useCard from '../../hooks/useCard';
import { SettingsSelector } from '../../state';
import { useRecoilValue } from 'recoil';
export interface IPinnedItemProps extends Page { }
export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
...pageData
}: React.PropsWithChildren<IPinnedItemProps>) => {
const settings = useRecoilValue(SettingsSelector);
const { escapedTitle } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
const openFile = React.useCallback(() => {
messageHandler.send(DashboardMessage.openFile, pageData.fmFilePath);
}, [pageData.fmFilePath]);
return (
<li className='group flex w-full border border-[var(--frontmatter-border)] rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)]'>
<button onClick={openFile} className='relative h-full w-1/3'>
{
pageData["fmPreviewImage"] ? (
<img
src={`${pageData["fmPreviewImage"]}`}
alt={pageData.title || ""}
className="absolute inset-0 h-full w-full object-left-top object-cover group-hover:brightness-75"
loading="lazy"
/>
) : (
<div
className={`h-full flex items-center justify-center bg-[var(--vscode-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)] border-r border-[var(--frontmatter-border)]`}
>
<MarkdownIcon className={`h-8 text-[var(--vscode-sideBarTitle-foreground)] opacity-80`} />
</div>
)
}
</button>
<button onClick={openFile} className='relative w-2/3 p-4 text-left flex items-start'>
<p className='font-bold'>{escapedTitle}</p>
<ContentActions
title={pageData.title}
path={pageData.fmFilePath}
relPath={pageData.fmRelFileWsPath}
scripts={settings?.scripts}
onOpen={openFile}
/>
</button>
</li>
);
};

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
export interface IPinIconProps {
className?: string;
}
export const PinIcon: React.FunctionComponent<IPinIconProps> = ({
className
}: React.PropsWithChildren<IPinIconProps>) => {
return (
<svg className={className || ""} stroke="currentColor" fill="currentColor" stroke-width=".5" viewBox="0 0 16 16" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M14 5v7h-.278c-.406 0-.778-.086-1.117-.258A2.528 2.528 0 0 1 11.73 11H8.87a3.463 3.463 0 0 1-.546.828 3.685 3.685 0 0 1-.735.633c-.27.177-.565.31-.882.398a3.875 3.875 0 0 1-.985.141h-.5V9H2l-1-.5L2 8h3.222V4h.5c.339 0 .664.047.977.14.312.094.607.227.883.4A3.404 3.404 0 0 1 8.87 6h2.859a2.56 2.56 0 0 1 .875-.734c.338-.172.71-.26 1.117-.266H14zm-.778 1.086a1.222 1.222 0 0 0-.32.156 1.491 1.491 0 0 0-.43.461L12.285 7H8.183l-.117-.336a2.457 2.457 0 0 0-.711-1.047C7.027 5.331 6.427 5.09 6 5v7c.427-.088 1.027-.33 1.355-.617.328-.287.565-.636.71-1.047L8.184 10h4.102l.18.297c.057.094.122.177.195.25.073.073.153.143.242.21.088.069.195.12.32.157V6.086z"></path></svg>
);
};

View File

@@ -1,3 +1,4 @@
export * from './useCard';
export * from './useExtensibility';
export * from './useMedia';
export * from './useMessages';

View File

@@ -0,0 +1,55 @@
import { useMemo } from 'react';
import { CardFields, Page } from '../models';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../localization';
export default function useCard(
pageData: Page,
cardFields: CardFields | undefined,
) {
const escapedTitle = useMemo(() => {
let value = pageData.title;
if (cardFields?.title) {
if (cardFields.title === "description") {
value = pageData.description;
} else if (cardFields?.title !== "title") {
value = pageData[cardFields?.title] || pageData.title;
}
} else if (cardFields?.title === null) {
return null;
}
if (value && typeof value !== 'string') {
return l10n.t(LocalizationKey.dashboardContentsItemInvalidTitle);
}
return value;
}, [pageData.title, pageData.description, cardFields?.title, pageData]);
const escapedDescription = useMemo(() => {
let value = pageData.description;
if (cardFields?.description) {
if (cardFields.description === "title") {
value = pageData.title;
} else if (cardFields?.description !== "description") {
value = pageData[cardFields?.description] || pageData.description;
}
} else if (cardFields?.description === null) {
return null;
}
if (value && typeof value !== 'string') {
return l10n.t(LocalizationKey.dashboardContentsItemInvalidDescription);
}
return value;
}, [pageData.description, pageData.title, cardFields?.description, pageData]);
return {
escapedTitle,
escapedDescription
};
}

View File

@@ -6,6 +6,7 @@ export interface Page {
// Front matter fields
fmFolder: string;
fmFilePath: string;
fmRelFileWsPath: string;
fmRelFilePath: string;
fmFileName: string;
fmModified: number;

View File

@@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const PinnedItemsAtom = atom<string[]>({
key: 'PinnedItemsAtom',
default: []
});

View File

@@ -1,6 +1,6 @@
import { Notifications } from './Notifications';
import { Uri } from 'vscode';
import { Folders } from '../commands/Folders';
import { Folders, WORKSPACE_PLACEHOLDER } from '../commands/Folders';
import { isValidFile } from './isValidFile';
import { parseWinPath } from './parseWinPath';
import { join } from 'path';
@@ -41,4 +41,15 @@ export class FilesHelper {
let absPath = join(parseWinPath(wsFolder?.fsPath || ''), filePath);
return parseWinPath(absPath);
}
/**
* Absolute path to rel path
* @param filePath
* @returns
*/
public static absToRelPath(filePath: string): string {
const wsFolder = Folders.getWorkspaceFolder();
const relPath = filePath.replace(wsFolder?.fsPath || '', WORKSPACE_PLACEHOLDER);
return parseWinPath(relPath);
}
}

View File

@@ -28,4 +28,12 @@ export abstract class BaseListener {
payload
});
}
public static sendError(command: DashboardCommand, requestId: string, error: any) {
Dashboard.postWebviewMessage({
command,
requestId,
error
});
}
}

View File

@@ -4,6 +4,7 @@ import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
import { Extension, Notifications } from '../../helpers';
import { PostMessageData } from '../../models';
import { PinnedItems } from '../../services';
import { BaseListener } from './BaseListener';
export class DashboardListener extends BaseListener {
@@ -29,6 +30,79 @@ export class DashboardListener extends BaseListener {
case DashboardMessage.showWarning:
Notifications.warning(msg.payload);
break;
case DashboardMessage.pinItem:
DashboardListener.pinItem(msg);
break;
case DashboardMessage.unpinItem:
DashboardListener.unpinItem(msg);
break;
case DashboardMessage.getPinnedItems:
DashboardListener.getPinnedItems(msg);
break;
}
}
/**
* Get the pinned items
* @param msg
*/
private static async getPinnedItems({ command, requestId }: PostMessageData) {
if (!requestId || !command) {
return;
}
const allPinned = (await PinnedItems.get()) || [];
this.sendRequest(command as any, requestId, allPinned);
}
/**
* Pin an item to the dashboard
* @param payload
*/
private static async pinItem({ command, requestId, payload }: PostMessageData) {
if (!requestId || !command || !payload) {
return;
}
const path = payload;
if (!path) {
this.sendError(command as any, requestId, 'No path provided.');
return;
}
const allPinned = await PinnedItems.pin(path);
if (!allPinned) {
this.sendError(command as any, requestId, 'Could not pin item.');
return;
}
this.sendRequest(command as any, requestId, allPinned);
}
/**
* Unpin an item from the dashboard
* @param param0
* @returns
*/
private static async unpinItem({ command, requestId, payload }: PostMessageData) {
if (!requestId || !command || !payload) {
return;
}
const path = payload;
if (!path) {
this.sendError(command as any, requestId, 'No path provided.');
return;
}
const updatedPinned = await PinnedItems.remove(path);
if (!updatedPinned) {
this.sendError(command as any, requestId, 'Could not unpin item.');
return;
}
this.sendRequest(command as any, requestId, updatedPinned);
}
}

View File

@@ -99,6 +99,14 @@ export enum LocalizationKey {
* Settings
*/
commonSettings = 'common.settings',
/**
* Pin
*/
commonPin = 'common.pin',
/**
* Unpin
*/
commonUnpin = 'common.unpin',
/**
* Content types
*/
@@ -227,6 +235,10 @@ export enum LocalizationKey {
* Make sure you registered a content folder in your project to let Front Matter find the contents.
*/
dashboardContentsOverviewNoFolders = 'dashboard.contents.overview.noFolders',
/**
* Pinned
*/
dashboardContentsOverviewPinned = 'dashboard.contents.overview.pinned',
/**
* Draft
*/

View File

@@ -18,6 +18,7 @@ import {
ContentType,
DateHelper,
Extension,
FilesHelper,
isValidFile,
Logger,
Notifications,
@@ -207,6 +208,7 @@ export class PagesParser {
// FrontMatter properties
fmFolder: folderTitle,
fmFilePath: filePath,
fmRelFileWsPath: FilesHelper.absToRelPath(filePath),
fmRelFilePath: parseWinPath(filePath).replace(wsFolder?.fsPath || '', ''),
fmFileName: fileName,
fmDraft: ContentType.getDraftStatus(article?.data),

111
src/services/PinnedItems.ts Normal file
View File

@@ -0,0 +1,111 @@
import { Config, JsonDB } from 'node-json-db';
import { Folders } from '../commands';
import { join } from 'path';
import { LocalStore } from '../constants';
import { FilesHelper, parseWinPath } from '../helpers';
const PINNED_DB = '/pinned';
export class PinnedItems {
/**
* Retrieve all the pinned items
* @returns
*/
public static async get() {
const db = PinnedItems.getPinnedDb();
if (!db) {
return undefined;
}
let allPinned: string[] = [];
if (await db.exists(PINNED_DB)) {
allPinned = (await db.getObject(PINNED_DB)) as string[];
}
return allPinned;
}
/**
* Pin an item
* @param path
*/
public static async pin(path: string) {
if (!path) {
return;
}
const relPath = FilesHelper.absToRelPath(path);
if (!relPath) {
return;
}
const db = PinnedItems.getPinnedDb();
if (!db) {
return;
}
let allPinned: string[] = [];
if (await db.exists(PINNED_DB)) {
allPinned = (await db.getObject(PINNED_DB)) as string[];
}
allPinned.push(relPath);
allPinned = [...new Set(allPinned)];
await db.push(PINNED_DB, allPinned, true);
return allPinned;
}
/**
* Remove a pinned item
* @param path
* @returns
*/
public static async remove(path: string) {
if (!path) {
return;
}
const relPath = FilesHelper.absToRelPath(path);
if (!relPath) {
return;
}
const db = PinnedItems.getPinnedDb();
if (!db) {
return;
}
let allPinned: string[] = [];
if (await db.exists(PINNED_DB)) {
allPinned = (await db.getObject(PINNED_DB)) as string[];
}
allPinned = allPinned.filter((p) => p !== relPath);
await db.push(PINNED_DB, allPinned, true);
return allPinned;
}
/**
* Retrieve the pinned database
* @returns
*/
private static getPinnedDb() {
const wsFolder = Folders.getWorkspaceFolder();
if (!wsFolder) {
return;
}
const dbFolder = join(
parseWinPath(wsFolder?.fsPath || ''),
LocalStore.rootFolder,
LocalStore.databaseFolder
);
const dbPath = join(dbFolder, LocalStore.pinnedItemsDatabaseFile);
return new JsonDB(new Config(dbPath, true, false, '/'));
}
}

View File

@@ -1,5 +1,6 @@
export * from './Credentials';
export * from './ModeSwitch';
export * from './PagesParser';
export * from './PinnedItems';
export * from './SponsorAI';
export * from './Terminal';