├── l10n ├── bundle.l10n.es.json ├── bundle.l10n.ko.json └── bundle.l10n.pt-br.json ├── .frontmatter ├── templates │ └── .gitkeep └── database │ ├── pinnedItemsDb.json │ ├── taxonomyDb.json │ └── mediaDb.json ├── .npmrc ├── src ├── dashboardWebView │ ├── constants │ │ ├── Storage.ts │ │ ├── GroupOption.ts │ │ ├── Tab.ts │ │ └── SortOption.ts │ ├── components │ │ ├── DataView │ │ │ ├── index.ts │ │ │ ├── SortableContainer.tsx │ │ │ └── DataFormControls.tsx │ │ ├── Preview │ │ │ └── index.ts │ │ ├── UnknownView │ │ │ └── index.ts │ │ ├── TaxonomyView │ │ │ └── index.ts │ │ ├── Chatbot │ │ │ ├── models │ │ │ │ └── AiInitResponse.ts │ │ │ ├── Loader.tsx │ │ │ ├── QuestionAnswer.tsx │ │ │ ├── Placeholder.tsx │ │ │ └── Header.tsx │ │ ├── Layout │ │ │ ├── index.ts │ │ │ ├── NavigationItem.tsx │ │ │ ├── NavigationBar.tsx │ │ │ └── PageLayout.tsx │ │ ├── Menu │ │ │ ├── index.ts │ │ │ ├── QuickAction.tsx │ │ │ ├── MenuItem.tsx │ │ │ └── ActionMenuButton.tsx │ │ ├── Header │ │ │ ├── index.ts │ │ │ ├── PaginationButton.tsx │ │ │ ├── ActionsBarItem.tsx │ │ │ ├── BooleanOption.tsx │ │ │ └── Tab.tsx │ │ ├── Common │ │ │ ├── Link.tsx │ │ │ ├── LinkButton.tsx │ │ │ └── Spinner.tsx │ │ ├── Media │ │ │ ├── List.tsx │ │ │ ├── DetailsItem.tsx │ │ │ ├── DetailsInput.tsx │ │ │ ├── Lightbox.tsx │ │ │ └── MediaHeaderBottom.tsx │ │ ├── WelcomeView │ │ │ └── WelcomeLink.tsx │ │ ├── Contents │ │ │ ├── Tags.tsx │ │ │ ├── I18nLabel.tsx │ │ │ └── Tag.tsx │ │ ├── ErrorView │ │ │ └── index.tsx │ │ ├── SettingsView │ │ │ ├── SettingsInput.tsx │ │ │ ├── SettingsCheckbox.tsx │ │ │ └── SettingsLink.tsx │ │ ├── SnippetsView │ │ │ └── SnippetInput.tsx │ │ ├── Configuration │ │ │ └── Common │ │ │ │ └── Folder.tsx │ │ └── Icons │ │ │ └── PinIcon.tsx │ ├── state │ │ ├── index.ts │ │ ├── atom │ │ │ ├── PageAtom.ts │ │ │ ├── PagedItems.ts │ │ │ ├── SearchAtom.ts │ │ │ ├── MediaTotalAtom.ts │ │ │ ├── LightboxAtom.ts │ │ │ ├── PinnedItems.ts │ │ │ ├── MediaFoldersAtom.ts │ │ │ ├── SearchReadyAtom.ts │ │ │ ├── FiltersAtom.ts │ │ │ ├── TabInfoAtom.ts │ │ │ ├── MultiSelectedItemsAtom.ts │ │ │ ├── AllPagesAtom.ts │ │ │ ├── AllStaticFoldersAtom.ts │ │ │ ├── SelectedMediaFolderAtom.ts │ │ │ ├── TabAtom.ts │ │ │ ├── AllContentFoldersAtom.ts │ │ │ ├── FilterValuesAtom.ts │ │ │ ├── ModeAtom.ts │ │ │ ├── SettingsAtom.ts │ │ │ ├── TagAtom.ts │ │ │ ├── LoadingAtom.ts │ │ │ ├── FolderAtom.ts │ │ │ ├── LocaleAtom.ts │ │ │ ├── LocalesAtom.ts │ │ │ ├── ViewAtom.ts │ │ │ ├── CategoryAtom.ts │ │ │ ├── GroupingAtom.ts │ │ │ ├── DashboardViewAtom.ts │ │ │ ├── ViewDataAtom.ts │ │ │ ├── SelectedItemActionAtom.ts │ │ │ ├── SortingAtom.ts │ │ │ └── index.ts │ │ └── selectors │ │ │ ├── TabSelector.ts │ │ │ ├── TagSelector.ts │ │ │ ├── PageSelector.ts │ │ │ ├── ViewSelector.ts │ │ │ ├── FolderSelector.ts │ │ │ ├── SearchSelector.ts │ │ │ ├── LoadingSelector.ts │ │ │ ├── SortingSelector.ts │ │ │ ├── CategorySelector.ts │ │ │ ├── GroupingSelector.ts │ │ │ ├── SettingsSelector.ts │ │ │ ├── ViewDataSelector.ts │ │ │ ├── MediaTotalSelector.ts │ │ │ ├── MediaFoldersSelector.ts │ │ │ ├── DashboardViewSelector.ts │ │ │ ├── SelectedMediaFolderSelector.ts │ │ │ └── index.ts │ ├── models │ │ ├── DashboardViewType.ts │ │ ├── Status.ts │ │ ├── PageMappings.ts │ │ ├── index.ts │ │ ├── NavigationType.ts │ │ ├── SortingOption.ts │ │ └── Page.ts │ ├── hooks │ │ ├── index.ts │ │ ├── useMediaFolder.tsx │ │ └── useSelectedItems.tsx │ ├── utils │ │ ├── index.ts │ │ ├── preserveColor.ts │ │ ├── opacityColor.ts │ │ └── getRelPath.ts │ ├── DashboardCommand.ts │ └── providers │ │ └── FilesProvider.tsx ├── panelWebView │ ├── state │ │ ├── index.ts │ │ └── atom │ │ │ ├── index.ts │ │ │ ├── RequiredFieldsAtom.ts │ │ │ └── PanelSettingsAtom.ts │ ├── components │ │ ├── VSCode │ │ │ ├── index.ts │ │ │ └── VSCodeLabel.tsx │ │ ├── CustomView │ │ │ └── index.ts │ │ ├── Actions │ │ │ ├── index.ts │ │ │ └── OpenOnWebsiteAction.tsx │ │ ├── DataBlock │ │ │ ├── index.ts │ │ │ └── DataBlockControls.tsx │ │ ├── VscodeComponents.ts │ │ ├── JsonField │ │ │ ├── index.ts │ │ │ └── JsonFieldControls.tsx │ │ ├── Icon.tsx │ │ ├── Fields │ │ │ ├── RequiredAsterix.tsx │ │ │ ├── index.ts │ │ │ ├── FieldMessage.tsx │ │ │ └── ChoiceButton.tsx │ │ ├── Icons │ │ │ ├── AddIcon.tsx │ │ │ ├── SymbolKeywordIcon.tsx │ │ │ ├── FileIcon.tsx │ │ │ ├── ArchiveIcon.tsx │ │ │ ├── ListUnorderedIcon.tsx │ │ │ ├── FolderOpenedIcon.tsx │ │ │ ├── ToggleIcon.tsx │ │ │ ├── index.ts │ │ │ ├── MarkdownIcon.tsx │ │ │ ├── SettingsIcon.tsx │ │ │ ├── TemplateIcon.tsx │ │ │ ├── HeartIcon.tsx │ │ │ ├── PageIcon.tsx │ │ │ ├── TagIcon.tsx │ │ │ └── RocketIcon.tsx │ │ ├── Spinner.tsx │ │ ├── OtherActionButton.tsx │ │ ├── Preview.tsx │ │ ├── ActionButton.tsx │ │ ├── CustomScript.tsx │ │ ├── SlugAction.tsx │ │ ├── SponsorMsg.tsx │ │ ├── ValidInfo.tsx │ │ ├── InitializeAction.tsx │ │ ├── SeoFieldInfo.tsx │ │ └── PublishAction.tsx │ ├── TagType.ts │ ├── hooks │ │ ├── usePrevious.tsx │ │ └── useStartCommand.tsx │ └── Command.ts ├── utils │ ├── isWindows.ts │ ├── sleep.ts │ ├── lstatAsync.ts │ ├── mkdirAsync.ts │ ├── rmdirAsync.ts │ ├── renameAsync.ts │ ├── unlinkAsync.ts │ ├── readdirAsync.ts │ ├── copyFileAsync.ts │ ├── readFileAsync.ts │ ├── writeFileAsync.ts │ ├── cn.ts │ ├── existsAsync.ts │ ├── getTitleField.ts │ ├── getDescriptionField.ts │ ├── formatInTimezone.ts │ ├── ignoreMsgCommand.ts │ ├── encodeEmoji.ts │ ├── joinUrl.ts │ ├── fetchWithTimeout.ts │ ├── getPlatform.ts │ ├── sentryInit.ts │ ├── getLocalizationFile.ts │ ├── getExtensibilityScripts.ts │ ├── getWebviewJsFiles.ts │ ├── evaluateCommand.ts │ ├── flattenObjectKeys.ts │ └── index.ts ├── models │ ├── ShellSetting.ts │ ├── LoadingType.ts │ ├── TaxonomyType.ts │ ├── Choice.ts │ ├── FilterType.ts │ ├── Mode.ts │ ├── SortOrder.ts │ ├── StaticFolder.ts │ ├── SortType.ts │ ├── CustomPanelViewResult.ts │ ├── i18nConfig.ts │ ├── VersionInfo.ts │ ├── CustomPlaceholder.ts │ ├── DataType.ts │ ├── DraftField.ts │ ├── BaseFieldProps.ts │ ├── Framework.ts │ ├── GitSettings.ts │ ├── Template.ts │ ├── Project.ts │ ├── UnmappedMedia.ts │ ├── BlockFieldData.ts │ ├── TaxonomyData.ts │ ├── ScriptAction.ts │ ├── PostMessageData.ts │ ├── SortingSetting.ts │ ├── DataFile.ts │ ├── DataFolder.ts │ ├── CustomTaxonomyData.ts │ ├── AstroCollections.ts │ ├── Snippets.ts │ ├── ContentFolder.ts │ ├── MediaContentType.ts │ ├── GitRepository.ts │ ├── MediaPaths.ts │ └── DashboardData.ts ├── constants │ ├── Navigation.ts │ ├── DefaultFileTypes.ts │ ├── Git.ts │ ├── Templates.ts │ ├── DefaultFieldValues.ts │ ├── NotificationType.ts │ ├── Snippet.ts │ ├── PreviewCommands.ts │ ├── StaticFolderPlaceholder.ts │ ├── SsgScripts.ts │ ├── DefaultFields.ts │ ├── SentryIgnore.ts │ ├── LocalStore.ts │ ├── DefaultFeatureFlags.ts │ ├── Features.ts │ ├── context.ts │ ├── index.ts │ ├── GeneralCommands.ts │ ├── ContentType.ts │ └── ExtensionState.ts ├── localization │ ├── index.ts │ └── localize.ts ├── parsers │ └── index.ts ├── listeners │ ├── general │ │ └── index.ts │ ├── panel │ │ ├── index.ts │ │ └── LocalizationListener.ts │ └── dashboard │ │ └── index.ts ├── helpers │ ├── StringHelpers.ts │ ├── GroupBy.ts │ ├── getNonce.ts │ ├── DebounceCallback.ts │ ├── decodeBase64Image.ts │ ├── parseWinPath.ts │ ├── isValidFile.ts │ ├── Telemetry.ts │ ├── getTaxonomyField.ts │ └── openFileInEditor.ts ├── components │ ├── uniforms-frontmatter │ │ ├── SelectField.css │ │ ├── ListField.css │ │ ├── ValidatedQuickForm.tsx │ │ ├── LabelField.css │ │ ├── BaseForm.tsx │ │ ├── AutoForm.tsx │ │ ├── ValidatedForm.tsx │ │ ├── ErrorsField.css │ │ ├── ListAddField.css │ │ ├── ListDelField.css │ │ ├── ErrorField.tsx │ │ ├── ListItemField.tsx │ │ ├── NestField.tsx │ │ ├── ErrorsField.tsx │ │ ├── QuickForm.tsx │ │ ├── SubmitField.tsx │ │ ├── AutoFields.tsx │ │ ├── LabelField.tsx │ │ └── HiddenField.tsx │ ├── features │ │ └── FeatureFlag.tsx │ ├── icons │ │ ├── ArrowClockwiseIcon.tsx │ │ ├── RenameIcon.tsx │ │ ├── ChatIcon.tsx │ │ ├── CompressIcon.tsx │ │ └── MergeIcon.tsx │ └── common │ │ └── Tooltip.tsx ├── services │ └── index.ts ├── providers │ ├── FrontMatterDecorationProvider.ts │ └── UriHandler.ts ├── commands │ └── index.ts └── hooks │ ├── useDebounce.tsx │ └── useDarkMode.tsx ├── assets ├── empty.svg ├── front-matter.png ├── frontmatter.woff ├── frontmatter-beta.png ├── frontmatter-teal-128x128.png ├── walkthrough │ ├── documentation.md │ ├── get-started.md │ └── support-the-project.md ├── icons │ ├── close-dark.svg │ ├── close-light.svg │ ├── strikethrough-dark.svg │ ├── strikethrough-light.svg │ ├── bold-dark.svg │ ├── bold-light.svg │ ├── code-dark.svg │ ├── code-light.svg │ ├── italic-dark.svg │ ├── italic-light.svg │ ├── options-dark.svg │ ├── options-light.svg │ ├── codeblock-dark.svg │ ├── codeblock-light.svg │ ├── heading-dark.svg │ ├── heading-light.svg │ ├── i18n-dark.svg │ ├── i18n-light.svg │ ├── blockquote-dark.svg │ ├── ordered-list-dark.svg │ ├── blockquote-light.svg │ ├── ordered-list-light.svg │ ├── chatbot-dark.svg │ ├── chatbot-light.svg │ ├── media-dark.svg │ ├── media-light.svg │ ├── unordered-list-dark.svg │ ├── unordered-list-light.svg │ ├── scissors-dark.svg │ ├── scissors-light.svg │ ├── frontmatter-small-dark.svg │ ├── frontmatter-small-light.svg │ └── frontmatter-small-teal.svg └── media │ └── reset.css ├── .prettierrc ├── .github ├── ISSUE_TEMPLATE │ ├── feedback_request.md │ ├── config.yml │ ├── showcase.md │ ├── feature_request.md │ └── bug_report.md └── FUNDING.yml ├── tailwind.panel.js ├── .templates └── documentation.md ├── .gitignore ├── .vscode ├── extensions.json ├── tasks.json └── launch.json ├── postcss.config.js ├── .all-contributorsrc ├── scripts ├── main-release.js └── watch-localization.js ├── sample └── script-sample.js ├── tsconfig.json ├── .vscodeignore ├── .eslintrc.json ├── SUPPORT.md └── LICENSE /l10n/bundle.l10n.es.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /l10n/bundle.l10n.ko.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.frontmatter/templates/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /l10n/bundle.l10n.pt-br.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true 2 | -------------------------------------------------------------------------------- /.frontmatter/database/pinnedItemsDb.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.frontmatter/database/taxonomyDb.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/dashboardWebView/constants/Storage.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/panelWebView/state/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atom'; 2 | -------------------------------------------------------------------------------- /src/panelWebView/components/VSCode/index.ts: -------------------------------------------------------------------------------- 1 | export * from './VSCodeLabel'; 2 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/DataView/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DataView'; 2 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Preview/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Preview'; 2 | -------------------------------------------------------------------------------- /src/panelWebView/components/CustomView/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CustomView'; 2 | -------------------------------------------------------------------------------- /src/utils/isWindows.ts: -------------------------------------------------------------------------------- 1 | export const isWindows = () => process.platform === 'win32'; -------------------------------------------------------------------------------- /assets/empty.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/UnknownView/index.ts: -------------------------------------------------------------------------------- 1 | export * from './UnknownView'; 2 | -------------------------------------------------------------------------------- /src/models/ShellSetting.ts: -------------------------------------------------------------------------------- 1 | export interface ShellSetting { 2 | path: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/panelWebView/components/Actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './OpenOnWebsiteAction'; 2 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/TaxonomyView/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TaxonomyView'; 2 | -------------------------------------------------------------------------------- /src/models/LoadingType.ts: -------------------------------------------------------------------------------- 1 | export type LoadingType = 'initPages' | 'loading' | undefined; 2 | -------------------------------------------------------------------------------- /src/models/TaxonomyType.ts: -------------------------------------------------------------------------------- 1 | export enum TaxonomyType { 2 | Tag = 1, 3 | Category 4 | } 5 | -------------------------------------------------------------------------------- /src/constants/Navigation.ts: -------------------------------------------------------------------------------- 1 | export const HOME_PAGE_NAVIGATION_ID = 'FrontMatter:RootFolder'; 2 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atom'; 2 | export * from './selectors'; 3 | -------------------------------------------------------------------------------- /src/localization/index.ts: -------------------------------------------------------------------------------- 1 | export * from './localization.enum'; 2 | export * from './localize'; 3 | -------------------------------------------------------------------------------- /src/models/Choice.ts: -------------------------------------------------------------------------------- 1 | export interface Choice { 2 | id: string; 3 | title: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/models/FilterType.ts: -------------------------------------------------------------------------------- 1 | export type FilterType = 'contentFolders' | 'tags' | 'categories'; 2 | -------------------------------------------------------------------------------- /src/models/Mode.ts: -------------------------------------------------------------------------------- 1 | export interface Mode { 2 | id: string; 3 | features: string[]; 4 | } 5 | -------------------------------------------------------------------------------- /src/models/SortOrder.ts: -------------------------------------------------------------------------------- 1 | export enum SortOrder { 2 | asc = 'asc', 3 | desc = 'desc' 4 | } 5 | -------------------------------------------------------------------------------- /src/parsers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FrontMatterParser'; 2 | export * from './ParserEngines'; 3 | -------------------------------------------------------------------------------- /src/constants/DefaultFileTypes.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_FILE_TYPES = ['.md', '.markdown', '.mdx']; 2 | -------------------------------------------------------------------------------- /src/listeners/general/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GitListener'; 2 | export * from './ModeListener'; 3 | -------------------------------------------------------------------------------- /src/constants/Git.ts: -------------------------------------------------------------------------------- 1 | export const GIT_CONFIG = { 2 | defaultCommitMessage: 'Synced by Front Matter' 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 2 | -------------------------------------------------------------------------------- /assets/front-matter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rootsongjc/vscode-front-matter-light/main/assets/front-matter.png -------------------------------------------------------------------------------- /assets/frontmatter.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rootsongjc/vscode-front-matter-light/main/assets/frontmatter.woff -------------------------------------------------------------------------------- /src/dashboardWebView/models/DashboardViewType.ts: -------------------------------------------------------------------------------- 1 | export enum DashboardViewType { 2 | Grid = 1, 3 | List 4 | } 5 | -------------------------------------------------------------------------------- /src/models/StaticFolder.ts: -------------------------------------------------------------------------------- 1 | export interface StaticFolder { 2 | path: string; 3 | relative?: boolean; 4 | } 5 | -------------------------------------------------------------------------------- /src/dashboardWebView/constants/GroupOption.ts: -------------------------------------------------------------------------------- 1 | export enum GroupOption { 2 | none = 1, 3 | Year, 4 | Draft 5 | } 6 | -------------------------------------------------------------------------------- /src/panelWebView/state/atom/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PanelSettingsAtom'; 2 | export * from './RequiredFieldsAtom'; 3 | -------------------------------------------------------------------------------- /assets/frontmatter-beta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rootsongjc/vscode-front-matter-light/main/assets/frontmatter-beta.png -------------------------------------------------------------------------------- /src/models/SortType.ts: -------------------------------------------------------------------------------- 1 | export enum SortType { 2 | string = 'string', 3 | number = 'number', 4 | date = 'date' 5 | } 6 | -------------------------------------------------------------------------------- /src/constants/Templates.ts: -------------------------------------------------------------------------------- 1 | export const Templates = { 2 | url: 'https://frontmatter.github.io/templates/templates.json' 3 | }; 4 | -------------------------------------------------------------------------------- /src/models/CustomPanelViewResult.ts: -------------------------------------------------------------------------------- 1 | export interface CustomPanelViewResult { 2 | title: string; 3 | content: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/models/i18nConfig.ts: -------------------------------------------------------------------------------- 1 | export interface I18nConfig { 2 | locale: string; 3 | title?: string; 4 | path?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/constants/DefaultFieldValues.ts: -------------------------------------------------------------------------------- 1 | export const DefaultFieldValues = { 2 | faultyCustomPlaceholder: '' 3 | }; 4 | -------------------------------------------------------------------------------- /src/constants/NotificationType.ts: -------------------------------------------------------------------------------- 1 | export const NOTIFICATION_TYPE = { 2 | requiredFieldValidation: 'requiredFieldValidation' 3 | }; 4 | -------------------------------------------------------------------------------- /src/dashboardWebView/models/Status.ts: -------------------------------------------------------------------------------- 1 | export enum Status { 2 | Active = 1, 3 | Completed, 4 | NotStarted, 5 | Optional 6 | } 7 | -------------------------------------------------------------------------------- /src/models/VersionInfo.ts: -------------------------------------------------------------------------------- 1 | export interface VersionInfo { 2 | usedVersion: string | undefined; 3 | installedVersion: string; 4 | } 5 | -------------------------------------------------------------------------------- /.frontmatter/database/mediaDb.json: -------------------------------------------------------------------------------- 1 | {"assets":{"v7.0.0":{"snippets-dashboard.png":{"caption":"Snippets dashboard","alt":"Snippets dashboard"}}}} -------------------------------------------------------------------------------- /assets/frontmatter-teal-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rootsongjc/vscode-front-matter-light/main/assets/frontmatter-teal-128x128.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": "none" 7 | } 8 | -------------------------------------------------------------------------------- /src/helpers/StringHelpers.ts: -------------------------------------------------------------------------------- 1 | export function firstToUpper(value: string) { 2 | return value.charAt(0).toUpperCase() + value.slice(1); 3 | } 4 | -------------------------------------------------------------------------------- /src/constants/Snippet.ts: -------------------------------------------------------------------------------- 1 | export const SNIPPET = { 2 | wrapper: { 3 | start: `FM:Snippet:Start`, 4 | end: `FM:Snippet:End` 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Chatbot/models/AiInitResponse.ts: -------------------------------------------------------------------------------- 1 | export interface AiInitResponse { 2 | company: string; 3 | chatId: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './NavigationBar'; 2 | export * from './NavigationItem'; 3 | export * from './PageLayout'; 4 | -------------------------------------------------------------------------------- /assets/walkthrough/documentation.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | Our documentation can be found at: [https://frontmatter.codes/docs](https://frontmatter.codes/docs) -------------------------------------------------------------------------------- /src/utils/lstatAsync.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { lstat as lstatCb } from 'fs'; 3 | 4 | export const lstatAsync = promisify(lstatCb); 5 | -------------------------------------------------------------------------------- /src/utils/mkdirAsync.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { mkdir as mkdirCb } from 'fs'; 3 | 4 | export const mkdirAsync = promisify(mkdirCb); 5 | -------------------------------------------------------------------------------- /src/utils/rmdirAsync.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { rmdir as rmdirCb } from 'fs'; 3 | 4 | export const rmdirAsync = promisify(rmdirCb); 5 | -------------------------------------------------------------------------------- /src/dashboardWebView/constants/Tab.ts: -------------------------------------------------------------------------------- 1 | export enum Tab { 2 | All = 'all', 3 | Published = 'published', 4 | Draft = 'draft', 5 | Scheduled = 'scheduled' 6 | } 7 | -------------------------------------------------------------------------------- /src/models/CustomPlaceholder.ts: -------------------------------------------------------------------------------- 1 | export interface CustomPlaceholder { 2 | id: string; 3 | value?: string; 4 | script?: string; 5 | command?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/DataType.ts: -------------------------------------------------------------------------------- 1 | export interface DataType { 2 | id: string; 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | schema: any; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/DraftField.ts: -------------------------------------------------------------------------------- 1 | export interface DraftField { 2 | name: string; 3 | type: 'boolean' | 'choice'; 4 | choices?: string[]; 5 | invert?: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /src/panelWebView/TagType.ts: -------------------------------------------------------------------------------- 1 | export enum TagType { 2 | tags = 'Tags', 3 | categories = 'Categories', 4 | keywords = 'Keywords', 5 | custom = 'Custom' 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/renameAsync.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { rename as renameCb } from 'fs'; 3 | 4 | export const renameAsync = promisify(renameCb); 5 | -------------------------------------------------------------------------------- /src/utils/unlinkAsync.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { unlink as unlinkCb } from 'fs'; 3 | 4 | export const unlinkAsync = promisify(unlinkCb); 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feedback_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feedback 3 | about: Tell more on what you think 4 | title: 'Feedback: ' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/SelectField.css: -------------------------------------------------------------------------------- 1 | .autoform__select_field { 2 | color: var(--frontmatter-select-foreground, var(--vscode-editor-foreground)); 3 | } 4 | -------------------------------------------------------------------------------- /src/dashboardWebView/models/PageMappings.ts: -------------------------------------------------------------------------------- 1 | import { Page } from './Page'; 2 | 3 | export interface PageMappings { 4 | tagged: Page[]; 5 | untagged: Page[]; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/BaseFieldProps.ts: -------------------------------------------------------------------------------- 1 | export interface BaseFieldProps { 2 | label: string; 3 | value: T | null; 4 | description?: string; 5 | required?: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/readdirAsync.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { readdir as readdirCb } from 'fs'; 3 | 4 | export const readdirAsync = promisify(readdirCb); 5 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/PageAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const PageAtom = atom({ 4 | key: 'PageAtom', 5 | default: 0 6 | }); 7 | -------------------------------------------------------------------------------- /src/utils/copyFileAsync.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { copyFile as copyFileCb } from 'fs'; 3 | 4 | export const copyFileAsync = promisify(copyFileCb); 5 | -------------------------------------------------------------------------------- /src/utils/readFileAsync.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { readFile as readFileCb } from 'fs'; 3 | 4 | export const readFileAsync = promisify(readFileCb); 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [estruyf] 4 | open_collective: frontmatter 5 | custom: ["https://www.buymeacoffee.com/zMeFRy9"] 6 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/PagedItems.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const PagedItems = atom({ 4 | key: 'PagedItems', 5 | default: [] 6 | }); 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/SearchAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const SearchAtom = atom({ 4 | key: 'SearchAtom', 5 | default: '' 6 | }); 7 | -------------------------------------------------------------------------------- /src/models/Framework.ts: -------------------------------------------------------------------------------- 1 | export interface Framework { 2 | name: string; 3 | dist: string; 4 | static: string | string[]; 5 | build: string; 6 | server?: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/writeFileAsync.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { writeFile as writeFileCb } from 'fs'; 3 | 4 | export const writeFileAsync = promisify(writeFileCb); 5 | -------------------------------------------------------------------------------- /tailwind.panel.js: -------------------------------------------------------------------------------- 1 | const defaultConfig = require("./tailwind.config") 2 | 3 | module.exports = { 4 | ...defaultConfig, 5 | corePlugins: { 6 | preflight: false, 7 | } 8 | } -------------------------------------------------------------------------------- /src/dashboardWebView/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ActionMenuButton'; 2 | export * from './MenuButton'; 3 | export * from './MenuItem'; 4 | export * from './QuickAction'; 5 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/MediaTotalAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const MediaTotalAtom = atom({ 4 | key: 'MediaTotalAtom', 5 | default: 0 6 | }); 7 | -------------------------------------------------------------------------------- /src/models/GitSettings.ts: -------------------------------------------------------------------------------- 1 | export interface GitSettings { 2 | isGitRepo: boolean; 3 | actions: boolean; 4 | disabledBranches: string[]; 5 | requiresCommitMessage: string[]; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/Template.ts: -------------------------------------------------------------------------------- 1 | export interface Template { 2 | name: string; 3 | description: string; 4 | url: string; 5 | version: string; 6 | author: string; 7 | type: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/constants/PreviewCommands.ts: -------------------------------------------------------------------------------- 1 | export const PreviewCommands = { 2 | toVSCode: { 3 | open: `preview.open` 4 | }, 5 | toWebview: { 6 | updateUrl: `preview.updateUrl` 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/LightboxAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const LightboxAtom = atom({ 4 | key: 'LightboxAtom', 5 | default: '' 6 | }); 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/PinnedItems.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const PinnedItemsAtom = atom({ 4 | key: 'PinnedItemsAtom', 5 | default: [] 6 | }); 7 | -------------------------------------------------------------------------------- /src/models/Project.ts: -------------------------------------------------------------------------------- 1 | export interface Project { 2 | name: string; 3 | default?: boolean; 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | configuration: any; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/UnmappedMedia.ts: -------------------------------------------------------------------------------- 1 | export interface UnmappedMedia { 2 | file: string; 3 | absPath: string; 4 | metadata: { 5 | title: string; 6 | [prop: string]: string; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /src/constants/StaticFolderPlaceholder.ts: -------------------------------------------------------------------------------- 1 | export const STATIC_FOLDER_PLACEHOLDER = { 2 | hexo: { 3 | postsFolder: 'source/_posts', 4 | placeholder: 'hexo:post_asset_folder' 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/MediaFoldersAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const MediaFoldersAtom = atom({ 4 | key: 'MediaFoldersAtom', 5 | default: [] 6 | }); 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/SearchReadyAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const SearchReadyAtom = atom({ 4 | key: 'SearchReadyAtom', 5 | default: false 6 | }); 7 | -------------------------------------------------------------------------------- /src/localization/localize.ts: -------------------------------------------------------------------------------- 1 | import * as l10n from '@vscode/l10n'; 2 | 3 | export const localize = (key: string, ...args: (string | number)[]): string => { 4 | return l10n.t(key, ...args); 5 | }; 6 | -------------------------------------------------------------------------------- /src/models/BlockFieldData.ts: -------------------------------------------------------------------------------- 1 | export interface BlockFieldData { 2 | parentFields: string[] | undefined; 3 | blockType: string | undefined; 4 | selectedIndex: number | string | undefined; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/TaxonomyData.ts: -------------------------------------------------------------------------------- 1 | import { CustomTaxonomy } from '.'; 2 | 3 | export interface TaxonomyData { 4 | tags: string[]; 5 | categories: string[]; 6 | customTaxonomy: CustomTaxonomy[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/panelWebView/state/atom/RequiredFieldsAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const RequiredFieldsAtom = atom({ 4 | key: 'RequiredFields', 5 | default: [] 6 | }); 7 | -------------------------------------------------------------------------------- /.templates/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | slug: 4 | description: 5 | date: 2019-08-22T15:20:28.000Z 6 | lastmod: 2019-08-22T15:20:28.000Z 7 | weight: 1 8 | type: documentation 9 | --- 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/FiltersAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const FiltersAtom = atom<{ [filter: string]: string }>({ 4 | key: 'FiltersAtom', 5 | default: {} 6 | }); 7 | -------------------------------------------------------------------------------- /src/models/ScriptAction.ts: -------------------------------------------------------------------------------- 1 | export type ScriptAction = 2 | | 'open' 3 | | 'copyMediaMetadata' 4 | | 'copyMediaMetadataAndDelete' 5 | | 'deleteMedia' 6 | | 'fieldAction' 7 | | 'promptCopilot'; 8 | -------------------------------------------------------------------------------- /src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/TabInfoAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const TabInfoAtom = atom<{ [tab: string]: number } | null>({ 4 | key: 'TabInfoAtom', 5 | default: {} 6 | }); 7 | -------------------------------------------------------------------------------- /src/models/PostMessageData.ts: -------------------------------------------------------------------------------- 1 | export interface PostMessageData { 2 | command: string; 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | payload: any; 5 | requestId?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/constants/SsgScripts.ts: -------------------------------------------------------------------------------- 1 | export const SsgScripts = { 2 | folder: '/ssg-scripts', 3 | astroContentCollectionScript: 'astro.collections.mjs', 4 | astroContentCollectionJSON: 'astro.collections.json' 5 | }; 6 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/MultiSelectedItemsAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const MultiSelectedItemsAtom = atom({ 4 | key: 'MultiSelectedItemsAtom', 5 | default: [] 6 | }); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | .DS_Store 6 | dist 7 | todo.md 8 | 9 | e2e/storage 10 | e2e/extensions 11 | e2e/sample 12 | 13 | localization.log 14 | localization.md 15 | .env 16 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/AllPagesAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { Page } from '../../models'; 3 | 4 | export const AllPagesAtom = atom({ 5 | key: 'AllPagesAtom', 6 | default: [] 7 | }); 8 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Credentials'; 2 | export * from './ModeSwitch'; 3 | export * from './PagesParser'; 4 | export * from './PinnedItems'; 5 | export * from './SponsorAI'; 6 | export * from './Terminal'; 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/AllStaticFoldersAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const AllStaticFoldersAtom = atom({ 4 | key: 'AllStaticFoldersAtom', 5 | default: undefined 6 | }); 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/SelectedMediaFolderAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const SelectedMediaFolderAtom = atom({ 4 | key: 'SelectedMediaFolderAtom', 5 | default: null 6 | }); 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/TabAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { Tab } from '../../constants/Tab'; 3 | 4 | export const TabAtom = atom({ 5 | key: 'TabAtom', 6 | default: Tab.All 7 | }); 8 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/ListField.css: -------------------------------------------------------------------------------- 1 | .autoform__list_field { 2 | margin-bottom: 1rem; 3 | margin-top: 1rem; 4 | padding: 10px; 5 | border: 1px solid var(--frontmatter-list-border, rgba(255, 255, 255, 0.2)); 6 | } 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/AllContentFoldersAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const AllContentFoldersAtom = atom({ 4 | key: 'AllContentFoldersAtom', 5 | default: undefined 6 | }); 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/FilterValuesAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const FilterValuesAtom = atom<{ [filter: string]: string[] | string[][] }>({ 4 | key: 'FilterValuesAtom', 5 | default: {} 6 | }); 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/ModeAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { Mode } from '../../../models'; 3 | 4 | export const ModeAtom = atom({ 5 | key: 'ModeAtom', 6 | default: undefined 7 | }); 8 | -------------------------------------------------------------------------------- /src/models/SortingSetting.ts: -------------------------------------------------------------------------------- 1 | import { SortOrder, SortType } from '.'; 2 | 3 | export interface SortingSetting { 4 | title: string; 5 | name: string; 6 | order: SortOrder; 7 | type: SortType; 8 | id?: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Filter'; 2 | export * from './FoldersFilter'; 3 | export * from './Grouping'; 4 | export * from './Header'; 5 | export * from './Searchbox'; 6 | export * from './Sorting'; 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useCard'; 2 | export * from './useExtensibility'; 3 | export * from './useMedia'; 4 | export * from './useMessages'; 5 | export * from './usePages'; 6 | export * from './usePagination'; 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/SettingsAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { Settings } from '../../models'; 3 | 4 | export const SettingsAtom = atom({ 5 | key: 'SettingsAtom', 6 | default: null 7 | }); 8 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/TagAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const DEFAULT_TAG_STATE = ''; 4 | 5 | export const TagAtom = atom({ 6 | key: 'TagAtom', 7 | default: DEFAULT_TAG_STATE 8 | }); 9 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/LoadingAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { LoadingType } from '../../../models'; 3 | 4 | export const LoadingAtom = atom({ 5 | key: 'LoadingAtom', 6 | default: undefined 7 | }); 8 | -------------------------------------------------------------------------------- /src/panelWebView/components/DataBlock/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DataBlockControls'; 2 | export * from './DataBlockField'; 3 | export * from './DataBlockRecord'; 4 | export * from './DataBlockRecords'; 5 | export * from './DataBlockSelector'; 6 | -------------------------------------------------------------------------------- /src/panelWebView/components/VscodeComponents.ts: -------------------------------------------------------------------------------- 1 | import { wrapWc } from 'wc-react'; 2 | 3 | // @bendera/vscode-webview-elements 4 | // export const VsCollapsible = wrapWc(`vscode-collapsible`); 5 | // export const VsLabel = wrapWc(`vscode-label`); 6 | -------------------------------------------------------------------------------- /src/utils/existsAsync.ts: -------------------------------------------------------------------------------- 1 | import { stat } from 'fs'; 2 | import { promisify } from 'util'; 3 | 4 | export const existsAsync = async (path: string) => { 5 | return promisify(stat)(path) 6 | .then(() => true) 7 | .catch(() => false); 8 | }; 9 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/FolderAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const DEFAULT_FOLDER_STATE = null; 4 | 5 | export const FolderAtom = atom({ 6 | key: 'FolderAtom', 7 | default: DEFAULT_FOLDER_STATE 8 | }); 9 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/LocaleAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const DEFAULT_LOCALE_STATE = ''; 4 | 5 | export const LocaleAtom = atom({ 6 | key: 'LocaleAtom', 7 | default: DEFAULT_LOCALE_STATE 8 | }); 9 | -------------------------------------------------------------------------------- /src/dashboardWebView/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MessageHandlers'; 2 | export * from './darkenColor'; 3 | export * from './getRelPath'; 4 | export * from './opacityColor'; 5 | export * from './preserveColor'; 6 | export * from './updateCssVariables'; 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["eliostruyf.vscode-typescript-exportallmodules", "esbenp.prettier-vscode"] 5 | } 6 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/ValidatedQuickForm.tsx: -------------------------------------------------------------------------------- 1 | import BaseForm from './BaseForm'; 2 | import QuickForm from './QuickForm'; 3 | import ValidatedForm from './ValidatedForm'; 4 | 5 | export default ValidatedForm.Validated(QuickForm.Quick(BaseForm)); 6 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/LocalesAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { I18nConfig } from '../../../models'; 3 | 4 | export const LocalesAtom = atom({ 5 | key: 'LocalesAtom', 6 | default: undefined 7 | }); 8 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/ViewAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { DashboardViewType } from '../../models'; 3 | 4 | export const ViewAtom = atom({ 5 | key: 'ViewAtom', 6 | default: DashboardViewType.Grid 7 | }); 8 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | 3 | module.exports = { 4 | plugins: { 5 | 'postcss-import': {}, 6 | 'tailwindcss/nesting': {}, 7 | tailwindcss: {}, 8 | autoprefixer: {}, 9 | } 10 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/CategoryAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const DEFAULT_CATEGORY_STATE = ''; 4 | 5 | export const CategoryAtom = atom({ 6 | key: 'CategoryAtom', 7 | default: DEFAULT_CATEGORY_STATE 8 | }); 9 | -------------------------------------------------------------------------------- /src/utils/getTitleField.ts: -------------------------------------------------------------------------------- 1 | import { DefaultFields, SETTING_SEO_TITLE_FIELD } from '../constants'; 2 | import { Settings } from '../helpers'; 3 | 4 | export const getTitleField = () => 5 | Settings.get(SETTING_SEO_TITLE_FIELD) || DefaultFields.Title; 6 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/GroupingAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { GroupOption } from '../../constants/GroupOption'; 3 | 4 | export const GroupingAtom = atom({ 5 | key: 'GroupingAtom', 6 | default: GroupOption.none 7 | }); 8 | -------------------------------------------------------------------------------- /src/helpers/GroupBy.ts: -------------------------------------------------------------------------------- 1 | export const groupBy = (array: any[], key: string) => { 2 | return array.reduce((result, currentValue) => { 3 | (result[currentValue[key]] = result[currentValue[key]] || []).push(currentValue); 4 | return result; 5 | }, {}); 6 | }; 7 | -------------------------------------------------------------------------------- /src/panelWebView/state/atom/PanelSettingsAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { PanelSettings } from '../../../models'; 3 | 4 | export const PanelSettingsAtom = atom({ 5 | key: 'PanelSettings', 6 | default: undefined 7 | }); 8 | -------------------------------------------------------------------------------- /src/dashboardWebView/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DashboardViewType'; 2 | export * from './NavigationType'; 3 | export * from './Page'; 4 | export * from './PageMappings'; 5 | export * from './Settings'; 6 | export * from './SortingOption'; 7 | export * from './Status'; 8 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/TabSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { TabAtom } from '..'; 3 | 4 | export const TabSelector = selector({ 5 | key: 'TabSelector', 6 | get: ({ get }) => { 7 | return get(TabAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/TagSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { TagAtom } from '..'; 3 | 4 | export const TagSelector = selector({ 5 | key: 'TagSelector', 6 | get: ({ get }) => { 7 | return get(TagAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/panelWebView/components/JsonField/index.ts: -------------------------------------------------------------------------------- 1 | export * from './JsonField'; 2 | export * from './JsonFieldControls'; 3 | export * from './JsonFieldForm'; 4 | export * from './JsonFieldRecord'; 5 | export * from './JsonFieldRecords'; 6 | export * from './JsonFieldSelector'; 7 | -------------------------------------------------------------------------------- /src/dashboardWebView/models/NavigationType.ts: -------------------------------------------------------------------------------- 1 | export enum NavigationType { 2 | Welcome = 'welcome', 3 | Contents = 'contents', 4 | Media = 'media', 5 | Data = 'data', 6 | Snippets = 'snippets', 7 | Taxonomy = 'taxonomy', 8 | Settings = 'settings' 9 | } 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/DashboardViewAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { NavigationType } from '../../models'; 3 | 4 | export const DashboardViewAtom = atom({ 5 | key: 'DashboardViewAtom', 6 | default: NavigationType.Contents 7 | }); 8 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/ViewDataAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { DashboardData } from '../../../models/DashboardData'; 3 | 4 | export const ViewDataAtom = atom({ 5 | key: 'ViewDataAtom', 6 | default: undefined 7 | }); 8 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/PageSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { PageAtom } from '..'; 3 | 4 | export const PageSelector = selector({ 5 | key: 'PageSelector', 6 | get: ({ get }) => { 7 | return get(PageAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/ViewSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { ViewAtom } from '..'; 3 | 4 | export const ViewSelector = selector({ 5 | key: 'ViewSelector', 6 | get: ({ get }) => { 7 | return get(ViewAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "imageSize": 100, 4 | "contributorsPerLine": 7, 5 | "contributorsSortAlphabetically": false, 6 | "badgeTemplate": "", 7 | "contributorTemplate": "", 8 | "types": {}, 9 | "skipCi": "true", 10 | "contributors": [] 11 | } -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/FolderSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { FolderAtom } from '..'; 3 | 4 | export const FolderSelector = selector({ 5 | key: 'FolderSelector', 6 | get: ({ get }) => { 7 | return get(FolderAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/SearchSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { SearchAtom } from '..'; 3 | 4 | export const SearchSelector = selector({ 5 | key: 'SearchSelector', 6 | get: ({ get }) => { 7 | return get(SearchAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /scripts/main-release.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const packageJson = require('../package.json'); 5 | packageJson.name = "vscode-front-matter"; 6 | 7 | fs.writeFileSync(path.join(path.resolve('.'), 'package.json'), JSON.stringify(packageJson, null, 2)); -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/LoadingSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { LoadingAtom } from '..'; 3 | 4 | export const LoadingSelector = selector({ 5 | key: 'LoadingSelector', 6 | get: ({ get }) => { 7 | return get(LoadingAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/SortingSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { SortingAtom } from '..'; 3 | 4 | export const SortingSelector = selector({ 5 | key: 'SortingSelector', 6 | get: ({ get }) => { 7 | return get(SortingAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/utils/getDescriptionField.ts: -------------------------------------------------------------------------------- 1 | import { DefaultFields, SETTING_SEO_DESCRIPTION_FIELD } from '../constants'; 2 | import { Settings } from '../helpers'; 3 | 4 | export const getDescriptionField = () => 5 | Settings.get(SETTING_SEO_DESCRIPTION_FIELD) || DefaultFields.Description; 6 | -------------------------------------------------------------------------------- /src/constants/DefaultFields.ts: -------------------------------------------------------------------------------- 1 | export const DefaultFields = { 2 | PublishingDate: `date`, 3 | LastModified: `lastmod`, 4 | Description: `description`, 5 | Title: `title`, 6 | Slug: `slug`, 7 | Type: `type`, 8 | ContentType: `fmContentType`, 9 | Keywords: `keywords` 10 | }; 11 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/CategorySelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { CategoryAtom } from '..'; 3 | 4 | export const CategorySelector = selector({ 5 | key: 'CategorySelector', 6 | get: ({ get }) => { 7 | return get(CategoryAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/GroupingSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { GroupingAtom } from '..'; 3 | 4 | export const GroupingSelector = selector({ 5 | key: 'GroupingSelector', 6 | get: ({ get }) => { 7 | return get(GroupingAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/SettingsSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { SettingsAtom } from '..'; 3 | 4 | export const SettingsSelector = selector({ 5 | key: 'SettingsSelector', 6 | get: ({ get }) => { 7 | return get(SettingsAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/ViewDataSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { ViewDataAtom } from '..'; 3 | 4 | export const ViewDataSelector = selector({ 5 | key: 'ViewDataSelector', 6 | get: ({ get }) => { 7 | return get(ViewDataAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /assets/icons/close-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/close-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/MediaTotalSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { MediaTotalAtom } from '..'; 3 | 4 | export const MediaTotalSelector = selector({ 5 | key: 'MediaTotalSelector', 6 | get: ({ get }) => { 7 | return get(MediaTotalAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/panelWebView/hooks/usePrevious.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export function usePrevious(value: T): T | undefined { 4 | const ref = useRef(); 5 | 6 | useEffect(() => { 7 | ref.current = value; 8 | }, [value]); 9 | 10 | return ref.current; 11 | } 12 | -------------------------------------------------------------------------------- /src/helpers/getNonce.ts: -------------------------------------------------------------------------------- 1 | export const getNonce = () => { 2 | let text = ''; 3 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 4 | for (let i = 0; i < 32; i++) { 5 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 6 | } 7 | return text; 8 | }; 9 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/MediaFoldersSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { MediaFoldersAtom } from '..'; 3 | 4 | export const MediaFoldersSelector = selector({ 5 | key: 'MediaFoldersSelector', 6 | get: ({ get }) => { 7 | return get(MediaFoldersAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/DashboardViewSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { DashboardViewAtom } from '..'; 3 | 4 | export const DashboardViewSelector = selector({ 5 | key: 'DashboardViewSelector', 6 | get: ({ get }) => { 7 | return get(DashboardViewAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/constants/SentryIgnore.ts: -------------------------------------------------------------------------------- 1 | export const SentryIgnore = [ 2 | `ResizeObserver loop limit exceeded`, 3 | `Cannot read properties of undefined (reading 'unobserve')`, 4 | `TypeError: Cannot read properties of undefined (reading 'unobserve')`, 5 | `ResizeObserver loop completed with undelivered notifications.` 6 | ]; 7 | -------------------------------------------------------------------------------- /src/models/DataFile.ts: -------------------------------------------------------------------------------- 1 | export interface DataFile { 2 | id: string; 3 | title: string; 4 | file: string; 5 | fileType: 'json' | 'yaml'; 6 | labelField: string; 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | schema?: any; 9 | type?: string; 10 | singleEntry?: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /src/dashboardWebView/models/SortingOption.ts: -------------------------------------------------------------------------------- 1 | import { SortOrder, SortType } from '../../models'; 2 | import { SortOption } from '../constants/SortOption'; 3 | 4 | export interface SortingOption { 5 | title?: string; 6 | name: string; 7 | id: SortOption | string; 8 | order: SortOrder; 9 | type: SortType; 10 | } 11 | -------------------------------------------------------------------------------- /src/helpers/DebounceCallback.ts: -------------------------------------------------------------------------------- 1 | export const debounceCallback = () => { 2 | let timeout: NodeJS.Timeout; 3 | 4 | return (fnc: any, time: number) => { 5 | const functionCall = (...args: any[]) => fnc.apply(args); 6 | clearTimeout(timeout); 7 | timeout = setTimeout(functionCall, time) as any; 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/SelectedItemActionAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const SelectedItemActionAtom = atom< 4 | | { 5 | path: string; 6 | action: 'view' | 'edit' | 'delete'; 7 | } 8 | | undefined 9 | >({ 10 | key: 'SelectedItemActionAtom', 11 | default: undefined 12 | }); 13 | -------------------------------------------------------------------------------- /assets/icons/strikethrough-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/strikethrough-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/constants/LocalStore.ts: -------------------------------------------------------------------------------- 1 | export const LocalStore = { 2 | rootFolder: '.frontmatter', 3 | contentFolder: 'content', 4 | databaseFolder: 'database', 5 | templatesFolder: 'templates', 6 | mediaDatabaseFile: 'mediaDb.json', 7 | taxonomyDatabaseFile: 'taxonomyDb.json', 8 | pinnedItemsDatabaseFile: 'pinnedItemsDb.json' 9 | }; 10 | -------------------------------------------------------------------------------- /src/models/DataFolder.ts: -------------------------------------------------------------------------------- 1 | export interface DataFolder { 2 | id: string; 3 | path: string; 4 | labelField: string; 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | schema?: any; 7 | type?: string; 8 | singleEntry?: boolean; 9 | enableFileCreation?: boolean; 10 | fileType?: 'json' | 'yaml'; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/LabelField.css: -------------------------------------------------------------------------------- 1 | .autoform__label { 2 | display: block; 3 | margin-bottom: 0.5rem; 4 | margin-top: 0.5rem; 5 | line-height: 16px; 6 | font-weight: bold; 7 | 8 | .autoform__label__required { 9 | color: var(--vscode-inputValidation-errorBorder); 10 | margin-left: 0.25rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/SelectedMediaFolderSelector.ts: -------------------------------------------------------------------------------- 1 | import { selector } from 'recoil'; 2 | import { SelectedMediaFolderAtom } from '..'; 3 | 4 | export const SelectedMediaFolderSelector = selector({ 5 | key: 'SelectedMediaFolderSelector', 6 | get: ({ get }) => { 7 | return get(SelectedMediaFolderAtom); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /assets/icons/bold-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/bold-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/script-sample.js: -------------------------------------------------------------------------------- 1 | 2 | const arguments = process.argv; 3 | 4 | if (arguments && arguments.length > 0) { 5 | const workspaceArg = arguments[2]; // The workspace path 6 | const fileArg = arguments[3]; // The file path 7 | const frontMatterArg = arguments[4]; // Front matter data 8 | 9 | console.log(`The content returned for your notification.`); 10 | } -------------------------------------------------------------------------------- /src/utils/formatInTimezone.ts: -------------------------------------------------------------------------------- 1 | import { SETTING_GLOBAL_TIMEZONE } from '../constants'; 2 | import { DateHelper, Settings } from '../helpers'; 3 | 4 | export const formatInTimezone = (date: Date, dateFormat: string) => { 5 | const timezone = Settings.get(SETTING_GLOBAL_TIMEZONE); 6 | return DateHelper.formatInTimezone(date, dateFormat, timezone) || ''; 7 | }; 8 | -------------------------------------------------------------------------------- /src/models/CustomTaxonomyData.ts: -------------------------------------------------------------------------------- 1 | import { BlockFieldData } from './BlockFieldData'; 2 | 3 | export interface CustomTaxonomyData { 4 | id: string | undefined; 5 | name: string | undefined; 6 | options?: string[] | undefined; 7 | option?: string | undefined; 8 | parents?: string[]; 9 | renderAsString?: boolean; 10 | blockData?: BlockFieldData; 11 | } 12 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IIconProps { 4 | name: string; 5 | } 6 | 7 | const Icon: React.FunctionComponent = ({ 8 | name 9 | }: React.PropsWithChildren) => { 10 | return ; 11 | }; 12 | 13 | Icon.displayName = 'Icon'; 14 | export { Icon }; 15 | -------------------------------------------------------------------------------- /assets/media/reset.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 13px; 4 | } 5 | 6 | *, 7 | *:before, 8 | *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | ol, 21 | ul { 22 | margin: 0; 23 | padding: 0; 24 | font-weight: normal; 25 | } 26 | 27 | img { 28 | max-width: 100%; 29 | height: auto; 30 | } -------------------------------------------------------------------------------- /assets/icons/code-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/code-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/italic-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/italic-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/SortingAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | import { SortOption } from '../../constants/SortOption'; 3 | import { SortingOption } from '../../models/SortingOption'; 4 | 5 | export const DEFAULT_SORTING_OPTION = SortOption.LastModifiedDesc; 6 | 7 | export const SortingAtom = atom({ 8 | key: 'SortingAtom', 9 | default: null 10 | }); 11 | -------------------------------------------------------------------------------- /src/listeners/panel/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ArticleListener'; 2 | export * from './BaseListener'; 3 | export * from './DataListener'; 4 | export * from './ExtensionListener'; 5 | export * from './FieldsListener'; 6 | export * from './MediaListener'; 7 | export * from './ScriptListener'; 8 | export * from './SettingsListener'; 9 | export * from './TaxonomyListener'; 10 | export * from './LocalizationListener'; 11 | -------------------------------------------------------------------------------- /src/utils/ignoreMsgCommand.ts: -------------------------------------------------------------------------------- 1 | import { GeneralCommands } from '../constants'; 2 | 3 | export const ignoreMsgCommand = (command: string) => { 4 | const toIgnore = [ 5 | GeneralCommands.toVSCode.logging.verbose, 6 | GeneralCommands.toVSCode.logging.info, 7 | GeneralCommands.toVSCode.logging.warn, 8 | GeneralCommands.toVSCode.logging.error 9 | ]; 10 | 11 | return toIgnore.includes(command); 12 | }; 13 | -------------------------------------------------------------------------------- /src/dashboardWebView/utils/preserveColor.ts: -------------------------------------------------------------------------------- 1 | export const preserveColor = (color: string | undefined) => { 2 | if (color) { 3 | if (color.startsWith('#') && color.length > 7) { 4 | return color.slice(0, 7); 5 | } else if (color.startsWith('rgba')) { 6 | const splits = color.split(','); 7 | splits.pop(); 8 | return `${splits.join(', ')}, 1)`; 9 | } 10 | } 11 | 12 | return color; 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/encodeEmoji.ts: -------------------------------------------------------------------------------- 1 | export const encodeEmoji = (text: string) => { 2 | if (!text || !/\p{Extended_Pictographic}/u.test(text)) { 3 | return text; 4 | } 5 | 6 | const characters = [...text].map((el) => { 7 | if (/\p{Extended_Pictographic}/u.test(el)) { 8 | return `\\u${el.codePointAt(0)?.toString(16).toUpperCase()}`; 9 | } 10 | return el; 11 | }); 12 | 13 | return characters.join(''); 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/BaseForm.tsx: -------------------------------------------------------------------------------- 1 | import { BaseForm } from 'uniforms'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | function Unstyled(parent: any) { 5 | class _ extends parent { 6 | static Unstyled = Unstyled; 7 | 8 | static displayName = `Unstyled${parent.displayName}`; 9 | } 10 | 11 | return _ as unknown as typeof BaseForm; 12 | } 13 | 14 | export default Unstyled(BaseForm); 15 | -------------------------------------------------------------------------------- /assets/icons/options-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/options-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/AutoForm.tsx: -------------------------------------------------------------------------------- 1 | import { AutoForm } from 'uniforms'; 2 | 3 | import ValidatedQuickForm from './ValidatedQuickForm'; 4 | 5 | function Auto(parent: unknown) { 6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 7 | class _ extends AutoForm.Auto(parent as any) { 8 | static Auto = Auto; 9 | } 10 | 11 | return _ as unknown as AutoForm; 12 | } 13 | 14 | export default Auto(ValidatedQuickForm); 15 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/ValidatedForm.tsx: -------------------------------------------------------------------------------- 1 | import { ValidatedForm } from 'uniforms'; 2 | 3 | import BaseForm from './BaseForm'; 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | function Validated(parent: any) { 7 | class _ extends ValidatedForm.Validated(parent) { 8 | static Validated = Validated; 9 | } 10 | 11 | return _ as unknown as ValidatedForm; 12 | } 13 | 14 | export default Validated(BaseForm); 15 | -------------------------------------------------------------------------------- /src/listeners/dashboard/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BaseListener'; 2 | export * from './DashboardListener'; 3 | export * from './DataListener'; 4 | export * from './ExtensionListener'; 5 | export * from './MediaListener'; 6 | export * from './PagesListener'; 7 | export * from './SettingsListener'; 8 | export * from './SnippetListener'; 9 | export * from './TaxonomyListener'; 10 | export * from './LocalizationListener'; 11 | export * from './SsgListener'; 12 | -------------------------------------------------------------------------------- /assets/icons/codeblock-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/codeblock-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/heading-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/icons/heading-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /scripts/watch-localization.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { exec } = require('node:child_process'); 3 | const { join } = require('path'); 4 | 5 | const localFile = '../l10n/bundle.l10n.json'; 6 | 7 | console.log(`Watching for file changes on ${localFile}`); 8 | exec(`npm run localization:generate`) 9 | 10 | fs.watchFile(join(__dirname, localFile), (curr, prev) => { 11 | console.log(`update enum`) 12 | exec(`npm run localization:generate`) 13 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6", 8 | "DOM" 9 | ], 10 | "typeRoots": [ 11 | "node_modules/@types" 12 | ], 13 | "sourceMap": true, 14 | "rootDir": "src", 15 | "strict": true, 16 | "jsx": "react", 17 | "strictNullChecks": true 18 | }, 19 | "exclude": [ 20 | "node_modules", 21 | ".vscode-test", 22 | "docs" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /assets/icons/i18n-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/i18n-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/walkthrough/get-started.md: -------------------------------------------------------------------------------- 1 | ## Getting started 2 | 3 | Thanks for installing Front Matter! 4 | 5 | To get started, open our dashboard which will guide you through the initialization process of your project. 6 | 7 | When you haven't initialized your project yet, you will see the Front Matter's welcome screen on which you will have to perform the following steps: 8 | 9 | - Project initialization 10 | - Content folders registration 11 | - Framework initialization 12 | -------------------------------------------------------------------------------- /src/constants/DefaultFeatureFlags.ts: -------------------------------------------------------------------------------- 1 | import { FEATURE_FLAG } from './Features'; 2 | 3 | export const DEFAULT_PANEL_FEATURE_FLAGS = Object.values(FEATURE_FLAG.panel).filter( 4 | (v) => v !== FEATURE_FLAG.panel.globalSettings 5 | ); 6 | 7 | export const DEFAULT_DASHBOARD_FEATURE_FLAGS = [ 8 | FEATURE_FLAG.dashboard.data.view, 9 | FEATURE_FLAG.dashboard.taxonomy.view, 10 | FEATURE_FLAG.dashboard.snippets.view, 11 | FEATURE_FLAG.dashboard.snippets.manage 12 | ]; 13 | -------------------------------------------------------------------------------- /src/dashboardWebView/DashboardCommand.ts: -------------------------------------------------------------------------------- 1 | export enum DashboardCommand { 2 | initializing = 'initializing', 3 | loading = 'loading', 4 | pages = 'pages', 5 | searchPages = 'searchPages', 6 | settings = 'settings', 7 | media = 'media', 8 | viewData = 'viewData', 9 | mediaUpdate = 'mediaUpdate', 10 | dataFileEntries = 'dataFileEntries', 11 | searchReady = 'searchReady', 12 | 13 | // Taxonomy dashboard 14 | setTaxonomyData = 'setTaxonomyData' 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/joinUrl.ts: -------------------------------------------------------------------------------- 1 | import { urlJoin } from 'url-join-ts'; 2 | import { parseWinPath } from '../helpers'; 3 | 4 | export const joinUrl = (baseUrl: string | undefined, ...paths: any[]): string => { 5 | const url = urlJoin(baseUrl, ...paths); 6 | 7 | // Get last path 8 | const lastPath = paths[paths.length - 1]; 9 | if (lastPath && lastPath.endsWith('/') && !url.endsWith('/')) { 10 | return url + '/'; 11 | } 12 | 13 | return parseWinPath(url); 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/fetchWithTimeout.ts: -------------------------------------------------------------------------------- 1 | export const fetchWithTimeout = async (url: string, options: any, timeout = 5000) => { 2 | try { 3 | const controller = new AbortController(); 4 | const id = setTimeout(() => controller.abort(), timeout); 5 | const response = await fetch(url, { ...options, signal: controller.signal }); 6 | clearTimeout(id); 7 | return response; 8 | } catch (error) { 9 | throw new Error(`Request timed out: ${url}`); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/panelWebView/components/Fields/RequiredAsterix.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IRequiredAsterixProps { 4 | required?: boolean; 5 | } 6 | 7 | export const RequiredAsterix: React.FunctionComponent = ({ 8 | required 9 | }: React.PropsWithChildren) => { 10 | if (!required) { 11 | return null; 12 | } 13 | 14 | return *; 15 | }; 16 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/AddIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IAddIconProps { } 4 | 5 | export const AddIcon: React.FunctionComponent = () => { 6 | return ( 7 | 14 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/panelWebView/components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ISpinnerProps { } 4 | 5 | const Spinner: React.FunctionComponent = () => { 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 | ); 13 | }; 14 | 15 | Spinner.displayName = 'Spinner'; 16 | export { Spinner }; 17 | -------------------------------------------------------------------------------- /src/providers/FrontMatterDecorationProvider.ts: -------------------------------------------------------------------------------- 1 | import { TextEditorDecorationType, window, ColorThemeKind } from 'vscode'; 2 | 3 | export class FrontMatterDecorationProvider { 4 | get(): TextEditorDecorationType { 5 | const colorThemeKind = window.activeColorTheme.kind; 6 | 7 | return window.createTextEditorDecorationType({ 8 | backgroundColor: colorThemeKind === ColorThemeKind.Dark ? '#ffffff14' : '#00000014', 9 | isWholeLine: true 10 | }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/dashboardWebView/constants/SortOption.ts: -------------------------------------------------------------------------------- 1 | export enum SortOption { 2 | PublishedAsc = 'PublishedAsc', 3 | PublishedDesc = 'PublishedDesc', 4 | LastModifiedAsc = 'LastModifiedAsc', 5 | LastModifiedDesc = 'LastModifiedDesc', 6 | FileNameAsc = 'FileNameAsc', 7 | FileNameDesc = 'FileNameDesc', 8 | SizeAsc = 'SizeAsc', 9 | SizeDesc = 'SizeDesc', 10 | CaptionAsc = 'CaptionAsc', 11 | CaptionDesc = 'CaptionDesc', 12 | AltAsc = 'AltAsc', 13 | AltDesc = 'AltDesc' 14 | } 15 | -------------------------------------------------------------------------------- /assets/icons/blockquote-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /assets/icons/ordered-list-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/icons/blockquote-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /assets/icons/ordered-list-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/helpers/decodeBase64Image.ts: -------------------------------------------------------------------------------- 1 | export const decodeBase64 = (dataString: string) => { 2 | const dataParts = dataString.split(';base64,'); 3 | 4 | if (dataParts?.length < 2) { 5 | return null; 6 | } 7 | 8 | const typePart = dataParts[0].split(':').pop() as string; 9 | const dataPart = dataParts.pop() as string; 10 | 11 | const response: any = {}; 12 | 13 | response.type = typePart; 14 | response.data = Buffer.from(dataPart, 'base64'); 15 | 16 | return response; 17 | }; 18 | -------------------------------------------------------------------------------- /src/helpers/parseWinPath.ts: -------------------------------------------------------------------------------- 1 | import { isWindows } from '../utils/isWindows'; 2 | 3 | export const parseWinPath = (path: string | undefined): string => { 4 | path = path?.split(`\\`).join(`/`) || ''; 5 | 6 | if (isWindows()) { 7 | // Check if path starts with a drive letter (e.g., "C:\") 8 | if (/^[a-zA-Z]:\\/.test(path)) { 9 | // Convert to lowercase drive letter 10 | path = path.charAt(0).toLowerCase() + path.slice(1); 11 | } 12 | } 13 | 14 | return path; 15 | }; 16 | -------------------------------------------------------------------------------- /src/panelWebView/Command.ts: -------------------------------------------------------------------------------- 1 | export enum Command { 2 | loading = 'loading', 3 | metadata = 'metadata', 4 | settings = 'settings', 5 | focusOnTags = 'focusOnTags', 6 | focusOnCategories = 'focusOnCategories', 7 | closeSections = 'closeSections', 8 | folderInfo = 'folderInfo', 9 | mediaSelectionData = 'mediaSelectionData', 10 | sendMediaUrl = 'sendMediaUrl', 11 | updatePlaceholder = 'updatePlaceholder', 12 | dataFileEntries = 'dataFileEntries', 13 | serverStarted = 'server-started' 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Documentation 4 | url: https://frontmatter.codes/docs 5 | about: See our documentation. 6 | - name: Changelog 7 | url: https://frontmatter.codes/updates 8 | about: See our changelog. 9 | - name: Front Matter website 10 | url: https://frontmatter.codes 11 | about: Our website. 12 | - name: Support Front Matter 13 | url: https://github.com/sponsors/estruyf 14 | about: Support Front Matter development. -------------------------------------------------------------------------------- /src/commands/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Article'; 2 | export * from './Backers'; 3 | export * from './Cache'; 4 | export * from './Chatbot'; 5 | export * from './Content'; 6 | export * from './Dashboard'; 7 | export * from './Diagnostics'; 8 | export * from './Folders'; 9 | export * from './Preview'; 10 | export * from './Project'; 11 | export * from './Settings'; 12 | export * from './StatusListener'; 13 | export * from './Taxonomy'; 14 | export * from './Template'; 15 | export * from './Wysiwyg'; 16 | export * from './i18n'; 17 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tsconfig.e2e.json 9 | **/*.map 10 | **/*.ts 11 | webpack.config.js 12 | node_modules 13 | docs 14 | tailwind.config.js 15 | sample 16 | postcss.config.js 17 | .templates 18 | .github 19 | scripts 20 | .all-contributorsrc 21 | assets/v2.* 22 | assets/v3.* 23 | assets/v4.* 24 | assets/sponsors 25 | dist/*.html 26 | frontmatter.json 27 | .frontmatter 28 | webpack 29 | README.beta.md 30 | e2e 31 | storage 32 | pnpm-lock.yaml -------------------------------------------------------------------------------- /assets/icons/chatbot-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/chatbot-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/media-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/media-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/ErrorsField.css: -------------------------------------------------------------------------------- 1 | .autoform-error { 2 | background-color: var( 3 | --frontmatter-error-background, 4 | var(--vscode-inputValidation-errorBackground) 5 | ); 6 | border: 1px solid var(--frontmatter-error-border, var(--vscode-inputValidation-errorBorder)); 7 | border-radius: 2px; 8 | margin: 20px 0px; 9 | padding: 10px; 10 | color: var(--frontmatter-error-foreground, var(--vscode-editor-foreground)); 11 | 12 | ul { 13 | margin-bottom: 0; 14 | } 15 | 16 | li { 17 | text-transform: capitalize; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /assets/icons/unordered-list-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /assets/icons/unordered-list-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/DataView/SortableContainer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SortableContainer } from 'react-sortable-hoc'; 3 | 4 | export interface ISortableContainerProps { } 5 | 6 | export const Container = SortableContainer( 7 | ({ children }: React.PropsWithChildren) => { 8 | 9 | return ( 10 |
    13 | {children} 14 |
15 | ); 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/showcase.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Showcase 3 | about: Let us know that you are using Front Matter and we'll add you on our showcase page 4 | title: 'Showcase: ' 5 | labels: 'showcase' 6 | assignees: '' 7 | --- 8 | 9 | **Title you want to give your site** 10 | 11 | Define a clear title that will be used on the showcase page. Example: `Front Matter`. 12 | 13 | **Link to the site** 14 | 15 | A URL to the site to add. 16 | 17 | **A nice and clean description** 18 | 19 | Keep it simple. Just let us know which static-site generator you used, and other frameworks. 20 | 21 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Common/Link.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ILinkProps { 4 | title: string; 5 | href: string; 6 | className?: string; 7 | } 8 | 9 | export const Link: React.FunctionComponent = ({ children, title, href, className }: React.PropsWithChildren) => { 10 | return ( 11 | 15 | {children} 16 | 17 | ); 18 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/hooks/useMediaFolder.tsx: -------------------------------------------------------------------------------- 1 | import { useRecoilState } from 'recoil'; 2 | import { MultiSelectedItemsAtom, SelectedMediaFolderAtom } from '../state'; 3 | 4 | export default function useMediaFolder() { 5 | const [selectedFolder, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom); 6 | const [, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom); 7 | 8 | const updateFolder = (folder: string) => { 9 | setSelectedFolder(folder); 10 | setSelectedFiles([]); 11 | }; 12 | 13 | return { 14 | selectedFolder, 15 | updateFolder 16 | }; 17 | } -------------------------------------------------------------------------------- /src/utils/getPlatform.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os'; 2 | 3 | /** 4 | * Determines the current operating system platform. 5 | * 6 | * @returns {'windows' | 'linux' | 'osx'} - A string representing the platform: 7 | * - 'windows' for Windows OS 8 | * - 'osx' for macOS 9 | * - 'linux' for Linux OS 10 | */ 11 | export const getPlatform = (): 'windows' | 'linux' | 'osx' => { 12 | const platform = os.platform(); 13 | if (platform === 'win32') { 14 | return 'windows'; 15 | } else if (platform === 'darwin') { 16 | return 'osx'; 17 | } 18 | 19 | return 'linux'; 20 | }; 21 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Media/List.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IListProps { 4 | gap?: number; 5 | } 6 | 7 | export const List: React.FunctionComponent = ({ 8 | gap, 9 | children 10 | }: React.PropsWithChildren) => { 11 | const gapClass = gap !== undefined ? `gap-y-${gap}` : ``; 12 | 13 | return ( 14 |
    18 | {children} 19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/ListAddField.css: -------------------------------------------------------------------------------- 1 | .autoform__list_add_field { 2 | display: flex; 3 | padding: 5px; 4 | border: 1px dashed var(--frontmatter-field-border, var(--vscode-editor-foreground)); 5 | width: 100%; 6 | justify-content: center; 7 | margin-top: 0.5rem; 8 | 9 | &:hover { 10 | border-color: var(--frontmatter-field-borderActive, var(--vscode-button-background)); 11 | color: var(--frontmatter-field-borderActive, var(--vscode-button-background)); 12 | cursor: pointer; 13 | } 14 | 15 | svg { 16 | height: 1rem; 17 | width: 1rem; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/models/AstroCollections.ts: -------------------------------------------------------------------------------- 1 | export interface AstroCollection { 2 | name: string; 3 | type: 'content' | 'data'; 4 | fields: AstroField[]; 5 | } 6 | 7 | export interface AstroField { 8 | name: string; 9 | type: 10 | | 'ZodString' 11 | | 'ZodNumber' 12 | | 'ZodBoolean' 13 | | 'ZodArray' 14 | | 'ZodEnum' 15 | | 'ZodDate' 16 | | 'ZodObject' 17 | | 'datetime' 18 | | 'email' 19 | | 'url' 20 | | 'image'; 21 | required: boolean; 22 | defaultValue?: string; 23 | options?: string[]; 24 | description?: string; 25 | fields?: AstroField[]; 26 | } 27 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/SymbolKeywordIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ISymbolKeywordIconProps { } 4 | 5 | export const SymbolKeywordIcon: React.FunctionComponent = () => { 6 | return ( 7 | 14 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /assets/walkthrough/support-the-project.md: -------------------------------------------------------------------------------- 1 | ## Support the project 2 | 3 | Front Matter is an open source project and we are always looking for new contributors, supporters, and partners. If you are interested in backing the project, please consider supporting it by donating. You can donate at via the following links: 4 | 5 | - [GitHub Sponsors](https://github.com/sponsors/estruyf) 6 | - [Open Collective](https://opencollective.com/frontmatter) 7 | 8 | > Each sponsor/backer will be mentioned on the [Front Matter](https://frontmatter.codes) website and on the [GitHub repository](https://github.com/estruyf/vscode-front-matter). -------------------------------------------------------------------------------- /src/dashboardWebView/components/Common/LinkButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ILinkButtonProps { 4 | title: string; 5 | onClick: () => void; 6 | } 7 | 8 | export const LinkButton: React.FunctionComponent = ({ children, title, onClick }: React.PropsWithChildren) => { 9 | return ( 10 | 17 | ); 18 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/components/WelcomeView/WelcomeLink.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IWelcomeLinkProps { 4 | href: string; 5 | title: string; 6 | } 7 | 8 | export const WelcomeLink: React.FunctionComponent = ({ href, title, children }: React.PropsWithChildren) => { 9 | return ( 10 | 15 | {children} 16 | 17 | ); 18 | }; -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/FileIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IFileIconProps { } 4 | 5 | export const FileIcon: React.FunctionComponent = () => { 6 | return ( 7 | 14 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/models/Snippets.ts: -------------------------------------------------------------------------------- 1 | import { Field } from './PanelSettings'; 2 | 3 | export interface Snippets { 4 | [snippetName: string]: Snippet; 5 | } 6 | 7 | export interface Snippet { 8 | title?: string; 9 | description: string; 10 | body: string[] | string; 11 | fields: SnippetField[]; 12 | openingTags?: string; 13 | closingTags?: string; 14 | isMediaSnippet?: boolean; 15 | sourcePath?: string; 16 | } 17 | 18 | export type SnippetSpecialPlaceholders = 'FM_SELECTED_TEXT' | string; 19 | 20 | export interface SnippetField extends Field { 21 | default?: SnippetSpecialPlaceholders; 22 | value?: unknown; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/ListDelField.css: -------------------------------------------------------------------------------- 1 | .autoform__list_del_field { 2 | display: flex; 3 | width: 100%; 4 | justify-content: center; 5 | margin-top: 0.5rem; 6 | 7 | &:hover { 8 | border-color: var(--vscode-button-background); 9 | color: var(--vscode-button-background); 10 | cursor: pointer; 11 | } 12 | 13 | .line { 14 | height: 1px; 15 | background: var(--frontmatter-list-border, var(--vscode-editor-foreground)); 16 | width: 100%; 17 | margin-right: 0.5rem; 18 | margin-top: 0.5rem; 19 | } 20 | 21 | svg { 22 | height: 1.25rem; 23 | width: 1.25rem; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/models/ContentFolder.ts: -------------------------------------------------------------------------------- 1 | import { I18nConfig } from './i18nConfig'; 2 | 3 | export interface ContentFolder { 4 | title: string; 5 | path: string; 6 | 7 | disableCreation?: boolean; 8 | excludeSubdir?: boolean; 9 | excludePaths?: string[]; 10 | previewPath?: string; 11 | trailingSlash?: boolean; 12 | filePrefix?: string; 13 | contentTypes?: string[]; 14 | originalPath?: string; 15 | $schema?: string; // Extended config 16 | extended?: boolean; // Extended config 17 | 18 | locale?: string; 19 | localeTitle?: string; 20 | localeSourcePath?: string; 21 | defaultLocale?: string; 22 | locales?: I18nConfig[]; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/ErrorField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { HTMLProps } from 'react'; 3 | import { Override, connectField, filterDOMProps } from 'uniforms'; 4 | 5 | export type ErrorFieldProps = Override< 6 | Omit, 'onChange'>, 7 | { error?: unknown; errorMessage?: string } 8 | >; 9 | 10 | function Error({ children, error, errorMessage, ...props }: ErrorFieldProps) { 11 | return !error ? null :
{children || errorMessage}
; 12 | } 13 | 14 | export default connectField(Error, { 15 | initialValue: false, 16 | kind: 'leaf' 17 | }); 18 | -------------------------------------------------------------------------------- /src/helpers/isValidFile.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_FILE_TYPES } from './../constants/DefaultFileTypes'; 2 | import { Settings } from '.'; 3 | import { SETTING_CONTENT_SUPPORTED_FILETYPES } from '../constants'; 4 | import { extname } from 'path'; 5 | 6 | export const isValidFile = (fileName: string) => { 7 | let supportedFiles = 8 | Settings.get(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES; 9 | supportedFiles = supportedFiles.map((f) => (f.startsWith(`.`) ? f : `.${f}`)); 10 | 11 | // Get the extension of the file path 12 | const extension = extname(fileName); 13 | 14 | return supportedFiles.includes(extension); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/features/FeatureFlag.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IFeatureFlagProps { 4 | flag: string; 5 | features: string[] | null; 6 | alternative?: JSX.Element; 7 | } 8 | 9 | export const FeatureFlag: React.FunctionComponent = ({ 10 | flag, 11 | features, 12 | alternative, 13 | children 14 | }: React.PropsWithChildren) => { 15 | if (!features || features.length === 0 || (features.length > 0 && !features.includes(flag))) { 16 | if (alternative) { 17 | return alternative; 18 | } 19 | 20 | return null; 21 | } 22 | 23 | return <>{children}; 24 | }; 25 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/ArchiveIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IArchiveIconProps { } 4 | 5 | export const ArchiveIcon: React.FunctionComponent = () => { 6 | return ( 7 | 14 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/helpers/Telemetry.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | 3 | export class Telemetry { 4 | public static isVscodeEnabled(): boolean { 5 | const config = workspace.getConfiguration('telemetry'); 6 | const isVscodeEnable = config.get<'off' | undefined>('enableTelemetry'); 7 | return isVscodeEnable === 'off' ? false : true; 8 | } 9 | 10 | /** 11 | * Checks if telemetry is enabled. 12 | * @returns {boolean} Returns true if telemetry is enabled, false otherwise. 13 | */ 14 | public static isEnabled(): boolean { 15 | const isVscodeEnable = Telemetry.isVscodeEnabled(); 16 | return isVscodeEnable ? false : true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "ignorePatterns": ["./**/*.js", "./webpack/*.config.js", "./e2e/*.ts"], 11 | "rules": { 12 | "no-throw-literal": "error", 13 | "no-unused-expressions": "error", 14 | "curly": "error", 15 | "class-methods-use-this": "warn", 16 | "no-console": "warn", 17 | "@typescript-eslint/no-empty-interface": "off", 18 | "no-extra-boolean-cast": "off" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/ListItemField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ReactNode } from 'react'; 3 | import { connectField } from 'uniforms'; 4 | 5 | import AutoField from './AutoField'; 6 | import ListDelField from './ListDelField'; 7 | 8 | export type ListItemFieldProps = { children?: ReactNode; value?: unknown }; 9 | 10 | function ListItem({ children = }: ListItemFieldProps) { 11 | return ( 12 |
13 | 14 | {children} 15 |
16 | ); 17 | } 18 | 19 | export default connectField(ListItem, { 20 | initialValue: false 21 | }); 22 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Media/DetailsItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IDetailsItemProps { 4 | title: string; 5 | details: string; 6 | } 7 | 8 | export const DetailsItem: React.FunctionComponent = ({ title, details }: React.PropsWithChildren) => { 9 | return ( 10 | <> 11 |
12 |
{title}
13 |
14 | {details} 15 |
16 |
17 | 18 | ); 19 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/state/selectors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CategorySelector'; 2 | export * from './DashboardViewSelector'; 3 | export * from './FolderSelector'; 4 | export * from './GroupingSelector'; 5 | export * from './LoadingSelector'; 6 | export * from './MediaFoldersSelector'; 7 | export * from './MediaTotalSelector'; 8 | export * from './PageSelector'; 9 | export * from './SearchSelector'; 10 | export * from './SelectedMediaFolderSelector'; 11 | export * from './SettingsSelector'; 12 | export * from './SortingSelector'; 13 | export * from './TabSelector'; 14 | export * from './TagSelector'; 15 | export * from './ViewDataSelector'; 16 | export * from './ViewSelector'; 17 | -------------------------------------------------------------------------------- /src/dashboardWebView/utils/opacityColor.ts: -------------------------------------------------------------------------------- 1 | export const opacityColor = (color: string | undefined, opacity: number) => { 2 | if (color) { 3 | if (color.startsWith('#')) { 4 | return `${color}${Math.round(opacity * 255) 5 | .toString(16) 6 | .padStart(2, '0')}`; 7 | } else if (color.startsWith('rgba')) { 8 | const splits = color.split(','); 9 | splits.pop(); 10 | return `${splits.join(', ')}, ${opacity})`; 11 | } else if (color.startsWith('rgb')) { 12 | return `${color.replace('rgb', 'rgba').replace(')', `, ${opacity})`)}`; 13 | } else { 14 | return color; 15 | } 16 | } 17 | 18 | return color; 19 | }; 20 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | }, 19 | { 20 | "type": "npm", 21 | "script": "build:ext", 22 | "group": { 23 | "kind": "build", 24 | "isDefault": true 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/ListUnorderedIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IListUnorderedIconProps { } 4 | 5 | export const ListUnorderedIcon: React.FunctionComponent = () => { 6 | return ( 7 | 14 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/FolderOpenedIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IFolderOpenedIconProps { } 4 | 5 | export const FolderOpenedIcon: React.FunctionComponent = () => { 6 | return ( 7 | 14 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/constants/Features.ts: -------------------------------------------------------------------------------- 1 | export const FEATURE_FLAG = { 2 | panel: { 3 | globalSettings: 'panel.globalSettings', 4 | seo: 'panel.seo', 5 | actions: 'panel.actions', 6 | metadata: 'panel.metadata', 7 | contentType: 'panel.contentType', 8 | gitActions: 'panel.gitActions', 9 | recentlyModified: 'panel.recentlyModified', 10 | otherActions: 'panel.otherActions' 11 | }, 12 | dashboard: { 13 | snippets: { 14 | view: 'dashboard.snippets.view', 15 | manage: 'dashboard.snippets.manage' 16 | }, 17 | data: { 18 | view: 'dashboard.data.view' 19 | }, 20 | taxonomy: { 21 | view: 'dashboard.taxonomy.view' 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Chatbot/Loader.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ILoaderProps { } 4 | 5 | export const Loader: React.FunctionComponent = (props: React.PropsWithChildren) => { 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ); 15 | }; -------------------------------------------------------------------------------- /src/utils/sentryInit.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from '@sentry/react'; 2 | import { Integrations } from '@sentry/tracing'; 3 | import { SENTRY_LINK, SentryIgnore } from '../constants'; 4 | 5 | export const SentryInit = ( 6 | version: string | null, 7 | environment: string | null 8 | ): Sentry.BrowserOptions => ({ 9 | dsn: SENTRY_LINK, 10 | integrations: [new Integrations.BrowserTracing()], 11 | tracesSampleRate: 0, // No performance tracing required 12 | release: version || '', 13 | environment: environment || '', 14 | ignoreErrors: SentryIgnore, 15 | beforeSend(event) { 16 | if (event.user) { 17 | delete event.user.ip_address; 18 | } 19 | return event; 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /src/dashboardWebView/hooks/useSelectedItems.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { useRecoilState } from 'recoil'; 3 | import { MultiSelectedItemsAtom } from '../state'; 4 | 5 | export default function useSelectedItems() { 6 | const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom); 7 | 8 | const onMultiSelect = useCallback((filePath: string) => { 9 | if (selectedFiles.includes(filePath)) { 10 | setSelectedFiles(selectedFiles.filter((file) => file !== filePath)); 11 | } else { 12 | setSelectedFiles([...selectedFiles, filePath]); 13 | } 14 | }, [selectedFiles]); 15 | 16 | return { 17 | selectedFiles, 18 | onMultiSelect 19 | }; 20 | } -------------------------------------------------------------------------------- /src/dashboardWebView/components/Chatbot/QuestionAnswer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Answer } from './Answer'; 3 | 4 | export interface IQuestionAnswerProps { 5 | question: string; 6 | answer: string; 7 | answerId: number; 8 | sources: string[]; 9 | } 10 | 11 | export const QuestionAnswer: React.FunctionComponent = ({ question, answer, answerId, sources }: React.PropsWithChildren) => { 12 | return ( 13 |
    14 |
  • {question}
  • 15 | 16 | 20 |
21 | ); 22 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/components/Header/PaginationButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IPaginationButtonProps { 4 | title: string; 5 | disabled?: boolean; 6 | onClick: () => void; 7 | } 8 | 9 | export const PaginationButton: React.FunctionComponent = ({ 10 | title, 11 | disabled, 12 | onClick 13 | }: React.PropsWithChildren) => { 14 | return ( 15 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/helpers/getTaxonomyField.ts: -------------------------------------------------------------------------------- 1 | import { ContentType } from '../models'; 2 | 3 | export const getTaxonomyField = ( 4 | taxonomyType: string, 5 | contentType: ContentType 6 | ): string | undefined => { 7 | let fieldName: string | undefined; 8 | 9 | if (taxonomyType === 'tags') { 10 | fieldName = contentType.fields.find((f) => f.name === 'tags')?.name || 'tags'; 11 | } else if (taxonomyType === 'categories') { 12 | fieldName = contentType.fields.find((f) => f.name === 'categories')?.name || 'categories'; 13 | } else { 14 | fieldName = contentType.fields.find( 15 | (f) => f.type === 'taxonomy' && f.taxonomyId === taxonomyType 16 | )?.name; 17 | } 18 | 19 | return fieldName; 20 | }; 21 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/ToggleIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IToggleIconProps { } 4 | 5 | export const ToggleIcon: React.FunctionComponent = () => { 6 | return ( 7 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/utils/getLocalizationFile.ts: -------------------------------------------------------------------------------- 1 | import { Uri, l10n } from 'vscode'; 2 | import { Extension, Logger, parseWinPath } from '../helpers'; 3 | import { readFileAsync } from './readFileAsync'; 4 | import { join } from 'path'; 5 | 6 | export const getLocalizationFile = async () => { 7 | try { 8 | const localeFilePath = 9 | l10n.uri?.fsPath || 10 | Uri.file(join(parseWinPath(Extension.getInstance().extensionPath.fsPath), `/l10n/bundle.l10n.json`)).fsPath; 11 | 12 | const fileContents = await readFileAsync(localeFilePath, 'utf-8'); 13 | return fileContents; 14 | } catch (error) { 15 | Logger.error(`Failed to get the localization file: ${(error as Error).message}`); 16 | return ''; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/icons/ArrowClockwiseIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IArrowClockwiseIconProps { 4 | className?: string; 5 | } 6 | 7 | export const ArrowClockwiseIcon: React.FunctionComponent = ({ className }: React.PropsWithChildren) => { 8 | return ( 9 | 12 | ); 13 | }; -------------------------------------------------------------------------------- /src/models/MediaContentType.ts: -------------------------------------------------------------------------------- 1 | import { Field } from '.'; 2 | 3 | export interface MediaContentType { 4 | name: string; 5 | fileTypes: string[] | null | undefined; 6 | fields: Field[]; 7 | } 8 | 9 | export const DEFAULT_MEDIA_CONTENT_TYPE: MediaContentType = { 10 | name: 'default', 11 | fileTypes: null, 12 | fields: [ 13 | { 14 | title: 'Title', 15 | name: 'title', 16 | type: 'string', 17 | required: false 18 | }, 19 | { 20 | title: 'Caption', 21 | name: 'caption', 22 | type: 'string', 23 | required: false 24 | }, 25 | { 26 | title: 'Alt text', 27 | name: 'alt', 28 | type: 'string', 29 | required: false 30 | } 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'Enhancement: ' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Contents/Tags.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Tag } from './Tag'; 3 | 4 | export interface ITagsProps { 5 | values?: string[]; 6 | tagField?: string | null | undefined; 7 | } 8 | 9 | export const Tags: React.FunctionComponent = ({ 10 | values, 11 | tagField 12 | }: React.PropsWithChildren) => { 13 | if (!values || values.length === 0) { 14 | return null; 15 | } 16 | 17 | return ( 18 |
19 | {values.map( 20 | (tag, index) => tag && ( 21 | 25 | ) 26 | )} 27 |
28 | ); 29 | }; -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AddIcon'; 2 | export * from './ArchiveIcon'; 3 | export * from './BranchIcon'; 4 | export * from './BugIcon'; 5 | export * from './CopilotIcon'; 6 | export * from './FileIcon'; 7 | export * from './FolderOpenedIcon'; 8 | export * from './FrontMatterIcon'; 9 | export * from './GitHubIcon'; 10 | export * from './HeartIcon'; 11 | export * from './ListUnorderedIcon'; 12 | export * from './MarkdownIcon'; 13 | export * from './PageIcon'; 14 | export * from './RocketIcon'; 15 | export * from './SettingsIcon'; 16 | export * from './SymbolKeywordIcon'; 17 | export * from './TagIcon'; 18 | export * from './TemplateIcon'; 19 | export * from './ToggleIcon'; 20 | export * from './WritingIcon'; 21 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/MarkdownIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IMarkdownIconProps { 4 | className?: string; 5 | } 6 | 7 | export const MarkdownIcon: React.FunctionComponent = ({ 8 | className 9 | }: React.PropsWithChildren) => { 10 | return ( 11 | 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Chatbot/Placeholder.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ChatIcon } from '../../../components/icons/ChatIcon'; 3 | 4 | export interface IPlaceholderProps { } 5 | 6 | export const Placeholder: React.FunctionComponent = ({ 7 | children, 8 | }: React.PropsWithChildren) => { 9 | return ( 10 |
11 | 12 | 13 |
14 | {children} 15 |
16 |
17 | ); 18 | }; -------------------------------------------------------------------------------- /src/models/GitRepository.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'vscode'; 2 | 3 | export type GitAPIState = 'uninitialized' | 'initialized'; 4 | 5 | export interface GitRepository { 6 | state: GitRepositoryState; 7 | rootUri: { 8 | fsPath: string; 9 | path: string; 10 | }; 11 | repository: { 12 | getBranches: () => Promise; 13 | }; 14 | } 15 | 16 | export interface GitRepositoryState { 17 | HEAD?: GitBranch; 18 | onDidChange: Event; 19 | } 20 | 21 | export interface GitBranch { 22 | type: number; 23 | name?: string; 24 | upstream: Upstream; 25 | commit: string; 26 | ahead: number; 27 | behind: number; 28 | } 29 | 30 | export interface Upstream { 31 | name: string; 32 | remote: string; 33 | commit: string; 34 | } 35 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/NestField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms'; 3 | 4 | import AutoField from './AutoField'; 5 | import { LabelField } from './LabelField'; 6 | 7 | export type NestFieldProps = HTMLFieldProps; 8 | 9 | function Nest({ children, fields, itemProps, label, ...props }: NestFieldProps) { 10 | return ( 11 |
12 | 13 | 14 | {children || fields.map((field) => )} 15 |
16 | ); 17 | } 18 | 19 | export default connectField(Nest); 20 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/ErrorsField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { HTMLProps } from 'react'; 3 | import { filterDOMProps, useForm } from 'uniforms'; 4 | import './ErrorsField.css'; 5 | 6 | export type ErrorsFieldProps = HTMLProps; 7 | 8 | export default function ErrorsField(props: ErrorsFieldProps) { 9 | const { error, schema } = useForm(); 10 | return !error && !props.children ? null : ( 11 |
12 |
13 | {props.children} 14 | 15 |
    16 | {schema.getErrorMessages(error).map((message, index) => ( 17 |
  • {message}
  • 18 | ))} 19 |
20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/SettingsIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ISettingsIconProps { } 4 | 5 | export const SettingsIcon: React.FunctionComponent = () => { 6 | return ( 7 | 14 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/icons/RenameIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IRenameIconProps { 4 | className: string; 5 | } 6 | 7 | export const RenameIcon: React.FunctionComponent = ({ 8 | className 9 | }: React.PropsWithChildren) => { 10 | return ( 11 | 12 | ); 13 | }; -------------------------------------------------------------------------------- /src/panelWebView/components/OtherActionButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IOtherActionButtonProps { 4 | className?: string; 5 | disabled?: boolean; 6 | onClick: (e: React.SyntheticEvent) => void; 7 | } 8 | 9 | const OtherActionButton: React.FunctionComponent = ({ 10 | className, 11 | disabled, 12 | onClick, 13 | children 14 | }: React.PropsWithChildren) => { 15 | return ( 16 |
17 | 20 |
21 | ); 22 | }; 23 | 24 | OtherActionButton.displayName = 'OtherActionButton'; 25 | export { OtherActionButton }; 26 | -------------------------------------------------------------------------------- /src/dashboardWebView/utils/getRelPath.ts: -------------------------------------------------------------------------------- 1 | import { parseWinPath } from '../../helpers/parseWinPath'; 2 | 3 | export const getRelPath = (path: string, staticFolder?: string, wsFolder?: string) => { 4 | let relPath: string | undefined = ''; 5 | if (wsFolder && path) { 6 | const wsFolderParsed = parseWinPath(wsFolder); 7 | const mediaParsed = parseWinPath(path); 8 | 9 | relPath = mediaParsed.split(wsFolderParsed).pop(); 10 | 11 | // If the static folder is the root, we can just return the relative path 12 | if (staticFolder === '/') { 13 | return relPath; 14 | } else if (staticFolder && relPath) { 15 | const staticFolderParsed = parseWinPath(staticFolder); 16 | relPath = relPath.split(staticFolderParsed).pop(); 17 | } 18 | } 19 | return relPath; 20 | }; 21 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/TemplateIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ITemplateIconProps { } 4 | 5 | export const TemplateIcon: React.FunctionComponent = () => { 6 | return ( 7 | 15 | 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/panelWebView/components/Preview.tsx: -------------------------------------------------------------------------------- 1 | import { Messenger } from '@estruyf/vscode/dist/client'; 2 | import * as React from 'react'; 3 | import { CommandToCode } from '../CommandToCode'; 4 | import { ActionButton } from './ActionButton'; 5 | import * as l10n from '@vscode/l10n'; 6 | import { LocalizationKey } from '../../localization'; 7 | 8 | export interface IPreviewProps { } 9 | 10 | const Preview: React.FunctionComponent = () => { 11 | const open = () => { 12 | Messenger.send(CommandToCode.openPreview); 13 | }; 14 | 15 | return ( 16 | 17 | {l10n.t(LocalizationKey.panelPreviewTitle)} 18 | 19 | ); 20 | }; 21 | 22 | Preview.displayName = 'Preview'; 23 | export { Preview }; 24 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/QuickForm.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable class-methods-use-this */ 2 | import { QuickForm } from 'uniforms'; 3 | 4 | import AutoField from './AutoField'; 5 | import BaseForm from './BaseForm'; 6 | import ErrorsField from './ErrorsField'; 7 | import SubmitField from './SubmitField'; 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | function Quick(parent: any) { 11 | class _ extends QuickForm.Quick(parent) { 12 | static Quick = Quick; 13 | 14 | getAutoField() { 15 | return AutoField; 16 | } 17 | 18 | getErrorsField() { 19 | return ErrorsField; 20 | } 21 | 22 | getSubmitField() { 23 | return SubmitField; 24 | } 25 | } 26 | 27 | return _ as unknown as QuickForm; 28 | } 29 | 30 | export default Quick(BaseForm); 31 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Media/DetailsInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { TextField } from '../Common/TextField'; 3 | 4 | export interface IDetailsInputProps { 5 | name: string; 6 | value: string; 7 | onChange: (value: string) => void; 8 | isTextArea?: boolean; 9 | } 10 | 11 | export const DetailsInput: React.FunctionComponent = ({ name, value, isTextArea, onChange }: React.PropsWithChildren) => { 12 | if (isTextArea) { 13 | return ( 14 | 20 | ); 21 | } 22 | 23 | return ( 24 | 29 | ); 30 | }; -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/HeartIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IHeartIconProps { 4 | className?: string; 5 | } 6 | 7 | export const HeartIcon: React.FunctionComponent = ({ 8 | className 9 | }: React.PropsWithChildren) => { 10 | return ( 11 | 18 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/panelWebView/components/ActionButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IActionButtonProps { 4 | title: string; 5 | className?: string; 6 | disabled?: boolean; 7 | onClick: (e: React.SyntheticEvent) => void; 8 | } 9 | 10 | const ActionButton: React.FunctionComponent = ({ 11 | className, 12 | onClick, 13 | disabled, 14 | title, 15 | children 16 | }: React.PropsWithChildren) => { 17 | return ( 18 |
19 | 22 |
23 | ); 24 | }; 25 | 26 | ActionButton.displayName = 'ActionButton'; 27 | export { ActionButton }; 28 | -------------------------------------------------------------------------------- /src/constants/context.ts: -------------------------------------------------------------------------------- 1 | export const CONTEXT = { 2 | canOpenPreview: 'frontMatter:CanOpenPreview', 3 | canOpenDashboard: 'frontMatter:CanOpenDashboard', 4 | isEnabled: 'frontMatter:enabled', 5 | isDashboardOpen: 'frontMatter:dashboard:open', 6 | wysiwyg: 'frontMatter:markdown:wysiwyg', 7 | backer: 'frontMatter:backers:supporter', 8 | isValidFile: 'frontMatter:file:isValid', 9 | isDevelopment: 'frontMatter:isDevelopment', 10 | 11 | isI18nEnabled: 'frontMatter:i18n:enabled', 12 | 13 | hasViewModes: 'frontMatter:has:modes', 14 | 15 | isSnippetsDashboardEnabled: 'frontMatter:dashboard:snippets:enabled', 16 | isDataDashboardEnabled: 'frontMatter:dashboard:data:enabled', 17 | 18 | isGitEnabled: 'frontMatter:git:enabled', 19 | 20 | projectSwitchEnabled: 'frontMatter:project:switch:enabled' 21 | }; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'Issue: ' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Device:** 28 | 29 | - OS: [e.g. iOS] 30 | - Front Matter CMS Version [e.g. 10.2.0] 31 | - Browser [e.g. chrome, safari] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /src/models/MediaPaths.ts: -------------------------------------------------------------------------------- 1 | import { ISizeCalculationResult } from 'image-size/dist/types/interface'; 2 | 3 | export interface MediaPaths { 4 | media: MediaInfo[]; 5 | total: number; 6 | folders: string[]; 7 | allContentFolders: string[]; 8 | allStaticfolders: string[]; 9 | selectedFolder: string; 10 | } 11 | 12 | export interface MediaInfo { 13 | filename: string; 14 | fsPath: string; 15 | vsPath: string | undefined; 16 | dimensions?: ISizeCalculationResult | undefined; 17 | mimeType?: string | undefined; 18 | mtime?: Date; 19 | mtimeMs?: number; 20 | ctime?: Date; 21 | size?: number; 22 | 23 | metadata: { 24 | title?: string | undefined; 25 | caption?: string | undefined; 26 | alt?: string | undefined; 27 | [fieldName: string]: string | string[] | Date | number | undefined; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/getExtensibilityScripts.ts: -------------------------------------------------------------------------------- 1 | import { Uri, Webview } from 'vscode'; 2 | import { Folders } from '../commands'; 3 | import { SETTING_EXTENSIBILITY_SCRIPTS } from '../constants'; 4 | import { Settings } from '../helpers'; 5 | 6 | export const getExtensibilityScripts = (webview: Webview) => { 7 | const extensibilityScripts = Settings.get(SETTING_EXTENSIBILITY_SCRIPTS) || []; 8 | 9 | const scriptsToLoad: string[] = []; 10 | for (const script of extensibilityScripts) { 11 | if (script.startsWith('https://')) { 12 | scriptsToLoad.push(script); 13 | } else { 14 | const absScriptPath = Folders.getAbsFilePath(script); 15 | const scriptUri = webview.asWebviewUri(Uri.file(absScriptPath)); 16 | scriptsToLoad.push(scriptUri.toString()); 17 | } 18 | } 19 | 20 | return scriptsToLoad; 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/SubmitField.tsx: -------------------------------------------------------------------------------- 1 | import { HTMLProps, Ref } from 'react'; 2 | import * as React from 'react'; 3 | import { Override, filterDOMProps, useForm } from 'uniforms'; 4 | 5 | export type SubmitFieldProps = Override< 6 | HTMLProps, 7 | { inputRef?: Ref; value?: string } 8 | >; 9 | 10 | export default function SubmitField({ 11 | disabled, 12 | inputRef, 13 | readOnly, 14 | value, 15 | ...props 16 | }: SubmitFieldProps) { 17 | const { error, state } = useForm(); 18 | 19 | return ( 20 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/panelWebView/components/CustomScript.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { CommandToCode } from '../CommandToCode'; 3 | import { ActionButton } from './ActionButton'; 4 | import { Messenger } from '@estruyf/vscode/dist/client'; 5 | 6 | export interface ICustomScriptProps { 7 | title: string; 8 | script: string; 9 | } 10 | 11 | const CustomScript: React.FunctionComponent = ({ 12 | title, 13 | script 14 | }: React.PropsWithChildren) => { 15 | const runCustomScript = () => { 16 | Messenger.send(CommandToCode.runCustomScript, { title, script }); 17 | }; 18 | 19 | return ( 20 | 21 | {title} 22 | 23 | ); 24 | }; 25 | 26 | CustomScript.displayName = 'CustomScript'; 27 | export { CustomScript }; 28 | -------------------------------------------------------------------------------- /src/panelWebView/components/VSCode/VSCodeLabel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IVSCodeLabelProps { } 4 | 5 | export const VSCodeLabel: React.FunctionComponent = ({ 6 | children 7 | }: React.PropsWithChildren) => { 8 | const DEFAULT_LINE_HEIGHT = 16; 9 | const DEFAULT_FONT_SIZE = 13; 10 | 11 | const INPUT_LINE_HEIGHT_RATIO = DEFAULT_LINE_HEIGHT / DEFAULT_FONT_SIZE; 12 | 13 | return ( 14 | 26 | ); 27 | }; -------------------------------------------------------------------------------- /src/panelWebView/components/SlugAction.tsx: -------------------------------------------------------------------------------- 1 | import { Messenger } from '@estruyf/vscode/dist/client'; 2 | import * as React from 'react'; 3 | import { CommandToCode } from '../CommandToCode'; 4 | import { ActionButton } from './ActionButton'; 5 | import * as l10n from '@vscode/l10n'; 6 | import { LocalizationKey } from '../../localization'; 7 | 8 | export interface ISlugActionProps { } 9 | 10 | const SlugAction: React.FunctionComponent< 11 | ISlugActionProps 12 | > = () => { 13 | const optimize = () => { 14 | Messenger.send(CommandToCode.updateSlug); 15 | }; 16 | 17 | return ( 18 | 19 | {l10n.t(LocalizationKey.panelSlugActionTitle)} 20 | 21 | ); 22 | }; 23 | 24 | SlugAction.displayName = 'SlugAction'; 25 | export { SlugAction }; 26 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/ErrorView/index.tsx: -------------------------------------------------------------------------------- 1 | import { ExclamationTriangleIcon } from '@heroicons/react/24/solid'; 2 | import * as React from 'react'; 3 | import * as l10n from '@vscode/l10n'; 4 | import { LocalizationKey } from '../../../localization'; 5 | 6 | export interface IErrorViewProps { } 7 | 8 | export const ErrorView: React.FunctionComponent = ( 9 | _: React.PropsWithChildren 10 | ) => { 11 | return ( 12 |
13 | 14 |

{l10n.t(LocalizationKey.commonErrorMessage)}

15 |

{l10n.t(LocalizationKey.dashboardErrorViewDescription)}

16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/panelWebView/components/Fields/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ChoiceButton'; 2 | export * from './ChoiceField'; 3 | export * from './ContentTypeRelationshipField'; 4 | export * from './CustomField'; 5 | export * from './DataFileField'; 6 | export * from './DateTimeField'; 7 | export * from './DraftField'; 8 | export * from './FieldCollection'; 9 | export * from './FieldMessage'; 10 | export * from './FieldTitle'; 11 | export * from './FileField'; 12 | export * from './ImageFallback'; 13 | export * from './ListField'; 14 | export * from './NumberField'; 15 | export * from './PreviewImage'; 16 | export * from './PreviewImageField'; 17 | export * from './RequiredAsterix'; 18 | export * from './SlugField'; 19 | export * from './TagPicker'; 20 | export * from './TextField'; 21 | export * from './Toggle'; 22 | export * from './WrapperField'; 23 | export * from './WysiwygField'; 24 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/AutoFields.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentType, createElement, Fragment } from 'react'; 2 | import { useForm } from 'uniforms'; 3 | 4 | import AutoField from './AutoField'; 5 | 6 | export type AutoFieldsProps = { 7 | autoField?: ComponentType<{ name: string }>; 8 | element?: ComponentType | string; 9 | fields?: string[]; 10 | omitFields?: string[]; 11 | }; 12 | 13 | export default function AutoFields({ 14 | autoField = AutoField, 15 | element = Fragment, 16 | fields, 17 | omitFields = [], 18 | ...props 19 | }: AutoFieldsProps) { 20 | const { schema } = useForm(); 21 | 22 | return createElement( 23 | element, 24 | props, 25 | (fields ?? schema.getSubfields()) 26 | .filter((field) => !omitFields.includes(field)) 27 | .map((field) => createElement(autoField, { key: field, name: field })) 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/LabelField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ReactNode } from 'react'; 3 | import './LabelField.css'; 4 | import * as l10n from '@vscode/l10n'; 5 | import { LocalizationKey } from '../../localization'; 6 | 7 | export interface ILabelFieldProps { 8 | id: string; 9 | label: string | ReactNode; 10 | required?: boolean; 11 | } 12 | 13 | export const LabelField: React.FunctionComponent = ({ 14 | label, 15 | id, 16 | required 17 | }: React.PropsWithChildren) => { 18 | return label ? ( 19 | 27 | ) : null; 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/getWebviewJsFiles.ts: -------------------------------------------------------------------------------- 1 | import { readFileAsync } from './readFileAsync'; 2 | import { Uri, Webview } from 'vscode'; 3 | import { Extension } from '../helpers'; 4 | 5 | export const getWebviewJsFiles = async (name: string, webview: Webview) => { 6 | const context = Extension.getInstance(); 7 | const extensionPath = context.extensionPath; 8 | const webviewFolder = Uri.joinPath(extensionPath, 'dist'); 9 | const manifestPath = Uri.joinPath(webviewFolder, `${name}.manifest.json`); 10 | const manifest = await readFileAsync(manifestPath.fsPath, 'utf8'); 11 | const manifestJson = JSON.parse(manifest); 12 | const entries = Object.entries(manifestJson).filter(([key]) => key.endsWith('.js')); 13 | const files = entries.map(([_, value]) => 14 | webview.asWebviewUri(Uri.joinPath(webviewFolder, value)).toString() 15 | ); 16 | return files; 17 | }; 18 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/PageIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IPageIconProps { 4 | className?: string; 5 | } 6 | 7 | export const PageIcon: React.FunctionComponent = ({ 8 | className 9 | }: React.PropsWithChildren) => { 10 | return ( 11 | 14 | ); 15 | }; -------------------------------------------------------------------------------- /src/components/icons/ChatIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IChatIconProps { 4 | className?: string; 5 | } 6 | 7 | export const ChatIcon: React.FunctionComponent = ({ className }: React.PropsWithChildren) => { 8 | return ( 9 | 10 | 11 | 12 | 13 | ); 14 | }; -------------------------------------------------------------------------------- /src/helpers/openFileInEditor.ts: -------------------------------------------------------------------------------- 1 | import { Uri, workspace, window } from 'vscode'; 2 | import { Logger } from './Logger'; 3 | import { Notifications } from './Notifications'; 4 | import * as l10n from '@vscode/l10n'; 5 | import { LocalizationKey } from '../localization'; 6 | import { Folders, WORKSPACE_PLACEHOLDER } from '../commands'; 7 | 8 | export const openFileInEditor = async (filePath: string) => { 9 | if (filePath) { 10 | if (filePath.startsWith(WORKSPACE_PLACEHOLDER)) { 11 | filePath = Folders.getAbsFilePath(filePath); 12 | } 13 | 14 | try { 15 | const doc = await workspace.openTextDocument(Uri.file(filePath)); 16 | await window.showTextDocument(doc, 1, false); 17 | } catch (e) { 18 | Notifications.error(l10n.t(LocalizationKey.helpersOpenFileInEditorError)); 19 | Logger.error(`${filePath}: ${(e as Error).message}`); 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Menu/QuickAction.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cn } from '../../../utils/cn'; 3 | 4 | export interface IQuickActionProps { 5 | title: string; 6 | className?: string; 7 | onClick: (e: React.MouseEvent) => void; 8 | } 9 | 10 | export const QuickAction: React.FunctionComponent = ({ 11 | title, 12 | className, 13 | onClick, 14 | children 15 | }: React.PropsWithChildren) => { 16 | return ( 17 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/SettingsView/SettingsInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { TextField as VSCodeTextField } from 'vscrui'; 3 | 4 | export interface ISettingsInputProps { 5 | label: string; 6 | name: string; 7 | value: string; 8 | placeholder?: string; 9 | onChange: (key: string, value: string) => void; 10 | fallback?: string; 11 | } 12 | 13 | export const SettingsInput: React.FunctionComponent = ({ 14 | label, 15 | name, 16 | value, 17 | placeholder, 18 | onChange, 19 | fallback 20 | }: React.PropsWithChildren) => { 21 | 22 | return ( 23 | onChange(name, value)}> 28 | {label} 29 | 30 | ); 31 | }; -------------------------------------------------------------------------------- /src/hooks/useDebounce.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useDebounce(value: T, delay: number) { 4 | // State and setters for debounced value 5 | const [debouncedValue, setDebouncedValue] = useState(value); 6 | 7 | useEffect( 8 | () => { 9 | // Update debounced value after delay 10 | const handler = setTimeout(() => { 11 | setDebouncedValue(value); 12 | }, delay); 13 | 14 | // Cancel the timeout if value changes (also on delay change or unmount) 15 | // This is how we prevent debounced value from updating if value is changed ... 16 | // .. within the delay period. Timeout gets cleared and restarted. 17 | return () => { 18 | clearTimeout(handler); 19 | }; 20 | }, 21 | [value, delay] // Only re-call effect if value or delay changes 22 | ); 23 | 24 | return debouncedValue; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/common/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Tooltip as TT } from 'react-tooltip' 3 | 4 | export interface ITooltipProps { 5 | id: string; 6 | render?: () => React.ReactNode; 7 | } 8 | 9 | export const Tooltip: React.FunctionComponent = ({ 10 | id, 11 | render 12 | }: React.PropsWithChildren) => { 13 | 14 | const tooltipClasses = `!py-[2px] !px-[8px] !rounded-[3px] !border-[var(--vscode-editorHoverWidget-border)] !border !border-solid !bg-[var(--vscode-editorHoverWidget-background)] !text-[var(--vscode-editorHoverWidget-foreground)] !font-normal !opacity-100 shadow-[0_2px_8px_var(--vscode-widget-shadow)] text-left`; 15 | 16 | return ( 17 | 25 | ); 26 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/components/Menu/MenuItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DropdownMenuItem } from '../../../components/shadcn/Dropdown'; 3 | 4 | export interface IMenuItemProps { 5 | title: JSX.Element | string; 6 | value?: any; 7 | isCurrent?: boolean; 8 | className?: string; 9 | disabled?: boolean; 10 | onClick: (value: any, e: React.MouseEvent) => void; 11 | } 12 | 13 | export const MenuItem: React.FunctionComponent = ({ 14 | title, 15 | value, 16 | isCurrent, 17 | className, 18 | disabled, 19 | onClick 20 | }: React.PropsWithChildren) => { 21 | return ( 22 | onClick(value, e)} 26 | > 27 | {title} 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/panelWebView/components/JsonField/JsonFieldControls.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useForm } from 'uniforms'; 3 | import { SubmitField } from 'uniforms-unstyled'; 4 | 5 | export interface IJsonFieldControlsProps { 6 | model: any | null; 7 | onClear: () => void; 8 | } 9 | 10 | export const JsonFieldControls: React.FunctionComponent = ({ 11 | model, 12 | onClear 13 | }: React.PropsWithChildren) => { 14 | const { formRef } = useForm(); 15 | 16 | return ( 17 |
18 | 19 | 20 | 31 |
32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/evaluateCommand.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | import { getShellPath } from '../utils'; 3 | import { Logger } from '../helpers'; 4 | 5 | /** 6 | * Evaluate the command dynamically using `which` command 7 | * @param command 8 | * @returns 9 | */ 10 | export const evaluateCommand = (command: string): Promise => { 11 | const shell = getShellPath(); 12 | let shellPath: string | undefined = undefined; 13 | if (typeof shell !== 'string' && !!shell) { 14 | shellPath = shell.path; 15 | } else { 16 | shellPath = shell || undefined; 17 | } 18 | 19 | return new Promise((resolve, reject) => { 20 | exec(`which ${command}`, { shell: shellPath }, (error, stdout) => { 21 | if (error) { 22 | Logger.error(`Error evaluating command: ${command}`); 23 | reject(error); 24 | return; 25 | } 26 | 27 | resolve(stdout.trim()); 28 | }); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /src/listeners/panel/LocalizationListener.ts: -------------------------------------------------------------------------------- 1 | import { GeneralCommands } from '../../constants'; 2 | import { PostMessageData } from '../../models'; 3 | import { BaseListener } from './BaseListener'; 4 | import { getLocalizationFile } from '../../utils/getLocalizationFile'; 5 | 6 | export class LocalizationListener extends BaseListener { 7 | /** 8 | * Process the messages 9 | * @param msg 10 | */ 11 | public static process(msg: PostMessageData) { 12 | switch (msg.command) { 13 | case GeneralCommands.toVSCode.getLocalization: 14 | this.getLocalization(msg.command, msg.requestId); 15 | break; 16 | } 17 | } 18 | 19 | public static async getLocalization(command: string, requestId?: string) { 20 | if (!command || !requestId) { 21 | return; 22 | } 23 | 24 | const fileContents = await getLocalizationFile(); 25 | this.sendRequest(command, requestId, fileContents); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/panelWebView/components/SponsorMsg.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SPONSOR_LINK } from '../../constants/Links'; 3 | import { HeartIcon } from './Icons/HeartIcon'; 4 | import * as l10n from '@vscode/l10n'; 5 | import { LocalizationKey } from '../../localization'; 6 | 7 | export interface ISponsorMsgProps { 8 | isBacker: boolean | undefined; 9 | } 10 | 11 | const SponsorMsg: React.FunctionComponent = ({ 12 | isBacker 13 | }: React.PropsWithChildren) => { 14 | if (isBacker) { 15 | return null; 16 | } 17 | 18 | return ( 19 |

20 | 21 | {l10n.t(LocalizationKey.commonSupport)} FrontMatter 22 | 23 |

24 | ); 25 | }; 26 | 27 | SponsorMsg.displayName = 'SponsorMsg'; 28 | export { SponsorMsg }; 29 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Layout/NavigationItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface INavigationItemProps { 4 | isSelected?: boolean; 5 | onClick?: () => void; 6 | } 7 | 8 | export const NavigationItem: React.FunctionComponent = ({ 9 | isSelected, 10 | onClick, 11 | children 12 | }: React.PropsWithChildren) => { 13 | return ( 14 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/TagIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ITagIconProps { } 4 | 5 | export const TagIcon: React.FunctionComponent = () => { 6 | return ( 7 | 14 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ContentType'; 2 | export * from './DefaultFeatureFlags'; 3 | export * from './DefaultFieldValues'; 4 | export * from './DefaultFields'; 5 | export * from './DefaultFileTypes'; 6 | export * from './Extension'; 7 | export * from './ExtensionState'; 8 | export * from './Features'; 9 | export * from './FrameworkDetectors'; 10 | export * from './GeneralCommands'; 11 | export * from './Git'; 12 | export * from './Links'; 13 | export * from './LocalStore'; 14 | export * from './Navigation'; 15 | export * from './NotificationType'; 16 | export * from './PreviewCommands'; 17 | export * from './SentryIgnore'; 18 | export * from './Snippet'; 19 | export * from './SsgScripts'; 20 | export * from './StaticFolderPlaceholder'; 21 | export * from './Templates'; 22 | export * from './charCode'; 23 | export * from './charMap'; 24 | export * from './context'; 25 | export * from './settings'; 26 | export * from './stopwords-en'; 27 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Header/ActionsBarItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cn } from '../../../utils/cn'; 3 | 4 | export interface IActionsBarItemProps { 5 | title?: string; 6 | className?: string; 7 | disabled?: boolean; 8 | onClick?: () => void; 9 | } 10 | 11 | export const ActionsBarItem: React.FunctionComponent = ({ 12 | children, 13 | className, 14 | disabled, 15 | onClick, 16 | title 17 | }: React.PropsWithChildren) => { 18 | return ( 19 | 28 | ); 29 | }; -------------------------------------------------------------------------------- /assets/icons/scissors-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/scissors-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/panelWebView/hooks/useStartCommand.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { FrameworkDetectors } from '../../constants/FrameworkDetectors'; 3 | import { PanelSettings } from '../../models'; 4 | 5 | export default function useStartCommand(settings?: PanelSettings) { 6 | const [startCommand, setStartCommand] = useState(null); 7 | 8 | useEffect(() => { 9 | if (settings?.commands?.start) { 10 | setStartCommand(settings?.commands?.start); 11 | return; 12 | } 13 | 14 | let command = ''; 15 | if (settings?.framework) { 16 | const framework = FrameworkDetectors.find((f) => f.framework.name === settings.framework); 17 | if (framework?.commands?.start) { 18 | command = framework.commands.start; 19 | } 20 | } 21 | 22 | setStartCommand(command); 23 | }, [settings?.framework, settings?.commands?.start]); 24 | 25 | return { 26 | startCommand 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/SnippetsView/SnippetInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { TextField } from '../Common/TextField'; 3 | 4 | export interface ISnippetInputProps { 5 | name: string; 6 | value?: string; 7 | placeholder?: string; 8 | onChange?: (value: string) => void; 9 | isTextArea?: boolean; 10 | } 11 | 12 | export const SnippetInput: React.FunctionComponent = ({ name, value, placeholder, isTextArea, onChange }: React.PropsWithChildren) => { 13 | if (isTextArea) { 14 | return ( 15 | 23 | ) 24 | } 25 | 26 | return ( 27 | 32 | ); 33 | }; -------------------------------------------------------------------------------- /src/panelWebView/components/Fields/FieldMessage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as l10n from '@vscode/l10n'; 3 | import { LocalizationKey } from '../../../localization'; 4 | 5 | export interface IFieldMessageProps { 6 | name: string; 7 | description?: string; 8 | showRequired?: boolean; 9 | } 10 | 11 | export const FieldMessage: React.FunctionComponent = ({ 12 | name, 13 | description, 14 | showRequired 15 | }: React.PropsWithChildren) => { 16 | if (!showRequired && !description) { 17 | return null; 18 | } 19 | 20 | if (showRequired) { 21 | return ( 22 |
23 | {l10n.t(LocalizationKey.panelFieldsFieldMessageRequired, name)} 24 |
25 | ); 26 | } 27 | 28 | if (description) { 29 | return
{description}
; 30 | } 31 | 32 | return null; 33 | }; 34 | -------------------------------------------------------------------------------- /src/panelWebView/components/ValidInfo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'; 3 | 4 | export interface IValidInfoProps { 5 | label?: string; 6 | isValid: boolean; 7 | className?: string; 8 | } 9 | 10 | const ValidInfo: React.FunctionComponent = ({ 11 | label, 12 | isValid, 13 | className, 14 | }: React.PropsWithChildren) => { 15 | return ( 16 |
17 | {isValid ? ( 18 | 19 | ) : ( 20 | 21 | )} 22 | {label && {label}} 23 |
24 | ); 25 | }; 26 | 27 | ValidInfo.displayName = 'ValidInfo'; 28 | export { ValidInfo }; 29 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/SettingsView/SettingsCheckbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Checkbox as VSCodeCheckbox } from 'vscrui'; 3 | 4 | export interface ISettingsCheckboxProps { 5 | label: string; 6 | name: string; 7 | value: boolean; 8 | onChange: (key: string, value: boolean) => void; 9 | } 10 | 11 | export const SettingsCheckbox: React.FunctionComponent = ({ 12 | label, 13 | name, 14 | value, 15 | onChange 16 | }: React.PropsWithChildren) => { 17 | const [isEnabled, setIsEnabled] = React.useState(false); 18 | 19 | const updateValue = (value: boolean) => { 20 | setIsEnabled(value); 21 | onChange(name, value); 22 | } 23 | 24 | React.useEffect(() => { 25 | setIsEnabled(value); 26 | }, [value]); 27 | 28 | return ( 29 | 32 | {label} 33 | 34 | ); 35 | }; -------------------------------------------------------------------------------- /src/panelWebView/components/Fields/ChoiceButton.tsx: -------------------------------------------------------------------------------- 1 | import { XMarkIcon } from '@heroicons/react/24/outline'; 2 | import * as React from 'react'; 3 | import * as l10n from '@vscode/l10n'; 4 | import { LocalizationKey } from '../../../localization'; 5 | 6 | export interface IChoiceButtonProps { 7 | title: string; 8 | value: string; 9 | className?: string; 10 | onClick: (value: string) => void; 11 | } 12 | 13 | export const ChoiceButton: React.FunctionComponent = ({ 14 | title, 15 | value, 16 | className, 17 | onClick 18 | }: React.PropsWithChildren) => { 19 | return ( 20 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Layout/NavigationBar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface INavigationBarProps { 4 | title?: string; 5 | bottom?: JSX.Element; 6 | } 7 | 8 | export const NavigationBar: React.FunctionComponent = ({ 9 | title, 10 | bottom, 11 | children 12 | }: React.PropsWithChildren) => { 13 | return ( 14 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/panelWebView/components/Icons/RocketIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IRocketIconProps { } 4 | 5 | export const RocketIcon: React.FunctionComponent = () => { 6 | return ( 7 | 14 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/constants/GeneralCommands.ts: -------------------------------------------------------------------------------- 1 | export const GeneralCommands = { 2 | toWebview: { 3 | setMode: 'setMode', 4 | git: { 5 | syncingStart: 'gitSyncingStart', 6 | syncingEnd: 'gitSyncingEnd', 7 | branchName: 'gitBranchName' 8 | }, 9 | setLocalization: 'setLocalization' 10 | }, 11 | toVSCode: { 12 | openLink: 'openLink', 13 | git: { 14 | isRepo: 'gitIsRepo', 15 | sync: 'gitSync', 16 | fetch: 'getFetch', 17 | getBranch: 'getBranch', 18 | selectBranch: 'gitSelectBranch' 19 | }, 20 | secrets: { 21 | get: 'getSecret', 22 | set: 'setSecret' 23 | }, 24 | content: { 25 | locales: 'getContentLocales' 26 | }, 27 | logging: { 28 | info: 'logInfo', 29 | warn: 'logWarn', 30 | error: 'logError', 31 | verbose: 'logVerbose' 32 | }, 33 | runCommand: 'runCommand', 34 | getLocalization: 'getLocalization', 35 | openOnWebsite: 'openOnWebsite' 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/providers/UriHandler.ts: -------------------------------------------------------------------------------- 1 | import { commands, Uri, window } from 'vscode'; 2 | import { EXTENSION_COMMAND_PREFIX } from '../constants'; 3 | 4 | export class UriHandler { 5 | /** 6 | * Register the URI handler 7 | */ 8 | public static register() { 9 | window.registerUriHandler({ 10 | handleUri(uri: Uri) { 11 | const queryParams = new URLSearchParams(uri.query); 12 | if (!queryParams.has('command')) { 13 | return; 14 | } 15 | 16 | const command = queryParams.get('command'); 17 | let args = queryParams.get('args'); 18 | 19 | if (!command || !command.startsWith(EXTENSION_COMMAND_PREFIX)) { 20 | return; 21 | } 22 | 23 | if (args) { 24 | try { 25 | args = JSON.parse(args); 26 | } catch (error) { 27 | // Ignore error 28 | } 29 | } 30 | 31 | commands.executeCommand(command, args); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Chatbot/Header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ChatIcon } from '../../../components/icons/ChatIcon'; 3 | import * as l10n from '@vscode/l10n'; 4 | import { LocalizationKey } from '../../../localization'; 5 | 6 | export interface IHeaderProps { } 7 | 8 | export const Header: React.FunctionComponent = (props: React.PropsWithChildren) => { 9 | return ( 10 |
11 |

12 | 13 | {l10n.t(LocalizationKey.dashboardChatbotHeaderHeading)} 14 |

15 |

21 | {l10n.t(LocalizationKey.dashboardChatbotHeaderDescription)} 22 |

23 |
24 | ); 25 | }; -------------------------------------------------------------------------------- /src/hooks/useDarkMode.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | export default function useDarkMode() { 4 | const setTheme = (elm: HTMLElement) => { 5 | if (elm) { 6 | const darkMode = elm.classList.contains('vscode-dark'); 7 | document.documentElement.classList.remove(`${darkMode ? 'light' : 'dark'}`); 8 | document.documentElement.classList.add(`${darkMode ? 'dark' : 'light'}`); 9 | } 10 | }; 11 | 12 | useEffect(() => { 13 | const mutationObserver = new MutationObserver((mutationsList, observer) => { 14 | const last = mutationsList 15 | .filter((item) => item.type === 'attributes' || item.attributeName === 'class') 16 | .pop(); 17 | setTheme(last?.target as HTMLElement); 18 | }); 19 | 20 | setTheme(document.body); 21 | 22 | mutationObserver.observe(document.body, { 23 | childList: false, 24 | attributes: true 25 | }); 26 | 27 | return () => { 28 | mutationObserver.disconnect(); 29 | }; 30 | }, ['']); 31 | } 32 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Contents/I18nLabel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Page } from '../../models'; 3 | import { ChevronDownIcon, LanguageIcon } from '@heroicons/react/24/outline'; 4 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; 5 | import { MenuItem } from '../Menu'; 6 | import { DashboardMessage } from '../../DashboardMessage'; 7 | import { messageHandler } from '@estruyf/vscode/dist/client'; 8 | 9 | export interface II18nLabelProps { 10 | page: Page; 11 | } 12 | 13 | export const I18nLabel: React.FunctionComponent = ({ 14 | page 15 | }: React.PropsWithChildren) => { 16 | if (!page.fmLocale) { 17 | return null; 18 | } 19 | 20 | return ( 21 |
22 | 23 | {page.fmLocale.title || page.fmLocale.locale} 24 |
25 | ); 26 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/components/Menu/ActionMenuButton.tsx: -------------------------------------------------------------------------------- 1 | import { EllipsisVerticalIcon } from '@heroicons/react/24/outline'; 2 | import * as React from 'react'; 3 | 4 | export interface IActionMenuButtonProps { 5 | title: string; 6 | disabled?: boolean; 7 | ref?: (instance: Element | null) => void; 8 | } 9 | 10 | export const ActionMenuButton: React.FunctionComponent = ({ 11 | title, 12 | disabled, 13 | ref 14 | }: React.PropsWithChildren) => { 15 | return ( 16 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | This article provides information on how to get support for Front Matter CMS. 4 | 5 | > 👉 Note: before participating in our community, please read our [code of conduct](./CODE_OF_CONDUCT.md). By interacting with this repository, organization, or community you agree to abide by its terms. 6 | 7 | ## Asking for help 8 | 9 | There are a few different ways to ask for help with Front Matter CMS: 10 | 11 | 1. **GitHub Discussions**: You can ask questions and share your experiences in the [Discussions](https://github.com/estruyf/vscode-front-matter/discussions) section of this repository. 12 | 2. **GitHub Issues**: If you encounter a bug or have a feature request, you can open an issue in the [Issues](https://github.com/estruyf/vscode-front-matter/issues) section of this repository. 13 | 3. **Discord**: You can join our [Discord](https://discord.gg/JBVtNMsJFB) server and ask your questions there. 14 | 15 | ## Contributing 16 | 17 | If you would like to contribute to Front Matter CMS, please read our [contributing guide](./CONTRIBUTING.md). 18 | -------------------------------------------------------------------------------- /src/panelWebView/components/DataBlock/DataBlockControls.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useForm } from 'uniforms'; 3 | import { SubmitField } from 'uniforms-unstyled'; 4 | import * as l10n from '@vscode/l10n'; 5 | import { LocalizationKey } from '../../../localization'; 6 | 7 | export interface IDataBlockControlsProps { 8 | model: any | null; 9 | onClear: () => void; 10 | } 11 | 12 | export const DataBlockControls: React.FunctionComponent = ({ 13 | model, 14 | onClear 15 | }: React.PropsWithChildren) => { 16 | const { formRef } = useForm(); 17 | 18 | return ( 19 |
20 | 21 | 22 | 33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /assets/icons/frontmatter-small-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /assets/icons/frontmatter-small-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /assets/icons/frontmatter-small-teal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /src/panelWebView/components/InitializeAction.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { PanelSettings } from '../../models'; 3 | import * as l10n from "@vscode/l10n" 4 | import { LocalizationKey } from '../../localization'; 5 | import { Messenger } from '@estruyf/vscode/dist/client'; 6 | import { CommandToCode } from '../CommandToCode'; 7 | 8 | export interface IInitializeActionProps { 9 | settings: PanelSettings | undefined; 10 | } 11 | 12 | export const InitializeAction: React.FunctionComponent = ({ settings }: React.PropsWithChildren) => { 13 | 14 | const initProject = () => { 15 | Messenger.send(CommandToCode.initProject); 16 | }; 17 | 18 | if (settings?.isInitialized) { 19 | return null; 20 | } 21 | 22 | return ( 23 |
24 | 30 |
31 | ); 32 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/models/Page.ts: -------------------------------------------------------------------------------- 1 | import { I18nConfig } from '../../models'; 2 | 3 | export interface Page { 4 | // Properties for caching 5 | fmCachePath: string; 6 | fmCacheModifiedTime: number; 7 | 8 | // Front matter fields 9 | fmFolder: string; 10 | fmFilePath: string; 11 | fmRelFileWsPath: string; 12 | fmRelFilePath: string; 13 | fmFileName: string; 14 | fmModified: number; 15 | fmPublished: number | null | undefined; 16 | fmDraft: 'Draft' | 'Published'; 17 | fmYear: number | null | undefined; 18 | fmPreviewImage: string; 19 | fmTags: string[]; 20 | fmCategories: string[]; 21 | fmContentType: string; 22 | fmDateFormat: string | undefined; 23 | 24 | // i18n fields 25 | fmDefaultLocale?: boolean; 26 | fmLocale?: I18nConfig; 27 | fmTranslations?: { 28 | [locale: string]: { 29 | locale: I18nConfig; 30 | path: string; 31 | } 32 | }; 33 | 34 | title: string; 35 | slug: string; 36 | date: string | Date; 37 | draft: boolean | string; 38 | description: string; 39 | 40 | preview?: string; 41 | [prop: string]: any; 42 | } 43 | -------------------------------------------------------------------------------- /src/dashboardWebView/providers/FilesProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Page } from '../models'; 3 | import { MediaInfo } from '../../models'; 4 | 5 | interface IFilesProviderProps { 6 | files: Page[] | MediaInfo[]; 7 | } 8 | 9 | const FilesContext = React.createContext(undefined); 10 | 11 | const FilesProvider: React.FunctionComponent = ({ files, children }: React.PropsWithChildren) => { 12 | return ( 13 | 18 | {children} 19 | 20 | ) 21 | }; 22 | 23 | const useFilesContext = (): IFilesProviderProps => { 24 | const loadFunc = React.useContext(FilesContext); 25 | 26 | if (loadFunc === undefined) { 27 | throw new Error('useFilesContext must be used within the FilesProvider'); 28 | } 29 | 30 | return loadFunc; 31 | }; 32 | 33 | FilesContext.displayName = 'FilesContext'; 34 | FilesProvider.displayName = 'FilesProvider'; 35 | 36 | export { FilesProvider, useFilesContext }; -------------------------------------------------------------------------------- /src/panelWebView/components/SeoFieldInfo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ValidInfo } from './ValidInfo'; 3 | import { VSCodeTableCell, VSCodeTableRow } from './VSCode/VSCodeTable'; 4 | 5 | export interface ISeoFieldInfoProps { 6 | title: string; 7 | value: string; 8 | recommendation: string; 9 | isValid?: boolean; 10 | className?: string; 11 | } 12 | 13 | const SeoFieldInfo: React.FunctionComponent = ({ 14 | title, 15 | value, 16 | recommendation, 17 | isValid, 18 | className 19 | }: React.PropsWithChildren) => { 20 | return ( 21 | 22 | {title} 23 | {isValid !== undefined ? : } {value}/{recommendation} 24 | 25 | ); 26 | }; 27 | 28 | SeoFieldInfo.displayName = 'SeoFieldInfo'; 29 | export { SeoFieldInfo }; 30 | -------------------------------------------------------------------------------- /src/panelWebView/components/Actions/OpenOnWebsiteAction.tsx: -------------------------------------------------------------------------------- 1 | import { messageHandler } from '@estruyf/vscode/dist/client'; 2 | import * as React from 'react'; 3 | import { ActionButton } from '../ActionButton'; 4 | import * as l10n from "@vscode/l10n" 5 | import { LocalizationKey } from '../../../localization'; 6 | import { GeneralCommands } from '../../../constants'; 7 | 8 | export interface IOpenOnWebsiteActionProps { 9 | baseUrl: string; 10 | slug: string; 11 | } 12 | 13 | export const OpenOnWebsiteAction: React.FunctionComponent = ({ 14 | baseUrl, 15 | slug 16 | }: React.PropsWithChildren) => { 17 | 18 | const open = () => { 19 | messageHandler.send(GeneralCommands.toVSCode.openOnWebsite, { 20 | websiteUrl: baseUrl, 21 | }); 22 | }; 23 | 24 | if (!baseUrl || !slug) { 25 | return null; 26 | } 27 | 28 | return ( 29 | 32 | {l10n.t(LocalizationKey.commonOpenOnWebsite)} 33 | 34 | ); 35 | }; -------------------------------------------------------------------------------- /src/utils/flattenObjectKeys.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | export const flattenObjectKeys = (obj: any, crntKey = '') => { 5 | let toReturn: string[] = []; 6 | const keys = Object.keys(obj); 7 | 8 | for (const key of keys) { 9 | const crntObj = obj[key]; 10 | 11 | if (typeof crntObj === 'object' && crntObj !== null && Object.keys(crntObj).length > 0) { 12 | const hasTextKeys = Object.keys(crntObj).some((subKey) => { 13 | if (typeof crntObj[subKey] === 'string') { 14 | return true; 15 | } 16 | 17 | return false; 18 | }); 19 | 20 | if (hasTextKeys) { 21 | toReturn.push(join(crntKey, key)); 22 | continue; 23 | } 24 | 25 | const flatKeyNames = flattenObjectKeys(crntObj, join(crntKey, key)); 26 | toReturn = [...toReturn, ...flatKeyNames]; 27 | } else if (typeof crntObj !== 'string' || Object.keys(crntObj).length === 0) { 28 | toReturn.push(join(crntKey, key)); 29 | } 30 | } 31 | 32 | return [...new Set(toReturn)]; 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cn'; 2 | export * from './copyFileAsync'; 3 | export * from './encodeEmoji'; 4 | export * from './evaluateCommand'; 5 | export * from './existsAsync'; 6 | export * from './fetchWithTimeout'; 7 | export * from './fieldWhenClause'; 8 | export * from './flattenObjectKeys'; 9 | export * from './formatInTimezone'; 10 | export * from './getDescriptionField'; 11 | export * from './getExtensibilityScripts'; 12 | export * from './getLocalizationFile'; 13 | export * from './getPlatform'; 14 | export * from './getShellPath'; 15 | export * from './getTitleField'; 16 | export * from './getWebviewJsFiles'; 17 | export * from './ignoreMsgCommand'; 18 | export * from './isWindows'; 19 | export * from './joinUrl'; 20 | export * from './lstatAsync'; 21 | export * from './mkdirAsync'; 22 | export * from './readFileAsync'; 23 | export * from './readdirAsync'; 24 | export * from './renameAsync'; 25 | export * from './rmdirAsync'; 26 | export * from './sentryInit'; 27 | export * from './sleep'; 28 | export * from './sortPages'; 29 | export * from './unlinkAsync'; 30 | export * from './writeFileAsync'; 31 | -------------------------------------------------------------------------------- /src/constants/ContentType.ts: -------------------------------------------------------------------------------- 1 | import { ContentType } from './../models/PanelSettings'; 2 | 3 | export const DEFAULT_CONTENT_TYPE_NAME = 'default'; 4 | 5 | export const DEFAULT_CONTENT_TYPE: ContentType = { 6 | name: DEFAULT_CONTENT_TYPE_NAME, 7 | pageBundle: false, 8 | previewPath: null, 9 | fields: [ 10 | { 11 | title: 'Title', 12 | name: 'title', 13 | type: 'string' 14 | }, 15 | { 16 | title: 'Description', 17 | name: 'description', 18 | type: 'string' 19 | }, 20 | { 21 | title: 'Publishing date', 22 | name: 'date', 23 | type: 'datetime', 24 | default: '{{now}}', 25 | isPublishDate: true 26 | }, 27 | { 28 | title: 'Content preview', 29 | name: 'preview', 30 | type: 'image' 31 | }, 32 | { 33 | title: 'Is in draft', 34 | name: 'draft', 35 | type: 'draft' 36 | }, 37 | { 38 | title: 'Tags', 39 | name: 'tags', 40 | type: 'tags' 41 | }, 42 | { 43 | title: 'Categories', 44 | name: 'categories', 45 | type: 'categories' 46 | } 47 | ] 48 | }; 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Elio Struyf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/models/DashboardData.ts: -------------------------------------------------------------------------------- 1 | import { Position } from 'vscode'; 2 | import { NavigationType } from '../dashboardWebView/models'; 3 | import { BlockFieldData } from './BlockFieldData'; 4 | import { ContentType } from '.'; 5 | 6 | export interface DashboardData { 7 | type: NavigationType; 8 | data?: ViewData; 9 | } 10 | 11 | export interface ViewData { 12 | filePath?: string; 13 | fieldName?: string; 14 | position?: Position; 15 | fileTitle?: string; 16 | contentType?: ContentType; 17 | selection?: string; 18 | range?: SnippetRange; 19 | snippetInfo?: SnippetInfo; 20 | pageBundle?: boolean; 21 | metadataInsert?: boolean; 22 | blockData?: BlockFieldData; 23 | parents?: string[]; 24 | multiple?: string[]; 25 | value?: string; 26 | 27 | // File fields 28 | type: 'file' | 'media'; 29 | fileExtensions?: string[]; 30 | } 31 | 32 | export interface SnippetRange { 33 | start: Position; 34 | end: Position; 35 | } 36 | 37 | export interface SnippetInfo { 38 | id: string; 39 | fields: SnippetInfoField[]; 40 | } 41 | 42 | export interface SnippetInfoField { 43 | name: string; 44 | value: string; 45 | } 46 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Configuration/Common/Folder.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { join } from 'path'; 3 | import * as l10n from '@vscode/l10n'; 4 | import { LocalizationKey } from '../../../../localization'; 5 | import { ContentFolder } from '../../../../models'; 6 | import { SelectItem } from '../../Steps/SelectItem'; 7 | 8 | export interface IFolderProps { 9 | wsFolder: string; 10 | folder: string; 11 | folders: ContentFolder[]; 12 | addFolder: (folder: string) => void; 13 | } 14 | 15 | export const Folder: React.FunctionComponent = ({ 16 | wsFolder, 17 | folder, 18 | folders, 19 | addFolder 20 | }: React.PropsWithChildren) => { 21 | 22 | const isAdded = React.useMemo( 23 | () => folders.find((f) => f.path.toLowerCase() === join(wsFolder, folder).toLowerCase()), 24 | [folder, folders, wsFolder] 25 | ); 26 | 27 | return ( 28 | addFolder(folder)} /> 33 | ); 34 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/components/Header/BooleanOption.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Messenger } from '@estruyf/vscode/dist/client'; 3 | import { DashboardMessage } from '../../DashboardMessage'; 4 | import { Checkbox as VSCodeCheckbox } from 'vscrui'; 5 | 6 | export interface IBooleanOptionProps { 7 | value: boolean | undefined | null; 8 | name: string; 9 | label: string; 10 | } 11 | 12 | export const BooleanOption: React.FunctionComponent = ({ 13 | value, 14 | name, 15 | label 16 | }: React.PropsWithChildren) => { 17 | const [isChecked, setIsChecked] = React.useState(false); 18 | 19 | const onChange = React.useCallback((newValue: boolean) => { 20 | setIsChecked(newValue); 21 | Messenger.send(DashboardMessage.updateSetting, { 22 | name: name, 23 | value: newValue 24 | }); 25 | }, [name]); 26 | 27 | React.useEffect(() => { 28 | setIsChecked(!!value); 29 | }, [value]); 30 | 31 | return ( 32 | 35 | {label} 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/icons/CompressIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ICompressIconProps { 4 | className?: string; 5 | } 6 | 7 | export const CompressIcon: React.FunctionComponent = ({ 8 | className 9 | }: React.PropsWithChildren) => { 10 | return ( 11 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Layout/PageLayout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useRecoilValue } from 'recoil'; 3 | import { SettingsSelector } from '../../state'; 4 | import { Header } from '../Header'; 5 | 6 | export interface IPageLayoutProps { 7 | header?: React.ReactNode; 8 | folders?: string[] | undefined; 9 | totalPages?: number | undefined; 10 | contentClass?: string; 11 | } 12 | 13 | export const PageLayout: React.FunctionComponent = ({ 14 | header, 15 | folders, 16 | totalPages, 17 | contentClass, 18 | children 19 | }: React.PropsWithChildren) => { 20 | const settings = useRecoilValue(SettingsSelector); 21 | 22 | return ( 23 |
24 |
25 | 26 |
32 | {children} 33 |
34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Icons/PinIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IPinIconProps { 4 | className?: string; 5 | } 6 | 7 | export const PinIcon: React.FunctionComponent = ({ 8 | className 9 | }: React.PropsWithChildren) => { 10 | return ( 11 | 12 | ); 13 | }; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Launch Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}", "--disable-extension=eliostruyf.vscode-front-matter" 15 | ], 16 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 17 | "preLaunchTask": "npm: build:ext" 18 | }, 19 | { 20 | "name": "Attach Extension", 21 | "type": "extensionHost", 22 | "request": "launch", 23 | "runtimeExecutable": "${execPath}", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", "--disable-extension=eliostruyf.vscode-front-matter" 26 | ], 27 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/dashboardWebView/state/atom/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AllContentFoldersAtom'; 2 | export * from './AllPagesAtom'; 3 | export * from './AllStaticFoldersAtom'; 4 | export * from './CategoryAtom'; 5 | export * from './DashboardViewAtom'; 6 | export * from './FilterValuesAtom'; 7 | export * from './FiltersAtom'; 8 | export * from './FolderAtom'; 9 | export * from './GroupingAtom'; 10 | export * from './LightboxAtom'; 11 | export * from './LoadingAtom'; 12 | export * from './LocaleAtom'; 13 | export * from './LocalesAtom'; 14 | export * from './MediaFoldersAtom'; 15 | export * from './MediaTotalAtom'; 16 | export * from './ModeAtom'; 17 | export * from './MultiSelectedItemsAtom'; 18 | export * from './PageAtom'; 19 | export * from './PagedItems'; 20 | export * from './PinnedItems'; 21 | export * from './SearchAtom'; 22 | export * from './SearchReadyAtom'; 23 | export * from './SelectedItemActionAtom'; 24 | export * from './SelectedMediaFolderAtom'; 25 | export * from './SettingsAtom'; 26 | export * from './SortingAtom'; 27 | export * from './TabAtom'; 28 | export * from './TabInfoAtom'; 29 | export * from './TagAtom'; 30 | export * from './ViewAtom'; 31 | export * from './ViewDataAtom'; 32 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/DataView/DataFormControls.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useForm } from 'uniforms'; 3 | import * as l10n from '@vscode/l10n'; 4 | import { LocalizationKey } from '../../../localization'; 5 | import { SubmitField } from '../../../components/uniforms-frontmatter'; 6 | import { Button } from 'vscrui'; 7 | 8 | export interface IDataFormControlsProps { 9 | model: any | null; 10 | onClear: () => void; 11 | } 12 | 13 | export const DataFormControls: React.FunctionComponent = ({ 14 | model, 15 | onClear 16 | }: React.PropsWithChildren) => { 17 | const { formRef } = useForm(); 18 | 19 | return ( 20 |
21 | 22 | 23 | 35 |
36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/uniforms-frontmatter/HiddenField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { HTMLProps, Ref, useEffect } from 'react'; 3 | import { Override, filterDOMProps, useField } from 'uniforms'; 4 | 5 | type ValueType = string | number | readonly string[] | undefined; 6 | export type HiddenFieldProps = Override< 7 | HTMLProps, 8 | { 9 | inputRef?: Ref; 10 | name: string; 11 | noDOM?: boolean; 12 | value?: ValueType; 13 | } 14 | >; 15 | 16 | export default function HiddenField({ value, ...rawProps }: HiddenFieldProps) { 17 | const props = useField(rawProps.name, rawProps, { initialValue: false })[0]; 18 | const defaultValue = (props.value as ValueType) ?? ''; 19 | 20 | useEffect(() => { 21 | if (value !== undefined && value !== props.value) { 22 | props.onChange(value); 23 | } 24 | }); 25 | 26 | return props.noDOM ? null : ( 27 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/icons/MergeIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface IMergeIconProps { 4 | className: string; 5 | } 6 | 7 | export const MergeIcon: React.FunctionComponent = ({ 8 | className 9 | }: React.PropsWithChildren) => { 10 | return ( 11 | 12 | 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/panelWebView/components/PublishAction.tsx: -------------------------------------------------------------------------------- 1 | import { Messenger } from '@estruyf/vscode/dist/client'; 2 | import * as React from 'react'; 3 | import { CommandToCode } from '../CommandToCode'; 4 | import { ActionButton } from './ActionButton'; 5 | import * as l10n from '@vscode/l10n'; 6 | import { LocalizationKey } from '../../localization'; 7 | 8 | export interface IPublishActionProps { 9 | draft: boolean; 10 | } 11 | 12 | const PublishAction: React.FunctionComponent = ( 13 | props: React.PropsWithChildren 14 | ) => { 15 | const { draft } = props; 16 | 17 | const publish = () => { 18 | Messenger.send(CommandToCode.publish); 19 | }; 20 | 21 | return ( 22 | 27 | {draft ? l10n.t(LocalizationKey.panelPublishActionPublish) : l10n.t(LocalizationKey.panelPublishActionUnpublish)} 28 | 29 | ); 30 | }; 31 | 32 | PublishAction.displayName = 'PublishAction'; 33 | export { PublishAction }; 34 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/SettingsView/SettingsLink.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useRecoilValue } from 'recoil'; 3 | import { SettingsSelector } from '../../state'; 4 | import { CogIcon } from '@heroicons/react/24/solid'; 5 | import { NavigationType } from '../../models'; 6 | import * as l10n from '@vscode/l10n'; 7 | import { LocalizationKey } from '../../../localization'; 8 | 9 | export interface ISettingsLinkProps { 10 | onNavigate: (navigationType: NavigationType) => void; 11 | } 12 | 13 | export const SettingsLink: React.FunctionComponent = ({ 14 | onNavigate 15 | }: React.PropsWithChildren) => { 16 | const settings = useRecoilValue(SettingsSelector); 17 | 18 | if (!settings) { 19 | return null; 20 | } 21 | 22 | return ( 23 | 31 | ); 32 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/components/Media/Lightbox.tsx: -------------------------------------------------------------------------------- 1 | import { basename } from 'path'; 2 | import * as React from 'react'; 3 | import { useRecoilState } from 'recoil'; 4 | import { LightboxAtom } from '../../state'; 5 | 6 | export interface ILightboxProps { } 7 | 8 | export const Lightbox: React.FunctionComponent = ( 9 | _: React.PropsWithChildren 10 | ) => { 11 | const [lightbox, setLightbox] = useRecoilState(LightboxAtom); 12 | 13 | if (!lightbox) { 14 | return null; 15 | } 16 | 17 | const hideLightbox = () => { 18 | setLightbox(null); 19 | }; 20 | 21 | return ( 22 |
26 |
27 | {basename(lightbox)} 32 |
33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Contents/Tag.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as l10n from '@vscode/l10n'; 3 | import { routePaths } from '../..'; 4 | import { LocalizationKey } from '../../../localization'; 5 | import { useNavigate } from 'react-router-dom'; 6 | 7 | export interface ITagProps { 8 | value?: string; 9 | tagField?: string | null | undefined; 10 | } 11 | 12 | export const Tag: React.FunctionComponent = ({ 13 | value, 14 | tagField 15 | }: React.PropsWithChildren) => { 16 | const navigate = useNavigate(); 17 | 18 | if (!value) { 19 | return null; 20 | } 21 | 22 | return ( 23 | 34 | ); 35 | }; -------------------------------------------------------------------------------- /src/dashboardWebView/components/Common/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { LoadingType } from '../../../models'; 3 | import * as l10n from '@vscode/l10n'; 4 | import { LocalizationKey } from '../../../localization'; 5 | 6 | export interface ISpinnerProps { 7 | type?: LoadingType; 8 | } 9 | 10 | export const Spinner: React.FunctionComponent = ( 11 | { type }: React.PropsWithChildren 12 | ) => { 13 | return ( 14 |
15 |
18 |
19 |
20 | 21 | { 22 | type === 'initPages' && ( 23 |
24 | {l10n.t(LocalizationKey.loadingInitPages)} 25 | 26 |
27 | ) 28 | } 29 |
30 | ); 31 | }; -------------------------------------------------------------------------------- /src/constants/ExtensionState.ts: -------------------------------------------------------------------------------- 1 | export const ExtensionState = { 2 | PagesView: `frontMatter:Pages:ViewType`, 3 | SelectedFolder: `frontMatter:SelectedFolder`, 4 | Version: `frontMatter:Version`, 5 | SettingPromoted: `frontMatter:Settings:Promoted`, 6 | MoveTemplatesFolder: `frontMatter:Templates:Move`, 7 | 8 | Project: { 9 | current: `frontMatter:Project:current` 10 | }, 11 | 12 | Dashboard: { 13 | Contents: { 14 | Sorting: `frontMatter:Dashboard:Contents:Sorting` 15 | }, 16 | Media: { 17 | Sorting: `frontMatter:Dashboard:Media:Sorting` 18 | }, 19 | Pages: { 20 | Cache: `frontMatter:Dashboard:Pages:Cache`, 21 | Index: `frontMatter:Dashboard:Pages:Index` 22 | } 23 | }, 24 | 25 | Settings: { 26 | Extends: `frontMatter:Settings:Extends` 27 | }, 28 | 29 | Updates: { 30 | v7_0_0: { 31 | dateFields: `frontMatter:Updates:v7.0.0:dateFields` 32 | } 33 | }, 34 | 35 | Secrets: { 36 | Deepl: { 37 | ApiKey: `frontMatter:Secrets:DeeplApiKey` 38 | }, 39 | Azure: { 40 | TranslatorKey: `frontMatter:Secrets:AzureTranslatorKey`, 41 | TranslatorRegion: `frontMatter:Secrets:AzureTranslatorRegion` 42 | } 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Header/Tab.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | import { NavigationType } from '../../models'; 4 | import { cn } from '../../../utils/cn'; 5 | 6 | export interface ITabProps { 7 | navigationType: NavigationType; 8 | onNavigate: (navigationType: NavigationType) => void; 9 | } 10 | 11 | export const Tab: React.FunctionComponent = ({ 12 | navigationType, 13 | onNavigate, 14 | children 15 | }: React.PropsWithChildren) => { 16 | const location = useLocation(); 17 | 18 | return ( 19 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/dashboardWebView/components/Media/MediaHeaderBottom.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useRecoilValue } from 'recoil'; 3 | import { NavigationType } from '../../models/NavigationType'; 4 | import { SettingsAtom } from '../../state'; 5 | import { Sorting } from '../Header'; 6 | import { Breadcrumb } from '../Header/Breadcrumb'; 7 | import { Pagination } from '../Header/Pagination'; 8 | 9 | export interface IMediaHeaderBottomProps { } 10 | 11 | export const MediaHeaderBottom: React.FunctionComponent = ( 12 | _: React.PropsWithChildren 13 | ) => { 14 | const settings = useRecoilValue(SettingsAtom); 15 | 16 | if (!settings?.wsFolder) { 17 | return null; 18 | } 19 | 20 | return ( 21 | 33 | ); 34 | }; 35 | --------------------------------------------------------------------------------