forked from iarv/vscode-front-matter
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b4e39b806 | |||
| 1fa73efe11 | |||
| ddefb9f138 | |||
| 5e258ac218 | |||
| d2b0228809 | |||
| a164a849da | |||
| 710ef136b4 | |||
| d3b7f73c66 | |||
| ee5af88851 | |||
| 482cbc3bf6 | |||
| 64f1da6355 | |||
| e27adececb | |||
| b391aa3270 | |||
| b58c02b6d0 |
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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) || [],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
) || ''
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user