diff --git a/CHANGELOG.md b/CHANGELOG.md index 73688a45..4a8e2c9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Change Log +## [5.9.0] - 2022-01-01 - 🎇🎆 + +### 🎨 Enhancements + +- Fixing the spinner which overlaps the global navigation bar +- Quick actions added for media files (edit, delete, insert markdown, insert snippet) +- [#199](https://github.com/estruyf/vscode-front-matter/issues/199): Search media files in the currently selected folder +- [#211](https://github.com/estruyf/vscode-front-matter/issues/211): Replace text selection on media inserts +- [#212](https://github.com/estruyf/vscode-front-matter/issues/212): Create folder watchers for content folders. When new content gets created, the dashboard updates. +- [#213](https://github.com/estruyf/vscode-front-matter/issues/213): New media folder overview design + +### 🐞 Fixes + +- [#210](https://github.com/estruyf/vscode-front-matter/issues/210): Fix for adding media files with uppercase file extensions +- [#214](https://github.com/estruyf/vscode-front-matter/issues/214): Fix for opening markdown file after creating it for the specified content type + ## [5.8.0] - 2021-12-21 - 🎄 ### 🎨 Enhancements diff --git a/package-lock.json b/package-lock.json index 5af57033..69fedcb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-front-matter-beta", - "version": "5.8.0", + "version": "5.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-front-matter-beta", - "version": "5.8.0", + "version": "5.9.0", "license": "MIT", "devDependencies": { "@bendera/vscode-webview-elements": "0.6.2", diff --git a/package.json b/package.json index a5fa9f03..266aa93a 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "vscode-front-matter-beta", "displayName": "Front Matter", - "description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...", + "description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "5.8.0", + "version": "5.9.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { @@ -113,12 +113,12 @@ "type": "string" } ], - "markdownDescription": "Specify the default sorting option for the content dashboard. You can use one of the values from the enum or define your own ID. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.sorting.default)", + "markdownDescription": "Specify the default sorting option for the content dashboard. You can use one of the values from the enum or define your own ID. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.defaultSorting)", "scope": "Content" }, "frontMatter.content.draftField": { "type": "object", - "markdownDescription": "Define the draft field you want to use to manage your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.draftField)", + "markdownDescription": "Define the draft field you want to use to manage your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.draftfield)", "default": { "name": "draft", "type": "boolean" @@ -156,7 +156,7 @@ "frontMatter.content.fmHighlight": { "type": "boolean", "default": true, - "markdownDescription": "Specify if you want to highlight the Front Matter in the Markdown file. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.fmhighlight)", + "markdownDescription": "Specify if you want to highlight the Front Matter in the Markdown file. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.fmhighlight)", "scope": "Content" }, "frontMatter.content.pageFolders": { @@ -197,7 +197,7 @@ "frontMatter.content.sorting": { "type": "array", "default": [], - "markdownDescription": "Define the sorting options for your dashboard content. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.sorting)", + "markdownDescription": "Define the sorting options for your dashboard content. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.sorting)", "items": { "type": "object", "properties": { @@ -243,7 +243,7 @@ "frontMatter.content.wysiwyg": { "type": "boolean", "default": true, - "markdownDescription": "Specifies if you want to enable/disable the What You See, Is What You Get (WYSIWYG) markdown controls. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.wysiwyg)", + "markdownDescription": "Specifies if you want to enable/disable the What You See, Is What You Get (WYSIWYG) markdown controls. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.wysiwyg)", "scope": "Content" }, "frontMatter.custom.scripts": { @@ -303,7 +303,7 @@ "frontMatter.dashboard.mediaSnippet": { "type": "array", "default": [], - "markdownDescription": "Specify the a snippet for your custom media insert markup. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.dashboard.mediaSnippet)", + "markdownDescription": "Specify the a snippet for your custom media insert markup. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.mediasnippet)", "items": { "type": "string", "description": "The parts of your snippet. Use `{mediaUrl}` as placeholder where the path of the image needs to be inserted." @@ -322,7 +322,7 @@ "frontMatter.framework.id": { "type": "string", "default": "", - "markdownDescription": "Specify the ID of your static site generator or framework you are using for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.framework.id)" + "markdownDescription": "Specify the ID of your static site generator or framework you are using for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.framework.id)" }, "frontMatter.media.defaultSorting": { "type": "string", @@ -333,7 +333,7 @@ "FileNameAsc", "FileNameDesc" ], - "markdownDescription": "Specify the default sorting option for the media dashboard. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.media.sorting.default)", + "markdownDescription": "Specify the default sorting option for the media dashboard. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.media.defaultsorting)", "scope": "Content" }, "frontMatter.panel.freeform": { @@ -357,7 +357,7 @@ "frontMatter.site.baseURL": { "type": "string", "default": "", - "markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.site.baseURL)", + "markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.site.baseurl)", "scope": "Site" }, "frontMatter.taxonomy.alignFilename": { @@ -376,7 +376,7 @@ }, "frontMatter.taxonomy.commaSeparatedFields": { "type": "array", - "markdownDescription": "Specify the fields names that Front Matter should treat as a comma-separated array. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.taxonomy.commaSeparatedFields)", + "markdownDescription": "Specify the fields names that Front Matter should treat as a comma-separated array. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.commaSeparatedFields)", "items": { "type": "string", "description": "Name of the fields you want to use as comma-separated arrays." @@ -388,7 +388,7 @@ "array", "null" ], - "markdownDescription": "Specify the type of contents you want to use for your articles/pages/etc. Make sure the `type` is correctly set in your front matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.taxonomy.contentTypes)", + "markdownDescription": "Specify the type of contents you want to use for your articles/pages/etc. Make sure the `type` is correctly set in your front matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.contentTypes)", "items": { "type": "object", "description": "Define the content types you want to use in Front Matter.", @@ -725,7 +725,7 @@ "warning", "error" ], - "markdownDescription": "Specifies the notifications you want to see. By default, all notifications types will be shown. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.global.notifications)", + "markdownDescription": "Specifies the notifications you want to see. By default, all notifications types will be shown. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.global.notifications)", "scope": "Templates" } } diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 179fc4e1..f81e9e57 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -12,7 +12,7 @@ import { format } from 'date-fns'; import { Dashboard } from './Dashboard'; import { parseWinPath } from '../helpers/parseWinPath'; import { MediaHelpers } from '../helpers/MediaHelpers'; -import { MediaListener } from '../listeners'; +import { MediaListener, PagesListener } from '../listeners'; export const WORKSPACE_PLACEHOLDER = `[[workspace]]`; @@ -288,6 +288,9 @@ export class Folders { })); await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folderDetails, true); + + // Reinitialize the folder listeners + PagesListener.startWatchers(); } /** diff --git a/src/dashboardWebView/components/Contents/Item.tsx b/src/dashboardWebView/components/Contents/Item.tsx index f9006bb2..24bd6bc8 100644 --- a/src/dashboardWebView/components/Contents/Item.tsx +++ b/src/dashboardWebView/components/Contents/Item.tsx @@ -26,7 +26,7 @@ export const Item: React.FunctionComponent = ({ fmFilePath, date, ti if (view === DashboardViewType.Grid) { return (
  • - + +
  • + + {folders.map((folder) => ( +
  • -
  • - {folders.map((folder) => ( -
  • -
    - - -
    -
  • - ))} - - -
    - -
    - + ))} + ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index 12259040..028f5f2d 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -15,9 +15,9 @@ import { Messenger } from '@estruyf/vscode/dist/client'; import { ClearFilters } from './ClearFilters'; import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon'; import {PhotographIcon} from '@heroicons/react/outline'; -import { Pagination } from '../Media/Pagination'; +import { MediaHeaderTop } from '../Media/MediaHeaderTop'; import { ChoiceButton } from '../ChoiceButton'; -import { Breadcrumb } from './Breadcrumb'; +import { MediaHeaderBottom } from '../Media/MediaHeaderBottom'; export interface IHeaderProps { settings: Settings | null; @@ -54,16 +54,20 @@ export const Header: React.FunctionComponent = ({totalPages, folde return (
    - -
    -
    - - -
    + +
    +
      +
    • + +
    • +
    • + +
    • +
    { @@ -101,7 +105,7 @@ export const Header: React.FunctionComponent = ({totalPages, folde
    -
    +
    @@ -121,8 +125,9 @@ export const Header: React.FunctionComponent = ({totalPages, folde { view === NavigationType.Media && ( <> - - + + + ) } diff --git a/src/dashboardWebView/components/Header/Pagination.tsx b/src/dashboardWebView/components/Header/Pagination.tsx new file mode 100644 index 00000000..85dbdda1 --- /dev/null +++ b/src/dashboardWebView/components/Header/Pagination.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { LIMIT } from '../../hooks/useMedia'; +import { MediaTotalSelector, PageAtom } from '../../state'; +import { PaginationButton } from './PaginationButton'; + +export interface IPaginationProps {} + +export const Pagination: React.FunctionComponent = (props: React.PropsWithChildren) => { + const [ page, setPage ] = useRecoilState(PageAtom); + const totalMedia = useRecoilValue(MediaTotalSelector); + + const totalPages = Math.ceil(totalMedia / LIMIT) - 1; + + const getButtons = (): number[] => { + const maxButtons = 5; + const buttons: number[] = []; + const start = page - maxButtons; + const end = page + maxButtons; + + for (let i = start; i <= end; i++) { + if (i >= 0 && i <= totalPages) { + buttons.push(i); + } + } + return buttons; + }; + + return ( +
    + { + if (page > 0) { + setPage(0) + } + }} /> + + { + if (page > 0) { + setPage(page - 1) + } + }} /> + + {getButtons().map((button) => ( + + ))} + + = totalPages} + onClick={() => setPage(page + 1)} /> + + = totalPages} + onClick={() => setPage(totalPages)} /> +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Media/PaginationButton.tsx b/src/dashboardWebView/components/Header/PaginationButton.tsx similarity index 100% rename from src/dashboardWebView/components/Media/PaginationButton.tsx rename to src/dashboardWebView/components/Header/PaginationButton.tsx diff --git a/src/dashboardWebView/components/Header/PaginationStatus.tsx b/src/dashboardWebView/components/Header/PaginationStatus.tsx new file mode 100644 index 00000000..49d6abf0 --- /dev/null +++ b/src/dashboardWebView/components/Header/PaginationStatus.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { MediaTotalSelector, PageAtom, SearchAtom, SelectedMediaFolderSelector } from '../../state'; +import { Messenger } from '@estruyf/vscode/dist/client'; +import { DashboardMessage } from '../../DashboardMessage'; +import { RefreshIcon } from '@heroicons/react/outline'; +import { LIMIT } from '../../hooks/useMedia'; + +export interface IPaginationStatusProps {} + +export const PaginationStatus: React.FunctionComponent = (props: React.PropsWithChildren) => { + const totalMedia = useRecoilValue(MediaTotalSelector); + const selectedFolder = useRecoilValue(SelectedMediaFolderSelector); + const [ page, setPage ] = useRecoilState(PageAtom); + const [ , setSearch ] = useRecoilState(SearchAtom); + + const getTotalPage = () => { + const mediaItems = ((page + 1) * LIMIT); + if (totalMedia < mediaItems) { + return totalMedia; + } + return mediaItems; + }; + + const refresh = () => { + setPage(0); + setSearch(''); + Messenger.send(DashboardMessage.refreshMedia, { folder: selectedFolder }); + } + + return ( +
    + + +

    + Showing {(page * LIMIT) + 1} to {getTotalPage()} of{' '} + {totalMedia} results +

    +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/Searchbox.tsx b/src/dashboardWebView/components/Header/Searchbox.tsx index 80e9dc63..3e088732 100644 --- a/src/dashboardWebView/components/Header/Searchbox.tsx +++ b/src/dashboardWebView/components/Header/Searchbox.tsx @@ -4,34 +4,43 @@ import { useRecoilState } from 'recoil'; import { useDebounce } from '../../../hooks/useDebounce'; import { SearchAtom } from '../../state'; -export interface ISearchboxProps {} +export interface ISearchboxProps { + placeholder?: string; +} -export const Searchbox: React.FunctionComponent = ({}: React.PropsWithChildren) => { +export const Searchbox: React.FunctionComponent = ({placeholder}: React.PropsWithChildren) => { const [ value, setValue ] = React.useState(''); - const [ , setDebounceValue ] = useRecoilState(SearchAtom); + const [ debounceSearchValue, setDebounceValue ] = useRecoilState(SearchAtom); const debounceSearch = useDebounce(value, 500); const handleChange = (event: React.ChangeEvent) => { setValue(event.target.value); }; + React.useEffect(() => { + if (!debounceSearchValue && value) { + setValue(''); + } + } , [debounceSearchValue]); + React.useEffect(() => { setDebounceValue(debounceSearch); }, [debounceSearch]); return ( -
    -
    +
    +
    + diff --git a/src/dashboardWebView/components/Header/Sorting.tsx b/src/dashboardWebView/components/Header/Sorting.tsx index d5d4d9f8..4e35c6f5 100644 --- a/src/dashboardWebView/components/Header/Sorting.tsx +++ b/src/dashboardWebView/components/Header/Sorting.tsx @@ -78,7 +78,7 @@ export const Sorting: React.FunctionComponent = ({disableCustomSo key={option.id} title={option.title || option.name} value={option} - isCurrent={option.id === crntSorting?.id} + isCurrent={option.id === crntSort.id} onClick={(value) => updateSorting(value)} /> ))} diff --git a/src/dashboardWebView/components/Media/FolderCreation.tsx b/src/dashboardWebView/components/Media/FolderCreation.tsx index b09c81e3..f35d6cb0 100644 --- a/src/dashboardWebView/components/Media/FolderCreation.tsx +++ b/src/dashboardWebView/components/Media/FolderCreation.tsx @@ -26,24 +26,28 @@ export const FolderCreation: React.FunctionComponent = (pr const scripts = (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFolder); if (scripts.length > 0) { return ( - ({ - title: s.title, - onClick: () => runCustomScript(s) - }))} - onClick={onFolderCreation} - disabled={!settings?.initialized} /> +
    + ({ + title: s.title, + onClick: () => runCustomScript(s) + }))} + onClick={onFolderCreation} + disabled={!settings?.initialized} /> +
    ) } return ( - +
    + +
    ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/Media/FolderItem.tsx b/src/dashboardWebView/components/Media/FolderItem.tsx index cea4febe..9b52ca00 100644 --- a/src/dashboardWebView/components/Media/FolderItem.tsx +++ b/src/dashboardWebView/components/Media/FolderItem.tsx @@ -16,11 +16,13 @@ export const FolderItem: React.FunctionComponent = ({ folder, const relFolderPath = wsFolder ? folder.replace(wsFolder, '') : folder; return ( -
  • - diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 9c3cbb58..f3706cef 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -1,6 +1,6 @@ import { Messenger } from '@estruyf/vscode/dist/client'; import { Menu } from '@headlessui/react'; -import {PhotographIcon} from '@heroicons/react/outline'; +import { ClipboardIcon, CodeIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline'; import { basename, dirname } from 'path'; import * as React from 'react'; import { useEffect } from 'react'; @@ -15,6 +15,7 @@ import { MenuItem, MenuItems } from '../Menu'; import { Alert } from '../Modals/Alert'; import { Metadata } from '../Modals/Metadata'; import { MenuButton } from './MenuButton' +import { QuickAction } from './QuickAction'; export interface IItemProps { media: MediaInfo; @@ -194,7 +195,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi return ( <> -
  • +
  • - -

    - Showing {(page * LIMIT) + 1} to {getTotalPage()} of{' '} - {totalMedia} results -

    -
  • - - - -
    - { - if (page > 0) { - setPage(0) - } - }} /> - - { - if (page > 0) { - setPage(page - 1) - } - }} /> - - {getButtons().map((button) => ( - - ))} - - = totalPages} - onClick={() => setPage(page + 1)} /> - - = totalPages} - onClick={() => setPage(totalPages)} /> -
    - - ); -}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Media/QuickAction.tsx b/src/dashboardWebView/components/Media/QuickAction.tsx new file mode 100644 index 00000000..e76c8368 --- /dev/null +++ b/src/dashboardWebView/components/Media/QuickAction.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; + +export interface IQuickActionProps { + title: string; + onClick: () => void; +} + +export const QuickAction: React.FunctionComponent = ({title, onClick, children}: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Menu/MenuItem.tsx b/src/dashboardWebView/components/Menu/MenuItem.tsx index aa1d930c..99492359 100644 --- a/src/dashboardWebView/components/Menu/MenuItem.tsx +++ b/src/dashboardWebView/components/Menu/MenuItem.tsx @@ -15,7 +15,7 @@ export const MenuItem: React.FunctionComponent = ({title, value, diff --git a/src/dashboardWebView/components/Menu/MenuItems.tsx b/src/dashboardWebView/components/Menu/MenuItems.tsx index 77c3b185..78f414b5 100644 --- a/src/dashboardWebView/components/Menu/MenuItems.tsx +++ b/src/dashboardWebView/components/Menu/MenuItems.tsx @@ -17,7 +17,7 @@ export const MenuItems: React.FunctionComponent = ({widthClass, leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - +
    {children}
    diff --git a/src/dashboardWebView/components/Spinner.tsx b/src/dashboardWebView/components/Spinner.tsx index de8e7cdf..18432177 100644 --- a/src/dashboardWebView/components/Spinner.tsx +++ b/src/dashboardWebView/components/Spinner.tsx @@ -4,7 +4,7 @@ export interface ISpinnerProps {} export const Spinner: React.FunctionComponent = (props: React.PropsWithChildren) => { return ( -
    +
    ); diff --git a/src/dashboardWebView/components/Startup.tsx b/src/dashboardWebView/components/Startup.tsx index 79e25e66..07156d94 100644 --- a/src/dashboardWebView/components/Startup.tsx +++ b/src/dashboardWebView/components/Startup.tsx @@ -21,7 +21,7 @@ export const Startup: React.FunctionComponent = ({settings}: Reac }, [settings?.openOnStart]); return ( -
    +
    = { + keys: [ + { name: 'filename', weight: 0.8 }, + { name: 'fsPath', weight: 0.5 }, + { name: 'caption', weight: 0.5 }, + { name: 'alt', weight: 0.5 } + ], + threshold: 0.2, + includeScore: true +}; + +export const LIMIT = 16; + +export default function useMedia() { + const [ media, setMedia ] = useState([]); + const [ page, setPage ] = useRecoilState(PageAtom); + const [ searchedMedia, setSearchedMedia ] = useState([]); + const [ , setSelectedFolder ] = useRecoilState(SelectedMediaFolderAtom); + const [ , setTotal ] = useRecoilState(MediaTotalAtom); + const [ , setFolders ] = useRecoilState(MediaFoldersAtom); + const [ , setLoading ] = useRecoilState(LoadingAtom); + const search = useRecoilValue(SearchAtom); + + const getMedia = useCallback(() => { + return searchedMedia.slice(page * LIMIT, ((page + 1) * LIMIT)); + }, [searchedMedia, page]); + + const messageListener = (message: MessageEvent>) => { + if (message.data.command === DashboardCommand.media) { + const data: MediaPaths = message.data.data as MediaPaths; + setLoading(false); + setMedia(data.media); + setTotal(data.total); + setFolders(data.folders); + setSelectedFolder(data.selectedFolder); + setSearchedMedia(data.media); + } + }; + + useEffect(() => { + if (search) { + const fuse = new Fuse(media, fuseOptions); + const results = fuse.search(search); + const newSearchedMedia = results.map(page => page.item); + + setSearchedMedia(newSearchedMedia); + setTotal(results.length); + + return; + } + + setSearchedMedia(media); + }, [search]); + + useEffect(() => { + Messenger.listen(messageListener); + + return () => { + Messenger.unlisten(messageListener); + } + }, []); + + return { + media: getMedia() + }; +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 3b46f7f5..3b9b3563 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,6 +18,7 @@ import { Content } from './commands/Content'; import ContentProvider from './providers/ContentProvider'; import { Wysiwyg } from './commands/Wysiwyg'; import { Diagnostics } from './commands/Diagnostics'; +import { PagesListener } from './listeners'; let frontMatterStatusBar: vscode.StatusBarItem; let statusDebouncer: { (fnc: any, time: number): void; }; @@ -40,6 +41,10 @@ export async function activate(context: vscode.ExtensionContext) { SettingsHelper.checkToPromote(); + // Start listening to the folders for content changes. + // This will make sure the dashboard is up to date + PagesListener.startWatchers(); + collection = vscode.languages.createDiagnosticCollection('frontMatter'); // Pages dashboard diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 55fc33ec..d942d818 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -2,7 +2,7 @@ import { PagesListener } from './../listeners/PagesListener'; import { ArticleHelper, Settings } from "."; import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; import { ContentType as IContentType, DraftField } from '../models'; -import { Uri, workspace, window } from 'vscode'; +import { Uri, workspace, window, commands } from 'vscode'; import { Folders } from "../commands/Folders"; import { Questions } from "./Questions"; import { writeFileSync } from "fs"; @@ -123,10 +123,7 @@ export class ContentType { writeFileSync(newFilePath, content, { encoding: "utf8" }); - const txtDoc = await workspace.openTextDocument(Uri.parse(newFilePath)); - if (txtDoc) { - window.showTextDocument(txtDoc); - } + await commands.executeCommand('vscode.open', Uri.file(newFilePath)); Notifications.info(`Your new content has been created.`); diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index 33ca36c0..a1d6815b 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -111,7 +111,6 @@ export class MediaHelpers { const total = files.length; // Get media set - files = files.slice(page * 16, ((page + 1) * 16)); files = files.map((file) => { try { const metadata = MediaLibrary.getInstance().get(file.fsPath); @@ -283,7 +282,15 @@ export class MediaHelpers { } } - await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || `![${data.alt || data.caption || ""}](${imgPath})`)); + const selection = editor?.selection; + await editor?.edit(builder => { + const snippet = data.snippet || `![${data.alt || data.caption || ""}](${imgPath})`; + if (selection !== undefined) { + builder.replace(selection, snippet); + } else { + builder.insert(new Position(line, character), snippet); + } + }); } panel.getMediaSelection(); } else { @@ -313,8 +320,9 @@ export class MediaHelpers { private static filterMedia(files: Uri[]) { return files.filter(file => { const ext = extname(file.fsPath); - return ['.jpg', '.jpeg', '.png', '.gif', '.svg'].includes(ext); + return ['.jpg', '.jpeg', '.png', '.gif', '.svg'].includes(ext.toLowerCase()); }).map((file) => ({ + filename: basename(file.fsPath), fsPath: file.fsPath, vsPath: Dashboard.getWebview()?.asWebviewUri(file).toString(), stats: undefined diff --git a/src/helpers/MediaLibrary.ts b/src/helpers/MediaLibrary.ts index 7671817d..430966df 100644 --- a/src/helpers/MediaLibrary.ts +++ b/src/helpers/MediaLibrary.ts @@ -27,11 +27,12 @@ export class MediaLibrary { workspace.onDidRenameFiles(e => { e.files.forEach(f => { + const path = f.oldUri.path.toLowerCase(); // Check if file is an image - if (f.oldUri.path.endsWith('.jpeg') || - f.oldUri.path.endsWith('.jpg') || - f.oldUri.path.endsWith('.png') || - f.oldUri.path.endsWith('.gif')) { + if (path.endsWith('.jpeg') || + path.endsWith('.jpg') || + path.endsWith('.png') || + path.endsWith('.gif')) { this.rename(f.oldUri.fsPath, f.newUri.fsPath); MediaHelpers.resetMedia(); } diff --git a/src/listeners/PagesListener.ts b/src/listeners/PagesListener.ts index 7296a9f5..94e71b07 100644 --- a/src/listeners/PagesListener.ts +++ b/src/listeners/PagesListener.ts @@ -1,7 +1,7 @@ import { isValidFile } from './../helpers/isValidFile'; import { existsSync } from "fs"; import { dirname, join } from "path"; -import { commands, Uri } from "vscode"; +import { commands, FileSystemWatcher, RelativePattern, Uri, workspace } from "vscode"; import { Dashboard } from "../commands/Dashboard"; import { Folders } from "../commands/Folders"; import { COMMAND_NAME, DefaultFields, SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD } from "../constants"; @@ -16,6 +16,35 @@ import { BaseListener } from "./BaseListener"; export class PagesListener extends BaseListener { + private static watchers: { [path: string]: FileSystemWatcher } = {}; + + /** + * Start watching the folders in the current workspace for content changes + */ + public static async startWatchers() { + const folders = Folders.get(); + + if (!folders || folders.length === 0) { + return; + } + + // Dispose all the current watchers + const paths = Object.keys(this.watchers); + for (const path of paths) { + const watcher = this.watchers[path]; + watcher.dispose(); + delete this.watchers[path]; + } + + // Recreate all the watchers + for (const folder of folders) { + const folderUri = Uri.parse(folder.path); + let watcher = workspace.createFileSystemWatcher(new RelativePattern(folderUri, "*")); + watcher.onDidCreate(async (uri: Uri) => this.getPagesData); + watcher.onDidDelete(async (uri: Uri) => this.getPagesData); + this.watchers[folderUri.fsPath] = watcher; + } + } /** * Process the messages for the dashboard views diff --git a/src/models/MediaPaths.ts b/src/models/MediaPaths.ts index e40a2b22..bf51df50 100644 --- a/src/models/MediaPaths.ts +++ b/src/models/MediaPaths.ts @@ -8,6 +8,7 @@ export interface MediaPaths { } export interface MediaInfo { + filename: string; fsPath: string; vsPath: string | undefined; dimensions?: ISizeCalculationResult | undefined; diff --git a/tsconfig.json b/tsconfig.json index 493a911b..c16f7952 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,9 @@ "es6", "DOM" ], + "typeRoots": [ + "node_modules/@types" + ], "sourceMap": true, "rootDir": "src", "strict": true,