diff --git a/package-lock.json b/package-lock.json index f90b9e7f..77c28a44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2359,6 +2359,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "fuse.js": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.4.6.tgz", + "integrity": "sha512-/gYxR/0VpXmWSfZOIPS3rWwU8SHgsRTwWuXhyb2O6s7aRuVtHtxCkR33bNYu3wyLyNx/Wpv0vU7FZy8Vj53VNw==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", diff --git a/package.json b/package.json index b1064f50..181b97b8 100644 --- a/package.json +++ b/package.json @@ -346,10 +346,6 @@ { "command": "frontMatter.collapseSections", "when": "false" - }, - { - "command": "frontMatter.dashboard", - "when": "frontMatterCanOpenDashboard" } ], "view/title": [ @@ -392,6 +388,7 @@ "css-loader": "5.2.7", "date-fns": "2.23.0", "downshift": "6.0.6", + "fuse.js": "6.4.6", "glob": "7.1.6", "gray-matter": "4.0.2", "html-loader": "1.3.2", diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 2707ae89..e5cb1c32 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -1,7 +1,7 @@ import { SETTINGS_CONTENT_STATIC_FOLDERS, SETTING_DATE_FIELD, SETTING_PREVIEW_HOST, SETTING_PREVIEW_PATHNAME, SETTING_SEO_DESCRIPTION_FIELD } from './../constants/settings'; import { ArticleHelper } from './../helpers/ArticleHelper'; import { join } from "path"; -import { commands, env, Uri, ViewColumn, Webview, WebviewOptions, WebviewPanel, WebviewPanelOptions, window, workspace } from "vscode"; +import { ColorThemeKind, commands, env, ThemeColor, Uri, ViewColumn, Webview, WebviewOptions, WebviewPanel, WebviewPanelOptions, window, workspace } from "vscode"; import { SettingsHelper } from '../helpers'; import { PreviewSettings } from '../models'; import { format } from 'date-fns'; @@ -12,10 +12,13 @@ import { DashboardCommand } from '../pagesView/DashboardCommand'; import { DashboardMessage } from '../pagesView/DashboardMessage'; import { Page } from '../pagesView/models/Page'; import { openFileInEditor } from '../helpers/openFileInEditor'; +import { COMMAND_NAME } from '../constants/Extension'; +import { Template } from './Template'; export class Dashboard { private static webview: WebviewPanel | null = null; + private static isDisposed: boolean = true; /**  * Init the dashboard @@ -24,6 +27,16 @@ export class Dashboard { const folders = Folders.get(); await commands.executeCommand('setContext', CONTEXT.canOpenDashboard, folders && folders.length > 0); } + + public static get isOpen(): boolean { + return !Dashboard.isDisposed; + } + + public static reveal() { + if (Dashboard.webview) { + Dashboard.webview.reveal(); + } + } /** * Open the markdown preview in the editor @@ -37,9 +50,11 @@ export class Dashboard { ViewColumn.One, { enableScripts: true - } + } ); + Dashboard.isDisposed = false; + Dashboard.webview.iconPath = { dark: Uri.file(join(extensionPath, 'assets/frontmatter-dark.svg')), light: Uri.file(join(extensionPath, 'assets/frontmatter.svg')) @@ -53,18 +68,35 @@ export class Dashboard { } }); + Dashboard.webview.onDidDispose(() => { + Dashboard.isDisposed = true; + }); + Dashboard.webview.webview.onDidReceiveMessage(async (msg) => { switch(msg.command) { case DashboardMessage.getData: + Dashboard.getSettings(); Dashboard.getPages(); break; case DashboardMessage.openFile: openFileInEditor(msg.data); break; + case DashboardMessage.createContent: + await commands.executeCommand(COMMAND_NAME.createContent); + break; } }); } + private static async getSettings() { + Dashboard.postWebviewMessage({ + command: DashboardCommand.settings, + data: { + folders: Folders.get(), + initialized: await Template.isInitialized() + } + }); + } private static async getPages() { const config = SettingsHelper.getConfig(); @@ -81,36 +113,38 @@ export class Dashboard { if (folderInfo) { for (const folder of folderInfo) { for (const file of folder.lastModified) { - const article = ArticleHelper.getFrontMatterByPath(file.filePath); + if (file.fileName.endsWith(`.md`) || file.fileName.endsWith(`.mdx`)) { + const article = ArticleHelper.getFrontMatterByPath(file.filePath); - if (article?.data.title) { - const page: Page = { - fmGroup: folder.title, - fmModified: file.mtime, - fmFilePath: file.filePath, - fmFileName: file.fileName, - title: article?.data.title, - slug: article?.data.slug, - date: article?.data[dateField] || "", - draft: article?.data.draft, - description: article?.data[descriptionField] || "", - }; - - if (article?.data.preview && crntWsFolder) { - const previewPath = join(crntWsFolder.uri.fsPath, staticFolder || "", article?.data.preview); - const previewUri = Uri.file(previewPath); - const preview = Dashboard.webview?.webview.asWebviewUri(previewUri); - page.preview = preview?.toString() || ""; + if (article?.data.title) { + const page: Page = { + fmGroup: folder.title, + fmModified: file.mtime, + fmFilePath: file.filePath, + fmFileName: file.fileName, + title: article?.data.title, + slug: article?.data.slug, + date: article?.data[dateField] || "", + draft: article?.data.draft, + description: article?.data[descriptionField] || "", + }; + + if (article?.data.preview && crntWsFolder) { + const previewPath = join(crntWsFolder.uri.fsPath, staticFolder || "", article?.data.preview); + const previewUri = Uri.file(previewPath); + const preview = Dashboard.webview?.webview.asWebviewUri(previewUri); + page.preview = preview?.toString() || ""; + } + + pages.push(page); } - - pages.push(page); } } } } Dashboard.postWebviewMessage({ - command: DashboardCommand.data, + command: DashboardCommand.pages, data: pages }); } @@ -136,12 +170,12 @@ export class Dashboard { - + - Front Matter + Front Matter Dashboard - +
Daily usage diff --git a/src/extension.ts b/src/extension.ts index 7a041934..acceed69 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -108,7 +108,6 @@ export async function activate({ subscriptions, extensionUri, extensionPath }: v vscode.workspace.onDidChangeConfiguration(() => { Template.init(); Preview.init(); - Dashboard.init(); Folders.updateVsCodeCtx(); const exView = ExplorerView.getInstance(); @@ -147,8 +146,13 @@ export async function activate({ subscriptions, extensionUri, extensionPath }: v subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.preview, () => Preview.open(extensionPath) )); // Pages dashboard - Dashboard.init(); - subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboard, () => Dashboard.open(extensionPath) )); + subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboard, () => { + if (Dashboard.isOpen) { + Dashboard.reveal(); + } else { + Dashboard.open(extensionPath); + } + })); // Subscribe all commands subscriptions.push( diff --git a/src/hooks/useDarkMode.tsx b/src/hooks/useDarkMode.tsx new file mode 100644 index 00000000..5055a4f8 --- /dev/null +++ b/src/hooks/useDarkMode.tsx @@ -0,0 +1,28 @@ +import { useState, useEffect } from 'react'; + +export default function useDarkMode() { + + const setTheme = (elm: HTMLElement) => { + if (elm) { + const darkMode = elm.classList.contains('vscode-dark'); + document.documentElement.classList.remove(`${darkMode ? "light" : "dark"}`); + document.documentElement.classList.add(`${darkMode ? "dark" : "light"}`); + } + }; + + useEffect(() => { + const mutationObserver = new MutationObserver((mutationsList, observer) => { + const last = mutationsList.filter(item => item.type === "attributes" || item.attributeName === 'class').pop(); + setTheme(last?.target as HTMLElement); + }); + + setTheme(document.body); + + mutationObserver.observe(document.body, { childList: false, attributes: true }) + + return () => { + mutationObserver.disconnect(); + }; + }, ['']); + +} \ No newline at end of file diff --git a/src/viewpanel/hooks/useDebounce.tsx b/src/hooks/useDebounce.tsx similarity index 84% rename from src/viewpanel/hooks/useDebounce.tsx rename to src/hooks/useDebounce.tsx index cca5bd92..398ec0cd 100644 --- a/src/viewpanel/hooks/useDebounce.tsx +++ b/src/hooks/useDebounce.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -export function useDebounce(value: string, delay: number) { +export function useDebounce(value: T, delay: number) { // State and setters for debounced value - const [debouncedValue, setDebouncedValue] = useState(value); + const [debouncedValue, setDebouncedValue] = useState(value); useEffect( () => { diff --git a/src/pagesView/DashboardCommand.ts b/src/pagesView/DashboardCommand.ts index b6510f8a..987fc7d8 100644 --- a/src/pagesView/DashboardCommand.ts +++ b/src/pagesView/DashboardCommand.ts @@ -1,4 +1,5 @@ export enum DashboardCommand { loading = "loading", - data = "data" + pages = "pages", + settings = "settings" } \ No newline at end of file diff --git a/src/pagesView/DashboardMessage.ts b/src/pagesView/DashboardMessage.ts index 2dfc0193..21c6d266 100644 --- a/src/pagesView/DashboardMessage.ts +++ b/src/pagesView/DashboardMessage.ts @@ -1,4 +1,6 @@ export enum DashboardMessage { getData = 'getData', openFile = 'openFile', + getTheme = 'getTheme', + createContent = 'createContent' } \ No newline at end of file diff --git a/src/pagesView/components/Dashboard.tsx b/src/pagesView/components/Dashboard.tsx index bfcfd667..1168bad1 100644 --- a/src/pagesView/components/Dashboard.tsx +++ b/src/pagesView/components/Dashboard.tsx @@ -5,50 +5,87 @@ import { Overview } from './Overview'; import { Header } from './Header'; import { Tab } from '../constants/Tab'; import { SortOption } from '../constants/SortOption'; +import Fuse from 'fuse.js'; +import { Page } from '../models/Page'; +import useDarkMode from '../../hooks/useDarkMode'; export interface IDashboardProps {} -export const Dashboard: React.FunctionComponent = (props: React.PropsWithChildren) => { - const { loading, pages } = useMessages(); +// TODO: Filter by tag / category + +const fuseOptions: Fuse.IFuseOptions = { + keys: [ + "title", + "slug", + "description", + "fmFileName" + ] +}; + +export const Dashboard: React.FunctionComponent = ({}: React.PropsWithChildren) => { + const { loading, pages, settings } = useMessages(); const [ tab, setTab ] = React.useState(Tab.All); const [ sorting, setSorting ] = React.useState(SortOption.LastModified); + const [ group, setGroup ] = React.useState(null); + const [ search, setSearch ] = React.useState(null); + const [ pageItems, setPageItems ] = React.useState([]); + useDarkMode(); - let pagesToShow = pages; - if (tab === Tab.Published) { - pagesToShow = pages.filter(page => !page.draft); - } else if (tab === Tab.Draft) { - pagesToShow = pages.filter(page => !!page.draft); - } else { - pagesToShow = pages; - } + React.useEffect(() => { + // Check if search needs to be performed + let searchedPages = pages; + if (search) { + const fuse = new Fuse(pages, fuseOptions); + const results = fuse.search(search); + searchedPages = results.map(page => page.item); + } - let pagesSorted = pagesToShow; - if (sorting === SortOption.FileNameAsc) { - pagesSorted = pagesToShow.sort((a, b) => a.fmFileName.toLowerCase().localeCompare(b.fmFileName.toLowerCase())); - } else if (sorting === SortOption.FileNameDesc) { - pagesSorted = pagesToShow.sort((a, b) => b.fmFileName.toLowerCase().localeCompare(a.fmFileName.toLowerCase())); - } else { - pagesSorted = pagesToShow.sort((a, b) => b.fmModified - a.fmModified); - } + // Filter the pages + let pagesToShow = searchedPages; + if (tab === Tab.Published) { + pagesToShow = searchedPages.filter(page => !page.draft); + } else if (tab === Tab.Draft) { + pagesToShow = searchedPages.filter(page => !!page.draft); + } else { + pagesToShow = searchedPages; + } - // Show draft/published - // Filter by draft - // Filter by folder (if multiple) - // TODO: Sort by last modified + // Sort the pages + let pagesSorted = pagesToShow; + if (sorting === SortOption.FileNameAsc) { + pagesSorted = pagesToShow.sort((a, b) => a.fmFileName.toLowerCase().localeCompare(b.fmFileName.toLowerCase())); + } else if (sorting === SortOption.FileNameDesc) { + pagesSorted = pagesToShow.sort((a, b) => b.fmFileName.toLowerCase().localeCompare(a.fmFileName.toLowerCase())); + } else { + pagesSorted = pagesToShow.sort((a, b) => b.fmModified - a.fmModified); + } + + if (group) { + pagesSorted = pagesSorted.filter(page => page.fmGroup === group); + } + + setPageItems(pagesSorted); + }, [ pages, tab, sorting, group, search ]); + + const pageGroups = [...new Set(pages.map(page => page.fmGroup))]; return ( -
+
setTab(tabId)} switchSorting={(sortId: SortOption) => setSorting(sortId)} + switchGroup={(groupId: string | null) => setGroup(groupId)} + onSearch={(value: string | null) => setSearch(value)} + settings={settings} /> - - -
- { loading ? : null } + { loading ? : } +
); }; \ No newline at end of file diff --git a/src/pagesView/components/DateField.tsx b/src/pagesView/components/DateField.tsx index 431fc788..59628ca6 100644 --- a/src/pagesView/components/DateField.tsx +++ b/src/pagesView/components/DateField.tsx @@ -11,6 +11,6 @@ export const DateField: React.FunctionComponent = ({value}: Rea const dateString = format(parsedValue, 'yyyy-MM-dd'); return ( - {dateString} + {dateString} ); }; \ No newline at end of file diff --git a/src/pagesView/components/Grouping.tsx b/src/pagesView/components/Grouping.tsx new file mode 100644 index 00000000..bd41d601 --- /dev/null +++ b/src/pagesView/components/Grouping.tsx @@ -0,0 +1,65 @@ +import { Menu, Transition } from '@headlessui/react'; +import { ChevronDownIcon } from '@heroicons/react/solid'; +import * as React from 'react'; +import { Fragment } from 'react'; +import { MenuItem } from './MenuItem'; + +export interface IGroupingProps { + groups: string[]; + crntGroup: string | null; + switchGroup: (group: string | null) => void; +} + +const DEFAULT_TYPE = "All types"; + +export const Grouping: React.FunctionComponent = ({groups, crntGroup, switchGroup}: React.PropsWithChildren) => { + if (groups.length <= 1) { + return null; + } + + return ( +
+ +
+ Showing: + + {crntGroup || DEFAULT_TYPE} + +
+ + + +
+ + + {groups.map((option) => ( + + ))} +
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Header.tsx b/src/pagesView/components/Header.tsx index e9f8831d..a23fe40c 100644 --- a/src/pagesView/components/Header.tsx +++ b/src/pagesView/components/Header.tsx @@ -1,96 +1,62 @@ -import { Menu, Transition } from '@headlessui/react'; import * as React from 'react'; import { Tab } from '../constants/Tab'; -import { ChevronDownIcon } from '@heroicons/react/solid'; -import { Fragment } from 'react'; import { SortOption } from '../constants/SortOption'; +import { Navigation } from './Navigation'; +import { Sorting } from './Sorting'; +import { Grouping } from './Grouping'; +import { MessageHelper } from '../../helpers/MessageHelper'; +import { DashboardMessage } from '../DashboardMessage'; +import { Searchbox } from './Searchbox'; +import { Settings } from '../models/Settings'; export interface IHeaderProps { + settings: Settings; + + // Navigation currentTab: Tab; - currentSorting: SortOption; - + totalPages: number; switchTab: (tabId: Tab) => void; + + // Sorting + currentSorting: SortOption; switchSorting: (sortId: SortOption) => void; + + // Grouping + groups: string[]; + crntGroup: string | null; + switchGroup: (group: string | null) => void; + + // Searching + onSearch: (value: string | null) => void; } -function classNames(...classes: any[]) { - return classes.filter(Boolean).join(' ') -} +export const Header: React.FunctionComponent = ({currentTab, currentSorting, switchSorting, switchTab, totalPages, crntGroup, groups, switchGroup, onSearch, settings}: React.PropsWithChildren) => { -export const tabs = [ - { name: 'All articles', id: Tab.All}, - { name: 'Published', id: Tab.Published }, - { name: 'In draft', id: Tab.Draft } -]; - -export const sortOptions = [ - { name: "Last modified", id: SortOption.LastModified }, - { name: "By filename (asc)", id: SortOption.FileNameAsc }, - { name: "By filename (desc)", id: SortOption.FileNameDesc }, -]; - -export const Header: React.FunctionComponent = ({currentTab, currentSorting, switchSorting, switchTab}: React.PropsWithChildren) => { + const createContent = () => { + MessageHelper.sendMessage(DashboardMessage.createContent); + }; return ( -
- +
+
+ + + +
-
- -
- - Sort - -
+
+ - - -
- {sortOptions.map((option) => ( - - - - ))} -
-
-
-
+ + +
); diff --git a/src/pagesView/components/Item.tsx b/src/pagesView/components/Item.tsx index 337214a5..3aa2cd7c 100644 --- a/src/pagesView/components/Item.tsx +++ b/src/pagesView/components/Item.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { MessageHelper } from '../../helpers/MessageHelper'; +import { MarkdownIcon } from '../../viewpanel/components/Icons/MarkdownIcon'; import { DashboardMessage } from '../DashboardMessage'; import { Page } from '../models/Page'; import { DateField } from './DateField'; @@ -15,19 +16,21 @@ export const Item: React.FunctionComponent = ({ fmFilePath, date, ti return (
  • -
  • diff --git a/src/pagesView/components/MenuItem.tsx b/src/pagesView/components/MenuItem.tsx new file mode 100644 index 00000000..c7107f0d --- /dev/null +++ b/src/pagesView/components/MenuItem.tsx @@ -0,0 +1,22 @@ +import { Menu } from '@headlessui/react'; +import * as React from 'react'; + +export interface IMenuItemProps { + title: string; + value: any; + isCurrent: boolean; + onClick: (value: any) => void; +} + +export const MenuItem: React.FunctionComponent = ({title, value, isCurrent, onClick}: React.PropsWithChildren) => { + return ( + + + + ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Navigation.tsx b/src/pagesView/components/Navigation.tsx new file mode 100644 index 00000000..923d91d6 --- /dev/null +++ b/src/pagesView/components/Navigation.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { Tab } from '../constants/Tab'; + +export interface INavigationProps { + currentTab: Tab; + totalPages: number; + switchTab: (tabId: Tab) => void; +} + +export const tabs = [ + { name: 'All articles', id: Tab.All}, + { name: 'Published', id: Tab.Published }, + { name: 'In draft', id: Tab.Draft } +]; + +export const Navigation: React.FunctionComponent = ({currentTab, totalPages, switchTab}: React.PropsWithChildren) => { + + return ( + + ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Overview.tsx b/src/pagesView/components/Overview.tsx index 3dc89a43..2c451608 100644 --- a/src/pagesView/components/Overview.tsx +++ b/src/pagesView/components/Overview.tsx @@ -1,13 +1,34 @@ import * as React from 'react'; +import { MarkdownIcon } from '../../viewpanel/components/Icons/MarkdownIcon'; import { Page } from '../models/Page'; +import { Settings } from '../models/Settings'; import { Item } from './Item'; import { List } from './List'; export interface IOverviewProps { pages: Page[]; + + settings: Settings; } -export const Overview: React.FunctionComponent = ({pages}: React.PropsWithChildren) => { +export const Overview: React.FunctionComponent = ({pages, settings}: React.PropsWithChildren) => { + + if (!pages || !pages.length) { + return ( +
    +
    + + { + settings?.folders?.length > 0 ? ( +

    No Markdown to show

    + ) : ( +

    Make sure you registered a content folder in your project to let Front Matter find the contents.

    + ) + } +
    +
    + ); + } return ( diff --git a/src/pagesView/components/Searchbox.tsx b/src/pagesView/components/Searchbox.tsx new file mode 100644 index 00000000..56ff48a6 --- /dev/null +++ b/src/pagesView/components/Searchbox.tsx @@ -0,0 +1,42 @@ +import { FilterIcon, SearchIcon } from '@heroicons/react/solid'; +import * as React from 'react'; +import { useDebounce } from '../../hooks/useDebounce'; + +export interface ISearchboxProps { + onSearch: (searchText: string) => void; +} + +export const Searchbox: React.FunctionComponent = ({onSearch}: React.PropsWithChildren) => { + const [ value, setValue ] = React.useState(''); + const debounceSearch = useDebounce(value, 500); + + const handleChange = (event: React.ChangeEvent) => { + setValue(event.target.value); + }; + + React.useEffect(() => { + onSearch(debounceSearch); + }, [debounceSearch]); + + return ( +
    +
    + +
    +
    +
    + +
    +
    +
    + ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Sorting.tsx b/src/pagesView/components/Sorting.tsx new file mode 100644 index 00000000..aab7c543 --- /dev/null +++ b/src/pagesView/components/Sorting.tsx @@ -0,0 +1,63 @@ +import { Menu, Transition } from '@headlessui/react'; +import * as React from 'react'; +import { SortOption } from '../constants/SortOption'; +import { ChevronDownIcon } from '@heroicons/react/solid'; +import { Fragment } from 'react'; +import { MenuItem } from './MenuItem'; + +export interface ISortingProps { + currentSorting: SortOption; + + switchSorting: (sortId: SortOption) => void; +} + +export const sortOptions = [ + { name: "Last modified", id: SortOption.LastModified }, + { name: "By filename (asc)", id: SortOption.FileNameAsc }, + { name: "By filename (desc)", id: SortOption.FileNameDesc }, +]; + +export const Sorting: React.FunctionComponent = ({currentSorting, switchSorting}: React.PropsWithChildren) => { + + const crntSort = sortOptions.find(x => x.id === currentSorting); + + return ( +
    + +
    + Sort by: + + {crntSort?.name} + +
    + + + +
    + {sortOptions.map((option) => ( + + ))} +
    +
    +
    +
    +
    + ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Spinner.tsx b/src/pagesView/components/Spinner.tsx index 52d3f9b8..de8e7cdf 100644 --- a/src/pagesView/components/Spinner.tsx +++ b/src/pagesView/components/Spinner.tsx @@ -4,7 +4,7 @@ export interface ISpinnerProps {} export const Spinner: React.FunctionComponent = (props: React.PropsWithChildren) => { return ( -
    +
    ); diff --git a/src/pagesView/components/Status.tsx b/src/pagesView/components/Status.tsx index 5b7e15ed..fea36c35 100644 --- a/src/pagesView/components/Status.tsx +++ b/src/pagesView/components/Status.tsx @@ -6,6 +6,6 @@ export interface IStatusProps { export const Status: React.FunctionComponent = ({draft}: React.PropsWithChildren) => { return ( - {draft ? "Draft" : "Published"} + {draft ? "Draft" : "Published"} ); }; \ No newline at end of file diff --git a/src/pagesView/hooks/useMessages.tsx b/src/pagesView/hooks/useMessages.tsx index 80616b72..cd812709 100644 --- a/src/pagesView/hooks/useMessages.tsx +++ b/src/pagesView/hooks/useMessages.tsx @@ -1,23 +1,29 @@ import { useState, useEffect } from 'react'; import { MessageHelper } from '../../helpers/MessageHelper'; +import { ContentFolder } from '../../models'; import { DashboardCommand } from '../DashboardCommand'; import { DashboardMessage } from '../DashboardMessage'; import { Page } from '../models/Page'; +import { Settings } from '../models/Settings'; const vscode = MessageHelper.getVsCodeAPI(); export default function useMessages(options?: any) { const [loading, setLoading] = useState(false); const [pages, setPages] = useState([]); + const [settings, setSettings] = useState({} as any); window.addEventListener('message', event => { const message = event.data; - + switch (message.command) { case DashboardCommand.loading: setLoading(message.data); break; - case DashboardCommand.data: + case DashboardCommand.settings: + setSettings(message.data); + break; + case DashboardCommand.pages: setPages(message.data); setLoading(false); break; @@ -26,11 +32,13 @@ export default function useMessages(options?: any) { useEffect(() => { setLoading(true); + vscode.postMessage({ command: DashboardMessage.getTheme }); vscode.postMessage({ command: DashboardMessage.getData }); }, ['']); return { loading, - pages + pages, + settings }; } \ No newline at end of file diff --git a/src/pagesView/models/Settings.ts b/src/pagesView/models/Settings.ts new file mode 100644 index 00000000..3886f5fe --- /dev/null +++ b/src/pagesView/models/Settings.ts @@ -0,0 +1,6 @@ +import { ContentFolder } from './../../models/ContentFolder'; + +export interface Settings { + folders: ContentFolder[]; + initialized: boolean +} \ No newline at end of file diff --git a/src/pagesView/styles.css b/src/pagesView/styles.css index 089ca285..d88b334c 100644 --- a/src/pagesView/styles.css +++ b/src/pagesView/styles.css @@ -4,7 +4,7 @@ .loader { - border-top-color: var(--vscode-activityBar-activeBorder);; + border-top-color: #15c2cb; animation: spinner 1.5s linear infinite; } diff --git a/src/viewpanel/CommandToCode.ts b/src/viewpanel/CommandToCode.ts index 71cc12cc..80fbcb3a 100644 --- a/src/viewpanel/CommandToCode.ts +++ b/src/viewpanel/CommandToCode.ts @@ -24,4 +24,5 @@ export enum CommandToCode { updatePreviewUrl = "update-preview-url", openInEditor = "open-in-editor", updateMetadata = "update-metadata", + openDashboard = "open-dashboard", } \ No newline at end of file diff --git a/src/viewpanel/components/BaseView.tsx b/src/viewpanel/components/BaseView.tsx index 151b3363..14e9ab46 100644 --- a/src/viewpanel/components/BaseView.tsx +++ b/src/viewpanel/components/BaseView.tsx @@ -16,6 +16,10 @@ export interface IBaseViewProps { export const BaseView: React.FunctionComponent = ({settings, folderAndFiles}: React.PropsWithChildren) => { + const openDashboard = () => { + MessageHelper.sendMessage(CommandToCode.openDashboard); + }; + const initProject = () => { MessageHelper.sendMessage(CommandToCode.initProject); }; @@ -32,6 +36,7 @@ export const BaseView: React.FunctionComponent = ({settings, fol
    +
    diff --git a/src/viewpanel/components/GlobalSettings.tsx b/src/viewpanel/components/GlobalSettings.tsx index 62b8fde2..43caf942 100644 --- a/src/viewpanel/components/GlobalSettings.tsx +++ b/src/viewpanel/components/GlobalSettings.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../../helpers/MessageHelper'; -import { useDebounce } from '../hooks/useDebounce'; +import { useDebounce } from '../../hooks/useDebounce'; import { Collapsible } from './Collapsible'; import { VsCheckbox, VsLabel } from './VscodeComponents'; diff --git a/src/viewpanel/components/Icons/MarkdownIcon.tsx b/src/viewpanel/components/Icons/MarkdownIcon.tsx index b342b232..fa84c441 100644 --- a/src/viewpanel/components/Icons/MarkdownIcon.tsx +++ b/src/viewpanel/components/Icons/MarkdownIcon.tsx @@ -1,9 +1,11 @@ import * as React from 'react'; -export interface IMarkdownIconProps {} +export interface IMarkdownIconProps { + className?: string; +} -export const MarkdownIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { +export const MarkdownIcon: React.FunctionComponent = ({className}: React.PropsWithChildren) => { return ( - + ); }; \ No newline at end of file diff --git a/src/webview/ExplorerView.ts b/src/webview/ExplorerView.ts index 33c78da3..869cc8fd 100644 --- a/src/webview/ExplorerView.ts +++ b/src/webview/ExplorerView.ts @@ -169,6 +169,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable { case CommandToCode.openPreview: await commands.executeCommand(COMMAND_NAME.preview); break; + case CommandToCode.openDashboard: + await commands.executeCommand(COMMAND_NAME.dashboard); + break; case CommandToCode.updatePreviewUrl: this.updatePreviewUrl(msg.data || ""); break; @@ -516,7 +519,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { * @param msg */ private postWebviewMessage(msg: { command: Command, data?: any }) { - this.panel!.webview.postMessage(msg); + this.panel?.webview?.postMessage(msg); } /** diff --git a/tailwind.config.js b/tailwind.config.js index a9db62d0..a408540b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -3,7 +3,7 @@ const colors = require('tailwindcss/colors'); module.exports = { mode: 'jit', purge: ['./src/**/*.{js,jsx,ts,tsx}'], - darkMode: false, // or 'media' or 'class' + darkMode: 'class', // or 'media' or 'class' theme: { extend: { colors: {