Compare commits

..

22 Commits

Author SHA1 Message Date
Elio Struyf d2ae94df34 Merge pull request #177 from estruyf/dev 2021-11-15 10:42:15 +01:00
Elio Struyf f799613b1a Update changelog 2021-11-15 10:38:46 +01:00
Elio Struyf 3f057a01d8 Removed char 2021-11-12 15:36:51 +01:00
Elio Struyf 3ad5136735 #173 - Persistent sorting 2021-11-12 12:11:46 +01:00
Elio Struyf e201ce6f83 updated changelog 2021-11-12 10:55:26 +01:00
Elio Struyf 383a3a7d4c Updated changelog 2021-11-10 15:19:12 +01:00
Elio Struyf 8261f1de1b #173 - Allow to specify your own sorting 2021-11-10 15:18:35 +01:00
Elio Struyf 5b38e6fa56 Updated changelog 2021-11-10 11:55:33 +01:00
Elio Struyf 717f34bc85 #174 - Enhancement for excluding sub-directories 2021-11-10 11:54:39 +01:00
Elio Struyf 47fb2a90a9 5.5.0 2021-11-10 11:16:24 +01:00
Elio Struyf 85a7221895 Updated flows 2021-11-09 10:02:38 +01:00
Elio Struyf 9618a89528 Merge pull request #169 from estruyf/dev 2021-11-05 09:25:09 +01:00
Elio Struyf 14f0af2754 Updated changelog 2021-11-05 09:24:26 +01:00
Elio Struyf ebe248670d 5.4.0 2021-11-03 12:00:56 +01:00
Elio Struyf 511960c4a9 #167 - Allow to set a preview path per content type 2021-11-03 12:00:49 +01:00
Elio Struyf 31fd1f93ce Url joining 2021-11-02 16:32:36 +01:00
Elio Struyf 6625b69170 #166 - Filename and folder logic for slug 2021-11-02 13:48:52 +01:00
Elio Struyf 9e8533fbb8 Updated changelog 2021-11-02 11:58:43 +01:00
Elio Struyf 9c9cbb7dcb #166 - Add preview button to panel when no markdown file is opened 2021-11-02 11:56:40 +01:00
Elio Struyf 079a13e161 Merge pull request #164 from estruyf/dev
Merge for 5.3.1
2021-10-29 10:25:17 +02:00
Elio Struyf 69c1e587d0 5.3.1 2021-10-29 10:18:31 +02:00
Elio Struyf 3996252531 #163 - Set workspace state instead of global 2021-10-29 10:18:20 +02:00
35 changed files with 354 additions and 74 deletions
+3
View File
@@ -25,3 +25,6 @@ jobs:
- name: Publish
run: npx vsce publish -p ${{ secrets.VSCE_PAT }} --baseImagesUrl https://raw.githubusercontent.com/estruyf/vscode-front-matter/dev
- name: Publish to open-vsx.org
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}
+2
View File
@@ -26,3 +26,5 @@ jobs:
- name: Publish
run: npx vsce publish -p ${{ secrets.VSCE_PAT }}
- name: Publish to open-vsx.org
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}
+22
View File
@@ -1,5 +1,27 @@
# Change Log
## [5.5.0] - 2021-11-15
As from this version onwards, the extension will be published to [open-vsx.org](https://open-vsx.org/).
### 🎨 Enhancements
- [#173](https://github.com/estruyf/vscode-front-matter/issues/173): Allow to specify your own sorting for the content dashboard
- [#174](https://github.com/estruyf/vscode-front-matter/issues/174): Added option to exclude sub-directories from page/markdown content retrieval
## [5.4.0] - 2021-11-05
### 🎨 Enhancements
- [#166](https://github.com/estruyf/vscode-front-matter/issues/166): Added preview button to the panel base view
- [#167](https://github.com/estruyf/vscode-front-matter/issues/167): Allow to set the preview path per content type
## [5.3.1] - 2021-10-29
### 🐞 Fixes
- [#163](https://github.com/estruyf/vscode-front-matter/issues/163): Setting workspace state instead of global state for the media view
## [5.3.0] - 2021-10-28 - [Release Notes](https://beta.frontmatter.codes/updates/v5.3.0)
### 🎨 Enhancements
+7 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vscode-front-matter-beta",
"version": "5.3.0",
"version": "5.5.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -6386,6 +6386,12 @@
}
}
},
"url-join-ts": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/url-join-ts/-/url-join-ts-1.0.5.tgz",
"integrity": "sha512-u+5gi7JyOwhj58ZKwkmkzFGHuepTpmwjqfUDGVjsJJstsCz63CJAINixgJaDcMbmuyWPJIxbtBpIfaDgOQ9KMQ==",
"dev": true
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+57 -1
View File
@@ -3,7 +3,7 @@
"displayName": "Front Matter",
"description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
"icon": "assets/frontmatter-teal-128x128.png",
"version": "5.3.0",
"version": "5.5.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -154,6 +154,11 @@
"path": {
"type": "string",
"description": "Path of the folder"
},
"excludeSubdir": {
"type": "boolean",
"default": false,
"description": "Exclude sub-directories"
}
},
"additionalProperties": false,
@@ -170,6 +175,48 @@
"markdownDescription": "Specify the folder name where all your assets are located. For instance in Hugo this is the `static` folder. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.publicfolder)",
"scope": "Content"
},
"frontMatter.content.sorting": {
"type": "object",
"default": [],
"markdownDescription": "Define the sorting options for your dashboard content. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.sorting)",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Name of the sorting label"
},
"name": {
"type": "string",
"description": "Name of the metadata field to sort by"
},
"order": {
"type": "string",
"enum": [
"asc",
"desc"
],
"description": "Order of the sorting"
},
"type": {
"type": "string",
"default": "string",
"enum": [
"string",
"date"
],
"description": "Type of the field value"
}
},
"additionalProperties": false,
"required": [
"title",
"name",
"order"
]
},
"scope": "Content"
},
"frontMatter.custom.scripts": {
"type": "array",
"default": [],
@@ -384,6 +431,14 @@
"type": "boolean",
"default": false,
"description": "Specify if you want to create a folder when creating new content."
},
"previewPath": {
"type": [
"null",
"string"
],
"default": null,
"description": "Defines a custom preview path for the content type."
}
},
"additionalProperties": false,
@@ -776,6 +831,7 @@
"ts-loader": "8.0.3",
"tslint": "6.1.3",
"typescript": "4.0.2",
"url-join-ts": "^1.0.5",
"wc-react": "github:estruyf/wc-react",
"webpack": "4.44.2",
"webpack-cli": "3.3.12"
+28 -2
View File
@@ -5,11 +5,12 @@ import { format } from "date-fns";
import { ArticleHelper, Settings, SlugHelper } from '../helpers';
import matter = require('gray-matter');
import { Notifications } from '../helpers/Notifications';
import { extname, basename } from 'path';
import { extname, basename, parse, dirname } from 'path';
import { COMMAND_NAME, DefaultFields } from '../constants';
import { DashboardData } from '../models/DashboardData';
import { ExplorerView } from '../explorerView/ExplorerView';
import { DateHelper } from '../helpers/DateHelper';
import { parseWinPath } from '../helpers/parseWinPath';
export class Article {
@@ -183,7 +184,7 @@ export class Article {
await vscode.workspace.fs.rename(editor.document.uri, vscode.Uri.file(newPath), {
overwrite: false
});
} catch (e) {
} catch (e: any) {
Notifications.error(`Failed to rename file: ${e?.message || e}`);
}
}
@@ -191,6 +192,31 @@ export class Article {
}
}
/**
* Retrieve the slug from the front matter
*/
public static getSlug() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const file = parseWinPath(editor.document.fileName);
if (!file.endsWith(`.md`) && !file.endsWith(`.mdx`)) {
return;
}
const parsedFile = parse(file);
if (parsedFile.name.toLowerCase() !== "index") {
return parsedFile.name;
}
const folderName = basename(dirname(file));
return folderName;
}
/**
* Toggle the page its draft mode
*/
+17 -8
View File
@@ -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, SETTINGS_FRAMEWORK_ID, SETTINGS_CONTENT_DRAFT_FIELD } 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, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING } 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 { DraftField, Framework, TaxonomyType } from '../models';
import { DraftField, Framework, SortingSetting, TaxonomyType } from '../models';
import { Folders } from './Folders';
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
@@ -39,7 +39,7 @@ export class Dashboard {
return Dashboard._viewData;
}
/** 
/**
* Init the dashboard
*/
public static async init() {
@@ -162,7 +162,7 @@ export class Dashboard {
}
break;
case DashboardMessage.setPageViewType:
Extension.getInstance().setState(ExtensionState.PagesView, msg.data);
Extension.getInstance().setState(ExtensionState.PagesView, msg.data, "workspace");
break;
case DashboardMessage.getMedia:
Dashboard.getMedia(msg?.data?.page, msg?.data?.folder);
@@ -192,6 +192,11 @@ export class Dashboard {
case DashboardMessage.setFramework:
Dashboard.setFramework(msg?.data);
break;
case DashboardMessage.setState:
if (msg?.data?.key && msg?.data?.value) {
Extension.getInstance().setState(msg?.data?.key, msg?.data?.value, "workspace");
}
break;
}
});
}
@@ -276,13 +281,17 @@ export class Dashboard {
categories: SettingsHelper.getTaxonomy(TaxonomyType.Category),
openOnStart: SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART),
versionInfo: ext.getVersion(),
pageViewType: await ext.getState<ViewType | undefined>(ExtensionState.PagesView),
pageViewType: await ext.getState<ViewType | undefined>(ExtensionState.PagesView, "workspace"),
mediaSnippet: SettingsHelper.get<string[]>(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [],
contentTypes: SettingsHelper.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
draftField: SettingsHelper.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
contentFolders: Folders.get().map(f => f.path),
customSorting: SettingsHelper.get<SortingSetting[]>(SETTINGS_CONTENT_SORTING),
contentFolders: Folders.get(),
crntFramework: SettingsHelper.get<string>(SETTINGS_FRAMEWORK_ID),
framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null,
dashboardState: {
sorting: await ext.getState<ViewType | undefined>(ExtensionState.Dashboard.Sorting, "workspace")
}
} as Settings
});
}
@@ -325,7 +334,7 @@ export class Dashboard {
// If the static folder is not set, retreive the last opened location
if (!selectedFolder) {
const stateValue = await Extension.getInstance().getState<string | undefined>(ExtensionState.SelectedFolder);
const stateValue = await Extension.getInstance().getState<string | undefined>(ExtensionState.SelectedFolder, "workspace");
if (stateValue !== HOME_PAGE_NAVIGATION_ID) {
// Support for page bundles
@@ -440,7 +449,7 @@ export class Dashboard {
}
// Store the last opened folder
await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder);
await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder, "workspace");
Dashboard.postWebviewMessage({
command: DashboardCommand.media,
+6 -4
View File
@@ -210,8 +210,8 @@ export class Folders {
if (projectStart) {
projectStart = projectStart.replace(/\\/g, '/');
projectStart = projectStart.startsWith('/') ? projectStart.substr(1) : projectStart;
const mdFiles = await workspace.findFiles(join(projectStart, '**/*.md'));
const mdxFiles = await workspace.findFiles(join(projectStart, '**/*.mdx'));
const mdFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md'));
const mdxFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx'));
let files = [...mdFiles, ...mdxFiles];
if (files) {
let fileStats: FileInfo[] = [];
@@ -263,7 +263,7 @@ export class Folders {
const folders: ContentFolder[] = Settings.get(SETTINGS_CONTENT_PAGE_FOLDERS) as ContentFolder[];
return folders.map(folder => ({
title: folder.title,
...folder,
path: Folders.absWsFolder(folder, wsFolder)
}));
}
@@ -274,10 +274,12 @@ export class Folders {
*/
private static async update(folders: ContentFolder[]) {
const wsFolder = Folders.getWorkspaceFolder();
let folderDetails = folders.map(folder => ({
title: folder.title,
...folder,
path: Folders.relWsFolder(folder, wsFolder)
}));
await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folderDetails, true);
}
+21 -6
View File
@@ -6,6 +6,8 @@ import { Settings } from '../helpers';
import { PreviewSettings } from '../models';
import { format } from 'date-fns';
import { DateHelper } from '../helpers/DateHelper';
import { Article } from '.';
import { urlJoin } from 'url-join-ts';
export class Preview {
@@ -32,12 +34,25 @@ export class Preview {
const article = editor ? ArticleHelper.getFrontMatter(editor) : null;
let slug = article?.data ? article.data.slug : "";
if (settings.pathname) {
let pathname = settings.pathname;
if (article?.data) {
const contentType = ArticleHelper.getContentType(article.data);
if (contentType && contentType.previewPath) {
pathname = contentType.previewPath;
}
}
if (!slug) {
slug = Article.getSlug();
}
if (pathname) {
const articleDate = ArticleHelper.getDate(article);
try {
slug = join(format(articleDate || new Date(), DateHelper.formatUpdate(settings.pathname) as string), slug);
slug = join(format(articleDate || new Date(), DateHelper.formatUpdate(pathname) as string), slug);
} catch (error) {
slug = join(settings.pathname, slug);
slug = join(pathname, slug);
}
}
@@ -113,9 +128,9 @@ export class Preview {
</head>
<body>
<div class="slug">
<input type="text" value="${join(localhostUrl.toString(), slug)}" disabled />
<input type="text" value="${urlJoin(localhostUrl.toString(), slug || '')}" disabled />
</div>
<iframe src="${join(localhostUrl.toString(), slug)}" >
<iframe src="${urlJoin(localhostUrl.toString(), slug || '')}" >
</body>
</html>`;
}
@@ -132,4 +147,4 @@ export class Preview {
pathname
};
}
}
}
+1
View File
@@ -5,6 +5,7 @@ export const DEFAULT_CONTENT_TYPE_NAME = 'default';
export const DEFAULT_CONTENT_TYPE: ContentType = {
"name": "default",
"pageBundle": false,
"previewPath": null,
"fields": [
{
"title": "Title",
+2 -1
View File
@@ -2,5 +2,6 @@
export const DefaultFields = {
PublishingDate: `date`,
LastModified: `lastmod`,
Description: `description`
Description: `description`,
Slug: `slug`
};
+4
View File
@@ -5,4 +5,8 @@ export const ExtensionState = {
Version: `frontMatter:Version`,
SettingPromoted: `frontMatter:Settings:Promoted`,
MoveTemplatesFolder: `frontMatter:Templates:Move`,
Dashboard: {
Sorting: `frontMatter:Dashboard:Sorting`,
}
};
+1
View File
@@ -40,6 +40,7 @@ export const SETTINGS_CONTENT_PAGE_FOLDERS = "content.pageFolders";
export const SETTINGS_CONTENT_STATIC_FOLDER = "content.publicFolder";
export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
export const SETTINGS_CONTENT_DRAFT_FIELD = "content.draftField";
export const SETTINGS_CONTENT_SORTING = "content.sorting";
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
+1
View File
@@ -19,4 +19,5 @@ export enum DashboardMessage {
updateMediaMetadata = 'updateMediaMetadata',
createMediaFolder = 'createMediaFolder',
setFramework = 'setFramework',
setState = 'setState',
}
@@ -31,9 +31,10 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (props: Rea
return false;
}
}
1
for (let i = 0; i < contentFolders.length; i++) {
const contentFolder = parseWinPath(contentFolders[i]) as string;
const folder = contentFolders[i];
const contentFolder = parseWinPath(folder.path) as string;
const relContentPath = folderPath.replace(contentFolder, '');
return relContentPath.length > 1 && folderPath.startsWith(contentFolder);
}
@@ -1,7 +1,7 @@
import { XCircleIcon } from '@heroicons/react/solid';
import * as React from 'react';
import { useRecoilValue, useResetRecoilState } from 'recoil';
import { SortingSelector, FolderSelector, TagSelector, CategorySelector, SortingAtom, DEFAULT_SORTING_OPTION, FolderAtom, DEFAULT_FOLDER_STATE, TagAtom, CategoryAtom, DEFAULT_TAG_STATE, DEFAULT_CATEGORY_STATE } from '../../state';
import { FolderSelector, TagSelector, CategorySelector, SortingAtom, FolderAtom, DEFAULT_FOLDER_STATE, TagAtom, CategoryAtom, DEFAULT_TAG_STATE, DEFAULT_CATEGORY_STATE } from '../../state';
import { DefaultValue } from 'recoil';
@@ -17,7 +17,6 @@ export interface IClearFiltersProps {}
export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (props: React.PropsWithChildren<IClearFiltersProps>) => {
const [ show, setShow ] = React.useState(false);
const sorting = useRecoilValue(SortingSelector);
const folder = useRecoilValue(FolderSelector);
const tag = useRecoilValue(TagSelector);
const category = useRecoilValue(CategorySelector);
@@ -36,19 +35,19 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (props:
};
React.useEffect(() => {
if (sorting !== DEFAULT_SORTING_OPTION || folder !== DEFAULT_FOLDER_STATE || tag !== DEFAULT_TAG_STATE || category !== DEFAULT_CATEGORY_STATE) {
if (folder !== DEFAULT_FOLDER_STATE || tag !== DEFAULT_TAG_STATE || category !== DEFAULT_CATEGORY_STATE) {
setShow(true);
} else {
setShow(false);
}
}, [sorting, folder, tag, category]);
}, [folder, tag, category]);
if (!show) return null;
return (
<button className="flex items-center hover:text-teal-600" onClick={reset} title={`Clear filters, grouping, and sorting`}>
<XCircleIcon className={`inline-block w-5 h-5 mr-1`} /><span>Clear</span>
<span className={`sr-only`}> filters, grouping, and sorting</span>
<span className={`sr-only`}> filters and grouping</span>
</button>
);
};
@@ -1,19 +1,19 @@
import { Menu } from '@headlessui/react';
import * as React from 'react';
import { useRecoilState } from 'recoil';
import { FolderAtom } from '../../state';
import { useRecoilState, useRecoilValue } from 'recoil';
import { FolderAtom, SettingsSelector } from '../../state';
import { MenuButton, MenuItem, MenuItems } from '../Menu';
export interface IFoldersProps {
folders: string[];
}
export interface IFoldersProps {}
const DEFAULT_TYPE = "All types";
export const Folders: React.FunctionComponent<IFoldersProps> = ({folders}: React.PropsWithChildren<IFoldersProps>) => {
export const Folders: React.FunctionComponent<IFoldersProps> = ({}: React.PropsWithChildren<IFoldersProps>) => {
const [ crntFolder, setCrntFolder ] = useRecoilState(FolderAtom);
const settings = useRecoilValue(SettingsSelector);
const contentFolders = settings?.contentFolders || [];
if (folders.length <= 1) {
if (contentFolders.length <= 1) {
return null;
}
@@ -29,12 +29,12 @@ export const Folders: React.FunctionComponent<IFoldersProps> = ({folders}: React
isCurrent={!crntFolder}
onClick={(value) => setCrntFolder(value)} />
{folders.map((option) => (
{contentFolders.map((option) => (
<MenuItem
key={option}
title={option}
value={option}
isCurrent={option === crntFolder}
key={option.title}
title={option.title}
value={option.title}
isCurrent={option.title === crntFolder}
onClick={(value) => setCrntFolder(value)} />
))}
</MenuItems>
@@ -98,7 +98,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
<div className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end space-x-4 lg:space-x-6 xl:space-x-8 bg-gray-200 border-b border-gray-300 dark:bg-vulcan-400 dark:border-vulcan-100`}>
<ClearFilters />
<Folders folders={folders || []} />
<Folders />
<Filter label={`Tag`} activeItem={crntTag} items={settings?.tags || []} onClick={(value) => setCrntTag(value)} />
@@ -1,37 +1,64 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { Menu } from '@headlessui/react';
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { ExtensionState } from '../../../constants';
import { SortOrder, SortType } from '../../../models';
import { SortOption } from '../../constants/SortOption';
import { SearchSelector, SortingAtom } from '../../state';
import { DashboardMessage } from '../../DashboardMessage';
import { SortingOption } from '../../models/SortingOption';
import { SearchSelector, SettingsSelector, SortingAtom } from '../../state';
import { MenuButton, MenuItem, MenuItems } from '../Menu';
export interface ISortingProps {}
export const sortOptions = [
{ name: "Last modified", id: SortOption.LastModified },
{ name: "By filename (asc)", id: SortOption.FileNameAsc },
{ name: "By filename (desc)", id: SortOption.FileNameDesc },
export const sortOptions: SortingOption[] = [
{ name: "Last modified", id: SortOption.LastModified, order: SortOrder.desc, type: SortType.string },
{ name: "By filename (asc)", id: SortOption.FileNameAsc, order: SortOrder.asc, type: SortType.string },
{ name: "By filename (desc)", id: SortOption.FileNameDesc, order: SortOrder.desc, type: SortType.string },
];
export const Sorting: React.FunctionComponent<ISortingProps> = ({}: React.PropsWithChildren<ISortingProps>) => {
const [ crntSorting, setCrntSorting ] = useRecoilState(SortingAtom);
const searchValue = useRecoilValue(SearchSelector);
const settings = useRecoilValue(SettingsSelector);
const crntSort = sortOptions.find(x => x.id === crntSorting);
const updateSorting = (value: SortingOption) => {
Messenger.send(DashboardMessage.setState, {
key: ExtensionState.Dashboard.Sorting,
value: value
})
setCrntSorting(value)
};
let allOptions = [...sortOptions];
if (settings?.customSorting) {
allOptions = [...allOptions, ...settings.customSorting.map((s) => ({
title: s.title || s.name,
name: s.name,
id: `${s.name}-${s.order}`,
order: s.order,
type: s.type
}))];
}
let crntSort = allOptions.find(x => x.id === crntSorting?.id) || sortOptions[0];
return (
<div className="flex items-center">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton label={`Sort by`} title={crntSort?.name || ""} disabled={!!searchValue} />
<MenuButton label={`Sort by`} title={crntSort?.title || crntSort?.name || ""} disabled={!!searchValue} />
<MenuItems>
{sortOptions.map((option) => (
{allOptions.map((option) => (
<MenuItem
key={option.id}
title={option.name}
value={option.id}
isCurrent={option.id === crntSorting}
onClick={(value) => setCrntSorting(value)} />
title={option.title || option.name}
value={option}
isCurrent={option.id === crntSorting?.id}
onClick={(value) => updateSorting(value)} />
))}
</MenuItems>
</Menu>
+3 -3
View File
@@ -1,5 +1,5 @@
export enum SortOption {
LastModified = 1,
FileNameAsc,
FileNameDesc
LastModified = "LastModified",
FileNameAsc = "FileNameAsc",
FileNameDesc = "FileNameDesc"
}
+56 -8
View File
@@ -3,8 +3,10 @@ import { SortOption } from '../constants/SortOption';
import { Tab } from '../constants/Tab';
import { Page } from '../models/Page';
import Fuse from 'fuse.js';
import { useRecoilValue } from 'recoil';
import { CategorySelector, FolderSelector, SearchSelector, SettingsSelector, SortingSelector, TabSelector, TagSelector } from '../state';
import { useRecoilState, useRecoilValue } from 'recoil';
import { CategorySelector, FolderSelector, SearchSelector, SettingsSelector, SortingAtom, TabSelector, TagSelector } from '../state';
import { SortOrder, SortType } from '../../models';
import { DateHelper } from '../../helpers/DateHelper';
const fuseOptions: Fuse.IFuseOptions<Page> = {
keys: [
@@ -16,16 +18,48 @@ const fuseOptions: Fuse.IFuseOptions<Page> = {
export default function usePages(pages: Page[]) {
const [ pageItems, setPageItems ] = useState<Page[]>([]);
const [ sorting, setSorting ] = useRecoilState(SortingAtom);
const settings = useRecoilValue(SettingsSelector);
const tab = useRecoilValue(TabSelector);
const sorting = useRecoilValue(SortingSelector);
const folder = useRecoilValue(FolderSelector);
const search = useRecoilValue(SearchSelector);
const tag = useRecoilValue(TagSelector);
const category = useRecoilValue(CategorySelector);
// Sort field value alphabetically
const sortAlphabetically = (property: string) => {
return (a: Page, b: Page) => {
if (a[property] < b[property]) {
return -1;
}
if (a[property] > b[property]) {
return 1;
}
return 0;
};
};
// Sort by date
const sortByDate = (property: string) => {
return (a: Page, b: Page) => {
const dateA = DateHelper.tryParse(a[property]);
const dateB = DateHelper.tryParse(b[property]);
return (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime();
};
};
useEffect(() => {
const draftField = settings?.draftField;
let usedSorting = sorting;
if (!usedSorting) {
const lastSort = settings?.dashboardState.sorting;
if (lastSort) {
setSorting(lastSort);
return;
}
}
// Check if search needs to be performed
let searchedPages = pages;
@@ -58,12 +92,26 @@ export default function usePages(pages: Page[]) {
// Sort the pages
let pagesSorted: Page[] = Object.assign([], pagesToShow);
if (!search) {
if (sorting === SortOption.FileNameAsc) {
pagesSorted = pagesToShow.sort((a, b) => a.fmFileName.toLowerCase().localeCompare(b.fmFileName.toLowerCase()));
} else if (sorting === SortOption.FileNameDesc) {
pagesSorted = pagesToShow.sort((a, b) => b.fmFileName.toLowerCase().localeCompare(a.fmFileName.toLowerCase()));
if (sorting && sorting.id === SortOption.FileNameAsc) {
pagesSorted = pagesSorted.sort(sortAlphabetically("fmFileName"));
} else if (sorting && sorting.id === SortOption.FileNameDesc) {
pagesSorted = pagesSorted.sort(sortAlphabetically("fmFileName")).reverse();
} else if (sorting && sorting.id === SortOption.LastModified) {
pagesSorted = pagesSorted.sort((a, b) => b.fmModified - a.fmModified);
} else if (sorting && sorting.id && sorting.name) {
const { order, name, type } = sorting;
if (type === SortType.string) {
pagesSorted = pagesSorted.sort(sortAlphabetically(name));
} else if (type === SortType.date) {
pagesSorted = pagesSorted.sort(sortByDate(name));
}
if (order === SortOrder.desc) {
pagesSorted = pagesSorted.reverse();
}
} else {
pagesSorted = pagesToShow.sort((a, b) => b.fmModified - a.fmModified);
pagesSorted = pagesSorted.sort((a, b) => b.fmModified - a.fmModified);
}
}
+9 -2
View File
@@ -1,7 +1,8 @@
import { VersionInfo } from '../../models/VersionInfo';
import { ViewType } from '../state';
import { ContentFolder } from '../../models/ContentFolder';
import { ContentType, DraftField, Framework } from '../../models';
import { ContentType, DraftField, Framework, SortingSetting } from '../../models';
import { SortingOption } from './SortingOption';
export interface Settings {
beta: boolean;
@@ -16,8 +17,14 @@ export interface Settings {
pageViewType: ViewType | undefined;
mediaSnippet: string[];
contentTypes: ContentType[];
contentFolders: string[];
contentFolders: ContentFolder[];
crntFramework: string;
framework: Framework | null | undefined;
draftField: DraftField | null | undefined;
customSorting: SortingSetting[] | undefined;
dashboardState: DashboardState;
}
export interface DashboardState {
sorting: SortingOption | null | undefined;
}
@@ -0,0 +1,10 @@
import { SortOrder, SortType } from "../../models";
import { SortOption } from "../constants/SortOption";
export interface SortingOption {
title?: string;
name: string;
id: SortOption | string;
order: SortOrder,
type: SortType;
}
@@ -1,9 +1,10 @@
import { atom } from 'recoil';
import { SortOption } from '../../constants/SortOption';
import { SortingOption } from '../../models/SortingOption';
export const DEFAULT_SORTING_OPTION = SortOption.LastModified;
export const SortingAtom = atom<SortOption>({
export const SortingAtom = atom<SortingOption | null>({
key: 'SortingAtom',
default: DEFAULT_SORTING_OPTION
default: null
});
+9
View File
@@ -270,6 +270,15 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
}
// Check slug
if (!updatedMetadata[DefaultFields.Slug]) {
const slug = Article.getSlug();
if (slug) {
updatedMetadata[DefaultFields.Slug] = slug;
}
}
this.postWebviewMessage({ command: Command.metadata, data: {
...updatedMetadata
+3 -1
View File
@@ -91,11 +91,13 @@ export class ArticleHelper {
}
}
}
return matter.stringify(content, data, ({
...TomlEngine,
...langOpts,
noArrayIndent: !indentArray,
skipInvalid: true,
noCompatMode: true,
lineWidth: 500,
indent: spaces || 2
} as DumpOptions as any));
+1 -1
View File
@@ -115,7 +115,7 @@ export class Extension {
const projectFolder = basename(workspace?.fsPath || "");
const paths = folders.map((folder: any) => ({
title: folder.title,
...folder,
path: `${WORKSPACE_PLACEHOLDER}${folder.fsPath.split(projectFolder).slice(1).join('')}`.split('\\').join('/')
}));
+2 -2
View File
@@ -38,7 +38,7 @@ export class Settings {
* Check if the setting is present in the workspace and ask to promote them to the global settings
*/
public static async checkToPromote() {
const isPromoted = await Extension.getInstance().getState<boolean | undefined>(ExtensionState.SettingPromoted);
const isPromoted = await Extension.getInstance().getState<boolean | undefined>(ExtensionState.SettingPromoted, "workspace");
if (!isPromoted) {
if (Settings.hasSettings()) {
window.showInformationMessage(`You have local settings. Would you like to promote them to the global settings ("frontmatter.json")?`, 'Yes', 'No').then(async (result) => {
@@ -47,7 +47,7 @@ export class Settings {
}
if (result === "No" || result === "Yes") {
Extension.getInstance().setState(ExtensionState.SettingPromoted, true);
Extension.getInstance().setState(ExtensionState.SettingPromoted, true, "workspace");
}
});
}
+2
View File
@@ -1,4 +1,6 @@
export interface ContentFolder {
title: string;
path: string;
excludeSubdir?: boolean;
}
+1
View File
@@ -25,6 +25,7 @@ export interface ContentType {
name: string;
fields: Field[];
previewPath?: string | null;
pageBundle?: boolean;
}
+4
View File
@@ -0,0 +1,4 @@
export enum SortOrder {
asc = 'asc',
desc = 'desc'
}
+4
View File
@@ -0,0 +1,4 @@
export enum SortType {
string = 'string',
date = 'date'
}
+8
View File
@@ -0,0 +1,8 @@
import { SortOrder, SortType } from ".";
export interface SortingSetting {
title: string;
name: string;
order: SortOrder;
type: SortType;
}
+3
View File
@@ -5,5 +5,8 @@ export * from './DraftField';
export * from './Framework';
export * from './MediaPaths';
export * from './PanelSettings';
export * from './SortOrder';
export * from './SortType';
export * from './SortingSetting';
export * from './TaxonomyType';
export * from './VersionInfo';
+5
View File
@@ -27,6 +27,10 @@ const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings, folderAndF
MessageHelper.sendMessage(CommandToCode.createContent);
};
const openPreview = () => {
MessageHelper.sendMessage(CommandToCode.openPreview);
};
return (
<div className="frontmatter">
<div className={`ext_actions`}>
@@ -37,6 +41,7 @@ const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings, folderAndF
<button onClick={openDashboard}>Open dashboard</button>
<button onClick={initProject} disabled={settings?.isInitialized}>Initialize project</button>
<button onClick={createContent} disabled={!settings?.isInitialized}>Create new content</button>
<button onClick={openPreview} disabled={!settings?.preview?.host}>Open site preview</button>
</div>
</Collapsible>