mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ae7cb27ce | ||
|
|
5e77419f5a | ||
|
|
ec9f55b982 | ||
|
|
fdcfdc971d | ||
|
|
2bc103026b | ||
|
|
23b1efec55 | ||
|
|
2a8d7b0ebe | ||
|
|
3b26944a4a | ||
|
|
78cac94dd6 | ||
|
|
9c6845ed8a |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
||||
# Change Log
|
||||
|
||||
## [10.1.0] - 2024-xx-xx
|
||||
|
||||
### ✨ New features
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
### ⚡️ Optimizations
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#768](https://github.com/estruyf/vscode-front-matter/issues/768): Update broken link to the documentation
|
||||
|
||||
## [10.0.1] - 2024-02-28
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
@@ -247,14 +247,6 @@
|
||||
background-color: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
.table__cell {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table__title {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.table__cell__seo_details {
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -281,11 +273,6 @@
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.seo__status__note {
|
||||
font-size: 10px;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
/* Fields */
|
||||
.field__toggle {
|
||||
position: relative;
|
||||
@@ -364,7 +351,7 @@ input:checked + .field__toggle__slider:before {
|
||||
}
|
||||
|
||||
/* File list */
|
||||
.file_list vscode-label {
|
||||
.file_list label {
|
||||
border-bottom: 1px solid var(--vscode-foreground);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
"common.back": "Back",
|
||||
"common.open": "Open",
|
||||
"common.openWithValue": "Open: {0}",
|
||||
"common.view": "View",
|
||||
"common.translate": "Translate",
|
||||
"common.languages": "Languages",
|
||||
"common.scripts": "Scripts",
|
||||
|
||||
"loading.initPages": "Loading content",
|
||||
|
||||
@@ -146,6 +150,10 @@
|
||||
"dashboard.filters.languageFilter.label": "Locale",
|
||||
"dashboard.filters.languageFilter.all": "All",
|
||||
|
||||
"dashboard.header.actionsBar.itemsSelected": "{0} selected",
|
||||
"dashboard.header.actionsBar.alertDelete.title": "Delete selected files",
|
||||
"dashboard.header.actionsBar.alertDelete.description": "Are you sure you want to delete the selected files?",
|
||||
|
||||
"dashboard.header.breadcrumb.home": "Home",
|
||||
|
||||
"dashboard.header.clearFilters.title": "Clear filters, grouping, and sorting",
|
||||
@@ -229,6 +237,9 @@
|
||||
"dashboard.media.folderCreation.hexo.create": "Create post asset folder",
|
||||
"dashboard.media.folderCreation.folder.create": "Create new folder",
|
||||
|
||||
"dashboard.media.folderItem.contentDirectory": "Content directory",
|
||||
"dashboard.media.folderItem.publicDirectory": "Public directory",
|
||||
|
||||
"dashboard.media.item.buttom.insert.image": "Insert image",
|
||||
"dashboard.media.item.buttom.insert.snippet": "Insert snippet",
|
||||
|
||||
|
||||
87
package-lock.json
generated
87
package-lock.json
generated
@@ -1,19 +1,18 @@
|
||||
{
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "10.0.1",
|
||||
"version": "10.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "10.0.1",
|
||||
"version": "10.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "^1.1.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
@@ -38,6 +37,7 @@
|
||||
"@types/vscode": "^1.73.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vscode-elements/elements": "^1.2.0",
|
||||
"@vscode/l10n": "^0.0.14",
|
||||
"@vscode/webview-ui-toolkit": "^1.2.2",
|
||||
"@webpack-cli/serve": "^1.7.0",
|
||||
@@ -85,7 +85,7 @@
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"recoil": "^0.4.1",
|
||||
"recoil": "^0.7.7",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.8",
|
||||
@@ -400,15 +400,6 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bendera/vscode-webview-elements": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@bendera/vscode-webview-elements/-/vscode-webview-elements-0.6.2.tgz",
|
||||
"integrity": "sha512-smtr+KvCKV2MwjVrmyvrhonpaXVpxCjTMXUQOwDwWSAQ42x5pnlpjCGElz2dljc5VHS1Mh1ovPSQ/P3jAm7vMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lit-element": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ctrl/tinycolor": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||
@@ -764,6 +755,21 @@
|
||||
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@lit-labs/ssr-dom-shim": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
|
||||
"integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@lit/reactive-element": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
|
||||
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/fast-element": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.12.0.tgz",
|
||||
@@ -2073,6 +2079,12 @@
|
||||
"integrity": "sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/uglify-js": {
|
||||
"version": "3.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.4.tgz",
|
||||
@@ -2337,6 +2349,15 @@
|
||||
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vscode-elements/elements": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vscode-elements/elements/-/elements-1.2.0.tgz",
|
||||
"integrity": "sha512-aCsf9iEnx+PE2rRfAySjvFTSgqP4NUvHG0nOc5AxFB1FXHyG/ayYA2TN9XpT7zuO024tRAu+XoKREbRC7uAmLA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lit": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/l10n": {
|
||||
"version": "0.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.14.tgz",
|
||||
@@ -6574,20 +6595,36 @@
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lit-element": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.5.1.tgz",
|
||||
"integrity": "sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==",
|
||||
"node_modules/lit": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lit/-/lit-3.1.2.tgz",
|
||||
"integrity": "sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lit-html": "^1.1.1"
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-element": "^4.0.4",
|
||||
"lit-html": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-element": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.4.tgz",
|
||||
"integrity": "sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-html": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-html": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.4.1.tgz",
|
||||
"integrity": "sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==",
|
||||
"dev": true
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.2.tgz",
|
||||
"integrity": "sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/load-json-file": {
|
||||
"version": "4.0.0",
|
||||
@@ -10193,9 +10230,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/recoil": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.4.1.tgz",
|
||||
"integrity": "sha512-vp6KPwlHOjJ4bJofmdDchmgI9ilMTCoUisK8/WYLl8dThH7e7KmtZttiLgvDb2Em99dUfTEsk8vT8L1nUMgqXQ==",
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz",
|
||||
"integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hamt_plus": "1.0.2"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"displayName": "Front Matter CMS",
|
||||
"description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and many more...",
|
||||
"icon": "assets/frontmatter-teal-128x128.png",
|
||||
"version": "10.0.1",
|
||||
"version": "10.1.0",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -2744,7 +2744,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "^1.1.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
@@ -2769,6 +2768,7 @@
|
||||
"@types/vscode": "^1.73.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vscode-elements/elements": "^1.2.0",
|
||||
"@vscode/l10n": "^0.0.14",
|
||||
"@vscode/webview-ui-toolkit": "^1.2.2",
|
||||
"@webpack-cli/serve": "^1.7.0",
|
||||
@@ -2816,7 +2816,7 @@
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"recoil": "^0.4.1",
|
||||
"recoil": "^0.7.7",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.8",
|
||||
|
||||
@@ -10,3 +10,16 @@ export const SENTRY_LINK =
|
||||
'https://1ac45704bbe74264a7b4674bdc2abf48@o1022172.ingest.sentry.io/5988293';
|
||||
|
||||
export const DOCS_SUBMODULES = 'https://frontmatter.codes/docs/git-integration#git-submodules';
|
||||
|
||||
export const WEBSITE_LINKS = {
|
||||
root: 'https://frontmatter.codes',
|
||||
api: {
|
||||
metrics: 'https://frontmatter.codes/api/metrics',
|
||||
ai: 'https://frontmatter.codes/api/ai'
|
||||
},
|
||||
docs: {
|
||||
dataDashboard: 'https://frontmatter.codes/docs/dashboard/datafiles-view',
|
||||
snippets: `https://frontmatter.codes/docs/snippets`,
|
||||
snippetsPlaceholders: `https://frontmatter.codes/docs/snippets#placeholders`
|
||||
}
|
||||
};
|
||||
|
||||
37
src/dashboardWebView/components/Common/ItemSelection.tsx
Normal file
37
src/dashboardWebView/components/Common/ItemSelection.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import useSelectedItems from '../../hooks/useSelectedItems';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface IItemSelectionProps {
|
||||
filePath: string;
|
||||
isRowItem?: boolean;
|
||||
}
|
||||
|
||||
export const ItemSelection: React.FunctionComponent<IItemSelectionProps> = ({
|
||||
filePath,
|
||||
isRowItem
|
||||
}: React.PropsWithChildren<IItemSelectionProps>) => {
|
||||
const { onMultiSelect, selectedFiles } = useSelectedItems();
|
||||
|
||||
const cssNames = useMemo(() => {
|
||||
if (isRowItem) {
|
||||
return 'block';
|
||||
}
|
||||
return `${selectedFiles.includes(filePath) ? 'block' : 'hidden'} absolute top-2 left-2`;
|
||||
}, [isRowItem, selectedFiles]);
|
||||
|
||||
return (
|
||||
<div className={`${cssNames} group-hover:block`}>
|
||||
<VSCodeCheckbox
|
||||
style={{
|
||||
boxShadow: isRowItem ? "" : "0 0 3px var(--frontmatter-border-preserve)"
|
||||
}}
|
||||
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
onMultiSelect(filePath);
|
||||
}}
|
||||
checked={selectedFiles.includes(filePath)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { TelemetryEvent } from '../../../constants';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { FilesProvider } from '../../providers/FilesProvider';
|
||||
|
||||
export interface IContentsProps {
|
||||
pages: Page[];
|
||||
@@ -32,18 +33,20 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageLayout folders={pageFolders} totalPages={pageItems.length}>
|
||||
<div className="w-full flex-grow max-w-full mx-auto pb-6">
|
||||
{loading ? <Spinner type={loading} /> : <Overview pages={pageItems} settings={settings} />}
|
||||
</div>
|
||||
<FilesProvider files={pageItems}>
|
||||
<PageLayout folders={pageFolders} totalPages={pageItems.length}>
|
||||
<div className="w-full flex-grow max-w-full mx-auto pb-6">
|
||||
{loading ? <Spinner type={loading} /> : <Overview pages={pageItems} settings={settings} />}
|
||||
</div>
|
||||
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=content" alt="Content metrics" />
|
||||
</PageLayout>
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=content" alt="Content metrics" />
|
||||
</PageLayout>
|
||||
</FilesProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '../..';
|
||||
import useCard from '../../hooks/useCard';
|
||||
import { I18nLabel } from './I18nLabel';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
|
||||
export interface IItemProps extends Page { }
|
||||
|
||||
@@ -133,6 +134,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
}
|
||||
</button>
|
||||
|
||||
<ItemSelection filePath={pageData.fmFilePath} />
|
||||
|
||||
<div className="relative p-4 w-full grow">
|
||||
{
|
||||
(statusPlaceholder || datePlaceholder) && (
|
||||
@@ -232,6 +235,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
className={`px-5 cursor-pointer w-full text-left grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8 py-2 border-b hover:bg-opacity-70 border-[var(--frontmatter-border)] hover:bg-[var(--vscode-sideBar-background)]`}
|
||||
>
|
||||
<div className="col-span-8 font-bold truncate flex items-center space-x-4">
|
||||
<ItemSelection filePath={pageData.fmFilePath} isRowItem />
|
||||
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import useCard from '../../hooks/useCard';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
|
||||
export interface IPinnedItemProps extends Page { }
|
||||
|
||||
@@ -21,7 +22,7 @@ export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
|
||||
}, [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)]'>
|
||||
<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)] relative'>
|
||||
<button onClick={openFile} className='relative h-full w-1/3'>
|
||||
{
|
||||
pageData["fmPreviewImage"] ? (
|
||||
@@ -41,6 +42,8 @@ export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
|
||||
}
|
||||
</button>
|
||||
|
||||
<ItemSelection filePath={pageData.fmFilePath} />
|
||||
|
||||
<button onClick={openFile} className='relative w-2/3 p-4 pr-6 text-left flex items-start'>
|
||||
<p className='font-bold'>{escapedTitle}</p>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Container } from './SortableContainer';
|
||||
import { SortableItem } from './SortableItem';
|
||||
import { ChevronRightIcon, CircleStackIcon } from '@heroicons/react/24/outline';
|
||||
import { DataType } from '../../../models/DataType';
|
||||
import { TelemetryEvent } from '../../../constants';
|
||||
import { TelemetryEvent, WEBSITE_LINKS } from '../../../constants';
|
||||
import { NavigationItem } from '../Layout';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
@@ -265,7 +265,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
<p className="text-xl mt-4">
|
||||
<a
|
||||
className={`text-[var(--frontmatter-link)] hover:text-[var(--frontmatter-link-hover)]`}
|
||||
href={`https://frontmatter.codes/docs/dashboard#data-files-view`}
|
||||
href={WEBSITE_LINKS.docs.dataDashboard}
|
||||
title={l10n.t(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
|
||||
255
src/dashboardWebView/components/Header/ActionsBar.tsx
Normal file
255
src/dashboardWebView/components/Header/ActionsBar.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationType, Page } from '../../models';
|
||||
import { CommandLineIcon, PencilIcon, TrashIcon, ChevronDownIcon, XMarkIcon, EyeIcon, LanguageIcon } from '@heroicons/react/24/outline';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MultiSelectedItemsAtom, SelectedItemActionAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state';
|
||||
import { ActionsBarItem } from './ActionsBarItem';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { CustomScript, ScriptType } from '../../../models';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { useFilesContext } from '../../providers/FilesProvider';
|
||||
import { COMMAND_NAME, GeneralCommands } from '../../../constants';
|
||||
|
||||
export interface IActionsBarProps {
|
||||
view: NavigationType;
|
||||
}
|
||||
|
||||
export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
view
|
||||
}: React.PropsWithChildren<IActionsBarProps>) => {
|
||||
const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom);
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
const [showAlert, setShowAlert] = React.useState(false);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const { files } = useFilesContext();
|
||||
|
||||
const viewFile = React.useCallback(() => {
|
||||
if (selectedFiles.length === 1) {
|
||||
if (view === NavigationType.Contents) {
|
||||
messageHandler.send(DashboardMessage.openFile, selectedFiles[0]);
|
||||
} else if (view === NavigationType.Media) {
|
||||
setSelectedItemAction({ path: selectedFiles[0], action: 'view' })
|
||||
}
|
||||
}
|
||||
}, [selectedFiles]);
|
||||
|
||||
const onDeleteConfirm = React.useCallback(() => {
|
||||
for (const file of selectedFiles) {
|
||||
if (file) {
|
||||
if (view === NavigationType.Contents) {
|
||||
messageHandler.send(DashboardMessage.deleteFile, file);
|
||||
} else if (view === NavigationType.Media) {
|
||||
messageHandler.send(DashboardMessage.deleteMedia, {
|
||||
file: file,
|
||||
folder: selectedFolder
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelectedFiles([]);
|
||||
setShowAlert(false);
|
||||
}, [selectedFiles]);
|
||||
|
||||
const runCustomScript = React.useCallback((script: CustomScript) => {
|
||||
for (const file of selectedFiles) {
|
||||
messageHandler.send(DashboardMessage.runCustomScript, {
|
||||
script,
|
||||
path: file
|
||||
});
|
||||
}
|
||||
}, [selectedFiles]);
|
||||
|
||||
const languageActions = React.useMemo(() => {
|
||||
const actions: React.ReactNode[] = [];
|
||||
|
||||
if (view === NavigationType.Contents && files.length > 0 && selectedFiles.length === 1) {
|
||||
const selectedItem = selectedFiles[0];
|
||||
const page = ((files || []) as Page[]).find((f: Page) => f.fmFilePath === selectedItem);
|
||||
|
||||
if (page?.fmLocale) {
|
||||
const locale = page.fmLocale;
|
||||
const translations = page.fmTranslations;
|
||||
|
||||
actions.push(
|
||||
<ActionsBarItem
|
||||
key="translate"
|
||||
onClick={() => {
|
||||
messageHandler.send(GeneralCommands.toVSCode.runCommand, {
|
||||
command: COMMAND_NAME.i18n.create,
|
||||
args: selectedItem
|
||||
})
|
||||
}}>
|
||||
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonTranslate)}</span>
|
||||
</ActionsBarItem>
|
||||
)
|
||||
|
||||
if (translations && Object.keys(translations).length > 0) {
|
||||
const crntLocale = translations[locale.locale];
|
||||
const otherLocales = Object.entries(translations).filter(([key]) => key !== locale.locale);
|
||||
|
||||
if (otherLocales.length > 0) {
|
||||
actions.push(
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className='flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'
|
||||
>
|
||||
<LanguageIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonLanguages)}</span>
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align='start'>
|
||||
|
||||
<DropdownMenuItem onClick={() => messageHandler.send(DashboardMessage.openFile, crntLocale.path)}>
|
||||
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{
|
||||
otherLocales.map(([key, value]) => (
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
onClick={() => messageHandler.send(DashboardMessage.openFile, value.path)}
|
||||
>
|
||||
<span>{value.locale.title || value.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}, [files, selectedFiles]);
|
||||
|
||||
const customScriptActions = React.useMemo(() => {
|
||||
if (!settings?.scripts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { scripts } = settings;
|
||||
let crntScripts: CustomScript[] = [];
|
||||
if (view === NavigationType.Contents) {
|
||||
crntScripts = (scripts || [])
|
||||
.filter((script) => (script.type === undefined || script.type === ScriptType.Content) && !script.bulk && !script.hidden);
|
||||
} else if (view === NavigationType.Media) {
|
||||
crntScripts = (scripts || [])
|
||||
.filter((script) => script.type === ScriptType.MediaFile && !script.hidden);
|
||||
}
|
||||
|
||||
if (crntScripts.length > 0) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className='flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] disabled:opacity-50 disabled:hover:text-[var(--vscode-tab-inactiveForeground)]'
|
||||
disabled={selectedFiles.length === 0}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonScripts)}</span>
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align='start'>
|
||||
{
|
||||
crntScripts.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.id || script.title}
|
||||
onClick={() => runCustomScript(script)}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [view, settings?.scripts, selectedFiles]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`w-full flex items-center justify-between py-2 px-4 border-b bg-[var(--vscode-sideBar-background)] text-[var(--vscode-sideBar-foreground)] border-[var(--frontmatter-border)]`}
|
||||
aria-label="Item actions"
|
||||
>
|
||||
<div className='flex items-center space-x-6'>
|
||||
<ActionsBarItem
|
||||
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
|
||||
onClick={viewFile}
|
||||
>
|
||||
<EyeIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonView)}</span>
|
||||
</ActionsBarItem>
|
||||
|
||||
{
|
||||
view === NavigationType.Media && (
|
||||
<>
|
||||
<ActionsBarItem
|
||||
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
|
||||
onClick={() => setSelectedItemAction({
|
||||
path: selectedFiles[0],
|
||||
action: 'edit'
|
||||
})}
|
||||
>
|
||||
<PencilIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonEdit)}</span>
|
||||
</ActionsBarItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{languageActions}
|
||||
|
||||
{customScriptActions}
|
||||
|
||||
<ActionsBarItem
|
||||
className='hover:text-[var(--vscode-statusBarItem-errorBackground)]'
|
||||
disabled={selectedFiles.length === 0}
|
||||
onClick={() => setShowAlert(true)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
</ActionsBarItem>
|
||||
</div>
|
||||
|
||||
{
|
||||
selectedFiles.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className='flex items-center hover:text-[var(--vscode-statusBarItem-warningBackground)]'
|
||||
onClick={() => setSelectedFiles([])}
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.dashboardHeaderActionsBarItemsSelected)}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{showAlert && (
|
||||
<Alert
|
||||
title={`${l10n.t(LocalizationKey.dashboardHeaderActionsBarAlertDeleteTitle)}`}
|
||||
description={l10n.t(LocalizationKey.dashboardHeaderActionsBarAlertDeleteDescription)}
|
||||
okBtnText={l10n.t(LocalizationKey.commonDelete)}
|
||||
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
|
||||
dismiss={() => setShowAlert(false)}
|
||||
trigger={onDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
26
src/dashboardWebView/components/Header/ActionsBarItem.tsx
Normal file
26
src/dashboardWebView/components/Header/ActionsBarItem.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../../../utils/cn';
|
||||
|
||||
export interface IActionsBarItemProps {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const ActionsBarItem: React.FunctionComponent<IActionsBarItemProps> = ({
|
||||
children,
|
||||
className,
|
||||
disabled,
|
||||
onClick
|
||||
}: React.PropsWithChildren<IActionsBarItemProps>) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(`flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] disabled:opacity-50 disabled:hover:text-[var(--vscode-tab-inactiveForeground)]`, className)}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -4,24 +4,25 @@ import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { HOME_PAGE_NAVIGATION_ID } from '../../../constants';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { SearchAtom, SelectedMediaFolderAtom, SettingsAtom } from '../../state';
|
||||
import { SearchAtom, SettingsAtom } from '../../state';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import useMediaFolder from '../../hooks/useMediaFolder';
|
||||
|
||||
export interface IBreadcrumbProps { }
|
||||
|
||||
export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (
|
||||
_: React.PropsWithChildren<IBreadcrumbProps>
|
||||
) => {
|
||||
const [selectedFolder, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const { selectedFolder, updateFolder } = useMediaFolder();
|
||||
const [, setSearchValue] = useRecoilState(SearchAtom);
|
||||
const [folders, setFolders] = React.useState<string[]>([]);
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
|
||||
const updateFolder = (folder: string) => {
|
||||
const updateMediaFolder = React.useCallback((folder: string) => {
|
||||
setSearchValue('');
|
||||
setSelectedFolder(folder);
|
||||
};
|
||||
updateFolder(folder);
|
||||
}, [updateFolder, setSearchValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!settings) {
|
||||
@@ -79,11 +80,11 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (
|
||||
}, [selectedFolder, settings]);
|
||||
|
||||
return (
|
||||
<ol role="list" className="flex space-x-4 px-5 flex-1">
|
||||
<ol role="list" className="flex space-x-2 px-4 flex-1">
|
||||
<li className="flex">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={() => setSelectedFolder(HOME_PAGE_NAVIGATION_ID)}
|
||||
onClick={() => updateMediaFolder(HOME_PAGE_NAVIGATION_ID)}
|
||||
className={`text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]`}
|
||||
>
|
||||
<HomeIcon className="flex-shrink-0 h-5 w-5" aria-hidden="true" />
|
||||
@@ -106,8 +107,8 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (
|
||||
</svg>
|
||||
|
||||
<button
|
||||
onClick={() => updateFolder(folder)}
|
||||
className={`ml-4 text-sm font-medium text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]`}
|
||||
onClick={() => updateMediaFolder(folder)}
|
||||
className={`ml-2 text-sm font-medium text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]`}
|
||||
>
|
||||
{basename(folder)}
|
||||
</button>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Grouping } from '.';
|
||||
import { ViewSwitch } from './ViewSwitch';
|
||||
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
import { GroupingSelector, SortingAtom } from '../../state';
|
||||
import { GroupingSelector, MultiSelectedItemsAtom, SortingAtom } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ClearFilters } from './ClearFilters';
|
||||
import { MediaHeaderTop } from '../Media/MediaHeaderTop';
|
||||
@@ -18,8 +18,7 @@ import { ArrowTopRightOnSquareIcon, BoltIcon, PlusIcon } from '@heroicons/react/
|
||||
import { HeartIcon } from '@heroicons/react/24/solid';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '../..';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { SyncButton } from './SyncButton';
|
||||
import { useMemo } from 'react';
|
||||
import { Pagination } from './Pagination';
|
||||
import { GroupOption } from '../../constants/GroupOption';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
@@ -32,6 +31,7 @@ import { SettingsLink } from '../SettingsView/SettingsLink';
|
||||
import { Link } from '../Common/Link';
|
||||
import { SPONSOR_LINK } from '../../../constants';
|
||||
import { Filters } from './Filters';
|
||||
import { ActionsBar } from './ActionsBar';
|
||||
|
||||
export interface IHeaderProps {
|
||||
header?: React.ReactNode;
|
||||
@@ -51,6 +51,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
}: React.PropsWithChildren<IHeaderProps>) => {
|
||||
const grouping = useRecoilValue(GroupingSelector);
|
||||
const resetSorting = useResetRecoilState(SortingAtom);
|
||||
const resetSelectedItems = useResetRecoilState(MultiSelectedItemsAtom);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination);
|
||||
@@ -70,6 +71,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
const updateView = (view: NavigationType) => {
|
||||
navigate(routePaths[view]);
|
||||
resetSorting();
|
||||
resetSelectedItems();
|
||||
};
|
||||
|
||||
const runBulkScript = (script: CustomScript) => {
|
||||
@@ -122,7 +124,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
|
||||
return (
|
||||
<div className={`w-full sticky top-0 z-20 bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]`}>
|
||||
<div className={`mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<div className={`overflow-x-auto mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<Tabs onNavigate={updateView} />
|
||||
|
||||
<div className='flex items-center space-x-2 pr-4'>
|
||||
@@ -160,12 +162,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
|
||||
{location.pathname === routePaths.contents && (
|
||||
<>
|
||||
<div className={`px-4 mt-3 mb-2 flex items-center justify-between`}>
|
||||
<Searchbox />
|
||||
|
||||
<div className={`flex items-center justify-end space-x-4 flex-1`}>
|
||||
{/* <SyncButton /> */}
|
||||
|
||||
<div className={`px-4 mt-2 mb-2 flex items-center justify-between`}>
|
||||
<div className={`flex items-center justify-start space-x-4 flex-1`}>
|
||||
<ChoiceButton
|
||||
title={l10n.t(LocalizationKey.dashboardHeaderHeaderCreateContent)}
|
||||
choices={choiceOptions}
|
||||
@@ -173,6 +171,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
disabled={!settings?.initialized}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Searchbox />
|
||||
</div>
|
||||
|
||||
<div className={`px-4 flex flex-row items-center border-b justify-between border-[var(--frontmatter-border)]`}>
|
||||
@@ -186,7 +186,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end border-b space-x-4 lg:space-x-6 xl:space-x-8 bg-[var(--vscode-panel-background)] border-[var(--frontmatter-border)]`}
|
||||
className={`overflow-x-auto py-2 px-4 w-full flex items-center justify-between lg:justify-end border-b space-x-4 lg:space-x-6 xl:space-x-8 bg-[var(--vscode-panel-background)] border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<ClearFilters />
|
||||
|
||||
@@ -208,6 +208,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
<Pagination totalPages={totalPages || 0} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ActionsBar view={NavigationType.Contents} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -216,6 +218,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
<MediaHeaderTop />
|
||||
|
||||
<MediaHeaderBottom />
|
||||
|
||||
<ActionsBar view={NavigationType.Media} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({
|
||||
}, [debounceSearch]);
|
||||
|
||||
return (
|
||||
<div className="flex space-x-4 flex-1">
|
||||
<div className="flex justify-end space-x-4 flex-1">
|
||||
<div className="min-w-0">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{l10n.t(LocalizationKey.commonSearch)}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const PageLayout: React.FunctionComponent<IPageLayoutProps> = ({
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<div className="flex flex-col h-full overflow-y-auto overflow-x-hidden">
|
||||
<Header header={header} folders={folders} totalPages={totalPages} settings={settings} />
|
||||
|
||||
<div
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import {
|
||||
AllContentFoldersAtom,
|
||||
AllStaticFoldersAtom,
|
||||
SelectedMediaFolderAtom,
|
||||
SettingsSelector,
|
||||
ViewDataSelector
|
||||
} from '../../state';
|
||||
@@ -18,13 +17,14 @@ import { extname } from 'path';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import useMediaFolder from '../../hooks/useMediaFolder';
|
||||
|
||||
export interface IFolderCreationProps { }
|
||||
|
||||
export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
|
||||
props: React.PropsWithChildren<IFolderCreationProps>
|
||||
_: React.PropsWithChildren<IFolderCreationProps>
|
||||
) => {
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderAtom);
|
||||
const { selectedFolder } = useMediaFolder();
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const allStaticFolders = useRecoilValue(AllStaticFoldersAtom);
|
||||
const allContentFolders = useRecoilValue(AllContentFoldersAtom);
|
||||
@@ -90,7 +90,7 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
|
||||
|
||||
if (scripts.length > 0) {
|
||||
return (
|
||||
<div className="flex flex-1 justify-end">
|
||||
<div className="flex flex-1 justify-start">
|
||||
{renderPostAssetsButton}
|
||||
<ChoiceButton
|
||||
title={l10n.t(LocalizationKey.dashboardMediaFolderCreationFolderCreate)}
|
||||
@@ -107,7 +107,7 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 justify-end">
|
||||
<div className="flex flex-1 justify-start">
|
||||
{renderPostAssetsButton}
|
||||
<button
|
||||
className={`inline-flex items-center px-3 py-1 border border-transparent text-xs leading-4 font-medium focus:outline-none rounded text-[var(--vscode-button-foreground)] bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] disabled:opacity-50`}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FolderIcon } from '@heroicons/react/24/solid';
|
||||
import { basename, join } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedMediaFolderAtom } from '../../state';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import useMediaFolder from '../../hooks/useMediaFolder';
|
||||
|
||||
export interface IFolderItemProps {
|
||||
folder: string;
|
||||
@@ -15,7 +16,7 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({
|
||||
wsFolder,
|
||||
staticFolder
|
||||
}: React.PropsWithChildren<IFolderItemProps>) => {
|
||||
const [, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const { updateFolder } = useMediaFolder();
|
||||
|
||||
const relFolderPath = wsFolder ? folder.replace(wsFolder, '') : folder;
|
||||
|
||||
@@ -29,9 +30,9 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({
|
||||
className={`group relative hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}
|
||||
>
|
||||
<button
|
||||
title={isContentFolder ? 'Content directory folder' : 'Public directory folder'}
|
||||
title={isContentFolder ? l10n.t(LocalizationKey.dashboardMediaFolderItemContentDirectory) : l10n.t(LocalizationKey.dashboardMediaFolderItemPublicDirectory)}
|
||||
className={`p-4 w-full flex flex-row items-center h-full`}
|
||||
onClick={() => setSelectedFolder(folder)}
|
||||
onClick={() => updateFolder(folder)}
|
||||
>
|
||||
<div className="relative mr-4">
|
||||
<FolderIcon className={`h-12 w-12`} />
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PlusIcon,
|
||||
VideoCameraIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import { basename } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
@@ -17,19 +17,21 @@ import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import {
|
||||
LightboxAtom,
|
||||
SelectedItemActionAtom,
|
||||
SelectedMediaFolderSelector,
|
||||
SettingsSelector,
|
||||
ViewDataSelector
|
||||
} from '../../state';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { InfoDialog } from '../Modals/InfoDialog';
|
||||
import { DetailsSlideOver } from './DetailsSlideOver';
|
||||
import { MediaSnippetForm } from './MediaSnippetForm';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { ItemMenu } from './ItemMenu';
|
||||
import { getRelPath } from '../../utils';
|
||||
import { Snippet } from '../../../models';
|
||||
import useMediaInfo from '../../hooks/useMediaInfo';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
|
||||
export interface IItemProps {
|
||||
media: MediaInfo;
|
||||
@@ -39,17 +41,17 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
media,
|
||||
}: React.PropsWithChildren<IItemProps>) => {
|
||||
const [, setLightbox] = useRecoilState(LightboxAtom);
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [showSnippetSelection, setShowSnippetSelection] = useState(false);
|
||||
const [snippet, setSnippet] = useState<Snippet | undefined>(undefined);
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [showSnippetFormDialog, setShowSnippetFormDialog] = useState(false);
|
||||
const [mediaData, setMediaData] = useState<any | undefined>(undefined);
|
||||
const [filename, setFilename] = useState<string | null>(null);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
const { mediaFolder, mediaDetails, isAudio, isImage, isVideo } = useMediaInfo(media);
|
||||
|
||||
const relPath = useMemo(() => {
|
||||
return getRelPath(media.fsPath, settings?.staticFolder, settings?.wsFolder);
|
||||
@@ -74,19 +76,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return viewData?.data?.position && mediaSnippets.length > 0;
|
||||
}, [viewData, mediaSnippets]);
|
||||
|
||||
const getFolder = () => {
|
||||
if (settings?.wsFolder && media.fsPath) {
|
||||
let relPath = media.fsPath.split(settings.wsFolder).pop();
|
||||
|
||||
if (settings.staticFolder && relPath) {
|
||||
relPath = relPath.split(settings.staticFolder).pop();
|
||||
}
|
||||
|
||||
return dirname(parseWinPath(relPath) || '');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const getFileName = () => {
|
||||
return basename(parseWinPath(media.fsPath) || '');
|
||||
};
|
||||
@@ -190,75 +179,17 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const getDimensions = () => {
|
||||
if (media.dimensions) {
|
||||
return `${media.dimensions.width} x ${media.dimensions.height}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const getSize = () => {
|
||||
if (media?.size) {
|
||||
const size = media.size / (1024 * 1024);
|
||||
if (size > 1) {
|
||||
return `${size.toFixed(2)} MB`;
|
||||
} else {
|
||||
return `${(size * 1024).toFixed(2)} KB`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const getMediaDetails = () => {
|
||||
let sizeDetails = [];
|
||||
|
||||
const dimensions = getDimensions();
|
||||
if (dimensions) {
|
||||
sizeDetails.push(dimensions);
|
||||
}
|
||||
|
||||
const size = getSize();
|
||||
if (size) {
|
||||
sizeDetails.push(size);
|
||||
}
|
||||
|
||||
return sizeDetails.join(' - ');
|
||||
};
|
||||
|
||||
const openLightbox = useCallback(() => {
|
||||
if (isImageFile) {
|
||||
if (isImage) {
|
||||
setLightbox(media.vsPath || '');
|
||||
}
|
||||
}, [media.vsPath]);
|
||||
|
||||
const updateMetadata = () => {
|
||||
setShowForm(true);
|
||||
setShowDetails(true);
|
||||
};
|
||||
|
||||
const isVideoFile = useMemo(() => {
|
||||
if (media.mimeType?.startsWith('video/')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const isAudioFile = useMemo(() => {
|
||||
if (media.mimeType?.startsWith('audio/')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const isImageFile = useMemo(() => {
|
||||
if (
|
||||
media.mimeType?.startsWith('image/') &&
|
||||
!media.mimeType?.startsWith('image/vnd.adobe.photoshop')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const updateMetadata = useCallback(() => {
|
||||
setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'edit'
|
||||
});
|
||||
}, [media]);
|
||||
|
||||
const renderMediaIcon = useMemo(() => {
|
||||
@@ -273,15 +204,15 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isImageFile) {
|
||||
if (isImage) {
|
||||
return <PhotoIcon className={`h-1/2 ${colors}`} />;
|
||||
}
|
||||
|
||||
if (isVideoFile) {
|
||||
if (isVideo) {
|
||||
icon = <VideoCameraIcon className={`h-4/6 ${colors}`} />;
|
||||
}
|
||||
|
||||
if (isAudioFile) {
|
||||
if (isAudio) {
|
||||
icon = <MusicalNoteIcon className={`h-4/6 ${colors}`} />;
|
||||
}
|
||||
|
||||
@@ -293,18 +224,18 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}, [media, isImageFile, isVideoFile, isAudioFile]);
|
||||
}, [media, isImage, isVideo, isAudio]);
|
||||
|
||||
const renderMedia = useMemo(() => {
|
||||
if (isAudioFile) {
|
||||
if (isAudio) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isVideoFile) {
|
||||
if (isVideo) {
|
||||
return <video src={media.vsPath} className="mx-auto object-cover" controls muted />;
|
||||
}
|
||||
|
||||
if (isImageFile) {
|
||||
if (isImage) {
|
||||
return (
|
||||
<img src={media.vsPath} alt={basename(media.fsPath)} className="mx-auto object-cover" />
|
||||
);
|
||||
@@ -336,7 +267,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
<>
|
||||
<li className={`group relative shadow-md hover:shadow-xl dark:shadow-none border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<button
|
||||
className={`group/button relative block w-full aspect-w-10 aspect-h-7 overflow-hidden h-48 ${isImageFile ? 'cursor-pointer' : 'cursor-default'} border-b border-[var(--frontmatter-border)]`}
|
||||
className={`group/button relative block w-full aspect-w-10 aspect-h-7 overflow-hidden h-48 ${isImage ? 'cursor-pointer' : 'cursor-default'} border-b border-[var(--frontmatter-border)]`}
|
||||
onClick={hasViewData ? undefined : openLightbox}
|
||||
>
|
||||
<div
|
||||
@@ -349,6 +280,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
>
|
||||
{renderMedia}
|
||||
</div>
|
||||
|
||||
<ItemSelection filePath={media.fsPath} />
|
||||
|
||||
{hasViewData && (
|
||||
<div
|
||||
className={`hidden group-hover/button:flex absolute top-0 right-0 bottom-0 left-0 items-center justify-center bg-black bg-opacity-70`}
|
||||
@@ -379,6 +313,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ItemSelection filePath={media.fsPath} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
@@ -393,14 +329,14 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
insertIntoArticle={insertIntoArticle}
|
||||
insertSnippet={insertSnippet}
|
||||
showUpdateMedia={updateMetadata}
|
||||
showMediaDetails={() => setShowDetails(true)}
|
||||
showMediaDetails={() => setSelectedItemAction({ path: media.fsPath, action: 'view' })}
|
||||
processSnippet={processSnippet}
|
||||
onDelete={() => setShowAlert(true)} />
|
||||
|
||||
<p className={`text-sm font-bold pointer-events-none flex items-center break-all text-[var(--vscode-foreground)]}`}>
|
||||
{basename(parseWinPath(media.fsPath) || '')}
|
||||
</p>
|
||||
{!isImageFile && media.metadata.title && (
|
||||
{!isImage && media.metadata.title && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}:
|
||||
@@ -430,7 +366,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonSize)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>
|
||||
{getMediaDetails()}
|
||||
{mediaDetails}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
@@ -459,29 +395,10 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</InfoDialog>
|
||||
)}
|
||||
|
||||
{showDetails && (
|
||||
<DetailsSlideOver
|
||||
imgSrc={media.vsPath || ''}
|
||||
size={getSize()}
|
||||
dimensions={getDimensions()}
|
||||
folder={getFolder()}
|
||||
media={media}
|
||||
showForm={showForm}
|
||||
isImageFile={isImageFile}
|
||||
isVideoFile={isVideoFile}
|
||||
onEdit={() => setShowForm(true)}
|
||||
onEditClose={() => setShowForm(false)}
|
||||
onDismiss={() => {
|
||||
setShowDetails(false);
|
||||
setShowForm(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showAlert && (
|
||||
<Alert
|
||||
title={`${l10n.t(LocalizationKey.commonDelete)}: ${basename(parseWinPath(media.fsPath) || '')}`}
|
||||
description={l10n.t(LocalizationKey.dashboardMediaItemAlertDeleteDescription, getFolder())}
|
||||
description={l10n.t(LocalizationKey.dashboardMediaItemAlertDeleteDescription, mediaFolder)}
|
||||
okBtnText={l10n.t(LocalizationKey.commonDelete)}
|
||||
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
|
||||
dismiss={() => setShowAlert(false)}
|
||||
|
||||
@@ -27,6 +27,8 @@ import { basename, extname, join } from 'path';
|
||||
import { MediaInfo } from '../../../models';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { MediaItemPanel } from './MediaItemPanel';
|
||||
import { FilesProvider } from '../../providers/FilesProvider';
|
||||
|
||||
export interface IMediaProps { }
|
||||
|
||||
@@ -162,111 +164,115 @@ export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
});
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<div className="w-full h-full pb-6" {...getRootProps()}>
|
||||
{viewData?.data?.filePath && (
|
||||
<div className={`text-lg text-center mb-6`}>
|
||||
<p>{l10n.t(LocalizationKey.dashboardMediaMediaDescription)}</p>
|
||||
<p className={`opacity-80 text-base`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDragActive && (
|
||||
<div className={`absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-50 text-[var(--vscode-foreground)] bg-[var(--vscode-editor-background)] opacity-75`}>
|
||||
<ArrowUpTrayIcon className={`h-32`} />
|
||||
<p className={`text-xl max-w-md text-center`}>
|
||||
{selectedFolder
|
||||
? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder)
|
||||
: l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allMedia.length === 0 && folders.length === 0 && !loading && (
|
||||
<div className={`flex items-center justify-center h-full`}>
|
||||
<div className={`max-w-xl text-center`}>
|
||||
<FrontMatterIcon
|
||||
className={`h-32 mx-auto opacity-90 mb-8 text-[var(--vscode-editor-foreground)]`}
|
||||
/>
|
||||
|
||||
<p className={`text-xl font-medium`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)}
|
||||
<FilesProvider files={allMedia}>
|
||||
<PageLayout>
|
||||
<div className="w-full h-full pb-6" {...getRootProps()}>
|
||||
{viewData?.data?.filePath && (
|
||||
<div className={`text-lg text-center mb-6`}>
|
||||
<p>{l10n.t(LocalizationKey.dashboardMediaMediaDescription)}</p>
|
||||
<p className={`opacity-80 text-base`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{contentFolders &&
|
||||
contentFolders.length > 0 &&
|
||||
contentFolders.map(
|
||||
(group, idx) =>
|
||||
group.folders &&
|
||||
group.folders.length > 0 && (
|
||||
<div key={`group-${idx}`} className={`mb-8`}>
|
||||
<h2 className="text-lg mb-8 first-letter:uppercase">
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: <b>{group.title}</b>
|
||||
</h2>
|
||||
|
||||
<List gap={0}>
|
||||
{group.folders.map((folder) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
folder={folder}
|
||||
staticFolder={currentStaticFolder}
|
||||
wsFolder={settings?.wsFolder}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{publicFolders && publicFolders.length > 0 && (
|
||||
<div className={`mb-8`}>
|
||||
{contentFolders && contentFolders.length > 0 && (
|
||||
<h2 className="text-lg mb-8">
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)}
|
||||
{currentStaticFolder && (
|
||||
<span>
|
||||
: <b>{currentStaticFolder}</b>
|
||||
</span>
|
||||
)}
|
||||
</h2>
|
||||
{isDragActive && (
|
||||
<div className={`absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-50 text-[var(--vscode-foreground)] bg-[var(--vscode-editor-background)] opacity-75`}>
|
||||
<ArrowUpTrayIcon className={`h-32`} />
|
||||
<p className={`text-xl max-w-md text-center`}>
|
||||
{selectedFolder
|
||||
? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder)
|
||||
: l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allMedia.length === 0 && folders.length === 0 && !loading && (
|
||||
<div className={`flex items-center justify-center h-full`}>
|
||||
<div className={`max-w-xl text-center`}>
|
||||
<FrontMatterIcon
|
||||
className={`h-32 mx-auto opacity-90 mb-8 text-[var(--vscode-editor-foreground)]`}
|
||||
/>
|
||||
|
||||
<p className={`text-xl font-medium`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{contentFolders &&
|
||||
contentFolders.length > 0 &&
|
||||
contentFolders.map(
|
||||
(group, idx) =>
|
||||
group.folders &&
|
||||
group.folders.length > 0 && (
|
||||
<div key={`group-${idx}`} className={`mb-8`}>
|
||||
<h2 className="text-lg mb-8 first-letter:uppercase">
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: <b>{group.title}</b>
|
||||
</h2>
|
||||
|
||||
<List gap={0}>
|
||||
{group.folders.map((folder) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
folder={folder}
|
||||
staticFolder={currentStaticFolder}
|
||||
wsFolder={settings?.wsFolder}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
<List gap={0}>
|
||||
{publicFolders.map((folder) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
folder={folder}
|
||||
staticFolder={currentStaticFolder}
|
||||
wsFolder={settings?.wsFolder}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
)}
|
||||
{publicFolders && publicFolders.length > 0 && (
|
||||
<div className={`mb-8`}>
|
||||
{contentFolders && contentFolders.length > 0 && (
|
||||
<h2 className="text-lg mb-8">
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)}
|
||||
{currentStaticFolder && (
|
||||
<span>
|
||||
: <b>{currentStaticFolder}</b>
|
||||
</span>
|
||||
)}
|
||||
</h2>
|
||||
)}
|
||||
|
||||
<List>
|
||||
{allMedia.map((file, idx) => (
|
||||
<Item key={file.fsPath} media={file} />
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
<List gap={0}>
|
||||
{publicFolders.map((folder) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
folder={folder}
|
||||
staticFolder={currentStaticFolder}
|
||||
wsFolder={settings?.wsFolder}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading && <Spinner />}
|
||||
<List>
|
||||
{allMedia.map((file, idx) => (
|
||||
<Item key={file.fsPath} media={file} />
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
|
||||
<Lightbox />
|
||||
<MediaItemPanel allMedia={allMedia} />
|
||||
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
{loading && <Spinner />}
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=media" alt="Media metrics" />
|
||||
</PageLayout>
|
||||
<Lightbox />
|
||||
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=media" alt="Media metrics" />
|
||||
</PageLayout>
|
||||
</FilesProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -81,14 +81,14 @@ export const MediaHeaderTop: React.FunctionComponent<
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={`py-3 px-4 flex items-center justify-between border-b border-[var(--frontmatter-border)]`}
|
||||
className={`py-2 px-4 flex items-center justify-between border-b border-[var(--frontmatter-border)]`}
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<Searchbox placeholder={l10n.t(LocalizationKey.dashboardMediaMediaHeaderTopSearchboxPlaceholder)} />
|
||||
<FolderCreation />
|
||||
|
||||
<PaginationStatus />
|
||||
|
||||
<FolderCreation />
|
||||
<Searchbox placeholder={l10n.t(LocalizationKey.dashboardMediaMediaHeaderTopSearchboxPlaceholder)} />
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
60
src/dashboardWebView/components/Media/MediaItemPanel.tsx
Normal file
60
src/dashboardWebView/components/Media/MediaItemPanel.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedItemActionAtom } from '../../state';
|
||||
import { MediaInfo } from '../../../models';
|
||||
import { DetailsSlideOver } from './DetailsSlideOver';
|
||||
import useMediaInfo from '../../hooks/useMediaInfo';
|
||||
|
||||
export interface IMediaItemPanelProps {
|
||||
allMedia: MediaInfo[];
|
||||
}
|
||||
|
||||
export const MediaItemPanel: React.FunctionComponent<IMediaItemPanelProps> = ({ allMedia }: React.PropsWithChildren<IMediaItemPanelProps>) => {
|
||||
const [media, setMedia] = useState<MediaInfo | undefined>(undefined);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [selectedItemAction, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
const { mediaFolder, mediaSize, mediaDimensions, isImage, isVideo } = useMediaInfo(media);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedItemAction && selectedItemAction.path) {
|
||||
const mediaFile = allMedia.find((m) => m.fsPath === selectedItemAction.path);
|
||||
setMedia(mediaFile);
|
||||
|
||||
if (selectedItemAction.action === 'edit') {
|
||||
setShowForm(true);
|
||||
setShowDetails(true);
|
||||
} else if (selectedItemAction.action === 'view') {
|
||||
setShowForm(false);
|
||||
setShowDetails(true);
|
||||
}
|
||||
|
||||
setSelectedItemAction(undefined);
|
||||
}
|
||||
}, [allMedia, selectedItemAction])
|
||||
|
||||
if (showDetails && media) {
|
||||
return (
|
||||
<DetailsSlideOver
|
||||
imgSrc={media.vsPath || ''}
|
||||
size={mediaSize}
|
||||
dimensions={mediaDimensions}
|
||||
folder={mediaFolder}
|
||||
media={media}
|
||||
showForm={showForm}
|
||||
isImageFile={isImage}
|
||||
isVideoFile={isVideo}
|
||||
onEdit={() => setShowForm(true)}
|
||||
onEditClose={() => setShowForm(false)}
|
||||
onDismiss={() => {
|
||||
setShowDetails(false);
|
||||
setShowForm(false);
|
||||
setMedia(undefined);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -14,7 +14,7 @@ export const MenuButton: React.FunctionComponent<IMenuButtonProps> = ({
|
||||
disabled
|
||||
}: React.PropsWithChildren<IMenuButtonProps>) => {
|
||||
return (
|
||||
<div className={`group flex items-center ${disabled ? 'opacity-50' : ''}`}>
|
||||
<div className={`group flex items-center shrink-0 ${disabled ? 'opacity-50' : ''}`}>
|
||||
<div className={`mr-2 font-medium flex items-center text-[var(--vscode-tab-inactiveForeground)]`}>
|
||||
{label}:
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { GeneralCommands } from '../../../constants';
|
||||
import { GeneralCommands, WEBSITE_LINKS } from '../../../constants';
|
||||
import { SnippetInput } from './SnippetInput';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
@@ -30,7 +30,7 @@ export const NewForm: React.FunctionComponent<INewFormProps> = ({
|
||||
const openLink = () => {
|
||||
Messenger.send(
|
||||
GeneralCommands.toVSCode.openLink,
|
||||
'https://frontmatter.codes/docs/snippets#placeholders'
|
||||
WEBSITE_LINKS.docs.snippetsPlaceholders
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlag } from '../../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../../constants';
|
||||
import { FEATURE_FLAG, WEBSITE_LINKS } from '../../../constants';
|
||||
import { TelemetryEvent } from '../../../constants/TelemetryEvent';
|
||||
import { SnippetParser } from '../../../helpers/SnippetParser';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
@@ -146,7 +146,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
|
||||
<p className="text-xl mt-4">
|
||||
<a
|
||||
className={`text-[var(--frontmatter-link)] hover:text-[var(--frontmatter-link-hover)]`}
|
||||
href={`https://frontmatter.codes/docs/snippets`}
|
||||
href={WEBSITE_LINKS.docs.snippets}
|
||||
title={l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsReadMore)}
|
||||
>
|
||||
{l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsReadMore)}
|
||||
|
||||
@@ -12,12 +12,12 @@ import {
|
||||
MediaTotalAtom,
|
||||
PageAtom,
|
||||
SearchAtom,
|
||||
SelectedMediaFolderAtom,
|
||||
SettingsAtom
|
||||
} from '../state';
|
||||
import Fuse from 'fuse.js';
|
||||
import usePagination from './usePagination';
|
||||
import { usePrevious } from '../../panelWebView/hooks/usePrevious';
|
||||
import useMediaFolder from './useMediaFolder';
|
||||
|
||||
const fuseOptions: Fuse.IFuseOptions<MediaInfo> = {
|
||||
keys: [
|
||||
@@ -35,7 +35,7 @@ export default function useMedia() {
|
||||
// const page = useRecoilValue(PageAtom);
|
||||
const [page, setPage] = useRecoilState(PageAtom);
|
||||
const [searchedMedia, setSearchedMedia] = useState<MediaInfo[]>([]);
|
||||
const [, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const { updateFolder } = useMediaFolder();
|
||||
const [, setTotal] = useRecoilState(MediaTotalAtom);
|
||||
const [, setFolders] = useRecoilState(MediaFoldersAtom);
|
||||
const [, setAllContentFolders] = useRecoilState(AllContentFoldersAtom);
|
||||
@@ -79,7 +79,7 @@ export default function useMedia() {
|
||||
setMedia(payload.media);
|
||||
setTotal(payload.total);
|
||||
setFolders(payload.folders);
|
||||
setSelectedFolder(payload.selectedFolder);
|
||||
updateFolder(payload.selectedFolder);
|
||||
if (search) {
|
||||
searchMedia(search, payload.media);
|
||||
} else {
|
||||
|
||||
17
src/dashboardWebView/hooks/useMediaFolder.tsx
Normal file
17
src/dashboardWebView/hooks/useMediaFolder.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { MultiSelectedItemsAtom, SelectedMediaFolderAtom } from '../state';
|
||||
|
||||
export default function useMediaFolder() {
|
||||
const [selectedFolder, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const [, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom);
|
||||
|
||||
const updateFolder = (folder: string) => {
|
||||
setSelectedFolder(folder);
|
||||
setSelectedFiles([]);
|
||||
};
|
||||
|
||||
return {
|
||||
selectedFolder,
|
||||
updateFolder
|
||||
};
|
||||
}
|
||||
91
src/dashboardWebView/hooks/useMediaInfo.tsx
Normal file
91
src/dashboardWebView/hooks/useMediaInfo.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { useMemo } from 'react';
|
||||
import { MediaInfo } from '../../models';
|
||||
import { dirname } from 'path';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsSelector } from '../state';
|
||||
import { parseWinPath } from '../../helpers/parseWinPath';
|
||||
|
||||
export default function useMediaInfo(media?: MediaInfo) {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const mediaFolder = useMemo(() => {
|
||||
if (settings?.wsFolder && media?.fsPath) {
|
||||
let relPath = media.fsPath.split(settings.wsFolder).pop();
|
||||
|
||||
if (settings.staticFolder && relPath) {
|
||||
relPath = relPath.split(settings.staticFolder).pop();
|
||||
}
|
||||
|
||||
return dirname(parseWinPath(relPath) || '');
|
||||
}
|
||||
return '';
|
||||
}, [media?.fsPath, settings?.staticFolder, settings?.wsFolder]);
|
||||
|
||||
const mediaSize = useMemo(() => {
|
||||
if (media?.size) {
|
||||
const size = media.size / (1024 * 1024);
|
||||
if (size > 1) {
|
||||
return `${size.toFixed(2)} MB`;
|
||||
} else {
|
||||
return `${(size * 1024).toFixed(2)} KB`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}, [media]);
|
||||
|
||||
const mediaDimensions = useMemo(() => {
|
||||
if (media?.dimensions) {
|
||||
return `${media.dimensions.width} x ${media.dimensions.height}`;
|
||||
}
|
||||
return '';
|
||||
}, [media]);
|
||||
|
||||
const mediaDetails = useMemo(() => {
|
||||
let sizeDetails = [];
|
||||
|
||||
if (mediaDimensions) {
|
||||
sizeDetails.push(mediaDimensions);
|
||||
}
|
||||
|
||||
if (mediaSize) {
|
||||
sizeDetails.push(mediaSize);
|
||||
}
|
||||
|
||||
return sizeDetails.join(' - ');
|
||||
}, [mediaDimensions, mediaSize]);
|
||||
|
||||
const isVideo = useMemo(() => {
|
||||
if (media?.mimeType?.startsWith('video/')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const isAudio = useMemo(() => {
|
||||
if (media?.mimeType?.startsWith('audio/')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const isImage = useMemo(() => {
|
||||
if (
|
||||
media?.mimeType?.startsWith('image/') &&
|
||||
!media?.mimeType?.startsWith('image/vnd.adobe.photoshop')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
return {
|
||||
mediaFolder,
|
||||
mediaSize,
|
||||
mediaDimensions,
|
||||
mediaDetails,
|
||||
isVideo,
|
||||
isAudio,
|
||||
isImage
|
||||
};
|
||||
}
|
||||
20
src/dashboardWebView/hooks/useSelectedItems.tsx
Normal file
20
src/dashboardWebView/hooks/useSelectedItems.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { MultiSelectedItemsAtom } from '../state';
|
||||
|
||||
export default function useSelectedItems() {
|
||||
const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom);
|
||||
|
||||
const onMultiSelect = useCallback((filePath: string) => {
|
||||
if (selectedFiles.includes(filePath)) {
|
||||
setSelectedFiles(selectedFiles.filter((file) => file !== filePath));
|
||||
} else {
|
||||
setSelectedFiles([...selectedFiles, filePath]);
|
||||
}
|
||||
}, [selectedFiles]);
|
||||
|
||||
return {
|
||||
selectedFiles,
|
||||
onMultiSelect
|
||||
};
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { Chatbot } from './components/Chatbot/Chatbot';
|
||||
import { updateCssVariables } from './utils';
|
||||
import { I10nProvider } from './providers/I10nProvider';
|
||||
import { SentryInit } from '../utils/sentryInit';
|
||||
import { WEBSITE_LINKS } from '../constants';
|
||||
|
||||
declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
getState: () => T;
|
||||
@@ -91,7 +92,7 @@ if (elm) {
|
||||
render(
|
||||
<I10nProvider>
|
||||
<SettingsProvider
|
||||
aiUrl='https://frontmatter.codes'
|
||||
aiUrl={WEBSITE_LINKS.root}
|
||||
experimental={experimental === 'true'}
|
||||
version={version || ""}>
|
||||
<Chatbot />
|
||||
|
||||
36
src/dashboardWebView/providers/FilesProvider.tsx
Normal file
36
src/dashboardWebView/providers/FilesProvider.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import { Page } from '../models';
|
||||
import { MediaInfo } from '../../models';
|
||||
|
||||
interface IFilesProviderProps {
|
||||
files: Page[] | MediaInfo[];
|
||||
}
|
||||
|
||||
const FilesContext = React.createContext<IFilesProviderProps | undefined>(undefined);
|
||||
|
||||
const FilesProvider: React.FunctionComponent<IFilesProviderProps> = ({ files, children }: React.PropsWithChildren<IFilesProviderProps>) => {
|
||||
return (
|
||||
<FilesContext.Provider
|
||||
value={{
|
||||
files
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FilesContext.Provider>
|
||||
)
|
||||
};
|
||||
|
||||
const useFilesContext = (): IFilesProviderProps => {
|
||||
const loadFunc = React.useContext(FilesContext);
|
||||
|
||||
if (loadFunc === undefined) {
|
||||
throw new Error('useFilesContext must be used within the FilesProvider');
|
||||
}
|
||||
|
||||
return loadFunc;
|
||||
};
|
||||
|
||||
FilesContext.displayName = 'FilesContext';
|
||||
FilesProvider.displayName = 'FilesProvider';
|
||||
|
||||
export { FilesProvider, useFilesContext };
|
||||
@@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const MultiSelectedItemsAtom = atom<string[]>({
|
||||
key: 'MultiSelectedItemsAtom',
|
||||
default: []
|
||||
});
|
||||
12
src/dashboardWebView/state/atom/SelectedItemActionAtom.ts
Normal file
12
src/dashboardWebView/state/atom/SelectedItemActionAtom.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const SelectedItemActionAtom = atom<
|
||||
| {
|
||||
path: string;
|
||||
action: 'view' | 'edit';
|
||||
}
|
||||
| undefined
|
||||
>({
|
||||
key: 'SelectedItemActionAtom',
|
||||
default: undefined
|
||||
});
|
||||
@@ -14,10 +14,12 @@ export * from './LocalesAtom';
|
||||
export * from './MediaFoldersAtom';
|
||||
export * from './MediaTotalAtom';
|
||||
export * from './ModeAtom';
|
||||
export * from './MultiSelectedItemsAtom';
|
||||
export * from './PageAtom';
|
||||
export * from './PinnedItems';
|
||||
export * from './SearchAtom';
|
||||
export * from './SearchReadyAtom';
|
||||
export * from './SelectedItemActionAtom';
|
||||
export * from './SelectedMediaFolderAtom';
|
||||
export * from './SettingsAtom';
|
||||
export * from './SortingAtom';
|
||||
|
||||
@@ -411,7 +411,7 @@
|
||||
}
|
||||
|
||||
.question {
|
||||
@apply relative ml-auto mr-3 w-5/6 rounded-full rounded-br-none bg-teal-900 py-2 px-4 text-whisper-500;
|
||||
@apply relative ml-auto mr-3 w-5/6 rounded-full rounded-br-none bg-teal-900 px-4 py-2 text-whisper-500;
|
||||
|
||||
&:after {
|
||||
--size: 1rem;
|
||||
|
||||
@@ -52,7 +52,12 @@ export const updateCssVariables = () => {
|
||||
);
|
||||
|
||||
// Borders
|
||||
const borderColor = styles.getPropertyValue('--vscode-panel-border');
|
||||
document.documentElement.style.setProperty('--frontmatter-border', 'var(--vscode-panel-border)');
|
||||
document.documentElement.style.setProperty(
|
||||
'--frontmatter-border-preserve',
|
||||
preserveColor(borderColor) || 'var(--vscode-panel-border)'
|
||||
);
|
||||
|
||||
// Other colors which should be preserved (no opacity)
|
||||
const buttonBackground = styles.getPropertyValue('--vscode-button-background');
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { workspace } from 'vscode';
|
||||
import { Extension, Settings } from '.';
|
||||
import { EXTENSION_BETA_ID, EXTENSION_ID, SETTING_TELEMETRY_DISABLE } from '../constants';
|
||||
|
||||
const METRICS_URL = 'https://frontmatter.codes/api/metrics';
|
||||
import {
|
||||
EXTENSION_BETA_ID,
|
||||
EXTENSION_ID,
|
||||
SETTING_TELEMETRY_DISABLE,
|
||||
WEBSITE_LINKS
|
||||
} from '../constants';
|
||||
|
||||
export class Telemetry {
|
||||
private static instance: Telemetry;
|
||||
@@ -77,7 +80,7 @@ export class Telemetry {
|
||||
|
||||
// Set a new timeout
|
||||
instance.timeout = setTimeout(async () => {
|
||||
await fetch(METRICS_URL, {
|
||||
await fetch(WEBSITE_LINKS.api.metrics, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@@ -151,6 +151,22 @@ export enum LocalizationKey {
|
||||
* Open: {0}
|
||||
*/
|
||||
commonOpenWithValue = 'common.openWithValue',
|
||||
/**
|
||||
* View
|
||||
*/
|
||||
commonView = 'common.view',
|
||||
/**
|
||||
* Translate
|
||||
*/
|
||||
commonTranslate = 'common.translate',
|
||||
/**
|
||||
* Languages
|
||||
*/
|
||||
commonLanguages = 'common.languages',
|
||||
/**
|
||||
* Scripts
|
||||
*/
|
||||
commonScripts = 'common.scripts',
|
||||
/**
|
||||
* Loading content
|
||||
*/
|
||||
@@ -483,6 +499,18 @@ export enum LocalizationKey {
|
||||
* All
|
||||
*/
|
||||
dashboardFiltersLanguageFilterAll = 'dashboard.filters.languageFilter.all',
|
||||
/**
|
||||
* {0} selected
|
||||
*/
|
||||
dashboardHeaderActionsBarItemsSelected = 'dashboard.header.actionsBar.itemsSelected',
|
||||
/**
|
||||
* Delete selected files
|
||||
*/
|
||||
dashboardHeaderActionsBarAlertDeleteTitle = 'dashboard.header.actionsBar.alertDelete.title',
|
||||
/**
|
||||
* Are you sure you want to delete the selected files?
|
||||
*/
|
||||
dashboardHeaderActionsBarAlertDeleteDescription = 'dashboard.header.actionsBar.alertDelete.description',
|
||||
/**
|
||||
* Home
|
||||
*/
|
||||
@@ -739,6 +767,14 @@ export enum LocalizationKey {
|
||||
* Create new folder
|
||||
*/
|
||||
dashboardMediaFolderCreationFolderCreate = 'dashboard.media.folderCreation.folder.create',
|
||||
/**
|
||||
* Content directory
|
||||
*/
|
||||
dashboardMediaFolderItemContentDirectory = 'dashboard.media.folderItem.contentDirectory',
|
||||
/**
|
||||
* Public directory
|
||||
*/
|
||||
dashboardMediaFolderItemPublicDirectory = 'dashboard.media.folderItem.publicDirectory',
|
||||
/**
|
||||
* Insert image
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
VsTable,
|
||||
VsTableBody,
|
||||
VsTableHeader,
|
||||
VsTableHeaderCell,
|
||||
VsTableRow,
|
||||
VsTableCell
|
||||
} from './VscodeComponents';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeTable, VSCodeTableBody, VSCodeTableCell, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
|
||||
export interface IArticleDetailsProps {
|
||||
details: {
|
||||
@@ -32,52 +25,55 @@ const ArticleDetails: React.FunctionComponent<IArticleDetailsProps> = ({
|
||||
<div className={`seo__status__details valid`}>
|
||||
<h4>{l10n.t(LocalizationKey.panelArticleDetailsTitle)}</h4>
|
||||
|
||||
<VsTable bordered>
|
||||
<VsTableHeader slot="header">
|
||||
<VsTableHeaderCell>
|
||||
{l10n.t(LocalizationKey.panelArticleDetailsType)}
|
||||
</VsTableHeaderCell>
|
||||
<VsTableHeaderCell>
|
||||
{l10n.t(LocalizationKey.panelArticleDetailsTotal)}
|
||||
</VsTableHeaderCell>
|
||||
</VsTableHeader>
|
||||
<VsTableBody slot="body">
|
||||
<VSCodeTable>
|
||||
<VSCodeTableHeader>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableHead>
|
||||
{l10n.t(LocalizationKey.panelArticleDetailsType)}
|
||||
</VSCodeTableHead>
|
||||
<VSCodeTableHead>
|
||||
{l10n.t(LocalizationKey.panelArticleDetailsTotal)}
|
||||
</VSCodeTableHead>
|
||||
</VSCodeTableRow>
|
||||
</VSCodeTableHeader>
|
||||
|
||||
<VSCodeTableBody>
|
||||
{details?.headings !== undefined && (
|
||||
<VsTableRow>
|
||||
<VsTableCell>{l10n.t(LocalizationKey.panelArticleDetailsHeadings)}</VsTableCell>
|
||||
<VsTableCell>{details.headings}</VsTableCell>
|
||||
</VsTableRow>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsHeadings)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.headings}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
{details?.paragraphs !== undefined && (
|
||||
<VsTableRow>
|
||||
<VsTableCell>{l10n.t(LocalizationKey.panelArticleDetailsParagraphs)}</VsTableCell>
|
||||
<VsTableCell>{details.paragraphs}</VsTableCell>
|
||||
</VsTableRow>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsParagraphs)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.paragraphs}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
{details?.internalLinks !== undefined && (
|
||||
<VsTableRow>
|
||||
<VsTableCell>{l10n.t(LocalizationKey.panelArticleDetailsInternalLinks)}</VsTableCell>
|
||||
<VsTableCell>{details.internalLinks}</VsTableCell>
|
||||
</VsTableRow>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsInternalLinks)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.internalLinks}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
{details?.externalLinks !== undefined && (
|
||||
<VsTableRow>
|
||||
<VsTableCell>{l10n.t(LocalizationKey.panelArticleDetailsExternalLinks)}</VsTableCell>
|
||||
<VsTableCell>{details.externalLinks}</VsTableCell>
|
||||
</VsTableRow>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsExternalLinks)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.externalLinks}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
{details?.images !== undefined && (
|
||||
<VsTableRow>
|
||||
<VsTableCell>{l10n.t(LocalizationKey.panelArticleDetailsImages)}</VsTableCell>
|
||||
<VsTableCell>{details.images}</VsTableCell>
|
||||
</VsTableRow>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsImages)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.images}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
</VsTableBody>
|
||||
</VsTable>
|
||||
</VSCodeTableBody>
|
||||
</VSCodeTable>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -66,7 +66,7 @@ const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({
|
||||
|
||||
return (
|
||||
<VsCollapsible title={title} onClick={triggerClick} open={isOpen}>
|
||||
<div className={`section collapsible__body ${className || ''}`} slot="body">
|
||||
<div className={`section collapsible__body ${className || ''}`}>
|
||||
{children}
|
||||
</div>
|
||||
</VsCollapsible>
|
||||
|
||||
@@ -5,9 +5,9 @@ import { useMemo } from 'react';
|
||||
import { Field } from '../../../models';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { IMetadata } from '../Metadata';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { VSCodeLabel } from '../VSCode';
|
||||
|
||||
export interface IContentTypeValidatorProps {
|
||||
fields: Field[];
|
||||
@@ -50,7 +50,7 @@ export const ContentTypeValidator: React.FunctionComponent<IContentTypeValidator
|
||||
|
||||
return (
|
||||
<div className="hint">
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
<div className={`metadata_field__label metadata_field__alert`}>
|
||||
<svg
|
||||
width="16"
|
||||
@@ -70,7 +70,7 @@ export const ContentTypeValidator: React.FunctionComponent<IContentTypeValidator
|
||||
{l10n.t(LocalizationKey.panelContentTypeContentTypeValidatorTitle)}
|
||||
</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
|
||||
|
||||
{l10n.t(LocalizationKey.panelContentTypeContentTypeValidatorHint).split(`\n`).map(s => (<p className="inline_hint" key={s}>{s}</p>))}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { RectangleStackIcon, PlusIcon } from '@heroicons/react/24/outline';
|
||||
import { RectangleStackIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import { DataBlockRecord } from '.';
|
||||
import { SortableContainer, SortEnd } from 'react-sortable-hoc';
|
||||
import { useCallback } from 'react';
|
||||
import { FieldGroup } from '../../../models';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { VSCodeLabel } from '../VSCode';
|
||||
|
||||
export interface IDataBlockRecordsProps {
|
||||
fieldGroups?: FieldGroup[];
|
||||
@@ -52,7 +52,7 @@ export const DataBlockRecords = ({
|
||||
|
||||
return (
|
||||
<div className="json_data__list">
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<div>
|
||||
<RectangleStackIcon style={{ width: '16px', height: '16px' }} />
|
||||
@@ -61,7 +61,7 @@ export const DataBlockRecords = ({
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
|
||||
<Container onSortEnd={onSort} useDragHandle>
|
||||
{records.map((v: any, idx: number) => (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { VSCodeLabel } from '../VSCode';
|
||||
|
||||
export interface IFieldBoundaryProps {
|
||||
fieldName: string;
|
||||
@@ -34,11 +34,11 @@ export default class FieldBoundary extends React.Component<
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className={`metadata_field`}>
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<span style={{ lineHeight: '16px' }}>{this.props.fieldName}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
<div className={`metadata_field__error`}>
|
||||
<span>
|
||||
{l10n.t(LocalizationKey.panelErrorBoundaryFieldBoundaryLabel)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import { RequiredAsterix } from './RequiredAsterix';
|
||||
import { VSCodeLabel } from '../VSCode';
|
||||
|
||||
export interface IFieldTitleProps {
|
||||
label: string | JSX.Element;
|
||||
@@ -23,7 +23,7 @@ export const FieldTitle: React.FunctionComponent<IFieldTitleProps> = ({
|
||||
}, [icon]);
|
||||
|
||||
return (
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
<div className={`metadata_field__label ${className || ''}`}>
|
||||
<div>
|
||||
{Icon}
|
||||
@@ -33,6 +33,6 @@ export const FieldTitle: React.FunctionComponent<IFieldTitleProps> = ({
|
||||
|
||||
{actionElement}
|
||||
</div>
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { FileInfo } from '../../models';
|
||||
import { FileItem } from './FileItem';
|
||||
import { VsLabel } from './VscodeComponents';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeLabel } from './VSCode';
|
||||
|
||||
export interface IFileListProps {
|
||||
folderName: string;
|
||||
@@ -22,9 +22,9 @@ const FileList: React.FunctionComponent<IFileListProps> = ({
|
||||
|
||||
return (
|
||||
<div className={`file_list`}>
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
{folderName} - {files.length === 1 ? l10n.t(LocalizationKey.panelFileListLabelSingular) : l10n.t(LocalizationKey.panelFileListLabelPlural)}: {totalFiles}
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
|
||||
<ul className="file_list__items">
|
||||
{files &&
|
||||
|
||||
@@ -2,9 +2,9 @@ import * as React from 'react';
|
||||
import { FolderInfo } from '../../models';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import { FileList } from './FileList';
|
||||
import { VsLabel } from './VscodeComponents';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeLabel } from './VSCode';
|
||||
|
||||
export interface IFolderAndFilesProps {
|
||||
data: FolderInfo[] | undefined;
|
||||
@@ -35,9 +35,9 @@ const FolderAndFiles: React.FunctionComponent<IFolderAndFilesProps> = ({
|
||||
/>
|
||||
</div>
|
||||
) : isBase ? (
|
||||
<VsLabel key={`${folder.title}-${idx}`}>
|
||||
<VSCodeLabel key={`${folder.title}-${idx}`}>
|
||||
{folder.title}: {folder.files} {folder.files > 1 ? l10n.t(LocalizationKey.panelFileListLabelPlural) : l10n.t(LocalizationKey.panelFileListLabelSingular)}
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -3,12 +3,12 @@ import { PanelSettings } from '../../models';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { useDebounce } from '../../hooks/useDebounce';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import { VsLabel } from './VscodeComponents';
|
||||
import useStartCommand from '../hooks/useStartCommand';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeLabel } from './VSCode';
|
||||
|
||||
export interface IGlobalSettingsProps {
|
||||
settings: PanelSettings | undefined;
|
||||
@@ -78,25 +78,25 @@ const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({
|
||||
title={l10n.t(LocalizationKey.panelGlobalSettingsTitle)}
|
||||
>
|
||||
<div className={`base__action`}>
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
{l10n.t(LocalizationKey.panelGlobalSettingsActionModifiedDateLabel)}
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
<VSCodeCheckbox checked={modifiedDateUpdate} onClick={onDateCheck}>
|
||||
{l10n.t(LocalizationKey.panelGlobalSettingsActionModifiedDateDescription)}
|
||||
</VSCodeCheckbox>
|
||||
</div>
|
||||
<div className={`base__action`}>
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
{l10n.t(LocalizationKey.panelGlobalSettingsActionFrontMatterLabel)}
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
<VSCodeCheckbox checked={fmHighlighting} onClick={onHighlightCheck}>
|
||||
{l10n.t(LocalizationKey.panelGlobalSettingsActionFrontMatterDescription)}
|
||||
</VSCodeCheckbox>
|
||||
</div>
|
||||
<div className={`base__action`}>
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
{l10n.t(LocalizationKey.panelGlobalSettingsActionPreviewLabel)}
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
<input
|
||||
type={`text`}
|
||||
placeholder={l10n.t(LocalizationKey.dashboardPreviewInputPlaceholder, `http://localhost:1313`)}
|
||||
@@ -105,9 +105,9 @@ const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className={`base__action`}>
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
{l10n.t(LocalizationKey.panelGlobalSettingsActionServerLabel)}
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
<input
|
||||
type={`text`}
|
||||
placeholder={l10n.t(LocalizationKey.panelGlobalSettingsActionServerPlaceholder, `hugo server -D`)}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ICheckIconProps {}
|
||||
|
||||
export const CheckIcon: React.FunctionComponent<ICheckIconProps> = (
|
||||
props: React.PropsWithChildren<ICheckIconProps>
|
||||
) => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14.431 3.323l-8.47 10-.79-.036-3.35-4.77.818-.574 2.978 4.24 8.051-9.506.764.646z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IWarningIconProps {}
|
||||
|
||||
export const WarningIcon: React.FunctionComponent<IWarningIconProps> = (
|
||||
props: React.PropsWithChildren<IWarningIconProps>
|
||||
) => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.56 1h.88l6.54 12.26-.44.74H1.44L1 13.26 7.56 1zM8 2.28L2.28 13H13.7L8 2.28zM8.625 12v-1h-1.25v1h1.25zm-1.25-2V6h1.25v4h-1.25z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -2,10 +2,10 @@ import * as React from 'react';
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { Field, PanelSettings } from '../../../models';
|
||||
import { PencilIcon } from '@heroicons/react/24/outline';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import { JsonFieldRecords, JsonFieldForm, JsonFieldSelector } from '.';
|
||||
import { SortEnd } from 'react-sortable-hoc';
|
||||
import { arrayMoveImmutable } from 'array-move';
|
||||
import { VSCodeLabel } from '../VSCode';
|
||||
|
||||
export interface IJsonFieldProps {
|
||||
label: string;
|
||||
@@ -101,12 +101,12 @@ export const JsonField: React.FunctionComponent<IJsonFieldProps> = ({
|
||||
|
||||
return (
|
||||
<div className="json_data__field">
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<PencilIcon style={{ width: '16px', height: '16px' }} />{' '}
|
||||
<span style={{ lineHeight: '16px' }}>{label}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
|
||||
<JsonFieldSelector
|
||||
field={field}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { CircleStackIcon, PlusIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import { JsonFieldRecord } from '.';
|
||||
import { SortableContainer, SortEnd } from 'react-sortable-hoc';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { VSCodeLabel } from '../VSCode';
|
||||
|
||||
export interface IJsonFieldRecordsProps {
|
||||
records: any[];
|
||||
@@ -33,7 +33,7 @@ export const JsonFieldRecords = ({
|
||||
|
||||
return (
|
||||
<div className="json_data__list">
|
||||
<VsLabel>
|
||||
<VSCodeLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<div>
|
||||
<CircleStackIcon style={{ width: '16px', height: '16px' }} />
|
||||
@@ -44,7 +44,7 @@ export const JsonFieldRecords = ({
|
||||
<PlusIcon style={{ width: '16px', height: '16px' }} />
|
||||
</button>
|
||||
</div>
|
||||
</VsLabel>
|
||||
</VSCodeLabel>
|
||||
|
||||
<Container onSortEnd={onSort} useDragHandle>
|
||||
{records.map((v: any, idx: number) => (
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
VsTable,
|
||||
VsTableBody,
|
||||
VsTableHeader,
|
||||
VsTableHeaderCell,
|
||||
VsTableRow,
|
||||
VsTableCell
|
||||
} from './VscodeComponents';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeTable, VSCodeTableBody, VSCodeTableCell, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
|
||||
export interface ISeoDetailsProps {
|
||||
allowedLength: number;
|
||||
@@ -23,30 +16,33 @@ const SeoDetails: React.FunctionComponent<ISeoDetailsProps> = (
|
||||
) => {
|
||||
const { allowedLength, title, value, valueTitle, noValidation } = props;
|
||||
|
||||
const validate = () => {
|
||||
const validate = React.useMemo(() => {
|
||||
if (noValidation) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value <= allowedLength ? 'valid' : 'not-valid';
|
||||
};
|
||||
}, [value, allowedLength, noValidation]);
|
||||
|
||||
return (
|
||||
<div className={`seo__status__details ${validate()}`}>
|
||||
<div className={`seo__status__details ${validate}`}>
|
||||
<h4>{title}</h4>
|
||||
|
||||
<VsTable bordered>
|
||||
<VsTableHeader slot="header">
|
||||
<VsTableHeaderCell className={validate()}>{valueTitle}</VsTableHeaderCell>
|
||||
<VsTableHeaderCell>{l10n.t(LocalizationKey.panelSeoDetailsRecommended)}</VsTableHeaderCell>
|
||||
</VsTableHeader>
|
||||
<VsTableBody slot="body">
|
||||
<VsTableRow>
|
||||
<VsTableCell className={validate()}>{value}</VsTableCell>
|
||||
<VsTableCell>{allowedLength}</VsTableCell>
|
||||
</VsTableRow>
|
||||
</VsTableBody>
|
||||
</VsTable>
|
||||
<VSCodeTable>
|
||||
<VSCodeTableHeader>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableHead className={validate}>{valueTitle}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{l10n.t(LocalizationKey.panelSeoDetailsRecommended)}</VSCodeTableHead>
|
||||
</VSCodeTableRow>
|
||||
</VSCodeTableHeader>
|
||||
|
||||
<VSCodeTableBody>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell className={validate}>{value}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{allowedLength}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
</VSCodeTableBody>
|
||||
</VSCodeTable>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { ValidInfo } from './ValidInfo';
|
||||
import { VsTableCell, VsTableRow } from './VscodeComponents';
|
||||
import { VSCodeTableCell, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
|
||||
export interface ISeoFieldInfoProps {
|
||||
title: string;
|
||||
@@ -16,15 +16,11 @@ const SeoFieldInfo: React.FunctionComponent<ISeoFieldInfoProps> = ({
|
||||
isValid
|
||||
}: React.PropsWithChildren<ISeoFieldInfoProps>) => {
|
||||
return (
|
||||
<VsTableRow>
|
||||
<VsTableCell className={`table__cell table__title`}>{title}</VsTableCell>
|
||||
<VsTableCell className={`table__cell`}>
|
||||
{value}/{recommendation}
|
||||
</VsTableCell>
|
||||
<VsTableCell className={`table__cell table__cell__validation`}>
|
||||
{isValid !== undefined ? <ValidInfo label={undefined} isValid={isValid} /> : <span>-</span>}
|
||||
</VsTableCell>
|
||||
</VsTableRow>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell className={`capitalize`}>{title}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{value}/{recommendation}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{isValid !== undefined ? <ValidInfo label={undefined} isValid={isValid} /> : <span>-</span>}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { ValidInfo } from './ValidInfo';
|
||||
import { VsTableCell, VsTableRow } from './VscodeComponents';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeTableCell, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
|
||||
export interface ISeoKeywordInfoProps {
|
||||
keyword: string;
|
||||
@@ -71,22 +71,22 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<VsTableRow>
|
||||
<VsTableCell className={`table__cell`}>{keyword}</VsTableCell>
|
||||
<VsTableCell className={`table__cell table__cell__validation table__cell__seo_details`}>
|
||||
<div>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{keyword}</VSCodeTableCell>
|
||||
<VSCodeTableCell className={` table__cell__validation`}>
|
||||
<div className='flex items-center'>
|
||||
<ValidInfo
|
||||
label={l10n.t(LocalizationKey.commonTitle)}
|
||||
isValid={!!title && title.toLowerCase().includes(keyword.toLowerCase())}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className='flex items-center'>
|
||||
<ValidInfo
|
||||
label={l10n.t(LocalizationKey.commonDescription)}
|
||||
isValid={!!description && description.toLowerCase().includes(keyword.toLowerCase())}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className='flex items-center'>
|
||||
<ValidInfo
|
||||
label={l10n.t(LocalizationKey.commonSlug)}
|
||||
isValid={
|
||||
@@ -96,16 +96,18 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className='flex items-center'>
|
||||
<ValidInfo
|
||||
label={l10n.t(LocalizationKey.panelSeoKeywordInfoValidInfoContent)}
|
||||
isValid={!!content && content.toLowerCase().includes(keyword.toLowerCase())}
|
||||
/>
|
||||
</div>
|
||||
{headings && headings.length > 0 && <div>{checkHeadings()}</div>}
|
||||
{wordCount && <div>{density()}</div>}
|
||||
</VsTableCell>
|
||||
</VsTableRow>
|
||||
{headings && headings.length > 0 &&
|
||||
<div className='flex items-center'>{checkHeadings()}</div>}
|
||||
{wordCount &&
|
||||
<div className='flex items-center'>{density()}</div>}
|
||||
</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { SeoKeywordInfo } from './SeoKeywordInfo';
|
||||
import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell } from './VscodeComponents';
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeTable, VSCodeTableBody, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
|
||||
export interface ISeoKeywordsProps {
|
||||
keywords: string[] | null;
|
||||
@@ -54,16 +54,19 @@ const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({
|
||||
<div className={`seo__status__keywords`}>
|
||||
<h4>{l10n.t(LocalizationKey.panelSeoKeywordsTitle)}</h4>
|
||||
|
||||
<VsTable bordered columns={['30%', 'auto']}>
|
||||
<VsTableHeader slot="header">
|
||||
<VsTableHeaderCell className={`table__cell`}>
|
||||
{l10n.t(LocalizationKey.panelSeoKeywordsHeaderKeyword)}
|
||||
</VsTableHeaderCell>
|
||||
<VsTableHeaderCell className={`table__cell`}>
|
||||
{l10n.t(LocalizationKey.panelSeoKeywordsHeaderDetails)}
|
||||
</VsTableHeaderCell>
|
||||
</VsTableHeader>
|
||||
<VsTableBody slot="body">
|
||||
<VSCodeTable>
|
||||
<VSCodeTableHeader>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableHead>
|
||||
{l10n.t(LocalizationKey.panelSeoKeywordsHeaderKeyword)}
|
||||
</VSCodeTableHead>
|
||||
<VSCodeTableHead>
|
||||
{l10n.t(LocalizationKey.panelSeoKeywordsHeaderDetails)}
|
||||
</VSCodeTableHead>
|
||||
</VSCodeTableRow>
|
||||
</VSCodeTableHeader>
|
||||
|
||||
<VSCodeTableBody>
|
||||
{validateKeywords().map((keyword, index) => {
|
||||
return (
|
||||
<ErrorBoundary key={keyword} fallback={<div />}>
|
||||
@@ -71,11 +74,11 @@ const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({
|
||||
</ErrorBoundary>
|
||||
);
|
||||
})}
|
||||
</VsTableBody>
|
||||
</VsTable>
|
||||
</VSCodeTableBody>
|
||||
</VSCodeTable>
|
||||
|
||||
{data.wordCount && (
|
||||
<div className={`seo__status__note`}>
|
||||
<div className={`text-xs mt-2`}>
|
||||
{l10n.t(LocalizationKey.panelSeoKeywordsDensity)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { SEO } from '../../models/PanelSettings';
|
||||
import { TagType } from '../TagType';
|
||||
import { ArticleDetails } from './ArticleDetails';
|
||||
@@ -9,9 +8,9 @@ import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon';
|
||||
import { SeoFieldInfo } from './SeoFieldInfo';
|
||||
import { SeoKeywords } from './SeoKeywords';
|
||||
import { TagPicker } from './TagPicker';
|
||||
import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell } from './VscodeComponents';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeTable, VSCodeTableBody, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
|
||||
export interface ISeoStatusProps {
|
||||
seo: SEO;
|
||||
@@ -27,90 +26,61 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
unsetFocus
|
||||
}: React.PropsWithChildren<ISeoStatusProps>) => {
|
||||
const { title, slug } = data;
|
||||
const [isOpen, setIsOpen] = React.useState(true);
|
||||
const tableRef = React.useRef<HTMLElement>();
|
||||
const pushUpdate = React.useRef((value: boolean) => {
|
||||
setTimeout(() => {
|
||||
setIsOpen(value);
|
||||
}, 10);
|
||||
}).current;
|
||||
|
||||
const { descriptionField, titleField } = seo;
|
||||
|
||||
// Workaround for lit components not updating render
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
let height = 0;
|
||||
|
||||
tableRef.current?.childNodes.forEach((elm: any) => {
|
||||
height += elm.clientHeight;
|
||||
});
|
||||
|
||||
if (height > 0 && tableRef.current) {
|
||||
tableRef.current.style.height = `${height}px`;
|
||||
}
|
||||
}, 10);
|
||||
}, [title, data[titleField], data[descriptionField], data?.articleDetails?.wordCount]);
|
||||
|
||||
const renderContent = () => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tableContent = React.useMemo(() => {
|
||||
return (
|
||||
<div>
|
||||
<div className={`seo__status__details`}>
|
||||
<h4>{l10n.t(LocalizationKey.panelSeoStatusTitle)}</h4>
|
||||
|
||||
<VsTable ref={tableRef} bordered zebra>
|
||||
<VsTableHeader slot="header">
|
||||
<VsTableHeaderCell className={`table__cell`}>
|
||||
{l10n.t(LocalizationKey.panelSeoStatusHeaderProperty)}
|
||||
</VsTableHeaderCell>
|
||||
<VsTableHeaderCell className={`table__cell`}>
|
||||
{l10n.t(LocalizationKey.panelSeoStatusHeaderLength)}
|
||||
</VsTableHeaderCell>
|
||||
<VsTableHeaderCell className={`table__cell`}>
|
||||
{l10n.t(LocalizationKey.panelSeoStatusHeaderValid)}
|
||||
</VsTableHeaderCell>
|
||||
</VsTableHeader>
|
||||
<VsTableBody slot="body">
|
||||
{data[titleField] && seo.title > 0 && (
|
||||
<VSCodeTable>
|
||||
<VSCodeTableHeader>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableHead>{l10n.t(LocalizationKey.panelSeoStatusHeaderProperty)}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{l10n.t(LocalizationKey.panelSeoStatusHeaderLength)}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{l10n.t(LocalizationKey.panelSeoStatusHeaderValid)}</VSCodeTableHead>
|
||||
</VSCodeTableRow>
|
||||
</VSCodeTableHeader>
|
||||
|
||||
<VSCodeTableBody>
|
||||
{data[titleField] && seo.title > 0 ? (
|
||||
<SeoFieldInfo
|
||||
title={titleField}
|
||||
value={data[titleField].length}
|
||||
recommendation={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.title)}
|
||||
isValid={data[titleField].length <= seo.title}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{slug && seo.slug > 0 && (
|
||||
{slug && seo.slug > 0 ? (
|
||||
<SeoFieldInfo
|
||||
title={`slug`}
|
||||
value={slug.length}
|
||||
recommendation={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.slug)}
|
||||
isValid={slug.length <= seo.slug}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{data[descriptionField] && seo.description > 0 && (
|
||||
{data[descriptionField] && seo.description > 0 ? (
|
||||
<SeoFieldInfo
|
||||
title={descriptionField}
|
||||
value={data[descriptionField].length}
|
||||
recommendation={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.description)}
|
||||
isValid={data[descriptionField].length <= seo.description}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{seo.content > 0 && data?.articleDetails?.wordCount > 0 && (
|
||||
{seo.content > 0 && data?.articleDetails?.wordCount > 0 ? (
|
||||
<SeoFieldInfo
|
||||
title={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoArticle)}
|
||||
value={data?.articleDetails?.wordCount}
|
||||
recommendation={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoWords, seo.content)}
|
||||
/>
|
||||
)}
|
||||
</VsTableBody>
|
||||
</VsTable>
|
||||
) : null}
|
||||
</VSCodeTableBody>
|
||||
</VSCodeTable>
|
||||
</div>
|
||||
|
||||
<SeoKeywords
|
||||
@@ -139,10 +109,10 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
<ArticleDetails details={data.articleDetails} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}, [data, seo, focusElm, unsetFocus]);
|
||||
|
||||
return (
|
||||
<Collapsible id={`seo`} title={l10n.t(LocalizationKey.panelSeoStatusCollapsibleTitle)} sendUpdate={pushUpdate}>
|
||||
<Collapsible id={`seo`} title={l10n.t(LocalizationKey.panelSeoStatusCollapsibleTitle)}>
|
||||
{!title && !data[descriptionField] ? (
|
||||
<div className={`seo__status__empty`}>
|
||||
<p>
|
||||
@@ -150,7 +120,7 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
renderContent()
|
||||
tableContent
|
||||
)}
|
||||
</Collapsible>
|
||||
);
|
||||
|
||||
27
src/panelWebView/components/VSCode/VSCodeLabel.tsx
Normal file
27
src/panelWebView/components/VSCode/VSCodeLabel.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IVSCodeLabelProps { }
|
||||
|
||||
export const VSCodeLabel: React.FunctionComponent<IVSCodeLabelProps> = ({
|
||||
children
|
||||
}: React.PropsWithChildren<IVSCodeLabelProps>) => {
|
||||
const DEFAULT_LINE_HEIGHT = 16;
|
||||
const DEFAULT_FONT_SIZE = 13;
|
||||
|
||||
const INPUT_LINE_HEIGHT_RATIO = DEFAULT_LINE_HEIGHT / DEFAULT_FONT_SIZE;
|
||||
|
||||
return (
|
||||
<label style={{
|
||||
color: "var(--vscode-foreground)",
|
||||
fontFamily: "var(--vscode-font-family)",
|
||||
fontSize: "var(--vscode-font-size)",
|
||||
fontWeight: "600",
|
||||
lineHeight: INPUT_LINE_HEIGHT_RATIO,
|
||||
cursor: "default",
|
||||
display: "block",
|
||||
padding: "5px 0"
|
||||
}}>
|
||||
{children}
|
||||
</label >
|
||||
);
|
||||
};
|
||||
103
src/panelWebView/components/VSCode/VSCodeTable.tsx
Normal file
103
src/panelWebView/components/VSCode/VSCodeTable.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "../../../utils/cn"
|
||||
|
||||
const VSCodeTable = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full text-base border-collapse indent-0 [&_tr:nth-child(2n)]:bg-[var(--vscode-keybindingTable-rowsBackground)]", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
VSCodeTable.displayName = "VSCodeTable"
|
||||
|
||||
const VSCodeTableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b bg-[var(--vscode-keybindingTable-headerBackground)]", className)} {...props} />
|
||||
))
|
||||
VSCodeTableHeader.displayName = "VSCodeTableHeader"
|
||||
|
||||
const VSCodeTableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
VSCodeTableBody.displayName = "VSCodeTableBody"
|
||||
|
||||
const VSCodeTableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-t font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
VSCodeTableFooter.displayName = "VSCodeTableFooter"
|
||||
|
||||
const VSCodeTableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-solid border-0 border-b border-b-[var(--vscode-editorGroup-border)] transition-colors [&_td]:border-r [&_td]:border-r-[var(--vscode-editorGroup-border)] [&_td:last-child]:border-r-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
VSCodeTableRow.displayName = "VSCodeTableRow"
|
||||
|
||||
const VSCodeTableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-6 px-2 py-2 text-left align-middle font-bold",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
VSCodeTableHead.displayName = "VSCodeTableHead"
|
||||
|
||||
const VSCodeTableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("border-solid border-0 h-6 px-2 overflow-hidden align-middle", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
VSCodeTableCell.displayName = "VSCodeTableCell"
|
||||
|
||||
export {
|
||||
VSCodeTable,
|
||||
VSCodeTableHeader,
|
||||
VSCodeTableBody,
|
||||
VSCodeTableFooter,
|
||||
VSCodeTableHead,
|
||||
VSCodeTableRow,
|
||||
VSCodeTableCell,
|
||||
}
|
||||
1
src/panelWebView/components/VSCode/index.ts
Normal file
1
src/panelWebView/components/VSCode/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './VSCodeLabel';
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { CheckIcon } from './Icons/CheckIcon';
|
||||
import { WarningIcon } from './Icons/WarningIcon';
|
||||
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
export interface IValidInfoProps {
|
||||
label?: string;
|
||||
@@ -14,13 +13,9 @@ const ValidInfo: React.FunctionComponent<IValidInfoProps> = ({
|
||||
return (
|
||||
<>
|
||||
{isValid ? (
|
||||
<span className="valid">
|
||||
<CheckIcon />
|
||||
</span>
|
||||
<CheckIcon className={`h-4 w-4 text-[#46ec86] mr-2`} />
|
||||
) : (
|
||||
<span className="warning">
|
||||
<WarningIcon />
|
||||
</span>
|
||||
<ExclamationTriangleIcon className={`h-4 w-4 text-[var(--vscode-statusBarItem-warningBackground)] mr-2`} />
|
||||
)}
|
||||
{label && <span>{label}</span>}
|
||||
</>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { wrapWc } from 'wc-react';
|
||||
|
||||
// @bendera/vscode-webview-elements
|
||||
export const VsTable = wrapWc(`vscode-table`);
|
||||
export const VsTableHeader = wrapWc(`vscode-table-header`);
|
||||
export const VsTableHeaderCell = wrapWc(`vscode-table-header-cell`);
|
||||
export const VsTableBody = wrapWc(`vscode-table-body`);
|
||||
export const VsTableRow = wrapWc(`vscode-table-row`);
|
||||
export const VsTableCell = wrapWc(`vscode-table-cell`);
|
||||
export const VsCollapsible = wrapWc(`vscode-collapsible`);
|
||||
export const VsLabel = wrapWc(`vscode-label`);
|
||||
// export const VsLabel = wrapWc(`vscode-label`);
|
||||
|
||||
@@ -9,14 +9,8 @@ import { SentryInit } from '../utils/sentryInit';
|
||||
import './styles.css';
|
||||
|
||||
// require('@vscode/codicons/dist/codicon.css');
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-table.js';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-table-header.js';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-table-header-cell.js';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-table-body.js';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-table-row.js';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-table-cell.js';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-collapsible.js';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-label.js';
|
||||
import '@vscode-elements/elements/dist/vscode-collapsible/index.js';
|
||||
// import '@bendera/vscode-webview-elements/dist/vscode-label/index.js';
|
||||
// import '@bendera/vscode-webview-elements/dist/vscode-checkbox.js';
|
||||
|
||||
// import '@vscode/webview-ui-toolkit/dist/esm/checkbox';
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from '../constants';
|
||||
import {
|
||||
SETTING_SEO_DESCRIPTION_LENGTH,
|
||||
SETTING_SEO_TITLE_LENGTH,
|
||||
WEBSITE_LINKS
|
||||
} from '../constants';
|
||||
import { Logger, Notifications, Settings, TaxonomyHelper } from '../helpers';
|
||||
import { TagType } from '../panelWebView/TagType';
|
||||
import { TaxonomyType } from '../models';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
|
||||
const AI_URL = 'https://frontmatter.codes/api/ai';
|
||||
const AI_URL = WEBSITE_LINKS.api.ai;
|
||||
// const AI_URL = 'http://localhost:3000/api/ai';
|
||||
|
||||
export class SponsorAi {
|
||||
|
||||
Reference in New Issue
Block a user