mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
||||
# Change Log
|
||||
|
||||
## [10.5.1] - 2024-10-23
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#873](https://github.com/estruyf/vscode-front-matter/issues/873): Add retry logic to get the AI model for calling GitHub Copilot
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#872](https://github.com/estruyf/vscode-front-matter/issues/872): Check the default field value as well for the field's `when` clause
|
||||
- [#874](https://github.com/estruyf/vscode-front-matter/issues/874): Fix media snippet markup insertion to article content's
|
||||
- [#875](https://github.com/estruyf/vscode-front-matter/issues/875): Clean up the exclamation marks from the file name when creating new content
|
||||
|
||||
## [10.5.0] - 2024-10-21 - [Release notes](https://beta.frontmatter.codes/updates/v10.5.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
@@ -131,7 +131,8 @@
|
||||
}
|
||||
|
||||
.article__tags__dropbox.open {
|
||||
border: 1px solid rgba(0, 0, 0, 0.9);
|
||||
border: 1px solid var(--vscode-focusBorder);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.article__tags ul {
|
||||
|
||||
671
package-lock.json
generated
671
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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.5.0",
|
||||
"version": "10.5.1",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
SETTING_MEDIA_SUPPORTED_MIMETYPES
|
||||
} from '../constants';
|
||||
import { SortingOption } from '../dashboardWebView/models';
|
||||
import { MediaInfo, MediaPaths, SortOrder, SortType } from '../models';
|
||||
import { BlockFieldData, MediaInfo, MediaPaths, SortOrder, SortType } from '../models';
|
||||
import { basename, join, parse, dirname, relative } from 'path';
|
||||
import { statSync } from 'fs';
|
||||
import { Uri, workspace, window, Position } from 'vscode';
|
||||
@@ -376,7 +376,18 @@ export class MediaHelpers {
|
||||
* Insert an image into the front matter or contents
|
||||
* @param data
|
||||
*/
|
||||
public static async insertMediaToMarkdown(data: any) {
|
||||
public static async insertMediaToMarkdown(data: {
|
||||
file: string;
|
||||
relPath: string;
|
||||
snippet: string;
|
||||
position: Position;
|
||||
title?: string;
|
||||
alt?: string;
|
||||
caption?: string;
|
||||
fieldName: string;
|
||||
parents: string[];
|
||||
blockData: BlockFieldData;
|
||||
}) {
|
||||
if (data?.file && data?.relPath) {
|
||||
await EditorHelper.showFile(data.file);
|
||||
Dashboard.resetViewData();
|
||||
@@ -444,7 +455,7 @@ export class MediaHelpers {
|
||||
const docType = Wysiwyg.getDocType(filePath);
|
||||
|
||||
let snippet = data.snippet || '';
|
||||
if (!data.Snippet) {
|
||||
if (!snippet) {
|
||||
if (docType === 'markdown') {
|
||||
snippet = `${isFile ? '' : '!'}[${caption}](${FrameworkDetector.relAssetPathUpdate(
|
||||
relPath,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const illegalRe = /[/?<>\\:*|"]/g;
|
||||
const illegalRe = /[/?<>\\:*|"!.,;{}[\]()_+=~`@#$%^&]/g;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const controlRe = /[\x00-\x1F\x80-\x9F]/g;
|
||||
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
const reservedRe = /^\.+$/;
|
||||
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
const windowsTrailingRe = /[. ]+$/;
|
||||
@@ -9,6 +9,7 @@ function sanitize(input: string, replacement: string) {
|
||||
if (typeof input !== 'string') {
|
||||
throw new Error('Input must be string');
|
||||
}
|
||||
|
||||
const sanitized = input
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement)
|
||||
|
||||
@@ -115,7 +115,12 @@ export class DataListener extends BaseListener {
|
||||
}
|
||||
|
||||
private static async copilotSuggestTitle(command: string, requestId?: string, title?: string) {
|
||||
if (!command || !requestId || !title) {
|
||||
if (!command || !requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
this.sendRequestError(command, requestId, 'No title provided');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ export interface IFieldCollectionProps {
|
||||
parentFields: string[],
|
||||
blockData?: BlockFieldData,
|
||||
onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void,
|
||||
parentBlock?: string | null
|
||||
parentBlock?: string | null,
|
||||
triggerUpdateOnDefault?: boolean
|
||||
) => (JSX.Element | null)[] | undefined;
|
||||
onChange: (field: string | undefined, value: any, parents: string[]) => void;
|
||||
}
|
||||
|
||||
@@ -55,8 +55,10 @@ export interface IWrapperFieldProps {
|
||||
parentFields: string[],
|
||||
blockData?: BlockFieldData,
|
||||
onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void,
|
||||
parentBlock?: string | null
|
||||
parentBlock?: string | null,
|
||||
triggerUpdateOnDefault?: boolean
|
||||
) => (JSX.Element | null)[] | undefined;
|
||||
triggerUpdateOnDefault?: boolean;
|
||||
}
|
||||
|
||||
export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
@@ -72,7 +74,8 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
parentBlock,
|
||||
onSendUpdate,
|
||||
unsetFocus,
|
||||
renderFields
|
||||
renderFields,
|
||||
triggerUpdateOnDefault
|
||||
}: React.PropsWithChildren<IWrapperFieldProps>) => {
|
||||
const [fieldValue, setFieldValue] = useState<any | undefined>(undefined);
|
||||
const [customFields, setCustomFields] = useState<{
|
||||
@@ -110,7 +113,9 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
value = getDate(value) || null;
|
||||
}
|
||||
|
||||
//onSendUpdate(field.name, value, parentFields);
|
||||
if (triggerUpdateOnDefault) {
|
||||
onSendUpdate(field.name, value, parentFields);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the field value contains a placeholder
|
||||
@@ -140,7 +145,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [field, parent]);
|
||||
}, [field, parent, triggerUpdateOnDefault]);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.fmExternal && window.fmExternal.getCustomFields) {
|
||||
@@ -437,7 +442,9 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
subMetadata,
|
||||
[...parentFields, field.name],
|
||||
blockData,
|
||||
onSendUpdate
|
||||
onSendUpdate,
|
||||
null,
|
||||
true
|
||||
)}
|
||||
</div>
|
||||
</FieldBoundary>
|
||||
|
||||
@@ -61,7 +61,8 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({
|
||||
blockData?: BlockFieldData,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void,
|
||||
parentBlock?: string | null
|
||||
parentBlock?: string | null,
|
||||
triggerUpdateOnDefault?: boolean
|
||||
): (JSX.Element | null)[] | undefined => {
|
||||
if (!ctFields || !settings) {
|
||||
return;
|
||||
@@ -83,6 +84,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({
|
||||
onSendUpdate={onFieldUpdate || onSendUpdate}
|
||||
unsetFocus={unsetFocus}
|
||||
renderFields={renderFields}
|
||||
triggerUpdateOnDefault={triggerUpdateOnDefault}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export default function useDropdownStyle(inputRef: React.MutableRefObject<HTMLInputElement | null>, inputHeight?: string) {
|
||||
const bottomStyle = `calc(100% - ${inputHeight || '38px'})`;
|
||||
const bottomStyle = `calc(100% - ${inputHeight || '5px'})`;
|
||||
const listItemHeight = 28;
|
||||
|
||||
const getDropdownStyle = useCallback((isOpen) => {
|
||||
|
||||
@@ -839,6 +839,8 @@ vscode-divider {
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
|
||||
border-radius: 0 0.25rem 0.25rem 0;
|
||||
|
||||
&:disabled {
|
||||
background: none;
|
||||
filter: brightness(100%);
|
||||
@@ -947,6 +949,8 @@ vscode-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 0 0.25rem 0.25rem 0;
|
||||
|
||||
span {
|
||||
margin-right: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
LanguageModelChatResponse,
|
||||
extensions,
|
||||
lm,
|
||||
version as VscodeVersion
|
||||
version as VscodeVersion,
|
||||
LanguageModelChat
|
||||
} from 'vscode';
|
||||
import { Logger, Settings, TaxonomyHelper } from '../helpers';
|
||||
import {
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
} from '../constants';
|
||||
import { TagType } from '../panelWebView/TagType';
|
||||
import { TaxonomyType } from '../models';
|
||||
import { sleep } from '../utils';
|
||||
|
||||
export class Copilot {
|
||||
private static personality =
|
||||
@@ -51,7 +53,7 @@ export class Copilot {
|
||||
LanguageModelChatMessage.User(`The title of the blog post is """${title}""".`)
|
||||
];
|
||||
|
||||
const chatResponse = await this.getChatResponse(messages);
|
||||
const chatResponse = await Copilot.getChatResponse(messages);
|
||||
if (!chatResponse) {
|
||||
return;
|
||||
}
|
||||
@@ -100,7 +102,7 @@ Response format: a single string wrapped in double quotes, e.g., "Boost your web
|
||||
);
|
||||
}
|
||||
|
||||
const chatResponse = await this.getChatResponse(messages);
|
||||
const chatResponse = await Copilot.getChatResponse(messages);
|
||||
|
||||
if (!chatResponse) {
|
||||
return;
|
||||
@@ -179,7 +181,7 @@ Example: SEO, website optimization, digital marketing.`
|
||||
);
|
||||
}
|
||||
|
||||
const chatResponse = await this.getChatResponse(messages);
|
||||
const chatResponse = await Copilot.getChatResponse(messages);
|
||||
|
||||
if (!chatResponse) {
|
||||
return;
|
||||
@@ -211,6 +213,9 @@ Example: SEO, website optimization, digital marketing.`
|
||||
|
||||
try {
|
||||
const model = await this.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
chatResponse = await model.sendRequest(messages, {}, new CancellationTokenSource().token);
|
||||
} catch (err) {
|
||||
Logger.error(`Copilot:getChatResponse:: ${(err as Error).message}`);
|
||||
@@ -229,7 +234,7 @@ Example: SEO, website optimization, digital marketing.`
|
||||
* Retrieves the chat model for the Copilot service.
|
||||
* @returns A Promise that resolves to the chat model.
|
||||
*/
|
||||
private static async getModel() {
|
||||
private static async getModel(retry = 0): Promise<LanguageModelChat | undefined> {
|
||||
// const models = await lm.selectChatModels();
|
||||
// console.log(models);
|
||||
const [model] = await lm.selectChatModels({
|
||||
@@ -237,6 +242,11 @@ Example: SEO, website optimization, digital marketing.`
|
||||
family: Settings.get<string>(SETTING_COPILOT_FAMILY) || 'gpt-4o-mini'
|
||||
});
|
||||
|
||||
if ((!model || !model.sendRequest) && retry <= 5) {
|
||||
await sleep(1000);
|
||||
return Copilot.getModel(retry + 1);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,17 @@ export const fieldWhenClause = (field: Field, parent: IMetadata, allFields?: Fie
|
||||
}
|
||||
}
|
||||
|
||||
const whenValue = parent[when.fieldRef];
|
||||
let whenValue = parent[when.fieldRef];
|
||||
|
||||
// If the value is not yet set, check if the field has a default value.
|
||||
if (
|
||||
typeof whenValue === 'undefined' &&
|
||||
parentField &&
|
||||
typeof parentField.default !== 'undefined'
|
||||
) {
|
||||
whenValue = parentField.default as string | IMetadata | string[] | null;
|
||||
}
|
||||
|
||||
if (when.caseSensitive || typeof when.caseSensitive === 'undefined') {
|
||||
return caseSensitive(when, field, whenValue);
|
||||
} else {
|
||||
@@ -61,7 +71,7 @@ const caseInsensitive = (
|
||||
*/
|
||||
const caseSensitive = (
|
||||
when: WhenClause,
|
||||
field: Field,
|
||||
_: Field,
|
||||
whenValue: string | IMetadata | string[] | null
|
||||
) => {
|
||||
switch (when.operator) {
|
||||
|
||||
@@ -20,6 +20,7 @@ export * from './readdirAsync';
|
||||
export * from './renameAsync';
|
||||
export * from './rmdirAsync';
|
||||
export * from './sentryInit';
|
||||
export * from './sleep';
|
||||
export * from './sortPages';
|
||||
export * from './unlinkAsync';
|
||||
export * from './writeFileAsync';
|
||||
|
||||
1
src/utils/sleep.ts
Normal file
1
src/utils/sleep.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
Reference in New Issue
Block a user