diff --git a/CHANGELOG.md b/CHANGELOG.md index 37890460..eb187e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - [#673](https://github.com/estruyf/vscode-front-matter/pull/673): Added git settings to the welcome view and settings view - [#727](https://github.com/estruyf/vscode-front-matter/pull/727): Updated Japanese translations thanks to [mayumihara](https://github.com/mayumih387) - [#737](https://github.com/estruyf/vscode-front-matter/issues/737): Optimize the grid layout of the content and media dashboards +- [#739](https://github.com/estruyf/vscode-front-matter/pull/739): New Git settings to disable and require a commit message - [#741](https://github.com/estruyf/vscode-front-matter/issues/741): Added message on the content dashboard when content is processed - [#747](https://github.com/estruyf/vscode-front-matter/issues/747): The `@frontmatter/extensibility` dependency now supports scripts for placeholders diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index b145ea1d..39e98d97 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -339,6 +339,10 @@ "dashboard.configuration.astro.astroContentTypes.empty": "No Astro Content Collections found.", "dashboard.configuration.astro.astroContentTypes.description": "The following Astro Content Collections can be used to generate a content-type.", + "panel.git.gitAction.title": "Publish changes", + "panel.git.gitAction.input.placeholder": "Commit message", + "panel.git.gitAction.button.fetch": "Fetch", + "panel.contentType.contentTypeValidator.title": "Content-type", "panel.contentType.contentTypeValidator.hint": "We noticed field differences between the content-type and the front matter data. \n Would you like to create, update, or set the content-type for this content?", "panel.contentType.contentTypeValidator.button.create": "Create content-type", diff --git a/package.json b/package.json index 74c32c24..ac054b33 100644 --- a/package.json +++ b/package.json @@ -888,6 +888,22 @@ "markdownDescription": "%setting.frontMatter.git.commitMessage.markdownDescription%", "default": "Synced by Front Matter" }, + "frontMatter.git.disableOnBranches": { + "type": "array", + "markdownDescription": "%setting.frontMatter.git.disableOnBranches.markdownDescription%", + "default": [], + "items": { + "type": "string" + } + }, + "frontMatter.git.requiresCommitMessage": { + "type": "array", + "markdownDescription": "%setting.frontMatter.git.requiresCommitMessage.markdownDescription%", + "default": [], + "items": { + "type": "string" + } + }, "frontMatter.git.submodule.pull": { "type": "boolean", "markdownDescription": "%setting.frontMatter.git.submodule.pull.markdownDescription%", diff --git a/package.nls.json b/package.nls.json index d17d013f..c5fa5524 100644 --- a/package.nls.json +++ b/package.nls.json @@ -253,5 +253,8 @@ "command.frontMatter.settings.refresh": "Refresh Front Matter Settings", "setting.frontMatter.config.dynamicFilePath.markdownDescription": "Specify the path to the dynamic config file (ex: [[workspace]]/config.js). [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.config.dynamicfilepath)", "setting.frontMatter.taxonomy.contentTypes.items.properties.allowAsSubContent.description": "Specify if the content type can be used as sub content.", - "setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "Specify if the content type is sub content." + "setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "Specify if the content type is sub content.", + + "setting.frontMatter.git.disableOnBranches.markdownDescription": "Specify the branches on which you want to disable the Git actions. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.git.disableonbranches)", + "setting.frontMatter.git.requiresCommitMessage.markdownDescription": "Specify if you want to require a commit message when publishing your changes for a specified branch. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.git.requirescommitmessage)" } \ No newline at end of file diff --git a/src/constants/GeneralCommands.ts b/src/constants/GeneralCommands.ts index bd641174..e761c335 100644 --- a/src/constants/GeneralCommands.ts +++ b/src/constants/GeneralCommands.ts @@ -1,14 +1,22 @@ export const GeneralCommands = { toWebview: { setMode: 'setMode', - gitSyncingStart: 'gitSyncingStart', - gitSyncingEnd: 'gitSyncingEnd', + git: { + syncingStart: 'gitSyncingStart', + syncingEnd: 'gitSyncingEnd', + branchName: 'gitBranchName' + }, setLocalization: 'setLocalization' }, toVSCode: { openLink: 'openLink', - gitSync: 'gitSync', - gitIsRepo: 'gitIsRepo', + git: { + isRepo: 'gitIsRepo', + sync: 'gitSync', + fetch: 'getFetch', + getBranch: 'getBranch', + selectBranch: 'gitSelectBranch' + }, getLocalization: 'getLocalization', openOnWebsite: 'openOnWebsite' } diff --git a/src/constants/TelemetryEvent.ts b/src/constants/TelemetryEvent.ts index e4a1dbc1..f717da6f 100644 --- a/src/constants/TelemetryEvent.ts +++ b/src/constants/TelemetryEvent.ts @@ -49,5 +49,6 @@ export const TelemetryEvent = { webviewTaxonomyDashboard: 'webviewTaxonomyDashboard', // Git - gitSync: 'gitSync' + gitSync: 'gitSync', + gitFetch: 'gitFetch' }; diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 7eb4fe42..8387adb1 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -100,6 +100,8 @@ export const SETTING_FRAMEWORK_START = 'framework.startCommand'; export const SETTING_SITE_BASEURL = 'site.baseURL'; export const SETTING_GIT_ENABLED = 'git.enabled'; +export const SETTING_GIT_DISABLED_BRANCHES = 'git.disableOnBranches'; +export const SETTING_GIT_REQUIRES_COMMIT_MSG = 'git.requiresCommitMessage'; export const SETTING_GIT_COMMIT_MSG = 'git.commitMessage'; export const SETTING_GIT_SUBMODULE_PULL = 'git.submodule.pull'; export const SETTING_GIT_SUBMODULE_PUSH = 'git.submodule.push'; diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index c90e9c3c..bf1a45e2 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -164,7 +164,7 @@ export const Header: React.FunctionComponent = ({
- + {/* */} = ( const [isSyncing, setIsSyncing] = useState(false); const pull = () => { - Messenger.send(GeneralCommands.toVSCode.gitSync); + Messenger.send(GeneralCommands.toVSCode.git.sync); }; const messageListener = (message: MessageEvent>) => { const { command } = message.data; - if (command === GeneralCommands.toWebview.gitSyncingStart) { + if (command === GeneralCommands.toWebview.git.syncingStart) { setIsSyncing(true); - } else if (command === GeneralCommands.toWebview.gitSyncingEnd) { + } else if (command === GeneralCommands.toWebview.git.syncingEnd) { setIsSyncing(false); } }; diff --git a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx index 2e1c3fc5..2be2adb3 100644 --- a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx +++ b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx @@ -255,7 +255,7 @@ export const StepsToGetStarted: React.FunctionComponent
), show: isGitRepo, - status: settings.git.actions ? Status.Completed : Status.NotStarted + status: settings.git?.actions ? Status.Completed : Status.NotStarted }, { id: `welcome-import`, @@ -293,12 +293,12 @@ export const StepsToGetStarted: React.FunctionComponent }, [settings.crntFramework, settings.framework]); React.useEffect(() => { - messageHandler.request(GeneralCommands.toVSCode.gitIsRepo).then((result) => { + messageHandler.request(GeneralCommands.toVSCode.git.isRepo).then((result) => { setIsGitRepo(result); }); - setIsGitEnabled(settings.git.actions); - }, [settings.git.actions]); + setIsGitEnabled(settings.git?.actions || false); + }, [settings.git?.actions]); React.useEffect(() => { const fetchTemplates = async () => { diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index 4d8cbbc4..7dd41d62 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -19,7 +19,7 @@ import { DataFile } from '../../models/DataFile'; export interface Settings { projects: Project[]; project: Project; - git: GitSettings; + git: GitSettings | undefined; beta: boolean; initialized: boolean; wsFolder: string; diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index ca4d1d4b..e581fbf8 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -79,16 +79,12 @@ export class DashboardSettings { const ext = Extension.getInstance(); const wsFolder = Folders.getWorkspaceFolder(); const isInitialized = await Project.isInitialized(); - const gitActions = Settings.get(SETTING_GIT_ENABLED); const pagination = Settings.get(SETTING_DASHBOARD_CONTENT_PAGINATION); const settings = { projects: Settings.getProjects(), project: Settings.getProject(), - git: { - isGitRepo: gitActions ? await GitListener.isGitRepository() : false, - actions: gitActions || false - }, + git: await GitListener.getSettings(), beta: ext.isBetaVersion(), wsFolder: wsFolder ? wsFolder.fsPath : '', staticFolder: Folders.getStaticFolderRelativePath(), diff --git a/src/helpers/PanelSettings.ts b/src/helpers/PanelSettings.ts index 121b4760..32788a42 100644 --- a/src/helpers/PanelSettings.ts +++ b/src/helpers/PanelSettings.ts @@ -31,7 +31,6 @@ import { SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TAXONOMY_CUSTOM, SETTING_TAXONOMY_FIELD_GROUPS, - SETTING_GIT_ENABLED, SETTING_SEO_TITLE_FIELD } from '../constants'; import { GitListener } from '../listeners/general'; @@ -49,14 +48,9 @@ import { Folders } from '../commands'; export class PanelSettings { public static async get(): Promise { - const gitActions = Settings.get(SETTING_GIT_ENABLED); - return { aiEnabled: Settings.get(SETTING_SPONSORS_AI_ENABLED) || false, - git: { - isGitRepo: gitActions ? await GitListener.isGitRepository() : false, - actions: gitActions || false - }, + git: await GitListener.getSettings(), 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/listeners/general/GitListener.ts b/src/listeners/general/GitListener.ts index 83b81439..8c7fc7b6 100644 --- a/src/listeners/general/GitListener.ts +++ b/src/listeners/general/GitListener.ts @@ -4,7 +4,9 @@ import { GIT_CONFIG, SETTING_DATE_FORMAT, SETTING_GIT_COMMIT_MSG, + SETTING_GIT_DISABLED_BRANCHES, SETTING_GIT_ENABLED, + SETTING_GIT_REQUIRES_COMMIT_MSG, SETTING_GIT_SUBMODULE_BRANCH, SETTING_GIT_SUBMODULE_FOLDER, SETTING_GIT_SUBMODULE_PULL, @@ -19,21 +21,58 @@ import { Extension, Logger, Notifications, + parseWinPath, processTimePlaceholders, Telemetry } from '../../helpers'; import { GeneralCommands } from './../../constants/GeneralCommands'; import simpleGit, { SimpleGit } from 'simple-git'; import { Folders } from '../../commands/Folders'; -import { commands } from 'vscode'; -import { PostMessageData } from '../../models'; +import { Event, commands, extensions } from 'vscode'; +import { GitAPIState, GitRepository, PostMessageData } from '../../models'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; export class GitListener { + private static gitAPI: { + onDidChangeState: Event; + onDidOpenRepository: Event; + onDidCloseRepository: Event; + getAPI: (version: number) => any; + repositories: GitRepository[]; + } | null = null; private static isRegistered: boolean = false; private static client: SimpleGit | null = null; private static subClient: SimpleGit | null = null; + private static repository: GitRepository | null = null; + private static branchName: string | null = null; + + /** + * Retrieves the Git settings. + * @returns {Promise<{ + * isGitRepo: boolean, + * actions: boolean, + * disabledBranches: string[], + * requiresCommitMessage: string[] + * }>} The Git settings. + */ + public static async getSettings() { + const gitActions = Settings.get(SETTING_GIT_ENABLED); + if (gitActions) { + return { + isGitRepo: gitActions ? await GitListener.isGitRepository() : false, + actions: gitActions || false, + disabledBranches: gitActions + ? Settings.get(SETTING_GIT_DISABLED_BRANCHES) || [] + : [], + requiresCommitMessage: gitActions + ? Settings.get(SETTING_GIT_REQUIRES_COMMIT_MSG) || [] + : [] + }; + } + + return; + } /** * Initialize the listener @@ -64,11 +103,19 @@ export class GitListener { */ public static process(msg: PostMessageData) { switch (msg.command) { - case GeneralCommands.toVSCode.gitIsRepo: - this.checkIsGitRepo(msg.command, msg.requestId); + case GeneralCommands.toVSCode.git.sync: + this.sync(msg.payload); break; - case GeneralCommands.toVSCode.gitSync: - this.sync(); + case GeneralCommands.toVSCode.git.fetch: + this.sync(undefined, false); + break; + case GeneralCommands.toVSCode.git.getBranch: + this.getBranch(msg.command, msg.requestId); + break; + case GeneralCommands.toVSCode.git.selectBranch: + this.selectBranch(); + case GeneralCommands.toVSCode.git.isRepo: + this.checkIsGitRepo(msg.command, msg.requestId); break; } } @@ -83,27 +130,41 @@ export class GitListener { } /** - * Run the sync + * Selects the current branch in the Git repository. + * @returns {Promise} A promise that resolves when the branch command has been executed. */ - public static async sync() { - try { - this.sendMsg(GeneralCommands.toWebview.gitSyncingStart, {}); + public static async selectBranch(): Promise { + const workspaceFolder = Folders.getWorkspaceFolder(); + await commands.executeCommand('git.checkout', workspaceFolder); + } - Telemetry.send(TelemetryEvent.gitSync); + /** + * Synchronizes the local repository with the remote repository. + * @param commitMsg The commit message for the push operation. + * @param isSync Determines whether to perform a sync operation (default: true) or a fetch operation. + */ + public static async sync(commitMsg?: string, isSync: boolean = true) { + try { + this.sendMsg(GeneralCommands.toWebview.git.syncingStart, isSync ? 'syncing' : 'fetching'); + + Telemetry.send(isSync ? TelemetryEvent.gitSync : TelemetryEvent.gitFetch); await this.pull(); - await this.push(); - this.sendMsg(GeneralCommands.toWebview.gitSyncingEnd, {}); + if (isSync) { + await this.push(commitMsg); + } + + this.sendMsg(GeneralCommands.toWebview.git.syncingEnd, {}); } catch (e) { Logger.error((e as Error).message); - this.sendMsg(GeneralCommands.toWebview.gitSyncingEnd, {}); + this.sendMsg(GeneralCommands.toWebview.git.syncingEnd, {}); } } /** - * Check if the current workspace is a git repository - * @returns + * Checks if the current workspace is a Git repository. + * @returns A boolean indicating whether the current workspace is a Git repository. */ public static async isGitRepository() { const git = this.getClient(); @@ -117,12 +178,15 @@ export class GitListener { Logger.warning(`Current workspace is not a GIT repository`); } + GitListener.vscodeGitProvider(); + return isRepo; } /** - * Pull the changes from the remote - * @returns + * Pulls the latest changes from the remote repository. + * If submoduleFolder is specified, it checks out the submoduleBranch for the submodule located in that folder. + * If submodulePull is true, it also updates the submodules with the latest changes from the remote repository. */ private static async pull() { const git = this.getClient(); @@ -157,11 +221,14 @@ export class GitListener { } /** - * Push the changes to the remote - * @returns + * Pushes the changes to the remote repository. + * + * @param commitMsg The commit message to use. If not provided, it will use the default commit message or the one specified in the settings. + * @returns A promise that resolves when the push operation is completed. */ - private static async push() { - let commitMsg = Settings.get(SETTING_GIT_COMMIT_MSG); + private static async push(commitMsg?: string) { + commitMsg = + commitMsg || Settings.get(SETTING_GIT_COMMIT_MSG) || 'Synced by Front Matter'; if (commitMsg) { const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string; @@ -240,9 +307,11 @@ export class GitListener { } /** - * Get the git client - * @param submoduleFolder - * @returns + * Retrieves the Git client instance based on the provided submodule folder. + * If no submodule folder is provided, it returns the main Git client instance. + * If a submodule folder is provided, it returns the submodule-specific Git client instance. + * @param submoduleFolder The path to the submodule folder. + * @returns The Git client instance or null if it cannot be retrieved. */ private static getClient(submoduleFolder: string = ''): SimpleGit | null { if (!submoduleFolder && this.client) { @@ -269,9 +338,100 @@ export class GitListener { } /** - * Send the message to the webview - * @param command - * @param payload + * Initializes the VS Code Git provider and sets up event listeners for repository changes. + * @returns {Promise} A promise that resolves when the Git provider is initialized. + */ + private static async vscodeGitProvider(): Promise { + if (!GitListener.gitAPI) { + const extension = extensions.getExtension('vscode.git'); + + /** + * Logic from: https://github.com/microsoft/vscode/blob/main/extensions/github/src/extension.ts + * initializeGitExtension + */ + if (extension) { + const gitExtension = extension.isActive ? extension.exports : await extension.activate(); + + // Get version 1 of the API + GitListener.gitAPI = gitExtension.getAPI(1); + + if (!GitListener.gitAPI) { + return; + } + + GitListener.listenToRepo(GitListener.gitAPI?.repositories); + + GitListener.gitAPI.onDidChangeState(() => { + GitListener.listenToRepo(GitListener.gitAPI?.repositories); + }); + + GitListener.gitAPI.onDidOpenRepository((repo: GitRepository) => { + GitListener.triggerBranchChange(repo); + }); + + GitListener.gitAPI.onDidCloseRepository((repo: GitRepository) => { + Logger.info(`Closed repo: ${repo?.state?.HEAD?.name}`); + }); + } + } + } + + /** + * Retrieves the branch name and sends a request. + * @param command - The command to send. + * @param requestId - The ID of the request. + */ + private static async getBranch(command: string, requestId?: string) { + if (!command || !requestId) { + return; + } + + this.sendRequest(command, requestId, GitListener.repository?.state?.HEAD.name); + } + + private static listenToRepo(repositories: GitRepository[] | undefined) { + if (!repositories) { + return; + } + + if (repositories && repositories.length === 1) { + GitListener.triggerBranchChange(repositories[0]); + } else if (repositories && repositories.length > 1) { + const wsFolder = Folders.getWorkspaceFolder(); + if (wsFolder) { + const repo = repositories.find( + (repo) => parseWinPath(repo.rootUri.fsPath) === parseWinPath(wsFolder.fsPath) + ); + if (repo) { + GitListener.triggerBranchChange(repo); + } + } + } + } + + /** + * Triggers a branch change event for the specified Git repository. + * @param repo The Git repository to monitor for branch changes. + */ + private static async triggerBranchChange(repo: GitRepository | null) { + if (repo && repo.state) { + if (repo.state.HEAD.name !== GitListener.branchName) { + GitListener.branchName = repo.state.HEAD.name; + GitListener.repository = repo; + + this.sendMsg(GeneralCommands.toWebview.git.branchName, GitListener.branchName); + + repo.state.onDidChange(() => { + GitListener.triggerBranchChange(repo); + }); + } + } + } + + /** + * Sends a message to the panel and the dashboard. + * @param command - The command to send. + * @param payload - The payload to send with the command. */ private static sendMsg(command: string, payload: any) { const extPath = Extension.getInstance().extensionPath; @@ -281,4 +441,23 @@ export class GitListener { Dashboard.postWebviewMessage({ command: command as any, payload }); } + + /** + * Sends a request to the webview panel. + * @param command - The command to send. + * @param requestId - The unique identifier for the request. + * @param payload - The payload to send with the request. + */ + private static sendRequest(command: string, requestId: string, payload: any) { + const extPath = Extension.getInstance().extensionPath; + const panel = PanelProvider.getInstance(extPath); + + panel.getWebview()?.postMessage({ + command, + requestId, + payload + }); + + Dashboard.postWebviewMessage({ command: command as any, requestId, payload }); + } } diff --git a/src/localization/localization.enum.ts b/src/localization/localization.enum.ts index 15053bc7..c62db1be 100644 --- a/src/localization/localization.enum.ts +++ b/src/localization/localization.enum.ts @@ -1123,6 +1123,18 @@ export enum LocalizationKey { * The following Astro Content Collections can be used to generate a content-type. */ dashboardConfigurationAstroAstroContentTypesDescription = 'dashboard.configuration.astro.astroContentTypes.description', + /** + * Publish changes + */ + panelGitGitActionTitle = 'panel.git.gitAction.title', + /** + * Commit message + */ + panelGitGitActionInputPlaceholder = 'panel.git.gitAction.input.placeholder', + /** + * Fetch + */ + panelGitGitActionButtonFetch = 'panel.git.gitAction.button.fetch', /** * Content-type */ diff --git a/src/models/GitRepository.ts b/src/models/GitRepository.ts new file mode 100644 index 00000000..0cb40ddc --- /dev/null +++ b/src/models/GitRepository.ts @@ -0,0 +1,34 @@ +import { Event } from 'vscode'; + +export type GitAPIState = 'uninitialized' | 'initialized'; + +export interface GitRepository { + state: GitRepositoryState; + rootUri: { + fsPath: string; + path: string; + }; + repository: { + getBranches: () => Promise; + }; +} + +export interface GitRepositoryState { + HEAD: GitBranch; + onDidChange: Event; +} + +export interface GitBranch { + type: number; + name: string; + upstream: Upstream; + commit: string; + ahead: number; + behind: number; +} + +export interface Upstream { + name: string; + remote: string; + commit: string; +} diff --git a/src/models/GitSettings.ts b/src/models/GitSettings.ts index e452ec54..f32c71d5 100644 --- a/src/models/GitSettings.ts +++ b/src/models/GitSettings.ts @@ -1,4 +1,6 @@ export interface GitSettings { isGitRepo: boolean; actions: boolean; + disabledBranches: string[]; + requiresCommitMessage: string[]; } diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 7d0f07a9..561e44e7 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -5,7 +5,7 @@ import { DashboardData } from './DashboardData'; import { DataType } from './DataType'; export interface PanelSettings { - git: GitSettings; + git: GitSettings | undefined; seo: SEO; slug: Slug; tags: string[]; diff --git a/src/models/index.ts b/src/models/index.ts index 1f4ef199..0f02ce37 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -12,6 +12,7 @@ export * from './DataFolder'; export * from './DataType'; export * from './DraftField'; export * from './Framework'; +export * from './GitRepository'; export * from './GitSettings'; export * from './LoadingType'; export * from './MediaPaths'; diff --git a/src/panelWebView/ViewPanel.tsx b/src/panelWebView/ViewPanel.tsx index de78eb23..2b32ae52 100644 --- a/src/panelWebView/ViewPanel.tsx +++ b/src/panelWebView/ViewPanel.tsx @@ -83,7 +83,7 @@ export const ViewPanel: React.FunctionComponent = ( ); } - if (loading || !localeReady) { + if (loading && !localeReady) { return ; } @@ -116,37 +116,44 @@ export const ViewPanel: React.FunctionComponent = (
- + {!loading && ()} - {settings && settings.seo && ( - - - - )} - {settings && metadata && ( + { + !loading && settings && settings.seo && ( + + + + ) + } + + {!loading && settings && metadata && ( )} - - - + { + !loading && ( + + + + ) + } diff --git a/src/panelWebView/components/ActionButton.tsx b/src/panelWebView/components/ActionButton.tsx index 9acf2eef..9097ac03 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: JSX.Element | string; + title: string; className?: string; disabled?: boolean; onClick: (e: React.SyntheticEvent) => void; @@ -11,12 +11,13 @@ const ActionButton: React.FunctionComponent = ({ className, onClick, disabled, - title + title, + children }: React.PropsWithChildren) => { return ( -
-
); diff --git a/src/panelWebView/components/Actions/OpenOnWebsiteAction.tsx b/src/panelWebView/components/Actions/OpenOnWebsiteAction.tsx index 1d3dc3a0..c8d6b2c7 100644 --- a/src/panelWebView/components/Actions/OpenOnWebsiteAction.tsx +++ b/src/panelWebView/components/Actions/OpenOnWebsiteAction.tsx @@ -28,6 +28,8 @@ export const OpenOnWebsiteAction: React.FunctionComponent + onClick={open}> + {l10n.t(LocalizationKey.commonOpenOnWebsite)} + ); }; \ No newline at end of file diff --git a/src/panelWebView/components/CustomScript.tsx b/src/panelWebView/components/CustomScript.tsx index 11225982..a6c547d5 100644 --- a/src/panelWebView/components/CustomScript.tsx +++ b/src/panelWebView/components/CustomScript.tsx @@ -16,7 +16,11 @@ const CustomScript: React.FunctionComponent = ({ Messenger.send(CommandToCode.runCustomScript, { title, script }); }; - return ; + return ( + + {title} + + ); }; CustomScript.displayName = 'CustomScript'; diff --git a/src/panelWebView/components/Fields/TextField.tsx b/src/panelWebView/components/Fields/TextField.tsx index f55651f9..097228a4 100644 --- a/src/panelWebView/components/Fields/TextField.tsx +++ b/src/panelWebView/components/Fields/TextField.tsx @@ -20,6 +20,7 @@ export interface ITextFieldProps extends BaseFieldProps { limit: number | undefined; rows?: number; name: string; + placeholder?: string; settings: PanelSettings; onChange: (txtValue: string) => void; } @@ -27,6 +28,7 @@ export interface ITextFieldProps extends BaseFieldProps { const WysiwygField = React.lazy(() => import('./WysiwygField')); export const TextField: React.FunctionComponent = ({ + placeholder, singleLine, wysiwyg, limit, @@ -154,6 +156,7 @@ export const TextField: React.FunctionComponent = ({ className={`metadata_field__input`} value={text || ''} onChange={(e) => onTextChange(e.currentTarget.value)} + placeholder={placeholder} style={{ border }} @@ -164,6 +167,7 @@ export const TextField: React.FunctionComponent = ({ rows={rows || 2} value={text || ''} onChange={(e) => onTextChange(e.currentTarget.value)} + placeholder={placeholder} style={{ border }} diff --git a/src/panelWebView/components/Git/GitAction.tsx b/src/panelWebView/components/Git/GitAction.tsx index dba055a0..efc7a5a1 100644 --- a/src/panelWebView/components/Git/GitAction.tsx +++ b/src/panelWebView/components/Git/GitAction.tsx @@ -1,6 +1,6 @@ -import { Messenger } from '@estruyf/vscode/dist/client'; +import { Messenger, messageHandler } from '@estruyf/vscode/dist/client'; import { EventData } from '@estruyf/vscode/dist/models'; -import { ArrowPathIcon } from '@heroicons/react/24/outline'; +import { ArrowDownTrayIcon, ArrowPathIcon } from '@heroicons/react/24/outline'; import * as React from 'react'; import { useEffect, useState } from 'react'; import { GeneralCommands } from '../../../constants'; @@ -8,6 +8,7 @@ import { PanelSettings } from '../../../models'; import { ActionButton } from '../ActionButton'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { BranchIcon } from '../Icons/BranchIcon'; export interface IGitActionProps { settings: PanelSettings | undefined; @@ -16,21 +17,91 @@ export interface IGitActionProps { export const GitAction: React.FunctionComponent = ({ settings }: React.PropsWithChildren) => { - const [isSyncing, setIsSyncing] = useState(false); + const [commitMessage, setCommitMessage] = useState(undefined); + const [crntBanch, setCrntBranch] = useState(undefined); + const [isSyncing, setIsSyncing] = useState<"syncing" | "fetching" | "idle">("idle"); - const pull = () => { - Messenger.send(GeneralCommands.toVSCode.gitSync); + const sync = () => { + Messenger.send(GeneralCommands.toVSCode.git.sync, commitMessage); }; - const messageListener = (message: MessageEvent>) => { - const { command } = message.data; + const fetch = () => { + Messenger.send(GeneralCommands.toVSCode.git.fetch); + }; - if (command === GeneralCommands.toWebview.gitSyncingStart) { - setIsSyncing(true); - } else if (command === GeneralCommands.toWebview.gitSyncingEnd) { - setIsSyncing(false); + const selectBranch = () => { + messageHandler.send(GeneralCommands.toVSCode.git.selectBranch) + } + + const messageListener = React.useCallback((message: MessageEvent>) => { + const { command, payload } = message.data; + + if (command === GeneralCommands.toWebview.git.syncingStart) { + setIsSyncing(payload || "syncing"); + } else if (command === GeneralCommands.toWebview.git.syncingStart) { + setIsSyncing("syncing"); + } else if (command === GeneralCommands.toWebview.git.syncingEnd) { + setCommitMessage(undefined); + setIsSyncing("idle"); + } else if (command === GeneralCommands.toWebview.git.branchName) { + setCrntBranch(payload || undefined); } - }; + }, []); + + const isCommitRequired = React.useMemo(() => { + const requiresCommitMessage = settings?.git?.requiresCommitMessage || []; + + if (!crntBanch) { + return {}; + } + + if (requiresCommitMessage && requiresCommitMessage.includes(crntBanch) && !commitMessage) { + return { + border: '1px solid var(--vscode-inputValidation-errorBorder)' + }; + } + + return {}; + }, [settings?.git?.requiresCommitMessage, crntBanch, commitMessage]) + + const isCommitDisabed = React.useMemo(() => { + const disabledBranches = settings?.git?.disabledBranches || []; + + if (!crntBanch) { + return true; + } + + if (disabledBranches && disabledBranches.includes(crntBanch)) { + return true; + } + + return false; + }, [settings?.git?.disabledBranches, crntBanch, commitMessage]) + + const isSyncDisabled = React.useMemo(() => { + const disabledBranches = settings?.git?.disabledBranches || []; + const requiresCommitMessage = settings?.git?.requiresCommitMessage || []; + + if (!crntBanch) { + return true; + } + + if (disabledBranches && disabledBranches.includes(crntBanch)) { + return true; + } + + if (requiresCommitMessage && requiresCommitMessage.includes(crntBanch)) { + return !commitMessage; + } + + return false; + }, [settings?.git?.disabledBranches, settings?.git?.requiresCommitMessage, crntBanch, commitMessage]); + + const fetchBranch = React.useCallback(() => { + messageHandler.request(GeneralCommands.toVSCode.git.getBranch).then((branch) => { + setCrntBranch(branch); + }); + }, []); useEffect(() => { Messenger.listen(messageListener); @@ -38,7 +109,11 @@ export const GitAction: React.FunctionComponent = ({ return () => { Messenger.unlisten(messageListener); }; - }, []); + }, [messageListener]); + + useEffect(() => { + fetchBranch(); + }, [fetchBranch]); if (!settings?.git?.actions || !settings?.git.isGitRepo) { return null; @@ -46,17 +121,59 @@ export const GitAction: React.FunctionComponent = ({ return (
- + + {l10n.t(LocalizationKey.panelGitGitActionTitle)} + + + + + +
+ setCommitMessage(e.target.value)} + disabled={isCommitDisabed} + /> + +
- +
- } - /> +
+ + +
+
+
+
); }; diff --git a/src/panelWebView/components/Icons/BranchIcon.tsx b/src/panelWebView/components/Icons/BranchIcon.tsx new file mode 100644 index 00000000..18a3c50e --- /dev/null +++ b/src/panelWebView/components/Icons/BranchIcon.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +export interface IBranchIconProps { + className?: string; +} + +export const BranchIcon: React.FunctionComponent = ({ + className +}: React.PropsWithChildren) => { + return ( + + + + ); +}; \ No newline at end of file diff --git a/src/panelWebView/components/Preview.tsx b/src/panelWebView/components/Preview.tsx index 5b8629f1..60472e9e 100644 --- a/src/panelWebView/components/Preview.tsx +++ b/src/panelWebView/components/Preview.tsx @@ -12,7 +12,11 @@ const Preview: React.FunctionComponent = (_: React.PropsWithChild Messenger.send(CommandToCode.openPreview); }; - return ; + return ( + + {l10n.t(LocalizationKey.panelPreviewTitle)} + + ); }; Preview.displayName = 'Preview'; diff --git a/src/panelWebView/components/PublishAction.tsx b/src/panelWebView/components/PublishAction.tsx index d705363e..42f68fa0 100644 --- a/src/panelWebView/components/PublishAction.tsx +++ b/src/panelWebView/components/PublishAction.tsx @@ -23,7 +23,9 @@ const PublishAction: React.FunctionComponent = ( onClick={publish} className={`${draft ? '' : 'secondary'}`} title={draft ? l10n.t(LocalizationKey.panelPublishActionPublish) : l10n.t(LocalizationKey.panelPublishActionUnpublish)} - /> + > + {draft ? l10n.t(LocalizationKey.panelPublishActionPublish) : l10n.t(LocalizationKey.panelPublishActionUnpublish)} + ); }; diff --git a/src/panelWebView/components/SlugAction.tsx b/src/panelWebView/components/SlugAction.tsx index a7705a7a..880f8181 100644 --- a/src/panelWebView/components/SlugAction.tsx +++ b/src/panelWebView/components/SlugAction.tsx @@ -14,7 +14,11 @@ const SlugAction: React.FunctionComponent< Messenger.send(CommandToCode.updateSlug); }; - return ; + return ( + + {l10n.t(LocalizationKey.panelSlugActionTitle)} + + ); }; SlugAction.displayName = 'SlugAction'; diff --git a/src/panelWebView/styles.css b/src/panelWebView/styles.css index 163bec84..9d0a9a40 100644 --- a/src/panelWebView/styles.css +++ b/src/panelWebView/styles.css @@ -976,12 +976,14 @@ vscode-divider { } /* Git actions */ +.git_actions__fetch, .git_actions__sync { display: flex; align-items: center; justify-content: center; } +.git_actions__fetch svg, .git_actions__sync svg { height: 1.25rem; width: 1.25rem;