Compare commits

..

14 Commits

Author SHA1 Message Date
Elio Struyf 1b4e39b806 Merge pull request #924 from estruyf/beta
v10.8.0 release
2025-02-27 12:09:01 +01:00
Elio Struyf 1fa73efe11 Updated changelog 2025-02-27 11:59:28 +01:00
Elio Struyf ddefb9f138 Copilot testing 2025-02-26 20:28:52 +01:00
Elio Struyf 5e258ac218 Feat: add file path support for slug generation and enhance slug template placeholders #922 2025-02-15 16:41:02 +01:00
Elio Struyf d2b0228809 Feat: improve filename sanitization and add normalization for special characters #921 2025-02-14 09:35:12 +01:00
Elio Struyf a164a849da Fix: add refresh button to media dashboard when custom scripts are defined 2025-02-12 15:01:13 +01:00
Elio Struyf 710ef136b4 Fix: improve media folder parsing on Windows and update path handling in various modules 2025-02-12 14:53:56 +01:00
Elio Struyf d3b7f73c66 Refactor: update import paths for parseWinPath and integrate it into joinUrl function 2025-02-12 12:41:22 +01:00
Elio Struyf ee5af88851 Fix: ensure Windows drive letters are lowercased and improve path parsing 2025-02-12 12:27:24 +01:00
Elio Struyf 482cbc3bf6 Issue: [[&mediaUrl]] placeholder in Media snippets is not relative #913 2025-02-06 11:40:22 +01:00
Elio Struyf 64f1da6355 Enhancement: auto switch to editor panel when opening a markdown file #915 2025-02-06 11:08:36 +01:00
Elio Struyf e27adececb Issue: "panel.gitActions" option in view modes is not present in the JSON schema #909 2025-02-06 10:24:43 +01:00
Elio Struyf b391aa3270 10.8.0 2025-02-06 10:20:39 +01:00
Elio Struyf b58c02b6d0 Issue: Stripping underscore in default filename #914 2025-02-06 10:20:31 +01:00
30 changed files with 223 additions and 48 deletions
+16
View File
@@ -1,5 +1,21 @@
# Change Log
## [10.8.0] - 2025-02-27 - [Release notes](https://beta.frontmatter.codes/updates/v10.8.0)
### 🎨 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
- [#922](https://github.com/estruyf/vscode-front-matter/issues/922): Added `{{fileName}}` and `{{sluggedFileName}}` placeholders for the slug template setting
### 🐞 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
## [10.7.0] - 2024-12-31 - [Release notes](https://beta.frontmatter.codes/updates/v10.7.0)
### 🎨 Enhancements
+3 -2
View File
@@ -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"
}
}
+2 -2
View File
@@ -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",
+9 -2
View File
@@ -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": {
@@ -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",
@@ -1213,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,
+2 -1
View File
@@ -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)"
}
}
+16 -5
View File
@@ -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);
}
}
}
+13 -3
View File
@@ -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;
}
}
@@ -636,15 +638,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) =>
parseWinPath(relative(parseWinPath(wsFolder.fsPath), folder))
);
Logger.verbose('Folders:getContentFolders:end');
return uniqueFolders.map((folder) => relative(wsFolder?.path || '', folder));
return relativeFolderPaths;
}
/**
+3 -3
View File
@@ -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: {
+1
View File
@@ -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';
@@ -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<IBooleanOptionProps> = ({
value,
name,
label
}: React.PropsWithChildren<IBooleanOptionProps>) => {
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 (
<VSCodeCheckbox
onChange={onChange}
checked={isChecked}>
{label}
</VSCodeCheckbox>
);
};
@@ -91,8 +91,9 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
if (scripts.length > 0) {
return (
<div className="flex flex-1 justify-start">
<div className="flex flex-1 justify-start space-x-2">
{renderPostAssetsButton}
<ChoiceButton
title={l10n.t(LocalizationKey.dashboardMediaFolderCreationFolderCreate)}
choices={scripts.map((s) => ({
@@ -103,6 +104,8 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
onClick={onFolderCreation}
disabled={!settings?.initialized}
/>
<RefreshDashboardData />
</div>
);
}
+11 -2
View File
@@ -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<IItemProps> = ({
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;
@@ -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<ICommonSettingsProps> = (pr
<Startup settings={settings} />
</div>
<div className='py-4'>
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsOpenPanelForSupportedFiles)}</h2>
<BooleanOption
label={l10n.t(LocalizationKey.settingsOpenPanelForSupportedFilesLabel)}
name={SETTING_PANEL_OPEN_ON_SUPPORTED_FILE}
value={settings?.openPanelForSupportedFiles} />
</div>
<div className='py-4'>
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsGit)}</h2>
+1
View File
@@ -31,6 +31,7 @@ export interface Settings {
categories: string[];
customTaxonomy: CustomTaxonomy[];
openOnStart: boolean | null;
openPanelForSupportedFiles: boolean | null;
versionInfo: VersionInfo;
pageViewType: DashboardViewType | undefined;
contentTypes: ContentType[];
+2 -1
View File
@@ -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();
}
};
+2 -2
View File
@@ -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
}`
);
@@ -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);
+2 -1
View File
@@ -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],
+4 -3
View File
@@ -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<DashboardViewType | undefined>(
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<string>(SETTING_FRAMEWORK_ID),
framework: !isInitialized && wsFolder ? await FrameworkDetector.get(wsFolder.fsPath) : null,
scripts: Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || [],
+2 -2
View File
@@ -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);
}
+1 -1
View File
@@ -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));
+15 -7
View File
@@ -1,17 +1,24 @@
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 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
.replace(illegalRe, replacement)
const normalizedInput = normalizeSpecialChars(input);
const sanitized = normalizedInput
.replace(illegalRe(isFileName || false), replacement)
.replace(controlRe, replacement)
.replace(reservedRe, replacement)
.replace(windowsReservedRe, replacement)
@@ -19,11 +26,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);
}
+13 -1
View File
@@ -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;
+13 -1
View File
@@ -1,3 +1,15 @@
import { isWindows } from '../utils/isWindows';
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;
};
+4 -2
View File
@@ -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
) || ''
);
+2 -1
View File
@@ -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);
}
+3 -1
View File
@@ -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);
+8
View File
@@ -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
*/
+17 -1
View File
@@ -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<void>} A promise that resolves when the command execution is complete.
*/
public static async openOnSupportedFile(): Promise<void> {
const openPanel = Settings.get<boolean>(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
+3 -1
View File
@@ -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<LanguageModelChat | undefined> {
// const models = await lm.selectChatModels();
// const models = await lm.selectChatModels({
// vendor: 'copilot'
// });
// console.log(models);
const [model] = await lm.selectChatModels({
vendor: 'copilot',
+2 -1
View File
@@ -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);
};