diff --git a/package-lock.json b/package-lock.json index e4c4bbe9..f90b9e7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,16 @@ "lit-element": "^2.5.1" } }, + "@headlessui/react": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.0.tgz", + "integrity": "sha512-C+FmBVF6YGvqcEI5fa2dfVbEaXr2RGR6Kw1E5HXIISIZEfsrH/yuCgsjWw5nlRF9vbCxmQ/EKs64GAdKeb8gCw==" + }, + "@heroicons/react": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-1.0.4.tgz", + "integrity": "sha512-3kOrTmo8+Z8o6AL0rzN82MOf8J5CuxhRLFhpI8mrn+3OqekA6d5eb1GYO3EYYo1Vn6mYQSMNTzCWbEwUInb0cQ==" + }, "@iarna/toml": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.3.tgz", @@ -1487,9 +1497,9 @@ "dev": true }, "date-fns": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.0.1.tgz", - "integrity": "sha512-C14oTzTZy8DH1Eq8N78owrCWvf3+cnJw88BTK/N3DYWVxDJuJzPaNdplzYxDYuuXXGvqBcO4Vy5SOrwAooXSWw==", + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.23.0.tgz", + "integrity": "sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==", "dev": true }, "debug": { diff --git a/package.json b/package.json index 79e80a0e..b1064f50 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "onCommand:frontMatter.init", "onCommand:frontMatter.collapseSections", "onCommand:frontMatter.preview", + "onCommand:frontMatter.dashboard", "onView:frontMatter.explorer" ], "main": "./dist/extension", @@ -101,6 +102,11 @@ "default": [], "markdownDescription": "This array of folders defines where the extension can easily create new content by running the create article command." }, + "frontMatter.content.publicFolder": { + "type": "string", + "default": "", + "markdownDescription": "Specify the folder name where all your assets are located. For instance in Hugo this is the `static` folder." + }, "frontMatter.custom.scripts": { "type": "array", "default": [], @@ -299,6 +305,11 @@ "command": "frontMatter.preview", "title": "Preview article", "category": "Front matter" + }, + { + "command": "frontMatter.dashboard", + "title": "Open pages dashboard", + "category": "Front matter" } ], "menus": { @@ -335,6 +346,10 @@ { "command": "frontMatter.collapseSections", "when": "false" + }, + { + "command": "frontMatter.dashboard", + "when": "frontMatterCanOpenDashboard" } ], "view/title": [ @@ -375,7 +390,7 @@ "@vscode/codicons": "0.0.20", "autoprefixer": "^10.3.2", "css-loader": "5.2.7", - "date-fns": "2.0.1", + "date-fns": "2.23.0", "downshift": "6.0.6", "glob": "7.1.6", "gray-matter": "4.0.2", @@ -396,6 +411,8 @@ "webpack-cli": "3.3.12" }, "dependencies": { + "@headlessui/react": "1.4.0", + "@heroicons/react": "1.0.4", "lodash.uniqby": "4.7.0" } } diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts new file mode 100644 index 00000000..2707ae89 --- /dev/null +++ b/src/commands/Dashboard.ts @@ -0,0 +1,154 @@ +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 { SettingsHelper } from '../helpers'; +import { PreviewSettings } from '../models'; +import { format } from 'date-fns'; +import { CONTEXT } from '../constants/context'; +import { Folders } from './Folders'; +import { getNonce } from '../helpers/getNonce'; +import { DashboardCommand } from '../pagesView/DashboardCommand'; +import { DashboardMessage } from '../pagesView/DashboardMessage'; +import { Page } from '../pagesView/models/Page'; +import { openFileInEditor } from '../helpers/openFileInEditor'; + + +export class Dashboard { + private static webview: WebviewPanel | null = null; + + /**  + * Init the dashboard + */ + public static async init() { + const folders = Folders.get(); + await commands.executeCommand('setContext', CONTEXT.canOpenDashboard, folders && folders.length > 0); + } + + /** + * Open the markdown preview in the editor + */ + public static async open(extensionPath: string) { + + // Create the preview webview + Dashboard.webview = window.createWebviewPanel( + 'frontMatterDashboard', + 'FrontMatter Dashboard', + ViewColumn.One, + { + enableScripts: true + } + ); + + Dashboard.webview.iconPath = { + dark: Uri.file(join(extensionPath, 'assets/frontmatter-dark.svg')), + light: Uri.file(join(extensionPath, 'assets/frontmatter.svg')) + }; + + Dashboard.webview.webview.html = Dashboard.getWebviewContent(Dashboard.webview.webview, Uri.parse(extensionPath)); + + Dashboard.webview.onDidChangeViewState(() => { + if (this.webview?.visible) { + console.log(`Dashboard opened`); + } + }); + + Dashboard.webview.webview.onDidReceiveMessage(async (msg) => { + switch(msg.command) { + case DashboardMessage.getData: + Dashboard.getPages(); + break; + case DashboardMessage.openFile: + openFileInEditor(msg.data); + break; + } + }); + } + + + private static async getPages() { + const config = SettingsHelper.getConfig(); + const wsFolders = workspace.workspaceFolders; + const crntWsFolder = wsFolders && wsFolders.length > 0 ? wsFolders[0] : null; + + const descriptionField = config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description"; + const dateField = config.get(SETTING_DATE_FIELD) as string || "date"; + const staticFolder = config.get(SETTINGS_CONTENT_STATIC_FOLDERS); + + const folderInfo = await Folders.getInfo(); + const pages: Page[] = []; + + if (folderInfo) { + for (const folder of folderInfo) { + for (const file of folder.lastModified) { + 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() || ""; + } + + pages.push(page); + } + } + } + } + + Dashboard.postWebviewMessage({ + command: DashboardCommand.data, + data: pages + }); + } + + /** + * Post data to the dashboard + * @param msg + */ + private static postWebviewMessage(msg: { command: DashboardCommand, data?: any }) { + Dashboard.webview?.webview.postMessage(msg); + } + + /** + * Retrieve the webview HTML contents + * @param webView + */ + private static getWebviewContent(webView: Webview, extensionPath: Uri): string { + const scriptUri = webView.asWebviewUri(Uri.joinPath(extensionPath, 'dist', 'pages.js')); + + const nonce = getNonce(); + + return ` + + + + + + + Front Matter + + +
+ + Daily usage + + + + + `; + } +} \ No newline at end of file diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index a038e018..69758fe2 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -122,7 +122,7 @@ export class Folders { /** * Get the registered folders information */ - public static async getInfo(): Promise { + public static async getInfo(limit?: number): Promise { const folders = Folders.get(); if (folders && folders.length > 0) { let folderInfo: FolderInfo[] = []; @@ -149,7 +149,11 @@ export class Folders { } } - fileStats = fileStats.sort((a, b) => b.mtime - a.mtime).slice(0, 10); + fileStats = fileStats.sort((a, b) => b.mtime - a.mtime); + + if (limit) { + fileStats = fileStats.slice(0, limit); + } folderInfo.push({ title: folder.title, @@ -172,7 +176,7 @@ export class Folders { * Get the folder settings * @returns */ - private static get() { + public static get() { const config = SettingsHelper.getConfig(); const folders: ContentFolder[] = config.get(SETTINGS_CONTENT_FOLDERS) as ContentFolder[]; return folders; diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index d54b3868..1b2acc8d 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -23,4 +23,5 @@ export const COMMAND_NAME = { createTemplate: getCommandName("createTemplate"), collapseSections: getCommandName("collapseSections"), preview: getCommandName("preview"), + dashboard: getCommandName("dashboard"), }; \ No newline at end of file diff --git a/src/constants/context.ts b/src/constants/context.ts index 2b09feb7..dd3eeefe 100644 --- a/src/constants/context.ts +++ b/src/constants/context.ts @@ -1,7 +1,6 @@ - - export const CONTEXT = { canInit: "frontMatterCanInit", canOpenPreview: "frontMatterCanOpenPreview", + canOpenDashboard: "frontMatterCanOpenDashboard", registeredFolders: 'frontMatter.registeredFolders' }; \ No newline at end of file diff --git a/src/constants/settings.ts b/src/constants/settings.ts index d5c58f3d..31934dde 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -34,4 +34,5 @@ export const SETTING_CUSTOM_SCRIPTS = "custom.scripts"; export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate"; export const SETTINGS_CONTENT_FOLDERS = "content.folders"; +export const SETTINGS_CONTENT_STATIC_FOLDERS = "content.publicFolder"; export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight"; \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 13d70417..7a041934 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,3 +1,4 @@ +import { Dashboard } from './commands/Dashboard'; import * as vscode from 'vscode'; import { Article, Settings, StatusListener } from './commands'; import { Folders } from './commands/Folders'; @@ -107,6 +108,7 @@ export async function activate({ subscriptions, extensionUri, extensionPath }: v vscode.workspace.onDidChangeConfiguration(() => { Template.init(); Preview.init(); + Dashboard.init(); Folders.updateVsCodeCtx(); const exView = ExplorerView.getInstance(); @@ -144,6 +146,10 @@ export async function activate({ subscriptions, extensionUri, extensionPath }: v Preview.init(); 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) )); + // Subscribe all commands subscriptions.push( insertTags, diff --git a/src/viewpanel/helper/MessageHelper.ts b/src/helpers/MessageHelper.ts similarity index 70% rename from src/viewpanel/helper/MessageHelper.ts rename to src/helpers/MessageHelper.ts index 78312f84..d95e8936 100644 --- a/src/viewpanel/helper/MessageHelper.ts +++ b/src/helpers/MessageHelper.ts @@ -1,4 +1,6 @@ -import { CommandToCode } from "../CommandToCode"; +import { DashboardMessage } from './../pagesView/DashboardMessage'; +import { CommandToCode } from "../viewpanel/CommandToCode"; + interface ClientVsCode { getState: () => T; @@ -16,7 +18,7 @@ export class MessageHelper { return MessageHelper.vscode; } - public static sendMessage = (command: CommandToCode, data?: any) => { + public static sendMessage = (command: CommandToCode | DashboardMessage, data?: any) => { if (data) { MessageHelper.vscode.postMessage({ command, data }); } else { diff --git a/src/helpers/getNonce.ts b/src/helpers/getNonce.ts new file mode 100644 index 00000000..5fceb93c --- /dev/null +++ b/src/helpers/getNonce.ts @@ -0,0 +1,10 @@ + + +export const getNonce = () => { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +}; \ No newline at end of file diff --git a/src/helpers/openFileInEditor.ts b/src/helpers/openFileInEditor.ts new file mode 100644 index 00000000..c0cc79e8 --- /dev/null +++ b/src/helpers/openFileInEditor.ts @@ -0,0 +1,13 @@ +import { Uri, workspace, window } from "vscode"; +import { Notifications } from "./Notifications"; + +export const openFileInEditor = async (filePath: string) => { + if (filePath) { + try { + const doc = await workspace.openTextDocument(Uri.file(filePath)); + await window.showTextDocument(doc, 1, false); + } catch (e) { + Notifications.error(`Couldn't open the file.`); + } + } +}; \ No newline at end of file diff --git a/src/pagesView/DashboardCommand.ts b/src/pagesView/DashboardCommand.ts new file mode 100644 index 00000000..b6510f8a --- /dev/null +++ b/src/pagesView/DashboardCommand.ts @@ -0,0 +1,4 @@ +export enum DashboardCommand { + loading = "loading", + data = "data" +} \ No newline at end of file diff --git a/src/pagesView/DashboardMessage.ts b/src/pagesView/DashboardMessage.ts new file mode 100644 index 00000000..2dfc0193 --- /dev/null +++ b/src/pagesView/DashboardMessage.ts @@ -0,0 +1,4 @@ +export enum DashboardMessage { + getData = 'getData', + openFile = 'openFile', +} \ No newline at end of file diff --git a/src/pagesView/components/Dashboard.tsx b/src/pagesView/components/Dashboard.tsx new file mode 100644 index 00000000..bfcfd667 --- /dev/null +++ b/src/pagesView/components/Dashboard.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { Spinner } from './Spinner'; +import useMessages from '../hooks/useMessages'; +import { Overview } from './Overview'; +import { Header } from './Header'; +import { Tab } from '../constants/Tab'; +import { SortOption } from '../constants/SortOption'; + +export interface IDashboardProps {} + +export const Dashboard: React.FunctionComponent = (props: React.PropsWithChildren) => { + const { loading, pages } = useMessages(); + const [ tab, setTab ] = React.useState(Tab.All); + const [ sorting, setSorting ] = React.useState(SortOption.LastModified); + + 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; + } + + 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); + } + + // Show draft/published + // Filter by draft + // Filter by folder (if multiple) + // TODO: Sort by last modified + + return ( +
+
+
setTab(tabId)} + switchSorting={(sortId: SortOption) => setSorting(sortId)} + /> + + +
+ + { loading ? : null } +
+ ); +}; \ No newline at end of file diff --git a/src/pagesView/components/DateField.tsx b/src/pagesView/components/DateField.tsx new file mode 100644 index 00000000..431fc788 --- /dev/null +++ b/src/pagesView/components/DateField.tsx @@ -0,0 +1,16 @@ +import { format, parseJSON } from 'date-fns'; +import * as React from 'react'; + +export interface IDateFieldProps { + value: Date | string; +} + +export const DateField: React.FunctionComponent = ({value}: React.PropsWithChildren) => { + + const parsedValue = typeof value === 'string' ? parseJSON(value) : value; + const dateString = format(parsedValue, 'yyyy-MM-dd'); + + return ( + {dateString} + ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Header.tsx b/src/pagesView/components/Header.tsx new file mode 100644 index 00000000..e9f8831d --- /dev/null +++ b/src/pagesView/components/Header.tsx @@ -0,0 +1,97 @@ +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'; + +export interface IHeaderProps { + currentTab: Tab; + currentSorting: SortOption; + + switchTab: (tabId: Tab) => void; + switchSorting: (sortId: SortOption) => void; +} + +function classNames(...classes: any[]) { + return classes.filter(Boolean).join(' ') +} + +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) => { + + return ( +
+ + +
+ +
+ + Sort + +
+ + + +
+ {sortOptions.map((option) => ( + + + + ))} +
+
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Item.tsx b/src/pagesView/components/Item.tsx new file mode 100644 index 00000000..337214a5 --- /dev/null +++ b/src/pagesView/components/Item.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import { MessageHelper } from '../../helpers/MessageHelper'; +import { DashboardMessage } from '../DashboardMessage'; +import { Page } from '../models/Page'; +import { DateField } from './DateField'; +import { Status } from './Status'; + +export interface IItemProps extends Page {} + +export const Item: React.FunctionComponent = ({ fmFilePath, date, title, draft, description, preview }: React.PropsWithChildren) => { + + const openFile = () => { + MessageHelper.sendMessage(DashboardMessage.openFile, fmFilePath); + }; + + return ( +
  • + +
  • + ); +}; diff --git a/src/pagesView/components/List.tsx b/src/pagesView/components/List.tsx new file mode 100644 index 00000000..abf0746b --- /dev/null +++ b/src/pagesView/components/List.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; + +export interface IListProps {} + +export const List: React.FunctionComponent = ({children}: React.PropsWithChildren) => { + return ( +
      + {children} +
    + ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Overview.tsx b/src/pagesView/components/Overview.tsx new file mode 100644 index 00000000..3dc89a43 --- /dev/null +++ b/src/pagesView/components/Overview.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { Page } from '../models/Page'; +import { Item } from './Item'; +import { List } from './List'; + +export interface IOverviewProps { + pages: Page[]; +} + +export const Overview: React.FunctionComponent = ({pages}: React.PropsWithChildren) => { + + return ( + + {pages.map(page => ( + + ))} + + ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Spinner.tsx b/src/pagesView/components/Spinner.tsx new file mode 100644 index 00000000..52d3f9b8 --- /dev/null +++ b/src/pagesView/components/Spinner.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; + +export interface ISpinnerProps {} + +export const Spinner: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( +
    +
    +
    + ); +}; \ No newline at end of file diff --git a/src/pagesView/components/Status.tsx b/src/pagesView/components/Status.tsx new file mode 100644 index 00000000..5b7e15ed --- /dev/null +++ b/src/pagesView/components/Status.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; + +export interface IStatusProps { + draft: boolean; +} + +export const Status: React.FunctionComponent = ({draft}: React.PropsWithChildren) => { + return ( + {draft ? "Draft" : "Published"} + ); +}; \ No newline at end of file diff --git a/src/pagesView/constants/SortOption.ts b/src/pagesView/constants/SortOption.ts new file mode 100644 index 00000000..dff49d61 --- /dev/null +++ b/src/pagesView/constants/SortOption.ts @@ -0,0 +1,5 @@ +export enum SortOption { + LastModified = 1, + FileNameAsc, + FileNameDesc +} \ No newline at end of file diff --git a/src/pagesView/constants/Tab.ts b/src/pagesView/constants/Tab.ts new file mode 100644 index 00000000..e1c1c51d --- /dev/null +++ b/src/pagesView/constants/Tab.ts @@ -0,0 +1,5 @@ +export enum Tab { + All = 'all', + Published = 'published', + Draft = 'draft', +}; \ No newline at end of file diff --git a/src/pagesView/hooks/useMessages.tsx b/src/pagesView/hooks/useMessages.tsx new file mode 100644 index 00000000..80616b72 --- /dev/null +++ b/src/pagesView/hooks/useMessages.tsx @@ -0,0 +1,36 @@ +import { useState, useEffect } from 'react'; +import { MessageHelper } from '../../helpers/MessageHelper'; +import { DashboardCommand } from '../DashboardCommand'; +import { DashboardMessage } from '../DashboardMessage'; +import { Page } from '../models/Page'; + +const vscode = MessageHelper.getVsCodeAPI(); + +export default function useMessages(options?: any) { + const [loading, setLoading] = useState(false); + const [pages, setPages] = useState([]); + + window.addEventListener('message', event => { + const message = event.data; + + switch (message.command) { + case DashboardCommand.loading: + setLoading(message.data); + break; + case DashboardCommand.data: + setPages(message.data); + setLoading(false); + break; + } + }); + + useEffect(() => { + setLoading(true); + vscode.postMessage({ command: DashboardMessage.getData }); + }, ['']); + + return { + loading, + pages + }; +} \ No newline at end of file diff --git a/src/pagesView/index.tsx b/src/pagesView/index.tsx new file mode 100644 index 00000000..ba00dddf --- /dev/null +++ b/src/pagesView/index.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; +import { render } from "react-dom"; +import { Dashboard } from "./components/Dashboard"; + +import './styles.css'; + +declare const acquireVsCodeApi: () => { + getState: () => T; + setState: (data: T) => void; + postMessage: (msg: unknown) => void; +}; + +const elm = document.querySelector("#app"); +render(, elm); \ No newline at end of file diff --git a/src/pagesView/models/Page.ts b/src/pagesView/models/Page.ts new file mode 100644 index 00000000..7c062809 --- /dev/null +++ b/src/pagesView/models/Page.ts @@ -0,0 +1,17 @@ +import { Uri } from "vscode"; + +export interface Page { + fmGroup: string; + fmFilePath: string; + fmFileName: string; + fmModified: number; + + title: string; + slug: string; + date: string | Date; + draft: string; + description: string; + + preview?: string; + [prop: string]: any; +} \ No newline at end of file diff --git a/src/pagesView/styles.css b/src/pagesView/styles.css new file mode 100644 index 00000000..089ca285 --- /dev/null +++ b/src/pagesView/styles.css @@ -0,0 +1,19 @@ +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + + +.loader { + border-top-color: var(--vscode-activityBar-activeBorder);; + animation: spinner 1.5s linear infinite; +} + +@-webkit-keyframes spinner { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } +} + +@keyframes spinner { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} \ No newline at end of file diff --git a/src/viewpanel/components/BaseView.tsx b/src/viewpanel/components/BaseView.tsx index 4d4dcace..151b3363 100644 --- a/src/viewpanel/components/BaseView.tsx +++ b/src/viewpanel/components/BaseView.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { FolderInfo, PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { Collapsible } from './Collapsible'; import { GlobalSettings } from './GlobalSettings'; import { OtherActions } from './OtherActions'; diff --git a/src/viewpanel/components/CustomScript.tsx b/src/viewpanel/components/CustomScript.tsx index e8b16514..76991086 100644 --- a/src/viewpanel/components/CustomScript.tsx +++ b/src/viewpanel/components/CustomScript.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { ActionButton } from './ActionButton'; export interface ICustomScriptProps { diff --git a/src/viewpanel/components/DateAction.tsx b/src/viewpanel/components/DateAction.tsx index dd6033af..adecf71b 100644 --- a/src/viewpanel/components/DateAction.tsx +++ b/src/viewpanel/components/DateAction.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { ActionButton } from './ActionButton'; export interface IDateActionProps {} diff --git a/src/viewpanel/components/FileList.tsx b/src/viewpanel/components/FileList.tsx index 10689329..716daeb7 100644 --- a/src/viewpanel/components/FileList.tsx +++ b/src/viewpanel/components/FileList.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { FileInfo } from '../../models'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { FileIcon } from './Icons/FileIcon'; import { MarkdownIcon } from './Icons/MarkdownIcon'; import { VsLabel } from './VscodeComponents'; diff --git a/src/viewpanel/components/GlobalSettings.tsx b/src/viewpanel/components/GlobalSettings.tsx index 38b26220..62b8fde2 100644 --- a/src/viewpanel/components/GlobalSettings.tsx +++ b/src/viewpanel/components/GlobalSettings.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { useDebounce } from '../hooks/useDebounce'; import { Collapsible } from './Collapsible'; import { VsCheckbox, VsLabel } from './VscodeComponents'; diff --git a/src/viewpanel/components/Metadata.tsx b/src/viewpanel/components/Metadata.tsx index 23225f61..75e04952 100644 --- a/src/viewpanel/components/Metadata.tsx +++ b/src/viewpanel/components/Metadata.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { TagType } from '../TagType'; import { Collapsible } from './Collapsible'; import { Toggle } from './Fields/Toggle'; diff --git a/src/viewpanel/components/OtherActions.tsx b/src/viewpanel/components/OtherActions.tsx index 0d123263..fa3dd60b 100644 --- a/src/viewpanel/components/OtherActions.tsx +++ b/src/viewpanel/components/OtherActions.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { Collapsible } from './Collapsible'; import { BugIcon } from './Icons/BugIcon'; import { CenterIcon } from './Icons/CenterIcon'; diff --git a/src/viewpanel/components/Preview.tsx b/src/viewpanel/components/Preview.tsx index c8dd7cb9..5f0d063c 100644 --- a/src/viewpanel/components/Preview.tsx +++ b/src/viewpanel/components/Preview.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; import { ActionButton } from './ActionButton'; export interface IPreviewProps { diff --git a/src/viewpanel/components/PublishAction.tsx b/src/viewpanel/components/PublishAction.tsx index 746b3a19..55d98941 100644 --- a/src/viewpanel/components/PublishAction.tsx +++ b/src/viewpanel/components/PublishAction.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; import { ActionButton } from './ActionButton'; export interface IPublishActionProps { diff --git a/src/viewpanel/components/SlugAction.tsx b/src/viewpanel/components/SlugAction.tsx index 83d3b9e1..85f57809 100644 --- a/src/viewpanel/components/SlugAction.tsx +++ b/src/viewpanel/components/SlugAction.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { SlugHelper } from '../../helpers/SlugHelper'; import { Slug } from '../../models/PanelSettings'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; import { ActionButton } from './ActionButton'; export interface ISlugActionProps { diff --git a/src/viewpanel/components/TagPicker.tsx b/src/viewpanel/components/TagPicker.tsx index 4de2e939..0d19e065 100644 --- a/src/viewpanel/components/TagPicker.tsx +++ b/src/viewpanel/components/TagPicker.tsx @@ -3,10 +3,10 @@ import { Tags } from './Tags'; import { usePrevious } from '../hooks/usePrevious'; import { CommandToCode } from '../CommandToCode'; import { TagType } from '../TagType'; -import { MessageHelper } from '../helper/MessageHelper'; import Downshift from 'downshift'; import { AddIcon } from './Icons/AddIcon'; import { VsLabel } from './VscodeComponents'; +import { MessageHelper } from '../../helpers/MessageHelper'; export interface ITagPickerProps { type: string; diff --git a/src/viewpanel/hooks/useMessages.tsx b/src/viewpanel/hooks/useMessages.tsx index 3a9f62c8..3490a824 100644 --- a/src/viewpanel/hooks/useMessages.tsx +++ b/src/viewpanel/hooks/useMessages.tsx @@ -1,8 +1,8 @@ import { useState, useEffect } from 'react'; +import { MessageHelper } from '../../helpers/MessageHelper'; import { FolderInfo, PanelSettings } from '../../models/PanelSettings'; import { Command } from '../Command'; import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../helper/MessageHelper'; import { TagType } from '../TagType'; const vscode = MessageHelper.getVsCodeAPI(); diff --git a/src/viewpanel/index.tsx b/src/viewpanel/index.tsx index 3e1da1c5..2a6983ca 100644 --- a/src/viewpanel/index.tsx +++ b/src/viewpanel/index.tsx @@ -2,8 +2,6 @@ import * as React from "react"; import { render } from "react-dom"; import { ViewPanel } from "./ViewPanel"; -import './styles.css'; - // require('@vscode/codicons/dist/codicon.css'); import '@bendera/vscode-webview-elements/dist/vscode-table'; import '@bendera/vscode-webview-elements/dist/vscode-table-header'; diff --git a/src/viewpanel/styles.css b/src/viewpanel/styles.css deleted file mode 100644 index de15c91d..00000000 --- a/src/viewpanel/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; \ No newline at end of file diff --git a/src/webview/ExplorerView.ts b/src/webview/ExplorerView.ts index 7864207c..33c78da3 100644 --- a/src/webview/ExplorerView.ts +++ b/src/webview/ExplorerView.ts @@ -18,7 +18,10 @@ import { Notifications } from '../helpers/Notifications'; import { COMMAND_NAME } from '../constants/Extension'; import { Folders } from '../commands/Folders'; import { Preview } from '../commands/Preview'; +import { getNonce } from '../helpers/getNonce'; +import { openFileInEditor } from '../helpers/openFileInEditor'; +const FILE_LIMIT = 10; export class ExplorerView implements WebviewViewProvider, Disposable { public static readonly viewType = "frontMatter.explorer"; @@ -170,7 +173,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { this.updatePreviewUrl(msg.data || ""); break; case CommandToCode.openInEditor: - this.openFileInEditor(msg.data); + openFileInEditor(msg.data); break; case CommandToCode.updateMetadata: this.updateMetadata(msg.data); @@ -253,20 +256,6 @@ export class ExplorerView implements WebviewViewProvider, Disposable { ArticleHelper.update(editor, article); } - /** - * Open the file via its path - */ - private async openFileInEditor(filePath: string) { - if (filePath) { - try { - const doc = await workspace.openTextDocument(Uri.file(filePath)); - await window.showTextDocument(doc, 1, false); - } catch (e) { - Notifications.error(`Couldn't open the file.`); - } - } - } - /** * Run a custom script * @param msg @@ -348,7 +337,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { public async getFoldersAndFiles() { this.postWebviewMessage({ command: Command.folderInfo, - data: await Folders.getInfo() || null + data: await Folders.getInfo(FILE_LIMIT) || null }); } @@ -530,18 +519,6 @@ export class ExplorerView implements WebviewViewProvider, Disposable { this.panel!.webview.postMessage(msg); } - /** - * Generate a unique nonce - */ - private getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; - } - /** * Retrieve the webview HTML contents * @param webView @@ -552,7 +529,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { const stylesUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'assets/media', 'styles.css')); const scriptUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'dist', 'viewpanel.js')); - const nonce = this.getNonce(); + const nonce = getNonce(); return ` diff --git a/tailwind.config.js b/tailwind.config.js index e9ecbcf7..a9db62d0 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,9 +1,112 @@ +const colors = require('tailwindcss/colors'); + module.exports = { mode: 'jit', purge: ['./src/**/*.{js,jsx,ts,tsx}'], darkMode: false, // or 'media' or 'class' theme: { - extend: {}, + extend: { + colors: { + white: colors.white, + gray: colors.trueGray, + "red": { + "50": "#ff7c7b", + "100": "#ff7271", + "200": "#ff6867", + "300": "#ff5e5d", + "400": "#ff5453", + "500": "#fe4a49", + "600": "#f4403f", + "700": "#ea3635", + "800": "#e02c2b", + "900": "#d62221" + }, + "blue": { + "50": "#90dfff", + "100": "#86d5ff", + "200": "#7ccbff", + "300": "#72c1ff", + "400": "#68b7fc", + "500": "#5eadf2", + "600": "#54a3e8", + "700": "#4a99de", + "800": "#408fd4", + "900": "#3685ca" + }, + "teal": { + "50": "#47f4fd", + "100": "#3deaf3", + "200": "#33e0e9", + "300": "#29d6df", + "400": "#1fccd5", + "500": "#15c2cb", + "600": "#0bb8c1", + "700": "#01aeb7", + "800": "#00a4ad", + "900": "#009aa3" + }, + "aqua": { + "50": "#76ffff", + "100": "#6cfffa", + "200": "#62fff0", + "300": "#58ffe6", + "400": "#4effdc", + "500": "#44ffd2", + "600": "#3af5c8", + "700": "#30ebbe", + "800": "#26e1b4", + "900": "#1cd7aa" + }, + "yellow": { + "50": "#ffff90", + "100": "#ffff86", + "200": "#ffff7c", + "300": "#fff872", + "400": "#ffee68", + "500": "#ffe45e", + "600": "#f5da54", + "700": "#ebd04a", + "800": "#e1c640", + "900": "#d7bc36" + }, + "whisper": { + "50": "#ffffff", + "100": "#ffffff", + "200": "#ffffff", + "300": "#ffffff", + "400": "#fdf9ff", + "500": "#f3eff5", + "600": "#e9e5eb", + "700": "#dfdbe1", + "800": "#d5d1d7", + "900": "#cbc7cd" + }, + "vulcan": { + "50": "#404551", + "100": "#363b47", + "200": "#2c313d", + "300": "#222733", + "400": "#181d29", + "500": "#0e131f", + "600": "#040915", + "700": "#00000b", + "800": "#000001", + "900": "#000000" + }, + "rose": { + "50": "#ff73da", + "100": "#ff69d0", + "200": "#ff5fc6", + "300": "#ff55bc", + "400": "#fb4bb2", + "500": "#f141a8", + "600": "#e7379e", + "700": "#dd2d94", + "800": "#d3238a", + "900": "#c91980" + } + } + }, }, variants: { extend: {}, diff --git a/webpack.config.js b/webpack.config.js index 7ef4694d..bfd5fdd3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -48,6 +48,34 @@ module.exports = [ resolve: { extensions: ['.ts', '.js', '.tsx', '.jsx'] }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + use: [{ + loader: 'ts-loader' + }] + } + ] + }, + performance: { + maxEntrypointSize: 400000, + maxAssetSize: 400000 + } + }, + { + name: 'pagesView', + target: 'web', + entry: './src/pagesView/index.tsx', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'pages.js' + }, + devtool: 'source-map', + resolve: { + extensions: ['.ts', '.js', '.tsx', '.jsx'] + }, module: { rules: [ {