diff --git a/CHANGELOG.md b/CHANGELOG.md index 0615fd2e..1580caca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [8.1.0] - 2022-xx-xx +### ✨ New features + +- [#377](https://github.com/estruyf/vscode-front-matter/issues/377): Git sync actions added on panel and content dashboard (pull and push your changes to remote) + ### 🎨 Enhancements - [#352](https://github.com/estruyf/vscode-front-matter/issues/352): Custom placeholders now support scripting @@ -11,6 +15,8 @@ ### ⚡️ Optimizations +- Internal post message optimizations to the webviews + ### 🐞 Fixes - [#378](https://github.com/estruyf/vscode-front-matter/issues/378): Fix last modified update only to content in content folders diff --git a/assets/media/styles.css b/assets/media/styles.css index f13676f6..0f0492bf 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -41,7 +41,8 @@ } .collapsible__body, -.ext_settings { +.ext_settings, +.git_actions { padding: 1rem 1.25rem; box-sizing: border-box; } diff --git a/package-lock.json b/package-lock.json index f716d25e..012e7989 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "recoil": "^0.4.1", "rimraf": "^3.0.2", "semver": "^7.3.7", + "simple-git": "^3.10.0", "style-loader": "2.0.0", "tailwindcss": "^2.2.7", "tailwindcss-nested-groups": "^1.2.4", @@ -217,6 +218,21 @@ "integrity": "sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg==", "dev": true }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true + }, "node_modules/@microsoft/fast-element": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.7.0.tgz", @@ -8800,6 +8816,21 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-git": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.10.0.tgz", + "integrity": "sha512-2w35xrS5rVtAW0g67LqtxCZN5cdddz/woQRfS0OJXaljXEoTychZ4jnE+CQgra/wX4ZvHeiChTUMenCwfIYEYw==", + "dev": true, + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -11023,6 +11054,21 @@ "integrity": "sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg==", "dev": true }, + "@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "requires": { + "debug": "^4.1.1" + } + }, + "@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true + }, "@microsoft/fast-element": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.7.0.tgz", @@ -17608,6 +17654,17 @@ "simple-concat": "^1.0.0" } }, + "simple-git": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.10.0.tgz", + "integrity": "sha512-2w35xrS5rVtAW0g67LqtxCZN5cdddz/woQRfS0OJXaljXEoTychZ4jnE+CQgra/wX4ZvHeiChTUMenCwfIYEYw==", + "dev": true, + "requires": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.4" + } + }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", diff --git a/package.json b/package.json index 7b79caa3..94b88749 100644 --- a/package.json +++ b/package.json @@ -642,6 +642,16 @@ "default": null, "markdownDescription": "Specify the command you want to use to start your static site generator or framework. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.framework.startcommand)" }, + "frontMatter.git.actions": { + "type": "boolean", + "markdownDescription": "Specify if you want to use the Git actions for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.git.actions)", + "default": false + }, + "frontMatter.git.commitMsg": { + "type": "string", + "markdownDescription": "Specify the commit message you want to use for the sync. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.git.commitmsg)", + "default": "Synced by Front Matter" + }, "frontMatter.global.activeMode": { "type": [ "string", @@ -2038,6 +2048,7 @@ "recoil": "^0.4.1", "rimraf": "^3.0.2", "semver": "^7.3.7", + "simple-git": "^3.10.0", "style-loader": "2.0.0", "tailwindcss": "^2.2.7", "tailwindcss-nested-groups": "^1.2.4", @@ -2061,4 +2072,4 @@ "dependencies": { "node-fetch": "^2.6.7" } -} \ No newline at end of file +} diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index fd02151e..e0d888b9 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -9,7 +9,7 @@ import { DashboardData } from '../models/DashboardData'; import { MediaLibrary } from '../helpers/MediaLibrary'; import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener, TaxonomyListener } from '../listeners/dashboard'; import { MediaListener as PanelMediaListener } from '../listeners/panel' -import { ModeListener } from '../listeners/general'; +import { GitListener, ModeListener } from '../listeners/general'; export class Dashboard { private static webview: WebviewPanel | null = null; @@ -146,6 +146,7 @@ export class Dashboard { TelemetryListener.process(msg); SnippetListener.process(msg); ModeListener.process(msg); + GitListener.process(msg); TaxonomyListener.process(msg); }); } diff --git a/src/constants/GeneralCommands.ts b/src/constants/GeneralCommands.ts index 476dc452..fe696fc0 100644 --- a/src/constants/GeneralCommands.ts +++ b/src/constants/GeneralCommands.ts @@ -3,8 +3,11 @@ export const GeneralCommands = { toWebview: { setMode: "setMode", + gitSyncingStart: "gitSyncingStart", + gitSyncingEnd: "gitSyncingEnd", }, toVSCode: { openLink: "openLink", + gitSync: "gitSync", } }; \ No newline at end of file diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 5e79dc4d..8e6f9620 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -78,6 +78,9 @@ export const SETTING_FRAMEWORK_START = "framework.startCommand"; export const SETTING_SITE_BASEURL = "site.baseURL"; +export const SETTING_GIT_ACTIONS = "git.actions"; +export const SETTING_GIT_COMMIT_MSG = "git.commitMsg"; + /** * @deprecated */ diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index 4ad2920d..464aa5fe 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -22,6 +22,7 @@ import { LightningBoltIcon, PlusIcon } from '@heroicons/react/outline'; import { useLocation, useNavigate } from 'react-router-dom'; import { routePaths } from '../..'; import { useEffect } from 'react'; +import { SyncButton } from './SyncButton'; export interface IHeaderProps { header?: React.ReactNode; @@ -108,6 +109,8 @@ export const Header: React.FunctionComponent = ({header, totalPage
+ + = (props: React.PropsWithChildren) => { + const settings = useRecoilValue(SettingsSelector); + const [ isSyncing, setIsSyncing ] = useState(false); + + const pull = () => { + Messenger.send(GeneralCommands.toVSCode.gitSync); + }; + + const messageListener = (message: MessageEvent>) => { + const { command, data } = message.data; + + if (command === GeneralCommands.toWebview.gitSyncingStart) { + setIsSyncing(true); + } else if (command === GeneralCommands.toWebview.gitSyncingEnd) { + setIsSyncing(false); + } + }; + + useEffect(() => { + Messenger.listen(messageListener); + + return () => { + Messenger.unlisten(messageListener); + } + }, []); + + if (!settings?.git?.actions || !settings?.git.isGitRepo) { + return null; + } + + return ( +
+ +
+ ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/hooks/useMessages.tsx b/src/dashboardWebView/hooks/useMessages.tsx index 7d7c83c5..f9315f17 100644 --- a/src/dashboardWebView/hooks/useMessages.tsx +++ b/src/dashboardWebView/hooks/useMessages.tsx @@ -18,7 +18,7 @@ export default function useMessages() { const [, setView] = useRecoilState(DashboardViewAtom); const [, setSearchReady] = useRecoilState(SearchReadyAtom); - Messenger.listen((event: MessageEvent>) => { + const messageListener = (event: MessageEvent>) => { const message = event.data; switch (message.command) { @@ -53,14 +53,20 @@ export default function useMessages() { setMode(message.data); break; } - }); + }; useEffect(() => { + Messenger.listen(messageListener); + setLoading(true); Messenger.send(DashboardMessage.getViewType); Messenger.send(DashboardMessage.getTheme); Messenger.send(DashboardMessage.getData); Messenger.send(DashboardMessage.getMode); + + return () => { + Messenger.unlisten(messageListener); + } }, ['']); return { diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index d9d94ae9..7add77bf 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -1,12 +1,13 @@ import { DataType } from './../../models/DataType'; import { VersionInfo } from '../../models/VersionInfo'; import { ContentFolder } from '../../models/ContentFolder'; -import { ContentType, CustomScript, CustomTaxonomy, DraftField, Framework, Snippets, SortingSetting } from '../../models'; +import { ContentType, CustomScript, CustomTaxonomy, DraftField, Framework, GitSettings, Snippets, SortingSetting } from '../../models'; import { SortingOption } from './SortingOption'; import { DashboardViewType } from '.'; import { DataFile } from '../../models/DataFile'; export interface Settings { + git: GitSettings; beta: boolean; initialized: boolean; wsFolder: string; diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index 12edc8e0..a61bd54a 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -7,7 +7,7 @@ import { TagType } from '../panelWebView/TagType'; import { WebviewHelper } from '@estruyf/vscode'; import { Extension } from '../helpers/Extension'; import { Telemetry } from '../helpers/Telemetry'; -import { ModeListener } from '../listeners/general'; +import { GitListener, ModeListener } from '../listeners/general'; export class ExplorerView implements WebviewViewProvider, Disposable { public static readonly viewType = "frontMatter.explorer"; @@ -82,6 +82,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { SettingsListener.process(msg); TaxonomyListener.process(msg); ModeListener.process(msg); + GitListener.process(msg); }); webviewView.onDidChangeVisibility(() => { diff --git a/src/extension.ts b/src/extension.ts index 8a0a634d..e5a7201b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,7 +14,7 @@ import { TagType } from './panelWebView/TagType'; import { ExplorerView } from './explorerView/ExplorerView'; import { Extension } from './helpers/Extension'; import { DashboardData } from './models/DashboardData'; -import { Logger, Settings as SettingsHelper } from './helpers'; +import { debounceCallback, Logger, Settings as SettingsHelper } from './helpers'; import { Content } from './commands/Content'; import ContentProvider from './providers/ContentProvider'; import { Wysiwyg } from './commands/Wysiwyg'; @@ -297,14 +297,4 @@ const handleAutoDateUpdate = (e: vscode.TextDocumentWillSaveEvent) => { const triggerShowDraftStatus = (location: string) => { Logger.info(`Triggering draft status update: ${location}`); statusDebouncer(() => { StatusListener.verify(frontMatterStatusBar, collection); }, 1000); -}; - -const debounceCallback = () => { - let timeout: NodeJS.Timeout; - - return (fnc: any, time: number) => { - const functionCall = (...args: any[]) => fnc.apply(args); - clearTimeout(timeout); - timeout = setTimeout(functionCall, time) as any; - }; }; \ No newline at end of file diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index a071bdc6..04e93fbb 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -1,8 +1,9 @@ +import { GitListener } from './../listeners/general/GitListener'; import { basename, join } from "path"; import { workspace } from "vscode"; import { Folders } from "../commands/Folders"; import { Project } from "../commands/Project"; -import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TEMPLATES_ENABLED } from "../constants"; +import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TEMPLATES_ENABLED, SETTING_GIT_ACTIONS } from "../constants"; import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models"; import { CustomScript, DraftField, Snippets, SortingSetting, TaxonomyType } from "../models"; import { DataFile } from "../models/DataFile"; @@ -19,8 +20,13 @@ export class DashboardSettings { const ext = Extension.getInstance(); const wsFolder = Folders.getWorkspaceFolder(); const isInitialized = Project.isInitialized(); + const gitActions = Settings.get(SETTING_GIT_ACTIONS); return { + git: { + isGitRepo: gitActions ? await GitListener.isGitRepository() : false, + actions: gitActions || false + }, beta: ext.isBetaVersion(), wsFolder: wsFolder ? wsFolder.fsPath : '', staticFolder: Folders.getStaticFolderRelativePath(), diff --git a/src/helpers/DebounceCallback.ts b/src/helpers/DebounceCallback.ts new file mode 100644 index 00000000..6075f985 --- /dev/null +++ b/src/helpers/DebounceCallback.ts @@ -0,0 +1,9 @@ +export const debounceCallback = () => { + let timeout: NodeJS.Timeout; + + return (fnc: any, time: number) => { + const functionCall = (...args: any[]) => fnc.apply(args); + clearTimeout(timeout); + timeout = setTimeout(functionCall, time) as any; + }; +}; \ No newline at end of file diff --git a/src/helpers/PanelSettings.ts b/src/helpers/PanelSettings.ts index 6664baf6..16b9a6e7 100644 --- a/src/helpers/PanelSettings.ts +++ b/src/helpers/PanelSettings.ts @@ -3,13 +3,20 @@ import { Extension, Settings } from "." import { Dashboard } from "../commands/Dashboard" import { Preview } from "../commands/Preview" import { Template } from "../commands/Template" -import { CONTEXT, DefaultFields, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_COMMA_SEPARATED_FIELDS, SETTING_CUSTOM_SCRIPTS, SETTING_DATE_FORMAT, SETTING_PANEL_FREEFORM, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_SLUG_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TAXONOMY_FIELD_GROUPS, SETTING_TAXONOMY_TAGS } from "../constants" +import { CONTEXT, DefaultFields, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_COMMA_SEPARATED_FIELDS, SETTING_CUSTOM_SCRIPTS, SETTING_DATE_FORMAT, SETTING_PANEL_FREEFORM, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_SLUG_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TAXONOMY_FIELD_GROUPS, SETTING_TAXONOMY_TAGS, SETTING_GIT_ACTIONS } from "../constants" +import { GitListener } from "../listeners/general" import { CustomScript, DataType, DraftField, FieldGroup, PanelSettings as IPanelSettings, ScriptType } from "../models" export class PanelSettings { public static async get(): Promise { + const gitActions = Settings.get(SETTING_GIT_ACTIONS); + return { + git: { + isGitRepo: gitActions ? await GitListener.isGitRepository() : false, + actions: gitActions || false + }, seo: { title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1, slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1, diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 251a5974..1833005f 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -8,30 +8,22 @@ import { Folders } from '../commands/Folders'; import { join, basename } from 'path'; import { existsSync, readFileSync, watch, writeFileSync } from 'fs'; import { Extension } from './Extension'; +import { debounceCallback } from './DebounceCallback'; export class Settings { public static globalFile = "frontmatter.json"; private static config: vscode.WorkspaceConfiguration; private static globalConfig: any; + public static init() { - const fmConfig = Settings.projectConfigPath; - if (fmConfig && existsSync(fmConfig)) { - const localConfig = readFileSync(fmConfig, 'utf8'); - Settings.globalConfig = JSON.parse(localConfig); - commands.executeCommand('setContext', CONTEXT.isEnabled, true); - } else { - Settings.globalConfig = undefined; - } + Settings.readConfig(); Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY); Settings.onConfigChange((global?: any) => { - if (global) { - Settings.globalConfig = Object.assign({}, global); - } else { - Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY); - } + Settings.readConfig(); + Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY); }); } @@ -61,6 +53,7 @@ export class Settings { */ public static onConfigChange(callback: (global?: any) => void) { const projectConfig = Settings.projectConfigPath; + const configDebouncer = debounceCallback(); workspace.onDidChangeConfiguration(() => { callback(); @@ -68,8 +61,10 @@ export class Settings { // Background listener for when it is not a user interaction if (projectConfig && existsSync(projectConfig)) { - watch(projectConfig, () => { - callback(); + let watcher = workspace.createFileSystemWatcher(projectConfig, true, false, true); + watcher.onDidChange(async (uri: Uri) => { + configDebouncer(() => callback(), 200); + // callback() }); } @@ -81,7 +76,8 @@ export class Settings { if (file) { const fileContents = file.getText(); const json = JSON.parse(fileContents); - callback(json); + configDebouncer(() => callback(json), 200); + // callback(json) } } }); @@ -346,6 +342,19 @@ export class Settings { return hasSetting; } + /** + * Get the project config path + * @returns + */ + public static get projectConfigPath() { + const wsFolder = Folders.getWorkspaceFolder(); + if (wsFolder) { + const fmConfig = join(wsFolder.fsPath, Settings.globalFile); + return fmConfig; + } + return undefined; + } + /** * Check if its the project config * @param filePath @@ -363,15 +372,16 @@ export class Settings { } /** - * Get the project config path - * @returns + * Read the global config file */ - public static get projectConfigPath() { - const wsFolder = Folders.getWorkspaceFolder(); - if (wsFolder) { - const fmConfig = join(wsFolder.fsPath, Settings.globalFile); - return fmConfig; + private static readConfig() { + const fmConfig = Settings.projectConfigPath; + if (fmConfig && existsSync(fmConfig)) { + const localConfig = readFileSync(fmConfig, 'utf8'); + Settings.globalConfig = JSON.parse(localConfig); + commands.executeCommand('setContext', CONTEXT.isEnabled, true); + } else { + Settings.globalConfig = undefined; } - return undefined; } } \ No newline at end of file diff --git a/src/helpers/index.ts b/src/helpers/index.ts index c7a90cc7..7284ae68 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -4,6 +4,7 @@ export * from './CustomScript'; export * from './DashboardSettings'; export * from './DataFileHelper'; export * from './DateHelper'; +export * from './DebounceCallback'; export * from './Extension'; export * from './FilesHelper'; export * from './FrameworkDetector'; diff --git a/src/listeners/general/GitListener.ts b/src/listeners/general/GitListener.ts new file mode 100644 index 00000000..a5271457 --- /dev/null +++ b/src/listeners/general/GitListener.ts @@ -0,0 +1,109 @@ +import { Settings } from './../../helpers/SettingsHelper'; +import { Dashboard } from '../../commands/Dashboard'; +import { ExplorerView } from '../../explorerView/ExplorerView'; +import { Extension, Logger } from '../../helpers'; +import { GeneralCommands } from './../../constants/GeneralCommands'; +import simpleGit, { SimpleGit } from 'simple-git'; +import { SETTING_GIT_COMMIT_MSG } from '../../constants'; +import { Folders } from '../../commands/Folders'; + +export class GitListener { + private static client: SimpleGit | null = null; + + /** + * Process the messages + * @param msg + */ + public static process(msg: { command: string, data: any }) { + switch(msg.command) { + case GeneralCommands.toVSCode.gitSync: + this.sync(); + break; + } + } + + public static async sync() { + try { + this.sendMsg(GeneralCommands.toWebview.gitSyncingStart, {}); + + await this.pull(); + await this.push(); + + this.sendMsg(GeneralCommands.toWebview.gitSyncingEnd, {}); + } catch (e) { + Logger.error((e as Error).message); + this.sendMsg(GeneralCommands.toWebview.gitSyncingEnd, {}); + } + } + + public static async isGitRepository() { + const git = this.getClient(); + if (!git) { + return false; + } + + const isRepo = await git.checkIsRepo(); + + if (!isRepo) { + Logger.warning(`Current workspace is not a GIT repository`); + } + + return isRepo; + } + + private static async pull() { + const git = this.getClient(); + if (!git) { + return; + } + + Logger.info(`Pulling from remote`); + await git.pull(); + } + + private static async push() { + const commitMsg = Settings.get(SETTING_GIT_COMMIT_MSG); + + const git = this.getClient(); + if (!git) { + return; + } + + Logger.info(`Pushing to remote`); + + const status = await git.status(); + + for (const file of status.not_added) { + await git.add(file); + } + for (const file of status.modified) { + await git.add(file); + } + + await git.commit(commitMsg || "Synced by Front Matter") + + await git.push(); + } + + private static getClient() { + const wsFolder = Folders.getWorkspaceFolder(); + + const options = { + baseDir: wsFolder?.fsPath || "", + binary: 'git', + maxConcurrentProcesses: 6, + } + + return simpleGit(options); + } + + private static sendMsg(command: string, data: any) { + + const extPath = Extension.getInstance().extensionPath; + const panel = ExplorerView.getInstance(extPath); + + panel.sendMessage({ command: command as any, data }); + + Dashboard.postWebviewMessage({ command: command as any, data }); + } +} \ No newline at end of file diff --git a/src/listeners/general/index.ts b/src/listeners/general/index.ts index 54a53db1..6af09d8f 100644 --- a/src/listeners/general/index.ts +++ b/src/listeners/general/index.ts @@ -1 +1,2 @@ export * from './ModeListener'; +export * from './GitListener'; diff --git a/src/models/GitSettings.ts b/src/models/GitSettings.ts new file mode 100644 index 00000000..ab79e4dd --- /dev/null +++ b/src/models/GitSettings.ts @@ -0,0 +1,5 @@ + +export interface GitSettings { + isGitRepo: boolean; + actions: boolean; +} \ No newline at end of file diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 7366d567..b9dfabb4 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -1,10 +1,11 @@ import { FileStat } from "vscode"; -import { DraftField } from "."; +import { DraftField, GitSettings } from "."; import { Choice } from "./Choice"; import { DashboardData } from "./DashboardData"; import { DataType } from "./DataType"; export interface PanelSettings { + git: GitSettings; seo: SEO; slug: Slug; tags: string[]; diff --git a/src/models/index.ts b/src/models/index.ts index 51c13234..e484e46e 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -9,6 +9,7 @@ export * from './DataFolder'; export * from './DataType'; export * from './DraftField'; export * from './Framework'; +export * from './GitSettings'; export * from './MediaPaths'; export * from './Mode'; export * from './PanelSettings'; diff --git a/src/panelWebView/ViewPanel.tsx b/src/panelWebView/ViewPanel.tsx index 0cd56ead..04466a0a 100644 --- a/src/panelWebView/ViewPanel.tsx +++ b/src/panelWebView/ViewPanel.tsx @@ -11,6 +11,7 @@ import { SponsorMsg } from './components/SponsorMsg'; import useMessages from './hooks/useMessages'; import { FeatureFlag } from '../components/features/FeatureFlag'; import { FEATURE_FLAG } from '../constants/Features'; +import { GitAction } from './components/Git/GitAction'; export interface IViewPanelProps { } @@ -41,6 +42,8 @@ export const ViewPanel: React.FunctionComponent = (props: React return (
+ + diff --git a/src/panelWebView/components/ActionButton.tsx b/src/panelWebView/components/ActionButton.tsx index b96a76ab..a4399710 100644 --- a/src/panelWebView/components/ActionButton.tsx +++ b/src/panelWebView/components/ActionButton.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; export interface IActionButtonProps { - title: string; + title: JSX.Element | string; className?: string; disabled?: boolean; onClick: (e: React.SyntheticEvent) => void; @@ -10,7 +10,9 @@ export interface IActionButtonProps { const ActionButton: React.FunctionComponent = ({className, onClick, disabled,title}: React.PropsWithChildren) => { return (
- +
); }; diff --git a/src/panelWebView/components/BaseView.tsx b/src/panelWebView/components/BaseView.tsx index b66c5689..93444a43 100644 --- a/src/panelWebView/components/BaseView.tsx +++ b/src/panelWebView/components/BaseView.tsx @@ -10,6 +10,7 @@ import { StartServerButton } from './StartServerButton'; import { FeatureFlag } from '../../components/features/FeatureFlag'; import { FEATURE_FLAG } from '../../constants/Features'; import { Messenger } from '@estruyf/vscode/dist/client'; +import { GitAction } from './Git/GitAction'; export interface IBaseViewProps { settings: PanelSettings | undefined; @@ -44,6 +45,8 @@ const BaseView: React.FunctionComponent = ({settings, folderAndF return (
+ + diff --git a/src/panelWebView/components/Git/GitAction.tsx b/src/panelWebView/components/Git/GitAction.tsx new file mode 100644 index 00000000..faff7db1 --- /dev/null +++ b/src/panelWebView/components/Git/GitAction.tsx @@ -0,0 +1,55 @@ +import { Messenger } from '@estruyf/vscode/dist/client'; +import { EventData } from '@estruyf/vscode/dist/models'; +import { RefreshIcon } from '@heroicons/react/outline'; +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import { GeneralCommands } from '../../../constants'; +import { PanelSettings } from '../../../models'; +import { ActionButton } from '../ActionButton'; + +export interface IGitActionProps { + settings: PanelSettings | undefined +} + +export const GitAction: React.FunctionComponent = ({ settings }: React.PropsWithChildren) => { + const [ isSyncing, setIsSyncing ] = useState(false); + + const pull = () => { + Messenger.send(GeneralCommands.toVSCode.gitSync); + }; + + const messageListener = (message: MessageEvent>) => { + const { command, data } = message.data; + + if (command === GeneralCommands.toWebview.gitSyncingStart) { + setIsSyncing(true); + } else if (command === GeneralCommands.toWebview.gitSyncingEnd) { + setIsSyncing(false); + } + }; + + useEffect(() => { + Messenger.listen(messageListener); + + return () => { + Messenger.unlisten(messageListener); + } + }, []); + + if (!settings?.git?.actions || !settings?.git.isGitRepo) { + return null; + } + + return ( +
+ + + Sync +
+ )} /> +
+ ); +}; \ No newline at end of file diff --git a/src/panelWebView/hooks/useMessages.tsx b/src/panelWebView/hooks/useMessages.tsx index 14aa91cb..68a51c89 100644 --- a/src/panelWebView/hooks/useMessages.tsx +++ b/src/panelWebView/hooks/useMessages.tsx @@ -7,6 +7,7 @@ import { Command } from '../Command'; import { CommandToCode } from '../CommandToCode'; import { TagType } from '../TagType'; import { Messenger } from '@estruyf/vscode/dist/client'; +import { EventData } from '@estruyf/vscode/dist/models'; export default function useMessages() { const [metadata, setMetadata] = useState({}); @@ -17,7 +18,7 @@ export default function useMessages() { const [mediaSelecting, setMediaSelecting] = useState(undefined); const [mode, setMode] = useState(undefined); - window.addEventListener('message', event => { + const messageListener = (event: MessageEvent>) => { const message = event.data; switch (message.command) { @@ -48,7 +49,7 @@ export default function useMessages() { setMode(message.data); break; } - }); + }; useEffect(() => { if (loading) { @@ -59,6 +60,7 @@ export default function useMessages() { }, [loading]) useEffect(() => { + Messenger.listen(messageListener); setLoading(true); // Show what you got after 5 seconds @@ -68,6 +70,10 @@ export default function useMessages() { Messenger.send(CommandToCode.getData); Messenger.send(CommandToCode.getMode); + + return () => { + Messenger.unlisten(messageListener); + } }, []); return { diff --git a/src/panelWebView/styles.css b/src/panelWebView/styles.css index e7bed06c..5afce082 100644 --- a/src/panelWebView/styles.css +++ b/src/panelWebView/styles.css @@ -1,4 +1,14 @@ +/* Animations */ +@keyframes spin { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } +} +/* CSS */ .block_field__form, .list_field__form { background-color: transparent; @@ -508,6 +518,23 @@ vscode-divider { } } +/* Git actions */ +.git_actions__sync { + display: flex; + align-items: center; + justify-content: center; +} + +.git_actions__sync svg { + height: 1.25rem; + width: 1.25rem; + margin-right: .5rem; +} + +.animate-spin { + animation: spin 1s linear infinite; +} + /* Quill changes */ .ql-toolbar.ql-snow, .ql-container.ql-snow { diff --git a/tailwind.config.js b/tailwind.config.js index 3e708823..c8be881c 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -6,6 +6,16 @@ module.exports = { darkMode: 'class', // or 'media' or 'class' theme: { extend: { + animation: { + 'reverse-spin': 'reverse-spin 1s linear infinite' + }, + keyframes: { + 'reverse-spin': { + from: { + transform: 'rotate(360deg)' + }, + } + }, colors: { white: colors.white, gray: colors.trueGray,