mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-06-27 21:41:31 +02:00
@@ -180,6 +180,11 @@
|
||||
"markdownDescription": "Specify if you want to open the dashboard when you start VS Code. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.openonstart)",
|
||||
"scope": "Dashboard"
|
||||
},
|
||||
"frontMatter.framework.id": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"markdownDescription": "Specify the ID of your static site generator or framework you are using for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.framework.id)"
|
||||
},
|
||||
"frontMatter.panel.freeform": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME } from '../constants';
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME, SETTINGS_FRAMEWORK_ID } from '../constants';
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { basename, dirname, extname, join, parse } from "path";
|
||||
import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env, Position } from "vscode";
|
||||
import { Settings as SettingsHelper } from '../helpers';
|
||||
import { TaxonomyType } from '../models';
|
||||
import { Framework, TaxonomyType } from '../models';
|
||||
import { Folders } from './Folders';
|
||||
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
|
||||
@@ -24,6 +24,7 @@ import { MediaLibrary } from '../helpers/MediaLibrary';
|
||||
import imageSize from 'image-size';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
import { FrameworkDetector } from '../helpers/FrameworkDetector';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
@@ -187,6 +188,9 @@ export class Dashboard {
|
||||
case DashboardMessage.createMediaFolder:
|
||||
await commands.executeCommand(COMMAND_NAME.createFolder, msg?.data);
|
||||
break;
|
||||
case DashboardMessage.setFramework:
|
||||
Dashboard.setFramework(msg?.data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -257,6 +261,8 @@ export class Dashboard {
|
||||
private static async getSettings() {
|
||||
const ext = Extension.getInstance();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
// const isInitialized = await Template.isInitialized();
|
||||
const isInitialized = false;
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.settings,
|
||||
@@ -265,7 +271,7 @@ export class Dashboard {
|
||||
wsFolder: wsFolder ? wsFolder.fsPath : '',
|
||||
staticFolder: SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDER),
|
||||
folders: Folders.get(),
|
||||
initialized: await Template.isInitialized(),
|
||||
initialized: isInitialized,
|
||||
tags: SettingsHelper.getTaxonomy(TaxonomyType.Tag),
|
||||
categories: SettingsHelper.getTaxonomy(TaxonomyType.Category),
|
||||
openOnStart: SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART),
|
||||
@@ -274,10 +280,30 @@ export class Dashboard {
|
||||
mediaSnippet: SettingsHelper.get<string[]>(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [],
|
||||
contentTypes: SettingsHelper.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
|
||||
contentFolders: Folders.get().map(f => f.path),
|
||||
crntFramework: SettingsHelper.get<string>(SETTINGS_FRAMEWORK_ID),
|
||||
framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null,
|
||||
} as Settings
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current site-generator or framework + related settings
|
||||
* @param frameworkId
|
||||
*/
|
||||
private static setFramework(frameworkId: string | null) {
|
||||
SettingsHelper.update(SETTINGS_FRAMEWORK_ID, frameworkId, true);
|
||||
|
||||
if (frameworkId) {
|
||||
const allFrameworks = FrameworkDetector.getAll();
|
||||
const framework = allFrameworks.find((f: Framework) => f.name === frameworkId);
|
||||
if (framework) {
|
||||
SettingsHelper.update(SETTINGS_CONTENT_STATIC_FOLDER, framework.static, true);
|
||||
} else {
|
||||
SettingsHelper.update(SETTINGS_CONTENT_STATIC_FOLDER, "", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a setting from the dashboard
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export const FrameworkDetectors = [
|
||||
{
|
||||
"framework": {"name": "gatsby", "dist": "public", "static": "static", "build": "gatsby build"},
|
||||
"requiredFiles": ["gatsby-config.js"],
|
||||
"requiredDependencies": ["gatsby"]
|
||||
},
|
||||
{
|
||||
"framework": {"name": "hugo", "dist": "public", "static": "static", "build": "hugo"},
|
||||
"requiredFiles": ["config.toml", "config.yaml", "config.yml"]
|
||||
},
|
||||
{
|
||||
"framework": {"name": "next", "dist": ".next", "static": "public", "build": "next build"},
|
||||
"requiredFiles": ["next.config.js"],
|
||||
"requiredDependencies": ["next"]
|
||||
},
|
||||
{
|
||||
"framework": {"name": "nuxt", "dist": "dist", "static": "static", "build": "nuxt"},
|
||||
"requiredFiles": ["nuxt.config.js"],
|
||||
"requiredDependencies": ["nuxt"]
|
||||
}
|
||||
];
|
||||
@@ -42,6 +42,8 @@ export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
|
||||
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
|
||||
export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
|
||||
|
||||
export const SETTINGS_FRAMEWORK_ID = "framework.id";
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@@ -17,5 +17,6 @@ export enum DashboardMessage {
|
||||
deleteMedia = 'deleteMedia',
|
||||
insertPreviewImage = 'insertPreviewImage',
|
||||
updateMediaMetadata = 'updateMediaMetadata',
|
||||
createMediaFolder = 'createMediaFolder'
|
||||
createMediaFolder = 'createMediaFolder',
|
||||
setFramework = 'setFramework',
|
||||
}
|
||||
@@ -4,22 +4,17 @@ import { Status } from '../../models/Status';
|
||||
|
||||
export interface IStepProps {
|
||||
name: string;
|
||||
description: string;
|
||||
description: JSX.Element;
|
||||
status: Status;
|
||||
showLine: boolean;
|
||||
onClick?: () => void;
|
||||
onClick?: () => void | undefined;
|
||||
}
|
||||
|
||||
export const Step: React.FunctionComponent<IStepProps> = ({name, description, status, showLine, onClick}: React.PropsWithChildren<IStepProps>) => {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
showLine ? (
|
||||
<div className={`-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full ${status === Status.Completed ? "bg-teal-600" : "bg-gray-300"}`} aria-hidden="true" />
|
||||
) : null
|
||||
}
|
||||
|
||||
<button className={`relative flex items-start group text-left ${onClick ? "" : "cursor-default"}`} onClick={() => { if (onClick) { onClick(); } }} disabled={!onClick}>
|
||||
const renderChildren = () => {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
status === Status.NotStarted && (
|
||||
<span className="h-9 flex items-center" aria-hidden="true">
|
||||
@@ -52,9 +47,31 @@ export const Step: React.FunctionComponent<IStepProps> = ({name, description, st
|
||||
|
||||
<span className="ml-4 min-w-0 flex flex-col">
|
||||
<span className="text-xs font-semibold tracking-wide uppercase text-vulcan-500 dark:text-whisper-500">{name}</span>
|
||||
<span className="text-sm text-vulcan-400 dark:text-whisper-600" dangerouslySetInnerHTML={{__html: description}} />
|
||||
<div className="text-sm text-vulcan-400 dark:text-whisper-600">{description}</div>
|
||||
</span>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
showLine ? (
|
||||
<div className={`-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full ${status === Status.Completed ? "bg-teal-600" : "bg-gray-300"}`} aria-hidden="true" />
|
||||
) : null
|
||||
}
|
||||
|
||||
{
|
||||
onClick ? (
|
||||
<button className={`relative flex items-start group text-left`} onClick={() => { if (onClick) { onClick(); } }} disabled={!onClick}>
|
||||
{renderChildren()}
|
||||
</button>
|
||||
) : (
|
||||
<div className="relative flex items-start group text-left">
|
||||
{renderChildren()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -4,36 +4,99 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Settings } from '../../models/Settings';
|
||||
import { Status } from '../../models/Status';
|
||||
import { Step } from './Step';
|
||||
import { useState } from 'react';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { MenuItem } from '../Menu';
|
||||
import { Framework } from '../../../models';
|
||||
import { ChevronDownIcon } from '@heroicons/react/outline';
|
||||
import { FrameworkDetectors } from '../../../constants/FrameworkDetectors';
|
||||
|
||||
export interface IStepsToGetStartedProps {
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps> = ({settings}: React.PropsWithChildren<IStepsToGetStartedProps>) => {
|
||||
const [framework, setFramework] = useState<string | null>(null);
|
||||
|
||||
const frameworks: Framework[] = FrameworkDetectors.map((detector: any) => detector.framework);
|
||||
|
||||
const setFrameworkAndSendMessage = (framework: string) => {
|
||||
setFramework(framework);
|
||||
Messenger.send(DashboardMessage.setFramework, framework);
|
||||
}
|
||||
|
||||
const steps = [
|
||||
{
|
||||
name: 'Initialize project',
|
||||
description: 'Initialize the project with a template folder and sample markdown file. The template folder can be used to define your own templates. <b>Start by clicking on this action</b>.',
|
||||
description: <>Initialize the project with a template folder and sample markdown file. The template folder can be used to define your own templates. <b>Start by clicking on this action</b>.</>,
|
||||
status: settings.initialized ? Status.Completed : Status.NotStarted,
|
||||
onClick: settings.initialized ? undefined : () => { Messenger.send(DashboardMessage.initializeProject); }
|
||||
},
|
||||
{
|
||||
name: 'Framework presets',
|
||||
description: (
|
||||
<div>
|
||||
<div>Select your site-generator or framework to prefill some of the recommended settings.</div>
|
||||
|
||||
<Menu as="div" className="relative inline-block text-left mt-4">
|
||||
<div>
|
||||
<Menu.Button className="group flex justify-center text-vulcan-500 hover:text-vulcan-600 dark:text-whisper-500 dark:hover:text-whisper-600 p-2 rounded-md border border-vulcan-400 dark:border-white">
|
||||
{framework ? framework : 'Select your framework'}
|
||||
<ChevronDownIcon
|
||||
className="flex-shrink-0 -mr-1 ml-1 h-5 w-5 text-gray-400 group-hover:text-gray-500 dark:text-whisper-600 dark:group-hover:text-whisper-700"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Menu.Items className={`w-40 origin-top-left absolute left-0 z-10 mt-2 rounded-md shadow-2xl bg-white dark:bg-vulcan-500 ring-1 ring-vulcan-400 dark:ring-white ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto`}>
|
||||
<div className="py-1">
|
||||
<MenuItem
|
||||
title={`other`}
|
||||
value={`other`}
|
||||
isCurrent={!framework}
|
||||
onClick={(value) => setFrameworkAndSendMessage(value)} />
|
||||
|
||||
<hr />
|
||||
|
||||
{frameworks.map((f) => (
|
||||
<MenuItem
|
||||
key={f.name}
|
||||
title={f.name}
|
||||
value={f.name}
|
||||
isCurrent={f.name === framework}
|
||||
onClick={(value) => setFrameworkAndSendMessage(value)} />
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Menu>
|
||||
</div>
|
||||
),
|
||||
status: settings.crntFramework ? Status.Completed : Status.NotStarted,
|
||||
onClick: undefined
|
||||
},
|
||||
{
|
||||
name: 'Register content folders (manual action)',
|
||||
description: 'Register your content folder(s). You can perform this action by right-clicking on the folder in the explorer view, and selecting <b>register folder</b>. Once a folder is set, Front Matter can be used to list all contents and allow you to create content.',
|
||||
description: <>Register your content folder(s). You can perform this action by right-clicking on the folder in the explorer view, and selecting <b>register folder</b>. Once a folder is set, Front Matter can be used to list all contents and allow you to create content.</>,
|
||||
status: settings.folders && settings.folders.length > 0 ? Status.Completed : Status.NotStarted
|
||||
},
|
||||
{
|
||||
name: 'Show the dashboard',
|
||||
description: 'Once both actions are completed, click on this action to load the dashboard.',
|
||||
description: <>Once both actions are completed, click on this action to load the dashboard.</>,
|
||||
status: (settings.initialized && settings.folders && settings.folders.length > 0) ? Status.Active : Status.NotStarted,
|
||||
onClick: (settings.initialized && settings.folders && settings.folders.length > 0) ? () => { Messenger.send(DashboardMessage.reload); } : undefined
|
||||
}
|
||||
];
|
||||
|
||||
React.useEffect(() => {
|
||||
if (settings.crntFramework) {
|
||||
setFramework(settings.crntFramework);
|
||||
}
|
||||
}, [settings.crntFramework]);
|
||||
|
||||
return (
|
||||
<nav aria-label="Progress">
|
||||
<ol role="list" className="overflow-hidden">
|
||||
<ol role="list">
|
||||
{steps.map((step, stepIdx) => (
|
||||
<li key={step.name} className={`${stepIdx !== steps.length - 1 ? 'pb-10' : ''} relative`}>
|
||||
<Step name={step.name} description={step.description} status={step.status} showLine={stepIdx !== steps.length - 1} onClick={step.onClick} />
|
||||
|
||||
@@ -33,11 +33,11 @@ export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({set
|
||||
</h1>
|
||||
|
||||
<p className="mt-3 text-base text-vulcan-300 dark:text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
|
||||
Thank you for taking the time to test out Front Matter!
|
||||
Thank you for using Front Matter!
|
||||
</p>
|
||||
|
||||
<p className="mt-3 text-base text-vulcan-300 dark:text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
|
||||
We try to aim to make Front Matter as easy to use as possible, but if you have any questions, please don't hesitate to reach out to us on GitHub.
|
||||
We try to aim to make Front Matter as easy to use as possible, but if you have any questions or suggestions. Please don't hesitate to reach out to us on GitHub.
|
||||
</p>
|
||||
|
||||
<div className="mt-5 w-full sm:mx-auto sm:max-w-lg lg:ml-0">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { VersionInfo } from '../../models/VersionInfo';
|
||||
import { ViewType } from '../state';
|
||||
import { ContentFolder } from '../../models/ContentFolder';
|
||||
import { ContentType } from '../../models';
|
||||
import { ContentType, Framework } from '../../models';
|
||||
|
||||
export interface Settings {
|
||||
beta: boolean;
|
||||
@@ -17,4 +17,6 @@ export interface Settings {
|
||||
mediaSnippet: string[];
|
||||
contentTypes: ContentType[];
|
||||
contentFolders: string[];
|
||||
crntFramework: string;
|
||||
framework: Framework | null | undefined;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { existsSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { FrameworkDetectors } from "../constants/FrameworkDetectors";
|
||||
import { Extension } from "./Extension";
|
||||
|
||||
export class FrameworkDetector {
|
||||
|
||||
public static get(folder: string) {
|
||||
return this.check(folder);
|
||||
}
|
||||
|
||||
public static getAll() {
|
||||
return FrameworkDetectors.map((detector: any) => detector.framework);
|
||||
}
|
||||
|
||||
private static check(folder: string) {
|
||||
const { dependencies, devDependencies } = Extension.getInstance().packageJson;
|
||||
|
||||
for (const detector of FrameworkDetectors) {
|
||||
if (detector && folder) {
|
||||
|
||||
// Verify by dependencies
|
||||
for (const dependency of detector.requiredDependencies ?? []) {
|
||||
const inDependencies = dependencies && dependencies[dependency]
|
||||
const inDevDependencies = devDependencies && devDependencies[dependency]
|
||||
if (inDependencies || inDevDependencies) {
|
||||
return detector.framework;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify by files
|
||||
for (const filename of detector.requiredFiles ?? []) {
|
||||
const fileExists = existsSync(resolve(folder, filename));
|
||||
if (fileExists) {
|
||||
return detector.framework;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
export interface Framework {
|
||||
name: string;
|
||||
dist: string;
|
||||
static: string;
|
||||
build: string;
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
export * from './Choice';
|
||||
export * from './ContentFolder';
|
||||
export * from './DashboardData';
|
||||
export * from './Framework';
|
||||
export * from './MediaPaths';
|
||||
export * from './PanelSettings';
|
||||
export * from './TaxonomyType';
|
||||
export * from './VersionInfo';
|
||||
|
||||
Reference in New Issue
Block a user