From b58c02b6d04afcb906f57c0e631c1b478b8ff674 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 6 Feb 2025 10:20:31 +0100 Subject: [PATCH 01/13] Issue: Stripping underscore in default filename #914 --- CHANGELOG.md | 12 ++++++++++++ src/helpers/ArticleHelper.ts | 2 +- src/helpers/Sanitize.ts | 14 ++++++++------ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d2debc1..603e0ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [10.8.0] - 2025-02-xx + +### ✨ New features + +### 🎨 Enhancements + +### ⚡️ Optimizations + +### 🐞 Fixes + +- [#914](https://github.com/estruyf/vscode-front-matter/issues/914): Fix sanitizing of default filenames with an `_` in it + ## [10.7.0] - 2024-12-31 - [Release notes](https://beta.frontmatter.codes/updates/v10.7.0) ### 🎨 Enhancements diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 25334a09..e42e4251 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -572,7 +572,7 @@ export class ArticleHelper { await mkdirAsync(newFolder, { recursive: true }); newFilePath = join( newFolder, - `${sanitize(contentType.defaultFileName ?? `index`)}.${ + `${sanitize(contentType.defaultFileName || `index`, { isFileName: true })}.${ fileExtension || contentType.fileType || fileType }` ); diff --git a/src/helpers/Sanitize.ts b/src/helpers/Sanitize.ts index 088142b7..9162b944 100644 --- a/src/helpers/Sanitize.ts +++ b/src/helpers/Sanitize.ts @@ -1,17 +1,18 @@ -const illegalRe = /[/?<>\\:*|"!.,;{}[\]()_+=~`@#$%^&]/g; +const illegalRe = (isFileName: boolean) => + isFileName ? /[/?<>\\:*|"!.,;{}[\]()+=~`@#$%^&]/g : /[/?<>\\:*|"!.,;{}[\]()_+=~`@#$%^&]/g; // eslint-disable-next-line no-control-regex const controlRe = /[\x00-\x1f\x80-\x9f]/g; const reservedRe = /^\.+$/; const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i; const windowsTrailingRe = /[. ]+$/; -function sanitize(input: string, replacement: string) { +function sanitize(input: string, replacement: string, isFileName?: boolean) { if (typeof input !== 'string') { throw new Error('Input must be string'); } const sanitized = input - .replace(illegalRe, replacement) + .replace(illegalRe(isFileName || false), replacement) .replace(controlRe, replacement) .replace(reservedRe, replacement) .replace(windowsReservedRe, replacement) @@ -19,11 +20,12 @@ function sanitize(input: string, replacement: string) { return sanitized; } -export default function (input: string, options?: any) { +export default function (input: string, options?: { replacement?: string; isFileName?: boolean }) { const replacement = (options && options.replacement) || ''; - const output = sanitize(input, replacement); + const isFileName = options && options.isFileName; + const output = sanitize(input, replacement, isFileName); if (replacement === '') { return output; } - return sanitize(output, ''); + return sanitize(output, '', isFileName); } From b391aa3270cfa33d44d4d1f18442dab649f67322 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 6 Feb 2025 10:20:39 +0100 Subject: [PATCH 02/13] 10.8.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6c42e57a..1c29c767 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-front-matter-beta", - "version": "10.7.0", + "version": "10.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-front-matter-beta", - "version": "10.7.0", + "version": "10.8.0", "license": "MIT", "devDependencies": { "@actions/core": "^1.10.0", diff --git a/package.json b/package.json index 3f6273be..bf845ec2 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Front Matter CMS", "description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "10.7.0", + "version": "10.8.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { From e27adececbfe38294f706153abe0d5cbc66d92db Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 6 Feb 2025 10:24:43 +0100 Subject: [PATCH 03/13] Issue: "panel.gitActions" option in view modes is not present in the JSON schema #909 --- CHANGELOG.md | 1 + package.json | 3 ++- src/constants/Features.ts | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 603e0ae7..7c53fcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### 🐞 Fixes +- [#909](https://github.com/estruyf/vscode-front-matter/issues/909): Schema fix for the view modes - [#914](https://github.com/estruyf/vscode-front-matter/issues/914): Fix sanitizing of default filenames with an `_` in it ## [10.7.0] - 2024-12-31 - [Release notes](https://beta.frontmatter.codes/updates/v10.7.0) diff --git a/package.json b/package.json index bf845ec2..572f128b 100644 --- a/package.json +++ b/package.json @@ -1061,8 +1061,9 @@ "panel.globalSettings", "panel.seo", "panel.actions", - "panel.contentType", "panel.metadata", + "panel.contentType", + "panel.gitActions", "panel.recentlyModified", "panel.otherActions", "dashboard.snippets.view", diff --git a/src/constants/Features.ts b/src/constants/Features.ts index d579911f..4aeb5f9d 100644 --- a/src/constants/Features.ts +++ b/src/constants/Features.ts @@ -4,10 +4,10 @@ export const FEATURE_FLAG = { seo: 'panel.seo', actions: 'panel.actions', metadata: 'panel.metadata', - recentlyModified: 'panel.recentlyModified', - otherActions: 'panel.otherActions', contentType: 'panel.contentType', - gitActions: 'panel.gitActions' + gitActions: 'panel.gitActions', + recentlyModified: 'panel.recentlyModified', + otherActions: 'panel.otherActions' }, dashboard: { snippets: { From 64f1da635544aea11c0e954acd9c74acd57e0125 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 6 Feb 2025 11:08:36 +0100 Subject: [PATCH 04/13] Enhancement: auto switch to editor panel when opening a markdown file #915 --- CHANGELOG.md | 2 + l10n/bundle.l10n.json | 5 ++- package.json | 6 +++ package.nls.json | 3 +- src/constants/settings.ts | 1 + .../components/Header/BooleanOption.tsx | 38 +++++++++++++++++++ .../SettingsView/CommonSettings.tsx | 12 +++++- src/dashboardWebView/models/Settings.ts | 1 + src/extension.ts | 3 +- src/helpers/DashboardSettings.ts | 7 ++-- src/localization/localization.enum.ts | 8 ++++ src/panelWebView/PanelProvider.ts | 18 ++++++++- 12 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 src/dashboardWebView/components/Header/BooleanOption.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c53fcc6..e7daf38e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### 🎨 Enhancements +- [#915](https://github.com/estruyf/vscode-front-matter/issues/915): Added a new setting `frontMatter.panel.openOnSupportedFile` which allows you to open the panel view on supported files + ### ⚡️ Optimizations ### 🐞 Fixes diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 2be9bf0a..2996492c 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -56,6 +56,8 @@ "settings.view.integration": "Integration", "settings.openOnStartup": "Open dashboard on startup", + "settings.openPanelForSupportedFiles": "Open panel for supported files", + "settings.openPanelForSupportedFiles.label": "Do you want to open the panel for supported files?", "settings.contentTypes": "Content types", "settings.contentFolders": "Content folders", "settings.diagnostic": "Diagnostic", @@ -799,7 +801,6 @@ "listeners.panel.dataListener.createDataFile.error": "No data file id or path defined.", "listeners.panel.dataListener.createDataFile.noFileName": "No filename provided.", - "listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "No active editor", "listeners.panel.taxonomyListener.aiSuggestTaxonomy.noData.error": "No article data", @@ -817,4 +818,4 @@ "services.sponsorAi.getTaxonomySuggestions.warning": "The AI taxonomy generation took too long. Please try again later.", "services.terminal.openLocalServerTerminal.terminalOption.message": "Starting local server" -} \ No newline at end of file +} diff --git a/package.json b/package.json index 572f128b..00bb84f4 100644 --- a/package.json +++ b/package.json @@ -1214,6 +1214,12 @@ }, "scope": "Media" }, + "frontMatter.panel.openOnSupportedFile": { + "type": "boolean", + "default": false, + "markdownDescription": "%setting.frontMatter.panel.openOnSupportedFile.markdownDescription%", + "scope": "Dashboard" + }, "frontMatter.panel.freeform": { "type": "boolean", "default": true, diff --git a/package.nls.json b/package.nls.json index f88d1f05..8110f097 100644 --- a/package.nls.json +++ b/package.nls.json @@ -184,6 +184,7 @@ "setting.frontMatter.media.contentTypes.items.properties.fields.properties.type.description": "Define the type of field", "setting.frontMatter.media.contentTypes.items.properties.fields.properties.single.description": "Is a single line field", + "setting.frontMatter.panel.openOnSupportedFile.markdownDescription": "Specifies if you want to open the panel when opening a supported file. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.openonsupportedfile) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.panel.openonsupportedfile%22%5D)", "setting.frontMatter.panel.freeform.markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.panel.freeform%22%5D)", "setting.frontMatter.panel.actions.disabled.markdownDescription": "Specify the actions you want to disable in the panel. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.panel.actions.disabled%22%5D)", "setting.frontMatter.preview.host.markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.preview.host%22%5D)", @@ -292,4 +293,4 @@ "setting.frontMatter.git.disableOnBranches.markdownDescription": "Specify the branches on which you want to disable the Git actions. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.git.disableonbranches) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.disableonbranches%22%5D)", "setting.frontMatter.git.requiresCommitMessage.markdownDescription": "Specify if you want to require a commit message when publishing your changes for a specified branch. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.git.requirescommitmessage) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.requirescommitmessage%22%5D)", "setting.frontMatter.copilot.family.markdownDescription": "Specify the LLM family of the Copilot you want to use. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.copilot.family) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.copilot.family%22%5D)" -} \ No newline at end of file +} diff --git a/src/constants/settings.ts b/src/constants/settings.ts index b2f92afd..6c96af7e 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -46,6 +46,7 @@ export const SETTING_TEMPLATES_FOLDER = 'templates.folder'; export const SETTING_TEMPLATES_PREFIX = 'templates.prefix'; export const SETTING_TEMPLATES_ENABLED = 'templates.enabled'; +export const SETTING_PANEL_OPEN_ON_SUPPORTED_FILE = 'panel.openOnSupportedFile'; export const SETTING_PANEL_FREEFORM = 'panel.freeform'; export const SETTING_PANEL_ACTIONS_DISABLED = 'panel.actions.disabled'; diff --git a/src/dashboardWebView/components/Header/BooleanOption.tsx b/src/dashboardWebView/components/Header/BooleanOption.tsx new file mode 100644 index 00000000..c569ae2b --- /dev/null +++ b/src/dashboardWebView/components/Header/BooleanOption.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { Messenger } from '@estruyf/vscode/dist/client'; +import { DashboardMessage } from '../../DashboardMessage'; +import { Checkbox as VSCodeCheckbox } from 'vscrui'; + +export interface IBooleanOptionProps { + value: boolean | undefined | null; + name: string; + label: string; +} + +export const BooleanOption: React.FunctionComponent = ({ + value, + name, + label +}: React.PropsWithChildren) => { + const [isChecked, setIsChecked] = React.useState(false); + + const onChange = React.useCallback((newValue: boolean) => { + setIsChecked(newValue); + Messenger.send(DashboardMessage.updateSetting, { + name: name, + value: newValue + }); + }, [name]); + + React.useEffect(() => { + setIsChecked(!!value); + }, [value]); + + return ( + + {label} + + ); +}; diff --git a/src/dashboardWebView/components/SettingsView/CommonSettings.tsx b/src/dashboardWebView/components/SettingsView/CommonSettings.tsx index 6dc47759..9a6df00b 100644 --- a/src/dashboardWebView/components/SettingsView/CommonSettings.tsx +++ b/src/dashboardWebView/components/SettingsView/CommonSettings.tsx @@ -6,11 +6,12 @@ import { useRecoilValue } from 'recoil'; import { SettingsSelector } from '../../state'; import { SettingsInput } from './SettingsInput'; import { Button as VSCodeButton } from 'vscrui'; -import { DOCS_SUBMODULES, FrameworkDetectors, GIT_CONFIG, SETTING_FRAMEWORK_START, SETTING_GIT_COMMIT_MSG, SETTING_GIT_ENABLED, SETTING_PREVIEW_HOST, SETTING_WEBSITE_URL } from '../../../constants'; +import { DOCS_SUBMODULES, FrameworkDetectors, GIT_CONFIG, SETTING_FRAMEWORK_START, SETTING_GIT_COMMIT_MSG, SETTING_GIT_ENABLED, SETTING_PANEL_OPEN_ON_SUPPORTED_FILE, SETTING_PREVIEW_HOST, SETTING_WEBSITE_URL } from '../../../constants'; import { messageHandler } from '@estruyf/vscode/dist/client'; import { DashboardMessage } from '../../DashboardMessage'; import { SettingsCheckbox } from './SettingsCheckbox'; import { ChevronRightIcon } from '@heroicons/react/24/outline'; +import { BooleanOption } from '../Header/BooleanOption'; export interface ICommonSettingsProps { } @@ -73,6 +74,15 @@ export const CommonSettings: React.FunctionComponent = (pr +
+

{l10n.t(LocalizationKey.settingsOpenPanelForSupportedFiles)}

+ + +
+

{l10n.t(LocalizationKey.settingsGit)}

diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index c197f2c8..d6e147a9 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -31,6 +31,7 @@ export interface Settings { categories: string[]; customTaxonomy: CustomTaxonomy[]; openOnStart: boolean | null; + openPanelForSupportedFiles: boolean | null; versionInfo: VersionInfo; pageViewType: DashboardViewType | undefined; contentTypes: ContentType[]; diff --git a/src/extension.ts b/src/extension.ts index e9e6ba19..6ddf29ae 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -245,13 +245,14 @@ export async function activate(context: vscode.ExtensionContext) { // eslint-disable-next-line @typescript-eslint/no-empty-function export function deactivate() {} -const triggerPageUpdate = (location: string) => { +const triggerPageUpdate = async (location: string) => { Logger.verbose(`Trigger page update: ${location}`); pageUpdateDebouncer(() => { StatusListener.verify(collection); }, 1000); if (location === 'onDidChangeActiveTextEditor') { + await PanelProvider.openOnSupportedFile(); PanelProvider.getInstance()?.updateCurrentFile(); } }; diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 3665589a..1ddfe164 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -31,7 +31,8 @@ import { SETTING_DASHBOARD_CONTENT_CARD_STATE, SETTING_DASHBOARD_CONTENT_CARD_DESCRIPTION, SETTING_WEBSITE_URL, - SETTING_MEDIA_CONTENTTYPES + SETTING_MEDIA_CONTENTTYPES, + SETTING_PANEL_OPEN_ON_SUPPORTED_FILE } from '../constants'; import { DashboardViewType, @@ -108,6 +109,7 @@ export class DashboardSettings { categories: (await TaxonomyHelper.get(TaxonomyType.Category)) || [], customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [], openOnStart: Settings.get(SETTING_DASHBOARD_OPENONSTART), + openPanelForSupportedFiles: Settings.get(SETTING_PANEL_OPEN_ON_SUPPORTED_FILE), versionInfo: ext.getVersion(), pageViewType: await ext.getState( ExtensionState.PagesView, @@ -119,8 +121,7 @@ export class DashboardSettings { contentFolders: await Folders.get(), filters: Settings.get<(FilterType | { title: string; name: string })[]>(SETTING_CONTENT_FILTERS), - grouping: - Settings.get<{ title: string; name: string }[]>(SETTING_CONTENT_GROUPING), + grouping: Settings.get<{ title: string; name: string }[]>(SETTING_CONTENT_GROUPING), crntFramework: Settings.get(SETTING_FRAMEWORK_ID), framework: !isInitialized && wsFolder ? await FrameworkDetector.get(wsFolder.fsPath) : null, scripts: Settings.get(SETTING_CUSTOM_SCRIPTS) || [], diff --git a/src/localization/localization.enum.ts b/src/localization/localization.enum.ts index 5deac3fb..a308124f 100644 --- a/src/localization/localization.enum.ts +++ b/src/localization/localization.enum.ts @@ -211,6 +211,14 @@ export enum LocalizationKey { * Open dashboard on startup */ settingsOpenOnStartup = 'settings.openOnStartup', + /** + * Open panel for supported files + */ + settingsOpenPanelForSupportedFiles = 'settings.openPanelForSupportedFiles', + /** + * Do you want to open the panel for supported files? + */ + settingsOpenPanelForSupportedFilesLabel = 'settings.openPanelForSupportedFiles.label', /** * Content types */ diff --git a/src/panelWebView/PanelProvider.ts b/src/panelWebView/PanelProvider.ts index ec92b12b..2f96fe4b 100644 --- a/src/panelWebView/PanelProvider.ts +++ b/src/panelWebView/PanelProvider.ts @@ -9,9 +9,10 @@ import { FieldsListener, LocalizationListener } from './../listeners/panel'; -import { SETTING_EXPERIMENTAL } from '../constants'; +import { SETTING_EXPERIMENTAL, SETTING_PANEL_OPEN_ON_SUPPORTED_FILE } from '../constants'; import { CancellationToken, + commands, Disposable, Uri, Webview, @@ -136,6 +137,21 @@ export class PanelProvider implements WebviewViewProvider, Disposable { }); } + /** + * Opens the panel if the active file is supported. + * + * @returns {Promise} A promise that resolves when the command execution is complete. + */ + public static async openOnSupportedFile(): Promise { + const openPanel = Settings.get(SETTING_PANEL_OPEN_ON_SUPPORTED_FILE); + if (openPanel) { + const activeFile = ArticleHelper.getActiveFile(); + if (activeFile) { + await commands.executeCommand('frontMatter.explorer.focus'); + } + } + } + /** * Post data to the panel * @param msg From 482cbc3bf6bda692c59d7a2308394510c186d726 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 6 Feb 2025 11:40:22 +0100 Subject: [PATCH 05/13] Issue: [[&mediaUrl]] placeholder in Media snippets is not relative #913 --- CHANGELOG.md | 1 + src/dashboardWebView/components/Media/Item.tsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7daf38e..58a1d30b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### 🐞 Fixes - [#909](https://github.com/estruyf/vscode-front-matter/issues/909): Schema fix for the view modes +- [#913](https://github.com/estruyf/vscode-front-matter/issues/913): Fix for relative media paths in page bundles - [#914](https://github.com/estruyf/vscode-front-matter/issues/914): Fix sanitizing of default filenames with an `_` in it ## [10.7.0] - 2024-12-31 - [Release notes](https://beta.frontmatter.codes/updates/v10.7.0) diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 1f480035..8a7c1e8c 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -7,7 +7,7 @@ import { PlusIcon, VideoCameraIcon, } from '@heroicons/react/24/outline'; -import { basename } from 'path'; +import { basename, parse } from 'path'; import * as React from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; @@ -55,8 +55,17 @@ export const Item: React.FunctionComponent = ({ const { mediaFolder, mediaDetails, isAudio, isImage, isVideo } = useMediaInfo(media); const relPath = useMemo(() => { + if (viewData?.data?.pageBundle && viewData?.data?.filePath) { + const articlePath = viewData?.data?.filePath; + const articleDir = parse(parseWinPath(articlePath)).dir; + + const mediaPath = parseWinPath(media.fsPath); + if (mediaPath.startsWith(articleDir)) { + return getRelPath(media.fsPath, undefined, articleDir); + } + } return getRelPath(media.fsPath, settings?.staticFolder, settings?.wsFolder); - }, [media.fsPath, settings?.staticFolder, settings?.wsFolder]); + }, [media.fsPath, settings?.staticFolder, settings?.wsFolder, viewData?.data?.pageBundle, viewData?.data?.filePath]); const hasViewData = useMemo(() => { return viewData?.data?.filePath !== undefined; From ee5af888510df9c0acd575c22dae073b37a59db7 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 12 Feb 2025 12:27:24 +0100 Subject: [PATCH 06/13] Fix: ensure Windows drive letters are lowercased and improve path parsing --- src/commands/Folders.ts | 12 ++++++++++-- src/helpers/parseWinPath.ts | 14 +++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 5817b30f..0aaf250b 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -636,15 +636,23 @@ export class Folders { } } + // For Windows, we need to make sure the drive letter is lowercased for consistency + if (isWindows()) { + folders = folders.map((folder) => parseWinPath(folder)); + } + // Filter out the workspace folder if (wsFolder) { - folders = folders.filter((folder) => folder !== wsFolder.fsPath); + folders = folders.filter((folder) => folder !== parseWinPath(wsFolder.fsPath)); } const uniqueFolders = [...new Set(folders)]; + const relativeFolderPaths = uniqueFolders.map((folder) => + relative(parseWinPath(wsFolder.fsPath), folder) + ); Logger.verbose('Folders:getContentFolders:end'); - return uniqueFolders.map((folder) => relative(wsFolder?.path || '', folder)); + return relativeFolderPaths; } /** diff --git a/src/helpers/parseWinPath.ts b/src/helpers/parseWinPath.ts index c756d944..e73a9e18 100644 --- a/src/helpers/parseWinPath.ts +++ b/src/helpers/parseWinPath.ts @@ -1,3 +1,15 @@ +import { isWindows } from '../utils'; + export const parseWinPath = (path: string | undefined): string => { - return path?.split(`\\`).join(`/`) || ''; + path = path?.split(`\\`).join(`/`) || ''; + + if (isWindows()) { + // Check if path starts with a drive letter (e.g., "C:\") + if (/^[a-zA-Z]:\\/.test(path)) { + // Convert to lowercase drive letter + path = path.charAt(0).toLowerCase() + path.slice(1); + } + } + + return path; }; From d3b7f73c662b41d0658857800b6fa51e46892705 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 12 Feb 2025 12:41:22 +0100 Subject: [PATCH 07/13] Refactor: update import paths for parseWinPath and integrate it into joinUrl function --- src/helpers/parseWinPath.ts | 2 +- src/utils/joinUrl.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/helpers/parseWinPath.ts b/src/helpers/parseWinPath.ts index e73a9e18..809d2c84 100644 --- a/src/helpers/parseWinPath.ts +++ b/src/helpers/parseWinPath.ts @@ -1,4 +1,4 @@ -import { isWindows } from '../utils'; +import { isWindows } from '../utils/isWindows'; export const parseWinPath = (path: string | undefined): string => { path = path?.split(`\\`).join(`/`) || ''; diff --git a/src/utils/joinUrl.ts b/src/utils/joinUrl.ts index 36bd3892..9ce92026 100644 --- a/src/utils/joinUrl.ts +++ b/src/utils/joinUrl.ts @@ -1,4 +1,5 @@ import { urlJoin } from 'url-join-ts'; +import { parseWinPath } from '../helpers'; export const joinUrl = (baseUrl: string | undefined, ...paths: any[]): string => { const url = urlJoin(baseUrl, ...paths); @@ -9,5 +10,5 @@ export const joinUrl = (baseUrl: string | undefined, ...paths: any[]): string => return url + '/'; } - return url; + return parseWinPath(url); }; From 710ef136b48cf67d037294038d082b755cf33a8f Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 12 Feb 2025 14:53:56 +0100 Subject: [PATCH 08/13] Fix: improve media folder parsing on Windows and update path handling in various modules --- CHANGELOG.md | 1 + src/commands/Folders.ts | 6 ++++-- src/helpers/FrameworkDetector.ts | 4 ++-- src/helpers/MediaHelpers.ts | 2 +- src/helpers/processPathPlaceholders.ts | 3 ++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a1d30b..84cf0355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### 🐞 Fixes +- Fix for media folder parsing on Windows - [#909](https://github.com/estruyf/vscode-front-matter/issues/909): Schema fix for the view modes - [#913](https://github.com/estruyf/vscode-front-matter/issues/913): Fix for relative media paths in page bundles - [#914](https://github.com/estruyf/vscode-front-matter/issues/914): Fix sanitizing of default filenames with an `_` in it diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 0aaf250b..8e6be394 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -219,7 +219,9 @@ export class Folders { : Folders.getAbsFilePath(assetFolder); const wsFolder = Folders.getWorkspaceFolder(); if (wsFolder) { - const relativePath = relative(parseWinPath(wsFolder.fsPath), parseWinPath(assetFolder)); + const relativePath = parseWinPath( + relative(parseWinPath(wsFolder.fsPath), parseWinPath(assetFolder)) + ); return relativePath === '' ? '/' : relativePath; } } @@ -648,7 +650,7 @@ export class Folders { const uniqueFolders = [...new Set(folders)]; const relativeFolderPaths = uniqueFolders.map((folder) => - relative(parseWinPath(wsFolder.fsPath), folder) + parseWinPath(relative(parseWinPath(wsFolder.fsPath), folder)) ); Logger.verbose('Folders:getContentFolders:end'); diff --git a/src/helpers/FrameworkDetector.ts b/src/helpers/FrameworkDetector.ts index 51972743..ac2ab623 100644 --- a/src/helpers/FrameworkDetector.ts +++ b/src/helpers/FrameworkDetector.ts @@ -137,7 +137,7 @@ export class FrameworkDetector { const assetDir = dirname(absAssetPath); const fileName = parse(absAssetPath); - relAssetPath = relative(fileDir, assetDir); + relAssetPath = parseWinPath(relative(fileDir, assetDir)); relAssetPath = join(relAssetPath, `${fileName.name}${fileName.ext}`); } // Support for HEXO image folder @@ -197,7 +197,7 @@ export class FrameworkDetector { const assetDir = dirname(absAssetPath); const fileName = parse(absAssetPath); - let relAssetPath = relative(fileDir, assetDir); + let relAssetPath = parseWinPath(relative(fileDir, assetDir)); relAssetPath = join(relAssetPath, `${fileName.name}${fileName.ext}`); return parseWinPath(relAssetPath); } diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index 3d39f456..db8a2036 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -422,7 +422,7 @@ export class MediaHelpers { // If the image exists in a content folder, the relative path needs to be used if (existsInContent) { - const relImgPath = relative(fileDir, imgDir); + const relImgPath = parseWinPath(relative(fileDir, imgDir)); relPath = join(relImgPath, basename(relPath)); diff --git a/src/helpers/processPathPlaceholders.ts b/src/helpers/processPathPlaceholders.ts index d7bda25f..93a51bbc 100644 --- a/src/helpers/processPathPlaceholders.ts +++ b/src/helpers/processPathPlaceholders.ts @@ -1,5 +1,6 @@ import { dirname, relative } from 'path'; import { ContentFolder } from '../models'; +import { parseWinPath } from './parseWinPath'; export const processPathPlaceholders = ( value: string, @@ -11,7 +12,7 @@ export const processPathPlaceholders = ( const relPathToken = '{{pathToken.relPath}}'; if (value.includes(relPathToken) && contentFolder?.path) { const dirName = dirname(filePath); - const relPath = relative(contentFolder.path, dirName); + const relPath = parseWinPath(relative(contentFolder.path, dirName)); value = value.replace(relPathToken, relPath); } From a164a849da5f7c5b5c3474b7fa4c66b8d63175ed Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 12 Feb 2025 15:01:13 +0100 Subject: [PATCH 09/13] Fix: add refresh button to media dashboard when custom scripts are defined --- CHANGELOG.md | 1 + src/dashboardWebView/components/Media/FolderCreation.tsx | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84cf0355..76827f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### 🐞 Fixes - Fix for media folder parsing on Windows +- Refresh button was not available on the media dashboard when having custom scripts defined - [#909](https://github.com/estruyf/vscode-front-matter/issues/909): Schema fix for the view modes - [#913](https://github.com/estruyf/vscode-front-matter/issues/913): Fix for relative media paths in page bundles - [#914](https://github.com/estruyf/vscode-front-matter/issues/914): Fix sanitizing of default filenames with an `_` in it diff --git a/src/dashboardWebView/components/Media/FolderCreation.tsx b/src/dashboardWebView/components/Media/FolderCreation.tsx index 5b00293a..6516e896 100644 --- a/src/dashboardWebView/components/Media/FolderCreation.tsx +++ b/src/dashboardWebView/components/Media/FolderCreation.tsx @@ -91,8 +91,9 @@ export const FolderCreation: React.FunctionComponent = ( if (scripts.length > 0) { return ( -
+
{renderPostAssetsButton} + ({ @@ -103,6 +104,8 @@ export const FolderCreation: React.FunctionComponent = ( onClick={onFolderCreation} disabled={!settings?.initialized} /> + +
); } From d2b02288094530f0d3f0d538e70740611f5391fd Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 14 Feb 2025 09:35:12 +0100 Subject: [PATCH 10/13] Feat: improve filename sanitization and add normalization for special characters #921 --- CHANGELOG.md | 1 + src/helpers/Sanitize.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76827f7c..a04a8488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 🎨 Enhancements - [#915](https://github.com/estruyf/vscode-front-matter/issues/915): Added a new setting `frontMatter.panel.openOnSupportedFile` which allows you to open the panel view on supported files +- [#921](https://github.com/estruyf/vscode-front-matter/issues/921): Improve the filename sanitization ### ⚡️ Optimizations diff --git a/src/helpers/Sanitize.ts b/src/helpers/Sanitize.ts index 9162b944..c3b167b8 100644 --- a/src/helpers/Sanitize.ts +++ b/src/helpers/Sanitize.ts @@ -1,17 +1,23 @@ const illegalRe = (isFileName: boolean) => - isFileName ? /[/?<>\\:*|"!.,;{}[\]()+=~`@#$%^&]/g : /[/?<>\\:*|"!.,;{}[\]()_+=~`@#$%^&]/g; + isFileName ? /[/?<>\\:*|"!.,;{}[\]()+=~`@#$%^&']/g : /[/?<>\\:*|"!.,;{}[\]()_+=~`@#$%^&']/g; // eslint-disable-next-line no-control-regex const controlRe = /[\x00-\x1f\x80-\x9f]/g; const reservedRe = /^\.+$/; const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i; const windowsTrailingRe = /[. ]+$/; +function normalizeSpecialChars(input: string): string { + return input.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); +} + function sanitize(input: string, replacement: string, isFileName?: boolean) { if (typeof input !== 'string') { throw new Error('Input must be string'); } - const sanitized = input + const normalizedInput = normalizeSpecialChars(input); + + const sanitized = normalizedInput .replace(illegalRe(isFileName || false), replacement) .replace(controlRe, replacement) .replace(reservedRe, replacement) From 5e258ac21830dd0afff864eca6b79b13c41bf02d Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 15 Feb 2025 16:41:02 +0100 Subject: [PATCH 11/13] Feat: add file path support for slug generation and enhance slug template placeholders #922 --- CHANGELOG.md | 5 +---- src/commands/Article.ts | 21 ++++++++++++++++----- src/helpers/ArticleHelper.ts | 2 +- src/helpers/ContentType.ts | 3 ++- src/helpers/SlugHelper.ts | 14 +++++++++++++- src/helpers/processArticlePlaceholders.ts | 6 ++++-- src/listeners/panel/DataListener.ts | 4 +++- 7 files changed, 40 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a04a8488..55564f5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,11 @@ ## [10.8.0] - 2025-02-xx -### ✨ New features - ### 🎨 Enhancements - [#915](https://github.com/estruyf/vscode-front-matter/issues/915): Added a new setting `frontMatter.panel.openOnSupportedFile` which allows you to open the panel view on supported files - [#921](https://github.com/estruyf/vscode-front-matter/issues/921): Improve the filename sanitization - -### ⚡️ Optimizations +- [#922](https://github.com/estruyf/vscode-front-matter/issues/922): Added `{{fileName}}` and `{{sluggedFileName}}` placeholders for the slug template setting ### 🐞 Fixes diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 75d70a4a..837262e1 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -172,7 +172,12 @@ export class Article { /** * Generate the new slug */ - public static generateSlug(title: string, article?: ParsedFrontMatter, slugTemplate?: string) { + public static generateSlug( + title: string, + article?: ParsedFrontMatter, + filePath?: string, + slugTemplate?: string + ) { if (!title) { return; } @@ -181,7 +186,7 @@ export class Article { const suffix = Settings.get(SETTING_SLUG_SUFFIX) as string; if (article?.data) { - const slug = SlugHelper.createSlug(title, article?.data, slugTemplate); + const slug = SlugHelper.createSlug(title, article?.data, filePath, slugTemplate); if (typeof slug === 'string') { return { @@ -224,7 +229,12 @@ export class Article { articleDate ); - const slugInfo = Article.generateSlug(articleTitle, article, contentType.slugTemplate); + const slugInfo = Article.generateSlug( + articleTitle, + article, + editor.document.uri.fsPath, + contentType.slugTemplate + ); if ( slugInfo && @@ -255,7 +265,8 @@ export class Article { article.data[pField.name] = processArticlePlaceholdersFromData( article.data[pField.name], article.data, - contentType + contentType, + editor.document.uri.fsPath ); article.data[pField.name] = processTimePlaceholders( article.data[pField.name], @@ -335,7 +346,7 @@ export class Article { } else { const article = ArticleHelper.getFrontMatter(editor); if (article?.data) { - return SlugHelper.createSlug(article.data[titleField], article.data, slugTemplate); + return SlugHelper.createSlug(article.data[titleField], article.data, file, slugTemplate); } } } diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index e42e4251..9f96db7a 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -684,7 +684,7 @@ export class ArticleHelper { } if (fieldName === 'slug' && (fieldValue === null || fieldValue === '')) { - fmData[fieldName] = SlugHelper.createSlug(title, fmData, slugTemplate); + fmData[fieldName] = SlugHelper.createSlug(title, fmData, filePath, slugTemplate); } fmData[fieldName] = await processArticlePlaceholdersFromPath(fmData[fieldName], filePath); diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index dcfe4e81..14438c26 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -1063,7 +1063,8 @@ export class ContentType { data[field.name] = processArticlePlaceholdersFromData( field.default as string, data, - contentType + contentType, + filePath ); data[field.name] = processTimePlaceholders( data[field.name], diff --git a/src/helpers/SlugHelper.ts b/src/helpers/SlugHelper.ts index 5122be8d..0f020408 100644 --- a/src/helpers/SlugHelper.ts +++ b/src/helpers/SlugHelper.ts @@ -1,6 +1,7 @@ -import { Settings } from '.'; +import { parseWinPath, Settings } from '.'; import { stopWords, charMap, SETTING_DATE_FORMAT, SETTING_SLUG_TEMPLATE } from '../constants'; import { processTimePlaceholders, processFmPlaceholders } from '.'; +import { parse } from 'path'; export class SlugHelper { /** @@ -11,6 +12,7 @@ export class SlugHelper { public static createSlug( articleTitle: string, articleData: { [key: string]: any }, + filePath?: string, slugTemplate?: string ): string | null { if (!articleTitle) { @@ -28,6 +30,16 @@ export class SlugHelper { } else if (slugTemplate.includes('{{seoTitle}}')) { const regex = new RegExp('{{seoTitle}}', 'g'); slugTemplate = slugTemplate.replace(regex, SlugHelper.slugify(articleTitle)); + } else if (slugTemplate.includes(`{{fileName}}`)) { + const file = parse(filePath || ''); + const fileName = file.name; + const regex = new RegExp('{{fileName}}', 'g'); + slugTemplate = slugTemplate.replace(regex, fileName); + } else if (slugTemplate.includes(`{{sluggedFileName}}`)) { + const file = parse(filePath || ''); + const fileName = SlugHelper.slugify(file.name); + const regex = new RegExp('{{sluggedFileName}}', 'g'); + slugTemplate = slugTemplate.replace(regex, fileName); } const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string; diff --git a/src/helpers/processArticlePlaceholders.ts b/src/helpers/processArticlePlaceholders.ts index 816271d3..1cd98c6b 100644 --- a/src/helpers/processArticlePlaceholders.ts +++ b/src/helpers/processArticlePlaceholders.ts @@ -6,7 +6,8 @@ import { SlugHelper } from './SlugHelper'; export const processArticlePlaceholdersFromData = ( value: string, data: { [key: string]: any }, - contentType: ContentType + contentType: ContentType, + filePath?: string ): string => { const titleField = getTitleField(); if (value.includes('{{title}}') && data[titleField]) { @@ -18,7 +19,7 @@ export const processArticlePlaceholdersFromData = ( const regex = new RegExp('{{slug}}', 'g'); value = value.replace( regex, - SlugHelper.createSlug(data[titleField] || '', data, contentType.slugTemplate) || '' + SlugHelper.createSlug(data[titleField] || '', data, filePath, contentType.slugTemplate) || '' ); } @@ -50,6 +51,7 @@ export const processArticlePlaceholdersFromPath = async ( SlugHelper.createSlug( article.data[titleField] || '', article.data, + filePath, contentType.slugTemplate ) || '' ); diff --git a/src/listeners/panel/DataListener.ts b/src/listeners/panel/DataListener.ts index 15369a25..2ea7f904 100644 --- a/src/listeners/panel/DataListener.ts +++ b/src/listeners/panel/DataListener.ts @@ -794,7 +794,9 @@ export class DataListener extends BaseListener { const crntFile = window.activeTextEditor?.document; const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string; value = - data && contentType ? processArticlePlaceholdersFromData(value, data, contentType) : value; + data && contentType + ? processArticlePlaceholdersFromData(value, data, contentType, crntFile?.uri.fsPath) + : value; value = processTimePlaceholders(value, dateFormat); value = processFmPlaceholders(value, data); From ddefb9f138a135788c8191005ef0492d38907fc4 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 26 Feb 2025 20:28:52 +0100 Subject: [PATCH 12/13] Copilot testing --- src/services/Copilot.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/Copilot.ts b/src/services/Copilot.ts index dbfa14f0..017f79f8 100644 --- a/src/services/Copilot.ts +++ b/src/services/Copilot.ts @@ -263,7 +263,9 @@ Example: SEO, website optimization, digital marketing.` * @returns A Promise that resolves to the chat model. */ private static async getModel(retry = 0): Promise { - // const models = await lm.selectChatModels(); + // const models = await lm.selectChatModels({ + // vendor: 'copilot' + // }); // console.log(models); const [model] = await lm.selectChatModels({ vendor: 'copilot', From 1fa73efe110a01acd17bbc9bb1b2f76a5480f2a1 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 27 Feb 2025 11:59:28 +0100 Subject: [PATCH 13/13] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55564f5e..85e70030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## [10.8.0] - 2025-02-xx +## [10.8.0] - 2025-02-27 - [Release notes](https://beta.frontmatter.codes/updates/v10.8.0) ### 🎨 Enhancements