diff --git a/CHANGELOG.md b/CHANGELOG.md index 52d4c8da..385dda11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ## [2.4.0] - 2020-08-xx - [#21](https://github.com/estruyf/vscode-front-matter/issues/21): Folding provider for Front Matter implemented +- [#55](https://github.com/estruyf/vscode-front-matter/issues/55): Highlight Front Matter in Markdown files +- [#56](https://github.com/estruyf/vscode-front-matter/issues/56): Action to collapse all Front Matter panel sections at once +- [#57](https://github.com/estruyf/vscode-front-matter/issues/57): New action added to provide better writing settings (only for Markdown files) +- [#58](https://github.com/estruyf/vscode-front-matter/issues/58): Sections remember their previous state (folded/unfolded) +- [#59](https://github.com/estruyf/vscode-front-matter/issues/59): Center layout view toggle action added ## [2.3.0] - 2020-08-10 diff --git a/assets/icons/close-dark.svg b/assets/icons/close-dark.svg new file mode 100644 index 00000000..78205361 --- /dev/null +++ b/assets/icons/close-dark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/icons/close-light.svg b/assets/icons/close-light.svg new file mode 100644 index 00000000..d56e3091 --- /dev/null +++ b/assets/icons/close-light.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/media/styles.css b/assets/media/styles.css index d006584d..141bf2f3 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -278,10 +278,21 @@ .ext_link_block { display: flex; align-items: center; + width: 100%; } .ext_link_block svg { margin-right: .5rem; + display: block; + width: 16px; + height: 16px; + min-width: 16px; +} + +.ext_link_block button span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .ext_link_block button, @@ -301,6 +312,16 @@ user-select: none; text-decoration: none; width: 100%; + white-space: nowrap; +} + +.ext_link_block button.active { + color: var(--vscode-button-foreground); + background: var(--vscode-button-background); +} +.ext_link_block button.active:hover { + cursor: pointer; + background: var(--vscode-button-hoverBackground); } .ext_link_block a:hover, diff --git a/package.json b/package.json index e2ba3d44..3ec9767b 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "onCommand:frontMatter.unregisterFolder", "onCommand:frontMatter.createContent", "onCommand:frontMatter.init", + "onCommand:frontMatter.collapseSections", "onView:frontMatter.explorer" ], "main": "./dist/extension", @@ -268,6 +269,15 @@ "command": "frontMatter.createTemplate", "title": "Create a template from current file", "category": "Front matter" + }, + { + "command": "frontMatter.collapseSections", + "title": "Collapse sections", + "category": "Front matter", + "icon": { + "light": "assets/icons/close-light.svg", + "dark": "assets/icons/close-dark.svg" + } } ], "menus": { @@ -296,8 +306,16 @@ { "command": "frontMatter.createTemplate", "when": "!frontMatterCanInit" + }, + { + "command": "frontMatter.collapseSections", + "when": "false" } - ] + ], + "view/title": [{ + "command": "frontMatter.collapseSections", + "group": "navigation" + }] }, "grammars": [ { diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index 4645c3cb..f063df2f 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -20,5 +20,6 @@ export const COMMAND_NAME = { registerFolder: getCommandName("registerFolder"), unregisterFolder: getCommandName("unregisterFolder"), createContent: getCommandName("createContent"), - createTemplate: getCommandName("createTemplate") + createTemplate: getCommandName("createTemplate"), + collapseSections: getCommandName("collapseSections"), }; \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index be50bd3b..f9ca56ad 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -26,6 +26,7 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension } }); + // Folding the front matter of markdown files vscode.languages.registerFoldingRangeProvider(mdSelector, new MarkdownFoldingProvider()); let insertTags = vscode.commands.registerCommand(COMMAND_NAME.insertTags, async () => { @@ -73,7 +74,7 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension if (folderPath) { Template.create(folderPath); } - }); + }); let createTemplate = vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate); @@ -96,6 +97,11 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension Template.init(); const projectInit = vscode.commands.registerCommand(COMMAND_NAME.init, Project.init); + // Collapse all sections in the webview + const collapseAll = vscode.commands.registerCommand(COMMAND_NAME.collapseSections, () => { + ExplorerView.getInstance()?.collapseAll(); + }); + // Things to do when configuration changes vscode.workspace.onDidChangeConfiguration(() => { Template.init(); @@ -137,7 +143,8 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension registerFolder, unregisterFolder, createContent, - projectInit + projectInit, + collapseAll ); } diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 45917630..0d13f0ed 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -9,6 +9,7 @@ export interface PanelSettings { isInitialized: boolean; modifiedDateUpdate: boolean; contentInfo: FolderInfo[] | null; + writingSettingsEnabled: boolean; } export interface SEO { diff --git a/src/providers/FrontMatterDecorationProvider.ts b/src/providers/FrontMatterDecorationProvider.ts new file mode 100644 index 00000000..0c15de3b --- /dev/null +++ b/src/providers/FrontMatterDecorationProvider.ts @@ -0,0 +1,14 @@ +import { TextEditorDecorationType, window, ColorThemeKind } from "vscode"; + + +export class FrontMatterDecorationProvider { + + get(): TextEditorDecorationType { + const colorThemeKind = window.activeColorTheme.kind; + + return window.createTextEditorDecorationType({ + backgroundColor: colorThemeKind === ColorThemeKind.Dark ? "#ffffff14" : "#00000014", + isWholeLine: true, + }); + } +} \ No newline at end of file diff --git a/src/providers/MarkdownFoldingProvider.ts b/src/providers/MarkdownFoldingProvider.ts index 5053ef86..ee87c042 100644 --- a/src/providers/MarkdownFoldingProvider.ts +++ b/src/providers/MarkdownFoldingProvider.ts @@ -1,4 +1,5 @@ -import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, FoldingRangeProvider, TextDocument } from 'vscode'; +import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, FoldingRangeProvider, Range, TextDocument, window, TextEditorDecorationType, Position } from 'vscode'; +import { FrontMatterDecorationProvider } from './FrontMatterDecorationProvider'; export class MarkdownFoldingProvider implements FoldingRangeProvider { @@ -16,11 +17,17 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider { start = i; } else if (start !== null && end === null) { end = i; + + const range = new Range(new Position(start, 0), new Position(end, line.length)); + const decoration = new FrontMatterDecorationProvider().get(); + window.activeTextEditor?.setDecorations(decoration, [range]); + ranges.push(new FoldingRange(start, end, FoldingRangeKind.Region)); return ranges; } } } + return ranges; } } \ No newline at end of file diff --git a/src/viewpanel/Command.ts b/src/viewpanel/Command.ts index 33ce40d3..1ab79dbc 100644 --- a/src/viewpanel/Command.ts +++ b/src/viewpanel/Command.ts @@ -3,5 +3,6 @@ export enum Command { metadata = "metadata", settings = "settings", focusOnTags = "focusOnTags", - focusOnCategories = "focusOnCategories" + focusOnCategories = "focusOnCategories", + closeSections = "closeSections", } \ No newline at end of file diff --git a/src/viewpanel/CommandToCode.ts b/src/viewpanel/CommandToCode.ts index eab2f4af..aba7eeea 100644 --- a/src/viewpanel/CommandToCode.ts +++ b/src/viewpanel/CommandToCode.ts @@ -17,4 +17,6 @@ export enum CommandToCode { createContent = "create-content", updateModifiedUpdating = "update-modified-updates", createTemplate = "create-template", + toggleCenterMode = "toggle-center-mode", + toggleWritingSettings = "toggle-writing-settings", } \ No newline at end of file diff --git a/src/viewpanel/ViewPanel.tsx b/src/viewpanel/ViewPanel.tsx index 1423a91d..40d46fc3 100644 --- a/src/viewpanel/ViewPanel.tsx +++ b/src/viewpanel/ViewPanel.tsx @@ -43,7 +43,7 @@ export const ViewPanel: React.FunctionComponent = (props: React settings && metadata && } - + { } @@ -78,7 +78,7 @@ export const ViewPanel: React.FunctionComponent = (props: React } - + ); diff --git a/src/viewpanel/components/ActionButton.tsx b/src/viewpanel/components/ActionButton.tsx new file mode 100644 index 00000000..799a88d4 --- /dev/null +++ b/src/viewpanel/components/ActionButton.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; + +export interface IActionButtonProps { + title: string; + className?: string; + disabled?: boolean; + onClick: (e: React.SyntheticEvent) => void; +} + +export const ActionButton: React.FunctionComponent = ({className, onClick, disabled,title}: React.PropsWithChildren) => { + return ( + + {title} + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Actions.tsx b/src/viewpanel/components/Actions.tsx index 22d2ae2f..61d85024 100644 --- a/src/viewpanel/components/Actions.tsx +++ b/src/viewpanel/components/Actions.tsx @@ -1,5 +1,8 @@ import * as React from 'react'; import { PanelSettings } from '../../models/PanelSettings'; +import { CommandToCode } from '../CommandToCode'; +import { MessageHelper } from '../helper/MessageHelper'; +import { ActionButton } from './ActionButton'; import { Collapsible } from './Collapsible'; import { CustomScript } from './CustomScript'; import { DateAction } from './DateAction'; @@ -19,8 +22,11 @@ export const Actions: React.FunctionComponent = (props: React.Pro } return ( - + + + MessageHelper.sendMessage(CommandToCode.toggleCenterMode)} title={`Toggle center mode`} /> + { metadata && metadata.title && } diff --git a/src/viewpanel/components/BaseView.tsx b/src/viewpanel/components/BaseView.tsx index d33b2058..56412990 100644 --- a/src/viewpanel/components/BaseView.tsx +++ b/src/viewpanel/components/BaseView.tsx @@ -23,9 +23,9 @@ export const BaseView: React.FunctionComponent = ({settings}: Re return ( - + - + Initialize project Create new content @@ -34,7 +34,7 @@ export const BaseView: React.FunctionComponent = ({settings}: Re { settings?.contentInfo && ( - + { settings.contentInfo.map(folder => ( @@ -48,7 +48,7 @@ export const BaseView: React.FunctionComponent = ({settings}: Re ) } - + ); diff --git a/src/viewpanel/components/Collapsible.tsx b/src/viewpanel/components/Collapsible.tsx index d905540f..75522084 100644 --- a/src/viewpanel/components/Collapsible.tsx +++ b/src/viewpanel/components/Collapsible.tsx @@ -1,14 +1,22 @@ import * as React from 'react'; +import { useEffect } from 'react'; +import { Command } from '../Command'; import { VsCollapsible } from './VscodeComponents'; export interface ICollapsibleProps { + id: string; title: string; className?: string; sendUpdate?: (open: boolean) => void; } -export const Collapsible: React.FunctionComponent = ({children, title, sendUpdate, className}: React.PropsWithChildren) => { - const [ isOpen, setIsOpen ] = React.useState(true); +export const Collapsible: React.FunctionComponent = ({id, children, title, sendUpdate, className}: React.PropsWithChildren) => { + const [ isOpen, setIsOpen ] = React.useState(false); + const collapseKey = `collapse-${id}`; + + const updateStorage = (value: boolean) => { + window.localStorage.setItem(collapseKey, value.toString()); + } // This is a work around for a lit-element issue of duplicate slot names const triggerClick = (e: React.MouseEvent) => { @@ -17,11 +25,29 @@ export const Collapsible: React.FunctionComponent = ({childre if (sendUpdate) { sendUpdate(!prev); } + + updateStorage(!prev); return !prev; }); } } + useEffect(() => { + const collapsed = window.localStorage.getItem(collapseKey); + if (collapsed === null || collapsed === 'true') { + setIsOpen(true); + updateStorage(true); + } + + window.addEventListener('message', event => { + const message = event.data; + if (message.command === Command.closeSections) { + setIsOpen(false); + updateStorage(false); + } + }); + }, ['']); + return ( diff --git a/src/viewpanel/components/CustomScript.tsx b/src/viewpanel/components/CustomScript.tsx index bffa4f9d..e8b16514 100644 --- a/src/viewpanel/components/CustomScript.tsx +++ b/src/viewpanel/components/CustomScript.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../helper/MessageHelper'; +import { ActionButton } from './ActionButton'; export interface ICustomScriptProps { title: string; @@ -14,8 +15,6 @@ export const CustomScript: React.FunctionComponent = ({title }; return ( - - {title} - + ); }; \ No newline at end of file diff --git a/src/viewpanel/components/DateAction.tsx b/src/viewpanel/components/DateAction.tsx index 27c49aeb..dd6033af 100644 --- a/src/viewpanel/components/DateAction.tsx +++ b/src/viewpanel/components/DateAction.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../helper/MessageHelper'; +import { ActionButton } from './ActionButton'; export interface IDateActionProps {} @@ -16,12 +17,8 @@ export const DateAction: React.FunctionComponent = (props: Rea return ( <> - - Set publish date - - - Set modified date - + + > ); }; \ No newline at end of file diff --git a/src/viewpanel/components/GlobalSettings.tsx b/src/viewpanel/components/GlobalSettings.tsx index 2bac74f6..1cf1c94e 100644 --- a/src/viewpanel/components/GlobalSettings.tsx +++ b/src/viewpanel/components/GlobalSettings.tsx @@ -7,9 +7,10 @@ import { VsCheckbox } from './VscodeComponents'; export interface IGlobalSettingsProps { settings: PanelSettings | undefined; + isBase?: boolean; } -export const GlobalSettings: React.FunctionComponent = ({settings}: React.PropsWithChildren) => { +export const GlobalSettings: React.FunctionComponent = ({settings, isBase}: React.PropsWithChildren) => { const { modifiedDateUpdate } = settings || {}; const onCheck = () => { @@ -18,7 +19,7 @@ export const GlobalSettings: React.FunctionComponent = ({s return ( <> - + diff --git a/src/viewpanel/components/Icons/WritingIcon.tsx b/src/viewpanel/components/Icons/WritingIcon.tsx new file mode 100644 index 00000000..dc6a2081 --- /dev/null +++ b/src/viewpanel/components/Icons/WritingIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface IWritingIconProps {} + +export const WritingIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/OtherActionButton.tsx b/src/viewpanel/components/OtherActionButton.tsx new file mode 100644 index 00000000..a9641fea --- /dev/null +++ b/src/viewpanel/components/OtherActionButton.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +export interface IOtherActionButtonProps { + className?: string; + disabled?: boolean; + onClick: (e: React.SyntheticEvent) => void; +} + +export const OtherActionButton: React.FunctionComponent = ({ className, disabled, onClick, children}: React.PropsWithChildren) => { + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/OtherActions.tsx b/src/viewpanel/components/OtherActions.tsx index c2bbc80a..0d963ec7 100644 --- a/src/viewpanel/components/OtherActions.tsx +++ b/src/viewpanel/components/OtherActions.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../helper/MessageHelper'; import { Collapsible } from './Collapsible'; @@ -7,12 +8,16 @@ import { FileIcon } from './Icons/FileIcon'; import { FolderOpenedIcon } from './Icons/FolderOpenedIcon'; import { SettingsIcon } from './Icons/SettingsIcon'; import { TemplateIcon } from './Icons/TemplateIcon'; +import { WritingIcon } from './Icons/WritingIcon'; +import { OtherActionButton } from './OtherActionButton'; export interface IOtherActionsProps { isFile: boolean; + settings: PanelSettings | undefined; + isBase?: boolean; } -export const OtherActions: React.FunctionComponent = ({isFile}: React.PropsWithChildren) => { +export const OtherActions: React.FunctionComponent = ({isFile, settings, isBase}: React.PropsWithChildren) => { const openSettings = () => { MessageHelper.sendMessage(CommandToCode.openSettings); @@ -30,27 +35,25 @@ export const OtherActions: React.FunctionComponent = ({isFil MessageHelper.sendMessage(CommandToCode.createTemplate); }; + const toggleWritingSettings = () => { + MessageHelper.sendMessage(CommandToCode.toggleWritingSettings); + }; + return ( <> - - - Create as template - + + {settings?.writingSettingsEnabled ? "Writing settings enabled" : "Enable writing settings"} + + Create as template + + Open settings + + Reveal file in folder + + Reveal project folder - Open settings - - - - Reveal file in folder - - - - Reveal project folder - - - - Report an issue + Report an issue > diff --git a/src/viewpanel/components/PublishAction.tsx b/src/viewpanel/components/PublishAction.tsx index 74c76a11..746b3a19 100644 --- a/src/viewpanel/components/PublishAction.tsx +++ b/src/viewpanel/components/PublishAction.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../helper/MessageHelper'; +import { ActionButton } from './ActionButton'; export interface IPublishActionProps { draft: boolean; @@ -16,8 +17,6 @@ export const PublishAction: React.FunctionComponent = (prop }; return ( - - {draft ? "Publish" : "Revert to draft"} - + ); }; \ No newline at end of file diff --git a/src/viewpanel/components/SeoStatus.tsx b/src/viewpanel/components/SeoStatus.tsx index 93411bbf..407b562c 100644 --- a/src/viewpanel/components/SeoStatus.tsx +++ b/src/viewpanel/components/SeoStatus.tsx @@ -88,7 +88,7 @@ export const SeoStatus: React.FunctionComponent = (props: React }, [title, data[descriptionField], data?.articleDetails?.wordCount]); return ( - setIsOpen(value)}> + setIsOpen(value)}> { renderContent() } ); diff --git a/src/viewpanel/components/SlugAction.tsx b/src/viewpanel/components/SlugAction.tsx index d4088317..83d3b9e1 100644 --- a/src/viewpanel/components/SlugAction.tsx +++ b/src/viewpanel/components/SlugAction.tsx @@ -3,6 +3,7 @@ import { SlugHelper } from '../../helpers/SlugHelper'; import { Slug } from '../../models/PanelSettings'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../helper/MessageHelper'; +import { ActionButton } from './ActionButton'; export interface ISlugActionProps { value: string; @@ -21,8 +22,6 @@ export const SlugAction: React.FunctionComponent = (props: Rea }; return ( - - Optimize slug - + ); }; \ No newline at end of file diff --git a/src/webview/ExplorerView.ts b/src/webview/ExplorerView.ts index 3de5c1f6..84f562da 100644 --- a/src/webview/ExplorerView.ts +++ b/src/webview/ExplorerView.ts @@ -152,6 +152,12 @@ export class ExplorerView implements WebviewViewProvider, Disposable { case CommandToCode.updateModifiedUpdating: this.updateModifiedUpdating(msg.data || false); break; + case CommandToCode.toggleWritingSettings: + this.toggleWritingSettings(); + break; + case CommandToCode.toggleCenterMode: + await commands.executeCommand(`workbench.action.toggleCenteredLayout`); + break; } }); @@ -201,6 +207,13 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } } + /** + * Trigger all sections to close + */ + public collapseAll() { + this.postWebviewMessage({ command: Command.closeSections }); + } + /** * Run a custom script * @param msg @@ -269,7 +282,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable { scripts: config.get(SETTING_CUSTOM_SCRIPTS), isInitialized: await Template.isInitialized(), contentInfo: await Folders.getInfo() || null, - modifiedDateUpdate: config.get(SETTING_AUTO_UPDATE_DATE) || false + modifiedDateUpdate: config.get(SETTING_AUTO_UPDATE_DATE) || false, + writingSettingsEnabled: this.isWritingSettingsEnabled() || false } as PanelSettings }); } @@ -383,6 +397,43 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } } + /** + * Toggle the writing settings + */ + private async toggleWritingSettings() { + const config = workspace.getConfiguration("", { languageId: "markdown" }); + const enabled = this.isWritingSettingsEnabled(); + + await config.update("editor.fontSize", enabled ? undefined : 14, false, true); + await config.update("editor.lineHeight", enabled ? undefined : 26, false, true); + await config.update("editor.wordWrap", enabled ? undefined : "wordWrapColumn", false, true); + await config.update("editor.wordWrapColumn", enabled ? undefined : 64, false, true); + await config.update("editor.lineNumbers", enabled ? undefined : "off", false, true); + await config.update("editor.quickSuggestions", enabled ? undefined : false, false, true); + await config.update("editor.minimap.enabled", enabled ? undefined : false, false, true); + + this.getSettings(); + } + + /** + * Check if the writing settings are enabled + */ + private isWritingSettingsEnabled() { + const config = workspace.getConfiguration("", { languageId: "markdown" }); + + const fontSize = config.get("editor.fontSize"); + const lineHeight = config.get("editor.lineHeight"); + const wordWrap = config.get("editor.wordWrap"); + const wordWrapColumn = config.get("editor.wordWrapColumn"); + const lineNumbers = config.get("editor.lineNumbers"); + const quickSuggestions = config.get("editor.quickSuggestions"); + + return fontSize && lineHeight && wordWrap && wordWrapColumn && lineNumbers && quickSuggestions !== undefined; + } + + /** + * Toggle the modified auto-update setting + */ private async updateModifiedUpdating(autoUpdate: boolean) { const config = workspace.getConfiguration(CONFIG_KEY); await config.update(SETTING_AUTO_UPDATE_DATE, autoUpdate); @@ -397,7 +448,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable { this.panel!.webview.postMessage(msg); } - + /** + * Generate a unique nonce + */ private getNonce() { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';