diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f62c853..d01d1bba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Change Log +## [10.1.0] - 2024-04-11 - [Release notes](https://beta.frontmatter.codes/updates/v10.1.0) + +### ✨ New features + +- [#671](https://github.com/estruyf/vscode-front-matter/issues/671): Command bar for contents and media dashboard + +### 🎨 Enhancements + +- [#773](https://github.com/estruyf/vscode-front-matter/issues/773): Added the ability to rename content files +- [#777](https://github.com/estruyf/vscode-front-matter/issues/777): Show an error in the metadata panel if something went wrong while parsing the front matter +- [#778](https://github.com/estruyf/vscode-front-matter/issues/778): Added the ability to open a file or webpage when custom scripts is completed +- [#783](https://github.com/estruyf/vscode-front-matter/issues/783): Always show the custom panel view +- [#785](https://github.com/estruyf/vscode-front-matter/issues/785): Adding common actions at the bottom of the content and media cards +- [#787](https://github.com/estruyf/vscode-front-matter/issues/787): Support for glob patterns in the page folder paths +- [#790](https://github.com/estruyf/vscode-front-matter/pull/790): Updated Japanese translations thanks to [mayumihara](https://github.com/mayumih387) + +### 🐞 Fixes + +- [#716](https://github.com/estruyf/vscode-front-matter/issues/716): Fix `dataFile` dropdown class +- [#768](https://github.com/estruyf/vscode-front-matter/issues/768): Update broken link to the documentation +- [#771](https://github.com/estruyf/vscode-front-matter/issues/771): Fix lowercase `data` tab label +- [#782](https://github.com/estruyf/vscode-front-matter/issues/782): Fix for setting the correct view when inserting media or snippets +- [#786](https://github.com/estruyf/vscode-front-matter/issues/786): Remove on startup as VSCode now triggers on known commands + ## [10.0.2] - 2024-03-01 ### 🐞 Fixes diff --git a/README.beta.md b/README.beta.md index 0e4cf3a1..114c4498 100644 --- a/README.beta.md +++ b/README.beta.md @@ -182,15 +182,31 @@ You can open showcase issues for the following things: ## 🖤 Backers & Sponsors 👇 🤘

- + Front Matter sponsors


-

+

+ + run.events - Event Management Platform + +

+ +
+ +

- - + Powered by Vercel + +

+ +
+ +

+ + Supported by the BEJS Community +

## 📊 Telemetry diff --git a/README.md b/README.md index eccd6a18..615a1a4d 100644 --- a/README.md +++ b/README.md @@ -185,9 +185,17 @@ You can open showcase issues for the following things:
+

+ + run.events - Event Management Platform + +

+ +
+

- Powered by Vercel + Powered by Vercel

diff --git a/assets/icons/media-dark.svg b/assets/icons/media-dark.svg index a8ba726b..3eea8202 100644 --- a/assets/icons/media-dark.svg +++ b/assets/icons/media-dark.svg @@ -1,3 +1,3 @@ - - - \ No newline at end of file + + + diff --git a/assets/icons/media-light.svg b/assets/icons/media-light.svg index cc126944..1c383630 100644 --- a/assets/icons/media-light.svg +++ b/assets/icons/media-light.svg @@ -1,3 +1,3 @@ - - - \ No newline at end of file + + + diff --git a/assets/icons/scissors-dark.svg b/assets/icons/scissors-dark.svg index 2e81042b..de9e89e5 100644 --- a/assets/icons/scissors-dark.svg +++ b/assets/icons/scissors-dark.svg @@ -1,3 +1,4 @@ - - - \ No newline at end of file + + + + diff --git a/assets/icons/scissors-light.svg b/assets/icons/scissors-light.svg index a2e41ffb..fa4463d0 100644 --- a/assets/icons/scissors-light.svg +++ b/assets/icons/scissors-light.svg @@ -1,3 +1,4 @@ - - - \ No newline at end of file + + + + diff --git a/assets/media/styles.css b/assets/media/styles.css index fd384c80..39803fb9 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -247,14 +247,6 @@ background-color: var(--vscode-button-secondaryHoverBackground); } -.table__cell { - overflow: hidden; -} - -.table__title { - text-transform: capitalize; -} - .table__cell__seo_details { padding: 10px; } @@ -281,11 +273,6 @@ margin-left: 0.5rem; } -.seo__status__note { - font-size: 10px; - padding: 3px 0; -} - /* Fields */ .field__toggle { position: relative; @@ -364,7 +351,7 @@ input:checked + .field__toggle__slider:before { } /* File list */ -.file_list vscode-label { +.file_list label { border-bottom: 1px solid var(--vscode-foreground); } diff --git a/l10n/bundle.l10n.fr.json b/l10n/bundle.l10n.fr.json index 68d68d09..e7da0220 100644 --- a/l10n/bundle.l10n.fr.json +++ b/l10n/bundle.l10n.fr.json @@ -105,7 +105,7 @@ "dashboard.header.tabs.contents": "Contenus", "dashboard.header.tabs.media": "Médias", "dashboard.header.tabs.snippets": "Snippets", - "dashboard.header.tabs.data": "données", + "dashboard.header.tabs.data": "Données", "dashboard.header.tabs.taxonomies": "Taxonomies", "dashboard.header.viewSwitch.toGrid": "Afficher en grille", "dashboard.header.viewSwitch.toList": "Afficher en liste", diff --git a/l10n/bundle.l10n.it.json b/l10n/bundle.l10n.it.json index aa38a8b4..3a6946db 100644 --- a/l10n/bundle.l10n.it.json +++ b/l10n/bundle.l10n.it.json @@ -105,7 +105,7 @@ "dashboard.header.tabs.contents": "Contenuto", "dashboard.header.tabs.media": "Media", "dashboard.header.tabs.snippets": "Snippets", - "dashboard.header.tabs.data": "dati", + "dashboard.header.tabs.data": "Dati", "dashboard.header.tabs.taxonomies": "Tassonomie", "dashboard.header.viewSwitch.toGrid": "Passa alla griglia", "dashboard.header.viewSwitch.toList": "Passa all'elenco", diff --git a/l10n/bundle.l10n.ja.json b/l10n/bundle.l10n.ja.json index 695fae0e..f54052e0 100644 --- a/l10n/bundle.l10n.ja.json +++ b/l10n/bundle.l10n.ja.json @@ -3,8 +3,8 @@ "common.edit": "編集", "common.delete": "削除", "common.cancel": "キャンセル", - "common.clear": "クリア", "common.apply": "適用", + "common.clear": "クリア", "common.clear.value": "値をクリア", "common.search": "検索", "common.save": "保存", @@ -35,6 +35,16 @@ "common.no": "いいえ", "common.openSettings": "設定を開く", "common.back": "戻る", + "common.open": "開く", + "common.openWithValue": "開く: {0}", + "common.openCustomActions": "カスタムコマンドを開く", + "common.view": "表示", + "common.translate": "翻訳する", + "common.languages": "言語", + "common.scripts": "スクリプト", + "common.rename": "ファイル名を変更する", + + "loading.initPages": "記事を読み込んでいます", "notifications.outputChannel.link": "出力ウィンドウ", "notifications.outputChannel.description": "詳細は{0}を確認してください。", @@ -42,18 +52,36 @@ "settings.view.common": "一般", "settings.view.contentFolders": "記事フォルダー", "settings.view.astro": "Astro", + "settings.view.integration": "統合機能", + "settings.openOnStartup": "起動時にダッシュボードを開く", "settings.contentTypes": "記事タイプ", "settings.contentFolders": "記事フォルダー", "settings.diagnostic": "診断", "settings.diagnostic.description": "診断プログラムを実行して、Front Matter CMS構成全体を確認できます。", "settings.diagnostic.link": "完全診断を実行する", + "settings.git": "Git同期", + "settings.git.enabled": "Git同期を有効にして、変更内容をリポジトリと簡単に同期させます。", + "settings.git.commitMessage": "コミットメッセージ", + "settings.git.submoduleInfo": "Gitサブモジュールを使用している場合は、サブモジュールの設定についてドキュメントを参照してください。", + "settings.git.submoduleLink": "Gitサブモジュールについて確認する", + "settings.integration.title": "統合機能", "settings.commonSettings.website.title": "ウェブサイトとSSGの設定", "settings.commonSettings.previewUrl": "プレビュー用URL", "settings.commonSettings.websiteUrl": "ウェブサイトのURL", "settings.commonSettings.startCommand": "SSG/フレームワーク起動コマンド", + "settings.integrationsView.deepl.title": "DeepL", + "settings.integrationsView.deepl.intput.label": "API key", + "settings.integrationsView.deepl.intput.placeholder": "DeepL API keyを入力", + + "settings.integrationsView.azure.title": "Azure AI Translator", + "settings.integrationsView.azure.intput.label": "サブスクリプションキー", + "settings.integrationsView.azure.intput.placeholder": "Azure AI Translatorのサブスクリプションキーを入力", + "settings.integrationsView.azure.region.label": "リージョン", + "settings.integrationsView.azure.region.placeholder": "Azure AI Translatorのリージョンを入力 例: westeurope", + "developer.title": "開発モード", "developer.reload.title": "ダッシュボードを再読み込み", "developer.reload.label": "再読み込み", @@ -81,6 +109,8 @@ "dashboard.contents.contentActions.menuItem.view": "開く", "dashboard.contents.contentActions.alert.title": "削除: {0}", "dashboard.contents.contentActions.alert.description": "本当に\"{0}\"を削除しますか?", + "dashboard.contents.contentActions.translations.create": "翻訳する", + "dashboard.contents.contentActions.translations.menu": "翻訳版", "dashboard.contents.item.invalidTitle": "<無効なタイトル>", "dashboard.contents.item.invalidDescription": "<無効なディスクリプション>", @@ -108,6 +138,7 @@ "dashboard.dataView.dataView.getStarted": "データタイプを選択して開始する", "dashboard.dataView.dataView.noDataFiles": "データファイルが見つかりませんでした", "dashboard.dataView.dataView.getStarted.link": "データファイルの利用方法について確認する", + "dashboard.dataView.dataView.update.message": "データエントリーを更新しました。", "dashboard.dataView.emptyView.heading": "最初にデータタイプを選んでください", @@ -118,6 +149,13 @@ "dashboard.errorView.description": "ダッシュボードを一旦閉じてからやり直してください。", + "dashboard.filters.languageFilter.label": "ロケール", + "dashboard.filters.languageFilter.all": "全て", + + "dashboard.header.actionsBar.itemsSelected": "{0}件を選択中", + "dashboard.header.actionsBar.alertDelete.title": "選択ファイルを削除", + "dashboard.header.actionsBar.alertDelete.description": "選択したファイルを本当に削除しますか?", + "dashboard.header.breadcrumb.home": "ホーム", "dashboard.header.clearFilters.title": "絞り込み・グループ・並べ替えを解除", @@ -201,10 +239,17 @@ "dashboard.media.folderCreation.hexo.create": "Assetフォルダーを作成", "dashboard.media.folderCreation.folder.create": "新規フォルダーを作成", + "dashboard.media.folderItem.contentDirectory": "コンテンツディレクトリー", + "dashboard.media.folderItem.publicDirectory": "Publicディレクトリー", + + "dashboard.media.item.buttom.insert.image": "画像を挿入", + "dashboard.media.item.buttom.insert.snippet": "スニペットを挿入", + "dashboard.media.item.quickAction.insert.field": "この画像を\"{0}\"フィールドに追加", "dashboard.media.item.quickAction.insert.markdown": "画像をMarkdown記法で挿入", "dashboard.media.item.quickAction.copy.path": "ファイルパスをコピー", "dashboard.media.item.quickAction.delete": "ファイルを削除", + "dashboard.media.item.menuItem.view": "メタデータの詳細を表示", "dashboard.media.item.menuItem.edit.metadata": "メタデータを編集", "dashboard.media.item.menuItem.insert.image": "画像を挿入", "dashboard.media.item.menuItem.reveal.media": "メディアの場所を表示", @@ -275,6 +320,8 @@ "dashboard.steps.stepsToGetStarted.contentFolders.information.description": "エクスプローラーでフォルダー名を右クリックして「フォルダーを登録」を選択する方法でも、フォルダーの登録が可能です。", "dashboard.steps.stepsToGetStarted.tags.name": "全てのタグとカテゴリーをインポート(オプション)", "dashboard.steps.stepsToGetStarted.tags.description": "Front Matterに記事用フォルダーが登録されました。記事から全てのタグとカテゴリーをインポートしますか?", + "dashboard.steps.stepsToGetStarted.git.name": "Git同期を有効化しますか?", + "dashboard.steps.stepsToGetStarted.git.description": "Git同期を有効にして、変更内容をリポジトリと簡単に同期させます。", "dashboard.steps.stepsToGetStarted.showDashboard.name": "ダッシュボードを開く", "dashboard.steps.stepsToGetStarted.showDashboard.description": "全ての設定が終わると、ダッシュボードが表示できるようになります。", "dashboard.steps.stepsToGetStarted.template.name": "設定用のテンプレートを使用する", @@ -283,6 +330,7 @@ "dashboard.steps.stepsToGetStarted.astroContentTypes.name": "Astroコンテンツコレクションのコンテンツタイプを作成する", "dashboard.taxonomyView.button.add.title": "\"{0}\"をタクソノミーに追加", + "dashboard.taxonomyView.button.tag.title": "タグを追加", "dashboard.taxonomyView.button.edit.title": "\"{0}\"を編集", "dashboard.taxonomyView.button.merge.title": "\"{0}\"をマージ", "dashboard.taxonomyView.button.move.title": "他のタクソノミーへ移行", @@ -329,6 +377,11 @@ "dashboard.configuration.astro.astroContentTypes.empty": "Astroコンテンツコレクションが見つかりません。", "dashboard.configuration.astro.astroContentTypes.description": "以下のAstroコンテンツコレクションは、コンテンツタイプを生成するために使用できます。", + "panel.git.gitAction.title": "変更の反映", + "panel.git.gitAction.branch.select": "ブランチを選択", + "panel.git.gitAction.input.placeholder": "コミットメッセージ", + "panel.git.gitAction.button.fetch": "フェッチ", + "panel.contentType.contentTypeValidator.title": "記事タイプ", "panel.contentType.contentTypeValidator.hint": "記事タイプのフィールドは設定と異なります。この記事の記事タイプを、作成・更新または設定しますか?", "panel.contentType.contentTypeValidator.button.create": "新しい記事タイプを作成", @@ -414,6 +467,7 @@ "panel.globalSettings.action.server.placeholder": "例: {0}", "panel.metadata.title": "メタデータ", + "panel.metadata.focusProblems": "詳細を「問題」表示で確認してください。", "panel.otherActions.title": "他のコマンド", "panel.otherActions.writingSettings.enabled": "ライティング設定が有効", @@ -474,6 +528,10 @@ "commands.article.setDate.error": "日付の表示形式の解析中に何らかの問題が発生しました。\"{0}\"の設定を確認してください。", "commands.article.updateSlug.error": "ファイル名を変更できませんでした。: {0}", + "commands.article.rename.fileNotExists.error": "ファイルが存在しません。", + "commands.article.rename.fileExists.error": "\"{0}\" というファイル名は既に存在しています。", + "commands.article.rename.fileName.title": "ファイル名を変更: {0}", + "commands.article.rename.fileName.prompt": "ファイル名", "commands.cache.cleared": "キャッシュがクリアされました。", @@ -501,6 +559,19 @@ "commands.folders.get.notificationError.remove.action": "フォルダー設定を削除", "commands.folders.get.notificationError.create.action": "フォルダーを作成", + "commands.i18n.create.warning.noFileSelected": "ファイルが選択されていません。", + "commands.i18n.create.warning.noFile": "ファイルが取得できませんでした。", + "commands.i18n.create.warning.noContentType": "現在のファイルの記事タイプを取得できませんでした。", + "commands.i18n.create.warning.noConfig": "i18nの設定が見つかりません。", + "commands.i18n.create.error.noLocaleDefinition": "現在のファイルのロケールを取得できませんでした。", + "commands.i18n.create.error.noLocales": "現在のファイルは利用可能なすべての言語に翻訳されています。", + "commands.i18n.create.error.noContentFolder": "現在のファイルの記事フォルダーを指定できませんでした。", + "commands.i18n.create.error.fileExists": "そのi18n翻訳は既に存在しています。", + "commands.i18n.create.success.created": "\"{0}\" i18n記事ファイルを作成しました。", + "commands.i18n.create.quickPick.title": "言語別の記事を作成", + "commands.i18n.create.quickPick.placeHolder": "どの言語で記事を作成しますか?", + "commands.i18n.translate.progress.title": "記事を翻訳しています...", + "commands.preview.panel.title": "プレビュー: {0}", "commands.preview.askUserToPickFolder.title": "プレビュー用の記事フォルダーを選択してください。", @@ -611,9 +682,6 @@ "helpers.extension.getVersion.changelog": "変更履歴を確認する", "helpers.extension.getVersion.starIt": "⭐️を付ける", "helpers.extension.getVersion.update.notification": "{0} が v{1} に更新されました!新機能をチェックしてください!", - "helpers.extension.migrateSettings.deprecated.warning": "\"{0}\"及び\"{1}\"の設定は非推奨になりました。代わりに\"isPublishDate\"と\"isModifiedDate\"の日付フィールドを使用してください。", - "helpers.extension.migrateSettings.deprecated.warning.hide": "非表示にする", - "helpers.extension.migrateSettings.deprecated.warning.seeGuide": "移行ガイドを読む", "helpers.extension.migrateSettings.templates.quickPick.title": "{0} - テンプレート", "helpers.extension.migrateSettings.templates.quickPick.placeholder": "テンプレート機能の使用を継続しますか?", "helpers.extension.checkIfExtensionCanRun.warning": "Front MatterのBETA版は安定版がインストールされている場合は利用できません。BETA版のみがインストールされていることを確認してください。", @@ -648,6 +716,7 @@ "helpers.questions.selectContentType.quickPick.title": "記事タイプ", "helpers.questions.selectContentType.quickPick.placeholder": "新規作成する記事の記事タイプを選択してください。", "helpers.questions.selectContentType.noSelection.warning": "記事タイプが選択されていません。", + "helpers.questions.selectContentType.quickPick.error.noContentTypes": "このフォルダーには、一致する記事タイプが設定されていません。", "helpers.seoHelper.checkLength.diagnostic.message": "記事{0}の文字数が{1}文字を超えています(現在の文字数: {2})。SEOの観点上、{1}文字以内に収めることが推奨されます。", @@ -690,6 +759,7 @@ "listeners.dashboard.settingsListener.triggerTemplate.progress.title": "テンプレートをダウンロードして初期化しています...", "listeners.dashboard.settingsListener.triggerTemplate.download.error": "テンプレートのダウンロードに失敗しました。", "listeners.dashboard.settingsListener.triggerTemplate.init.error": "テンプレートの初期化に失敗しました。", + "listeners.dashboard.settingsListener.setSecretValue.message": "設定が更新されました。", "listeners.dashboard.snippetListener.addSnippet.missingFields.warning": "スニペットのタイトルまたはbodyが空です。", "listeners.dashboard.snippetListener.addSnippet.exists.warning": "同じタイトルのスニペットが既に存在しています。", @@ -700,6 +770,7 @@ "listeners.panel.dataListener.aiSuggestTaxonomy.noEditor.error": "アクティブなエディターがありません。", "listeners.panel.dataListener.aiSuggestTaxonomy.noData.error": "記事データがありません。", "listeners.panel.dataListener.getDataFileEntries.noDataFiles.error": "データファイルのエントリーが見つかりませんでした。", + "listeners.panel.dataListener.pushMetadata.frontMatter.error": "front matterの解析中にエラーが発生しまいsた。ファイルの内容を確認してください。", "listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "アクティブなエディターがありません。", diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 523f8d44..c7410152 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -37,6 +37,12 @@ "common.back": "Back", "common.open": "Open", "common.openWithValue": "Open: {0}", + "common.openCustomActions": "Open custom actions", + "common.view": "View", + "common.translate": "Translate", + "common.languages": "Languages", + "common.scripts": "Scripts", + "common.rename": "Rename", "loading.initPages": "Loading content", @@ -68,7 +74,7 @@ "settings.integrationsView.deepl.title": "DeepL", "settings.integrationsView.deepl.intput.label": "API key", - "settings.integrationsView.deepl.intput.placeholder": "Enter your Azure Translator API key", + "settings.integrationsView.deepl.intput.placeholder": "Enter your Deepl API key", "settings.integrationsView.azure.title": "Azure AI Translator Service", "settings.integrationsView.azure.intput.label": "Subscription key", @@ -146,6 +152,10 @@ "dashboard.filters.languageFilter.label": "Locale", "dashboard.filters.languageFilter.all": "All", + "dashboard.header.actionsBar.itemsSelected": "{0} selected", + "dashboard.header.actionsBar.alertDelete.title": "Delete selected files", + "dashboard.header.actionsBar.alertDelete.description": "Are you sure you want to delete the selected files?", + "dashboard.header.breadcrumb.home": "Home", "dashboard.header.clearFilters.title": "Clear filters, grouping, and sorting", @@ -199,7 +209,7 @@ "dashboard.header.tabs.contents": "Contents", "dashboard.header.tabs.media": "Media", "dashboard.header.tabs.snippets": "Snippets", - "dashboard.header.tabs.data": "data", + "dashboard.header.tabs.data": "Data", "dashboard.header.tabs.taxonomies": "Taxonomies", "dashboard.header.viewSwitch.toGrid": "Change to grid", @@ -229,6 +239,9 @@ "dashboard.media.folderCreation.hexo.create": "Create post asset folder", "dashboard.media.folderCreation.folder.create": "Create new folder", + "dashboard.media.folderItem.contentDirectory": "Content directory", + "dashboard.media.folderItem.publicDirectory": "Public directory", + "dashboard.media.item.buttom.insert.image": "Insert image", "dashboard.media.item.buttom.insert.snippet": "Insert snippet", @@ -454,6 +467,7 @@ "panel.globalSettings.action.server.placeholder": "Example: {0}", "panel.metadata.title": "Metadata", + "panel.metadata.focusProblems": "Check the problems view for more information", "panel.otherActions.title": "Other actions", "panel.otherActions.writingSettings.enabled": "Writing settings enabled", @@ -514,6 +528,10 @@ "commands.article.setDate.error": "Something failed while parsing the date format. Check your \"{0}\" setting.", "commands.article.updateSlug.error": "Failed to rename file: {0}", + "commands.article.rename.fileNotExists.error": "The file did not exist", + "commands.article.rename.fileExists.error": "A file with the name \"{0}\" already exists", + "commands.article.rename.fileName.title": "Rename: {0}", + "commands.article.rename.fileName.prompt": "File name", "commands.cache.cleared": "Cache cleared", @@ -752,6 +770,7 @@ "listeners.panel.dataListener.aiSuggestTaxonomy.noEditor.error": "No active editor", "listeners.panel.dataListener.aiSuggestTaxonomy.noData.error": "No article data", "listeners.panel.dataListener.getDataFileEntries.noDataFiles.error": "Couldn't find data file entries", + "listeners.panel.dataListener.pushMetadata.frontMatter.error": "Something went wrong while parsing your front matter. Please check the contents of your file.", "listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "No active editor", diff --git a/package-lock.json b/package-lock.json index 0cc40e9b..de7ee19e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,18 @@ { "name": "vscode-front-matter-beta", - "version": "10.0.2", + "version": "10.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-front-matter-beta", - "version": "10.0.2", + "version": "10.1.0", "license": "MIT", "dependencies": { "@radix-ui/react-dropdown-menu": "^2.0.6" }, "devDependencies": { "@actions/core": "^1.10.0", - "@bendera/vscode-webview-elements": "0.6.2", "@estruyf/vscode": "^1.1.0", "@headlessui/react": "^1.7.18", "@heroicons/react": "^2.1.1", @@ -38,6 +37,7 @@ "@types/vscode": "^1.73.0", "@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/parser": "^5.50.0", + "@vscode-elements/elements": "^1.2.0", "@vscode/l10n": "^0.0.14", "@vscode/webview-ui-toolkit": "^1.2.2", "@webpack-cli/serve": "^1.7.0", @@ -85,7 +85,7 @@ "react-quill": "^2.0.0", "react-router-dom": "^6.8.0", "react-sortable-hoc": "^2.0.0", - "recoil": "^0.4.1", + "recoil": "^0.7.7", "remark-gfm": "^3.0.1", "rimraf": "^3.0.2", "semver": "^7.3.8", @@ -400,15 +400,6 @@ "node": ">=6.9.0" } }, - "node_modules/@bendera/vscode-webview-elements": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@bendera/vscode-webview-elements/-/vscode-webview-elements-0.6.2.tgz", - "integrity": "sha512-smtr+KvCKV2MwjVrmyvrhonpaXVpxCjTMXUQOwDwWSAQ42x5pnlpjCGElz2dljc5VHS1Mh1ovPSQ/P3jAm7vMQ==", - "dev": true, - "dependencies": { - "lit-element": "^2.5.1" - } - }, "node_modules/@ctrl/tinycolor": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", @@ -764,6 +755,21 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", + "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==", + "dev": true + }, + "node_modules/@lit/reactive-element": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "dev": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, "node_modules/@microsoft/fast-element": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.12.0.tgz", @@ -2073,6 +2079,12 @@ "integrity": "sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==", "dev": true }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true + }, "node_modules/@types/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.4.tgz", @@ -2337,6 +2349,15 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vscode-elements/elements": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vscode-elements/elements/-/elements-1.2.0.tgz", + "integrity": "sha512-aCsf9iEnx+PE2rRfAySjvFTSgqP4NUvHG0nOc5AxFB1FXHyG/ayYA2TN9XpT7zuO024tRAu+XoKREbRC7uAmLA==", + "dev": true, + "dependencies": { + "lit": "^3.1.2" + } + }, "node_modules/@vscode/l10n": { "version": "0.0.14", "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.14.tgz", @@ -6574,20 +6595,36 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/lit": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.2.tgz", + "integrity": "sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==", + "dev": true, + "dependencies": { + "@lit/reactive-element": "^2.0.4", + "lit-element": "^4.0.4", + "lit-html": "^3.1.2" + } + }, "node_modules/lit-element": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.5.1.tgz", - "integrity": "sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.4.tgz", + "integrity": "sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==", "dev": true, "dependencies": { - "lit-html": "^1.1.1" + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.0.4", + "lit-html": "^3.1.2" } }, "node_modules/lit-html": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.4.1.tgz", - "integrity": "sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==", - "dev": true + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.2.tgz", + "integrity": "sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==", + "dev": true, + "dependencies": { + "@types/trusted-types": "^2.0.2" + } }, "node_modules/load-json-file": { "version": "4.0.0", @@ -10193,9 +10230,9 @@ } }, "node_modules/recoil": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.4.1.tgz", - "integrity": "sha512-vp6KPwlHOjJ4bJofmdDchmgI9ilMTCoUisK8/WYLl8dThH7e7KmtZttiLgvDb2Em99dUfTEsk8vT8L1nUMgqXQ==", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", "dev": true, "dependencies": { "hamt_plus": "1.0.2" diff --git a/package.json b/package.json index 78a5510a..509933d6 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,14 @@ "displayName": "Front Matter CMS", "description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "10.0.2", + "version": "10.1.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { "color": "#0e131f", "theme": "dark" }, - "badges": [ - { + "badges": [{ "description": "version", "url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square", "href": "https://github.com/estruyf/vscode-front-matter" @@ -51,8 +50,7 @@ }, "activationEvents": [ "workspaceContains:**/.frontmatter", - "workspaceContains:**/frontmatter.json", - "onStartupFinished" + "workspaceContains:**/frontmatter.json" ], "main": "./dist/extension.js", "contributes": { @@ -71,12 +69,16 @@ "**/.frontmatter/config/*.json": "jsonc" } }, - "keybindings": [ - { + "keybindings": [{ "command": "frontMatter.dashboard", "key": "alt+d" }, { + "command": "workbench.action.webview.reloadWebviewAction", + "key": "ctrl+r", + "mac": "cmd+r", + "when": "activeWebviewPanelId == frontMatterDashboard" + }, { "command": "frontMatter.insertMedia", "key": "ctrl+shift+i", "mac": "cmd+shift+i", @@ -90,23 +92,19 @@ } ], "viewsContainers": { - "activitybar": [ - { - "id": "frontmatter-explorer", - "title": "FM", - "icon": "$(fm-logo)" - } - ] + "activitybar": [{ + "id": "frontmatter-explorer", + "title": "FM", + "icon": "$(fm-logo)" + }] }, "views": { - "frontmatter-explorer": [ - { - "id": "frontMatter.explorer", - "name": "Front Matter", - "icon": "$(fm-logo)", - "type": "webview" - } - ] + "frontmatter-explorer": [{ + "id": "frontMatter.explorer", + "name": "Front Matter", + "icon": "$(fm-logo)", + "type": "webview" + }] }, "configuration": { "title": "%settings.configuration.title%", @@ -174,8 +172,7 @@ "frontMatter.content.defaultFileType": { "type": "string", "default": "md", - "oneOf": [ - { + "oneOf": [{ "enum": [ "md", "mdx" @@ -191,8 +188,7 @@ "frontMatter.content.defaultSorting": { "type": "string", "default": "", - "oneOf": [ - { + "oneOf": [{ "enum": [ "LastModifiedAsc", "LastModifiedDesc", @@ -540,8 +536,7 @@ "categories" ], "markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%", - "items": [ - { + "items": [{ "type": "string", "enum": [ "contentFolders", @@ -614,8 +609,7 @@ "command": { "$id": "#scriptCommand", "type": "string", - "anyOf": [ - { + "anyOf": [{ "enum": [ "node", "bash", @@ -811,8 +805,7 @@ "title", "file" ], - "anyOf": [ - { + "anyOf": [{ "required": [ "schema" ] @@ -866,8 +859,7 @@ "id", "path" ], - "anyOf": [ - { + "anyOf": [{ "required": [ "schema" ] @@ -1108,29 +1100,26 @@ } } }, - "default": [ - { - "name": "default", - "fileTypes": null, - "fields": [ - { - "title": "Title", - "name": "title", - "type": "string" - }, - { - "title": "Caption", - "name": "caption", - "type": "string" - }, - { - "title": "Alt text", - "name": "alt", - "type": "string" - } - ] - } - ], + "default": [{ + "name": "default", + "fileTypes": null, + "fields": [{ + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Caption", + "name": "caption", + "type": "string" + }, + { + "title": "Alt text", + "name": "alt", + "type": "string" + } + ] + }], "scope": "Media" }, "frontMatter.media.supportedMimeTypes": { @@ -1360,8 +1349,7 @@ "default": "", "description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%", "not": { - "anyOf": [ - { + "anyOf": [{ "const": "" }, { @@ -1555,8 +1543,7 @@ "type", "name" ], - "allOf": [ - { + "allOf": [{ "if": { "properties": { "type": { @@ -1764,51 +1751,48 @@ "fields" ] }, - "default": [ - { - "name": "default", - "pageBundle": false, - "fields": [ - { - "title": "Title", - "name": "title", - "type": "string" - }, - { - "title": "Description", - "name": "description", - "type": "string" - }, - { - "title": "Publishing date", - "name": "date", - "type": "datetime", - "default": "{{now}}", - "isPublishDate": true - }, - { - "title": "Content preview", - "name": "preview", - "type": "image" - }, - { - "title": "Is in draft", - "name": "draft", - "type": "boolean" - }, - { - "title": "Tags", - "name": "tags", - "type": "tags" - }, - { - "title": "Categories", - "name": "categories", - "type": "categories" - } - ] - } - ], + "default": [{ + "name": "default", + "pageBundle": false, + "fields": [{ + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Description", + "name": "description", + "type": "string" + }, + { + "title": "Publishing date", + "name": "date", + "type": "datetime", + "default": "{{now}}", + "isPublishDate": true + }, + { + "title": "Content preview", + "name": "preview", + "type": "image" + }, + { + "title": "Is in draft", + "name": "draft", + "type": "boolean" + }, + { + "title": "Tags", + "name": "tags", + "type": "tags" + }, + { + "title": "Categories", + "name": "categories", + "type": "categories" + } + ] + }], "scope": "Taxonomy" }, "frontMatter.taxonomy.customTaxonomy": { @@ -1821,8 +1805,7 @@ "type": "string", "description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%", "not": { - "anyOf": [ - { + "anyOf": [{ "const": "" }, { @@ -2007,8 +1990,7 @@ } } }, - "commands": [ - { + "commands": [{ "command": "frontMatter.project.switch", "title": "%command.frontMatter.project.switch%", "category": "Front Matter", @@ -2334,21 +2316,16 @@ } } ], - "submenus": [ - { - "id": "frontmatter.submenu", - "label": "Front Matter" - } - ], + "submenus": [{ + "id": "frontmatter.submenu", + "label": "Front Matter" + }], "menus": { - "webview/context": [ - { - "command": "workbench.action.webview.openDeveloperTools", - "when": "frontMatter:isDevelopment" - } - ], - "editor/title": [ - { + "webview/context": [{ + "command": "workbench.action.webview.openDeveloperTools", + "when": "frontMatter:isDevelopment" + }], + "editor/title": [{ "command": "frontMatter.markup.heading", "group": "navigation@-133", "when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg" @@ -2434,14 +2411,11 @@ "when": "resourceFilename == 'frontmatter.json'" } ], - "explorer/context": [ - { - "submenu": "frontmatter.submenu", - "group": "frontmatter@1" - } - ], - "frontmatter.submenu": [ - { + "explorer/context": [{ + "submenu": "frontmatter.submenu", + "group": "frontmatter@1" + }], + "frontmatter.submenu": [{ "command": "frontMatter.createFromTemplate", "when": "explorerResourceIsFolder", "group": "frontmatter@1" @@ -2457,8 +2431,7 @@ "group": "frontmatter@3" } ], - "commandPalette": [ - { + "commandPalette": [{ "command": "frontMatter.init", "when": "frontMatterCanInit" }, @@ -2635,8 +2608,7 @@ "when": "frontMatter:file:isValid == true" } ], - "view/title": [ - { + "view/title": [{ "command": "frontMatter.chatbot", "group": "navigation@0", "when": "view == frontMatter.explorer" @@ -2668,57 +2640,52 @@ } ] }, - "grammars": [ - { - "path": "./syntaxes/hugo.tmLanguage.json", - "scopeName": "frontmatter.markdown.hugo", - "injectTo": [ - "text.html.markdown" - ] - } - ], - "walkthroughs": [ - { - "id": "frontmatter.welcome", - "title": "Get started with Front Matter", - "description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.", - "steps": [ - { - "id": "frontmatter.welcome.init", - "title": "Get started", - "description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)", - "media": { - "markdown": "assets/walkthrough/get-started.md" - }, - "completionEvents": [ - "onContext:frontMatterInitialized" - ] + "grammars": [{ + "path": "./syntaxes/hugo.tmLanguage.json", + "scopeName": "frontmatter.markdown.hugo", + "injectTo": [ + "text.html.markdown" + ] + }], + "walkthroughs": [{ + "id": "frontmatter.welcome", + "title": "Get started with Front Matter", + "description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.", + "steps": [{ + "id": "frontmatter.welcome.init", + "title": "Get started", + "description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)", + "media": { + "markdown": "assets/walkthrough/get-started.md" }, - { - "id": "frontmatter.welcome.documentation", - "title": "Documentation", - "description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)", - "media": { - "markdown": "assets/walkthrough/documentation.md" - }, - "completionEvents": [ - "onLink:https://frontmatter.codes/docs" - ] + "completionEvents": [ + "onContext:frontMatterInitialized" + ] + }, + { + "id": "frontmatter.welcome.documentation", + "title": "Documentation", + "description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)", + "media": { + "markdown": "assets/walkthrough/documentation.md" }, - { - "id": "frontmatter.welcome.supporter", - "title": "Support the project", - "description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)", - "media": { - "markdown": "assets/walkthrough/support-the-project.md" - }, - "completionEvents": [ - "onLink:https://github.com/sponsors/estruyf" - ] - } - ] - } - ] + "completionEvents": [ + "onLink:https://frontmatter.codes/docs" + ] + }, + { + "id": "frontmatter.welcome.supporter", + "title": "Support the project", + "description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)", + "media": { + "markdown": "assets/walkthrough/support-the-project.md" + }, + "completionEvents": [ + "onLink:https://github.com/sponsors/estruyf" + ] + } + ] + }] }, "scripts": { "dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*", @@ -2744,7 +2711,6 @@ }, "devDependencies": { "@actions/core": "^1.10.0", - "@bendera/vscode-webview-elements": "0.6.2", "@estruyf/vscode": "^1.1.0", "@headlessui/react": "^1.7.18", "@heroicons/react": "^2.1.1", @@ -2769,6 +2735,7 @@ "@types/vscode": "^1.73.0", "@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/parser": "^5.50.0", + "@vscode-elements/elements": "^1.2.0", "@vscode/l10n": "^0.0.14", "@vscode/webview-ui-toolkit": "^1.2.2", "@webpack-cli/serve": "^1.7.0", @@ -2816,7 +2783,7 @@ "react-quill": "^2.0.0", "react-router-dom": "^6.8.0", "react-sortable-hoc": "^2.0.0", - "recoil": "^0.4.1", + "recoil": "^0.7.7", "remark-gfm": "^3.0.1", "rimraf": "^3.0.2", "semver": "^7.3.8", @@ -2847,4 +2814,4 @@ "dependencies": { "@radix-ui/react-dropdown-menu": "^2.0.6" } -} +} \ No newline at end of file diff --git a/package.nls.ja.json b/package.nls.ja.json index 53f39863..b12fc555 100644 --- a/package.nls.ja.json +++ b/package.nls.ja.json @@ -48,6 +48,7 @@ "command.frontMatter.markup.unorderedlist": "順序なしリスト", "command.frontMatter.git.sync": "同期", "command.frontMatter.cache.clear": "キャッシュをクリア", + "command.frontMatter.i18n.create": "新しい翻訳を作成", "settings.configuration.title": "Front Matter: チームで作業する場合はfrontmatter.jsonで設定してください。", "setting.frontMatter.projects.markdownDescription": "Front Matter CMSを利用するプロジェクトを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.projects)", "setting.frontMatter.projects.items.properties.name.markdownDescription": "プロジェクトの名前を指定します。", @@ -60,7 +61,7 @@ "setting.frontMatter.content.defaultFileType.markdownDescription": "新しい記事を作成する際の既定のファイル形式を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.defaultfiletype)", "setting.frontMatter.content.defaultSorting.markdownDescription": "ダッシュボード上に表示される記事一覧の既定の並び順を設定します。Enum(列挙型)や任意のIDを指定してください。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.defaultsorting)", "setting.frontMatter.content.draftField.markdownDescription": "記事の下書きステータスを管理するフィールドを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.draftfield)", - "setting.frontMatter.content.draftField.properties.type.description": "使用する下書きフィールドの種類", + "setting.frontMatter.content.draftField.properties.type.description": "使用する下書きフィールドの型", "setting.frontMatter.content.draftField.properties.name.description": "使用するフィールドの名前", "setting.frontMatter.content.draftField.properties.invert.description": "既定では、記事が下書きの場合、下書きフィールドは true に設定されます。これを true に設定すると、false に設定されます。", "setting.frontMatter.content.draftField.properties.choices.description": "フィールドの選択肢のリスト", @@ -75,6 +76,12 @@ "setting.frontMatter.content.pageFolders.items.properties.filePrefix.description": "ファイル名の接頭辞を定義します。", "setting.frontMatter.content.pageFolders.items.properties.contentTypes.description": "現在の場所に使用できる記事タイプを定義します。定義しない場合は、全ての記事タイプを使用できます。", "setting.frontMatter.content.pageFolders.items.properties.disableCreation.description": "フォルダー内の新しい記事の作成を無効にします。", + "setting.frontMatter.content.pageFolders.items.properties.defaultLocale.description": "ページフォルダー用デフォルトのロケールIDを設定します。このフォルダー内の全ての記事が`frontMatter.content.i18n`で設定された言語へ翻訳可能になります。", + "setting.frontMatter.content.pageFolders.items.properties.locales.description": "ページフォルダーで利用するロケールを設定します。この設定は記事の翻訳に使用されます。", + "setting.frontMatter.content.i18n.markdownDescription": "ウェブサイトで利用するロケールを設定します。この設定はページフォルダーレベルの設定より優先されます。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.i18n)", + "setting.frontMatter.content.i18n.items.properties.title.description": "言語名", + "setting.frontMatter.content.i18n.items.properties.locale.description": "言語コード", + "setting.frontMatter.content.i18n.items.properties.path.description": "言語フォルダーの相対パス", "setting.frontMatter.content.placeholders.markdownDescription": "記事タイプとテンプレートで使用するプレースホルダーを配列で定義して、記事のfront matterを自動で入力できるようにします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.placeholders)", "setting.frontMatter.content.placeholders.items.properties.id.description": "プレースホルダーのIDを記事タイプまたはテンプレートで、次のように使用します: {{placeholder}}", "setting.frontMatter.content.placeholders.items.properties.value.description": "プレースホルダーの値", @@ -92,6 +99,7 @@ "setting.frontMatter.content.sorting.items.properties.type.description": "フィールド値の型", "setting.frontMatter.content.supportedFileTypes.markdownDescription": "Front Matterでサポートされるファイル形式を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.supportedfiletypes)", "setting.frontMatter.content.wysiwyg.markdownDescription": "What You See, Is What You Get(WYSIWYG)Markdownコントロールを有効にします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.wysiwyg)", + "setting.frontMatter.content.filters.markdownDescription": "ダッシュボードで利用するフィルターを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.filters)", "setting.frontMatter.custom.scripts.markdownDescription": "実行するNode.jsスクリプトのパスを指定します。現在のファイルのパスが引数として渡されます。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.custom.scripts)", "setting.frontMatter.custom.scripts.items.properties.id.description": "スクリプトの ID。", "setting.frontMatter.custom.scripts.items.properties.title.description": "スクリプトに付けるタイトル。ボタンのタイトルとして表示されます。", @@ -100,8 +108,8 @@ "setting.frontMatter.custom.scripts.items.properties.bulk.description": "全ての記事ファイルに対してスクリプトを実行する", "setting.frontMatter.custom.scripts.items.properties.output.description": "スクリプト出力を出力する場所を定義します。デフォルトは通知表示ですが、エディターパネルに表示するように指定できます。", "setting.frontMatter.custom.scripts.items.properties.outputType.description": "エディター・パネルの出力のタイプ。たとえば、「マークダウン」に変更するために使用できます", - "setting.frontMatter.custom.scripts.items.properties.type.description": "スクリプトが使用される型。", - "setting.frontMatter.custom.scripts.items.properties.command.description": "実行するスクリプトの種類。", + "setting.frontMatter.custom.scripts.items.properties.type.description": "スクリプトが使用される型", + "setting.frontMatter.custom.scripts.items.properties.command.description": "実行するスクリプトの種類", "setting.frontMatter.custom.scripts.items.properties.hidden.description": "UI からアクションを非表示にする", "setting.frontMatter.custom.scripts.items.properties.environments.items.properties.type.description": "スクリプトを使用する必要がある環境タイプ", "setting.frontMatter.custom.scripts.items.properties.environments.items.properties.script.description": "実行するスクリプトへのパス", @@ -152,7 +160,18 @@ "setting.frontMatter.global.notifications.markdownDescription": "表示したい通知を設定します。既定では全ての通知が表示されます。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.global.notifications)", "setting.frontMatter.global.disabledNotifications.markdownDescription": "これは、Front Matter CMSで無効にできる通知タイプの配列です。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.global.disablednotifications)", "setting.frontMatter.media.defaultSorting.markdownDescription": "ダッシュボードのメディア一覧での既定の並び順を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.defaultsorting)", - "setting.frontMatter.media.supportedMimeTypes.markdownDescription": "メディアファイルでサポートされるMIMEタイプを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes)", + "setting.frontMatter.media.supportedMimeTypes.markdownDescription": "メディアコンテンツでサポートされるMIMEタイプを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes)", + + "setting.frontMatter.media.contentTypes.markdownDescription": "Front Matterで利用するメディアコンテンツのタイプを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.contenttypes)", + "setting.frontMatter.media.contentTypes.items.description": "Front Matterで利用するメディアフコンテンツのタイプについての説明。", + "setting.frontMatter.media.contentTypes.items.properties.name.description": "メディアコンテンツのタイプ名", + "setting.frontMatter.media.contentTypes.items.properties.fileTypes.description": "利用可能とするメディアコンテンツのタイプの指定", + "setting.frontMatter.media.contentTypes.items.properties.fields.description": "メディアコンテンツのフィールドを設定します。", + "setting.frontMatter.media.contentTypes.items.properties.fields.properties.title.description": "UI表示用のタイトル", + "setting.frontMatter.media.contentTypes.items.properties.fields.properties.name.description": "フィールドの名前", + "setting.frontMatter.media.contentTypes.items.properties.fields.properties.type.description": "フィールド値の型", + "setting.frontMatter.media.contentTypes.items.properties.fields.properties.single.description": "単一行フィールド", + "setting.frontMatter.panel.freeform.markdownDescription": "未登録のタグ/カテゴリーをタグピッカーに入力可能にするかどうかを設定します(有効にすると、後で保存するオプションが使えます)。規定値はtrue。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform)", "setting.frontMatter.panel.actions.disabled.markdownDescription": "パネル内で非表示にしたいコマンドを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled)", "setting.frontMatter.preview.host.markdownDescription": "プレビュー表示に使用するホストのURLを設定します(例:`http://localhost:1313`)。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host)", @@ -164,11 +183,11 @@ "setting.frontMatter.taxonomy.commaSeparatedFields.items.description": "コンマ区切りの配列として使用するフィールドの名前。", "setting.frontMatter.taxonomy.contentTypes.markdownDescription": "記事・ページ・その他で利用したい記事タイプを設定します。front matterで正しく`type`が設定されていることを確認してください。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.contenttypes)", "setting.frontMatter.taxonomy.contentTypes.items.description": "Front Matterで使用する記事タイプを定義します。", - "setting.frontMatter.taxonomy.contentTypes.items.properties.name.description": "フィールドの種類を定義する", + "setting.frontMatter.taxonomy.contentTypes.items.properties.name.description": "フィールドの種類を定義します。", "setting.frontMatter.taxonomy.contentTypes.items.properties.fileType.description": "作成する記事タイプを指定します。", - "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.description": "記事タイプのフィールドを定義する", + "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.description": "記事タイプのフィールドを定義します。", "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.description": "Front Matterで使用する記事タイプを定義します。", - "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.type.description": "フィールドの種類を定義する", + "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.type.description": "フィールド値の型", "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.name.description": "使用するフィールドの名前", "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.title.description": "UI に表示するタイトル", "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.description.description": "UI に表示する説明", @@ -176,8 +195,8 @@ "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.description": "選択肢を定義する", "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.id.description": "選択肢 ID", "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.title.description": "選択肢のタイトル", - "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "単一行フィールドである", - "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "WYSIWYG フィールド (HTML 出力) である", + "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "単一行フィールド", + "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "WYSIWYG フィールド (HTML 出力) ", "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.multiple.description": "複数の値を選択できますか?", "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPreviewImage.description": "画像フィールドをプレビューとして使用できるかどうかを指定します。記事タイプごとに使用できるプレビュー画像は1つだけです。", "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.hidden.description": "メタデータ セクションからフィールドを非表示にしますか?", @@ -210,6 +229,7 @@ "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description": "比較で大文字と小文字を区別するかどうかを指定します。デフォルト: true", "setting.frontMatter.taxonomy.contentTypes.items.properties.pageBundle.description": "新しい記事を作成するときにフォルダーを作成するかどうかを指定します。", "setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description": "記事タイプのカスタム プレビュー パスを定義します。", + "setting.frontMatter.taxonomy.contentTypes.items.properties.slugTemplate.description": "記事タイプのカスタムスラッグのテンプレートを設定します。", "setting.frontMatter.taxonomy.contentTypes.items.properties.template.description": "新しい記事の作成に使用できるオプションのテンプレート。", "setting.frontMatter.taxonomy.contentTypes.items.properties.postScript.description": "新しい記事の作成後に使用できるオプションのポストスクリプト。", "setting.frontMatter.taxonomy.contentTypes.items.properties.filePrefix.description": "ファイル名の接頭辞を定義します。", @@ -236,6 +256,7 @@ "setting.frontMatter.taxonomy.seoTitleLength.markdownDescription": "SEOに適したタイトルの文字数を設定します。(`-1`に設定するとオフになります。)[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seotitlelength)", "setting.frontMatter.taxonomy.slugPrefix.markdownDescription": "スラッグに付与する接頭辞を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugprefix)", "setting.frontMatter.taxonomy.slugSuffix.markdownDescription": "スラッグに付与する接尾辞を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugsuffix)", + "setting.frontMatter.taxonomy.slugTemplate.markdownDescription": "記事を作成する際のカスタムスラッグのテンプレートを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugtemplate)", "setting.frontMatter.taxonomy.tags.markdownDescription": "Front Matterで利用するタグを管理します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.tags)", "setting.frontMatter.telemetry.disable.markdownDescription": "利用状況の送信をオフにします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.telemetry.disable)", "setting.frontMatter.templates.enabled.markdownDescription": "テンプレート機能を利用します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.enabled)", @@ -250,5 +271,8 @@ "command.frontMatter.settings.refresh": "Front Matterの設定の再読み込み", "setting.frontMatter.config.dynamicFilePath.markdownDescription": "動的構成ファイルへのパスを指定します (例: [[ワークスペース]]/config.js)。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.config.dynamicfilepath)", "setting.frontMatter.taxonomy.contentTypes.items.properties.allowAsSubContent.description": "記事をサブコンテンツとして作成可能かどうかを設定します。", - "setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "記事をサブコンテンツとして作成するかどうかを設定します。" + "setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "記事をサブコンテンツとして作成するかどうかを設定します。", + + "setting.frontMatter.git.disableOnBranches.markdownDescription": "Git Actionsを無効化したいブランチを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.git.disableonbranches)", + "setting.frontMatter.git.requiresCommitMessage.markdownDescription": "特定のブランチを更新する場合にコミットメッセージを必須にします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.git.requirescommitmessage)" } \ No newline at end of file diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 667b7165..5b7b0028 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -1,3 +1,13 @@ +import { + Position, + TextDocument, + TextDocumentWillSaveEvent, + TextEdit, + Uri, + commands, + window, + workspace +} from 'vscode'; import { Folders } from './Folders'; import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType'; import { isValidFile } from './../helpers/isValidFile'; @@ -13,7 +23,6 @@ import { TelemetryEvent, SETTING_SLUG_TEMPLATE } from './../constants'; -import * as vscode from 'vscode'; import { CustomPlaceholder, Field } from '../models'; import { format } from 'date-fns'; import { @@ -33,17 +42,35 @@ import { Telemetry } from '../helpers/Telemetry'; import { ParsedFrontMatter } from '../parsers'; import { MediaListener } from '../listeners/panel'; import { NavigationType } from '../dashboardWebView/models'; -import { Position } from 'vscode'; import { SNIPPET } from '../constants/Snippet'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; export class Article { + /** + * Registers the commands for the Article class. + * + * @param subscriptions - The array of subscriptions to register the commands with. + */ + public static async registerCommands(subscriptions: unknown[]) { + subscriptions.push( + commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate) + ); + + subscriptions.push(commands.registerCommand(COMMAND_NAME.generateSlug, Article.updateSlug)); + + // Inserting an image in Markdown + subscriptions.push(commands.registerCommand(COMMAND_NAME.insertMedia, Article.insertMedia)); + + // Inserting a snippet in Markdown + subscriptions.push(commands.registerCommand(COMMAND_NAME.insertSnippet, Article.insertSnippet)); + } + /** * Sets the article date */ public static async setDate() { - const editor = vscode.window.activeTextEditor; + const editor = window.activeTextEditor; if (!editor) { return; } @@ -77,7 +104,7 @@ export class Article { * Sets the article lastmod date */ public static async setLastModifiedDate() { - const editor = vscode.window.activeTextEditor; + const editor = window.activeTextEditor; if (!editor) { return; } @@ -91,9 +118,7 @@ export class Article { ArticleHelper.update(editor, updatedArticle as ParsedFrontMatter); } - public static async setLastModifiedDateOnSave( - document: vscode.TextDocument - ): Promise { + public static async setLastModifiedDateOnSave(document: TextDocument): Promise { const updatedArticle = this.setLastModifiedDateInner(document); if (typeof updatedArticle === 'undefined') { @@ -105,9 +130,7 @@ export class Article { return [update]; } - private static setLastModifiedDateInner( - document: vscode.TextDocument - ): ParsedFrontMatter | undefined { + private static setLastModifiedDateInner(document: TextDocument): ParsedFrontMatter | undefined { const article = ArticleHelper.getFrontMatterFromDocument(document); // Only set the date, if there is already front matter set @@ -160,7 +183,7 @@ export class Article { Telemetry.send(TelemetryEvent.generateSlug); const updateFileName = Settings.get(SETTING_SLUG_UPDATE_FILE_NAME) as string; - const editor = vscode.window.activeTextEditor; + const editor = window.activeTextEditor; if (!editor) { return; @@ -219,7 +242,7 @@ export class Article { // Check if the file name should be updated by the slug // This is required for systems like Jekyll if (updateFileName) { - const editor = vscode.window.activeTextEditor; + const editor = window.activeTextEditor; if (editor) { const ext = extname(editor.document.fileName); const fileName = basename(editor.document.fileName); @@ -237,7 +260,7 @@ export class Article { try { await editor.document.save(); - await vscode.workspace.fs.rename(editor.document.uri, vscode.Uri.file(newPath), { + await workspace.fs.rename(editor.document.uri, Uri.file(newPath), { overwrite: false }); } catch (e: unknown) { @@ -257,7 +280,7 @@ export class Article { * Retrieve the slug from the front matter */ public static getSlug() { - const editor = vscode.window.activeTextEditor; + const editor = window.activeTextEditor; if (!editor) { return; } @@ -297,7 +320,7 @@ export class Article { * Toggle the page its draft mode */ public static async toggleDraft() { - const editor = vscode.window.activeTextEditor; + const editor = window.activeTextEditor; if (!editor) { return; } @@ -315,7 +338,7 @@ export class Article { * Article auto updater * @param event */ - public static async autoUpdate(event: vscode.TextDocumentWillSaveEvent) { + public static async autoUpdate(event: TextDocumentWillSaveEvent) { const document = event.document; if (document && ArticleHelper.isSupportedFile(document)) { const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE); @@ -355,7 +378,7 @@ export class Article { * Insert an image from the media dashboard into the article */ public static async insertMedia() { - const editor = vscode.window.activeTextEditor; + const editor = window.activeTextEditor; if (!editor) { return; } @@ -367,7 +390,7 @@ export class Article { const position = editor.selection.active; const selectionText = editor.document.getText(editor.selection); - await vscode.commands.executeCommand(COMMAND_NAME.dashboard, { + await commands.executeCommand(COMMAND_NAME.dashboard, { type: 'media', data: { pageBundle: !!contentType.pageBundle, @@ -386,7 +409,7 @@ export class Article { * Insert a snippet into the article */ public static async insertSnippet() { - const editor = vscode.window.activeTextEditor; + const editor = window.activeTextEditor; if (!editor) { return; } @@ -442,7 +465,7 @@ export class Article { const article = ArticleHelper.getFrontMatter(editor); const contentType = article ? ArticleHelper.getContentType(article) : undefined; - await vscode.commands.executeCommand(COMMAND_NAME.dashboard, { + await commands.executeCommand(COMMAND_NAME.dashboard, { type: NavigationType.Snippets, data: { fileTitle: article?.data.title || '', diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index edc8912c..598b1ef5 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -24,7 +24,6 @@ import { ExtensionListener, SnippetListener, TaxonomyListener, - LogListener, LocalizationListener, SsgListener } from '../listeners/dashboard'; @@ -237,7 +236,6 @@ export class Dashboard { ModeListener.process(msg); GitListener.process(msg); TaxonomyListener.process(msg); - LogListener.process(msg); SsgListener.process(msg); }); } diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 4493374d..108ecde9 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -315,6 +315,26 @@ export class Folders { const contentFolders: ContentFolder[] = []; + // Check if wildcard is used + const wildcardFolders = folders.filter((f) => f.path.includes('*')); + if (wildcardFolders && wildcardFolders.length > 0) { + for (const folder of wildcardFolders) { + folders = folders.filter((f) => f.path !== folder.path); + + const folderPath = Folders.absWsFolder(folder, wsFolder); + const subFolders = glob.sync(folderPath, { ignore: '**/node_modules/**' }); + for (const subFolder of subFolders) { + const subFolderPath = parseWinPath(subFolder); + + folders.push({ + ...folder, + title: `${folder.title} (${subFolderPath.replace(wsFolder?.fsPath || '', '')})`, + path: subFolderPath + }); + } + } + } + folders.forEach((folder) => { if (!folder.title) { folder.title = basename(folder.path); diff --git a/src/components/icons/ArrowClockwiseIcon.tsx b/src/components/icons/ArrowClockwiseIcon.tsx new file mode 100644 index 00000000..acdc86d8 --- /dev/null +++ b/src/components/icons/ArrowClockwiseIcon.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; + +export interface IArrowClockwiseIconProps { + className?: string; +} + +export const ArrowClockwiseIcon: React.FunctionComponent = ({ className }: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/components/icons/RenameIcon.tsx b/src/components/icons/RenameIcon.tsx new file mode 100644 index 00000000..d59dcc66 --- /dev/null +++ b/src/components/icons/RenameIcon.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; + +export interface IRenameIconProps { + className: string; +} + +export const RenameIcon: React.FunctionComponent = ({ + className +}: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/constants/GeneralCommands.ts b/src/constants/GeneralCommands.ts index 5db92184..efa20688 100644 --- a/src/constants/GeneralCommands.ts +++ b/src/constants/GeneralCommands.ts @@ -24,6 +24,11 @@ export const GeneralCommands = { content: { locales: 'getContentLocales' }, + logging: { + info: 'logInfo', + warn: 'logWarn', + error: 'logError' + }, runCommand: 'runCommand', getLocalization: 'getLocalization', openOnWebsite: 'openOnWebsite' diff --git a/src/constants/Links.ts b/src/constants/Links.ts index 1bea4ebe..7c839c31 100644 --- a/src/constants/Links.ts +++ b/src/constants/Links.ts @@ -10,3 +10,16 @@ export const SENTRY_LINK = 'https://1ac45704bbe74264a7b4674bdc2abf48@o1022172.ingest.sentry.io/5988293'; export const DOCS_SUBMODULES = 'https://frontmatter.codes/docs/git-integration#git-submodules'; + +export const WEBSITE_LINKS = { + root: 'https://frontmatter.codes', + api: { + metrics: 'https://frontmatter.codes/api/metrics', + ai: 'https://frontmatter.codes/api/ai' + }, + docs: { + dataDashboard: 'https://frontmatter.codes/docs/dashboard/datafiles-view', + snippets: `https://frontmatter.codes/docs/snippets`, + snippetsPlaceholders: `https://frontmatter.codes/docs/snippets#placeholders` + } +}; diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index 7d658e9f..73a2fe97 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -30,6 +30,7 @@ export enum DashboardMessage { getPinnedItems = 'getPinnedItems', pinItem = 'pinItem', unpinItem = 'unpinItem', + rename = 'rename', // Media Dashboard getMedia = 'getMedia', @@ -74,7 +75,6 @@ export enum DashboardMessage { getState = 'getState', runCustomScript = 'runCustomScript', sendTelemetry = 'sendTelemetry', - logError = 'logError', showNotification = 'showNotification', // Settings diff --git a/src/dashboardWebView/components/App.tsx b/src/dashboardWebView/components/App.tsx index c0f14260..2a2b896c 100644 --- a/src/dashboardWebView/components/App.tsx +++ b/src/dashboardWebView/components/App.tsx @@ -9,7 +9,7 @@ import { Contents } from './Contents/Contents'; import { Media } from './Media/Media'; import { DataView } from './DataView'; import { Snippets } from './SnippetsView/Snippets'; -import { FEATURE_FLAG } from '../../constants'; +import { FEATURE_FLAG, GeneralCommands } from '../../constants'; import { Messenger } from '@estruyf/vscode/dist/client'; import { TaxonomyView } from './TaxonomyView'; import { Route, Routes, useNavigate } from 'react-router-dom'; @@ -18,7 +18,6 @@ import { useEffect, useMemo, useState } from 'react'; import { UnknownView } from './UnknownView'; import { ErrorBoundary } from '@sentry/react'; import { ErrorView } from './ErrorView'; -import { DashboardMessage } from '../DashboardMessage'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; import { SettingsView } from './SettingsView/SettingsView'; @@ -55,6 +54,21 @@ export const App: React.FunctionComponent = ({ return isAllowed(mode?.features || [], FEATURE_FLAG.dashboard.taxonomy.view); }, [mode?.features]); + const checkDevMode = (retry: number = 0) => { + if (!window.fmExternal) { + if (retry < 5) { + setTimeout(() => checkDevMode(retry + 1), 150); + } else { + setIsDevMode(false); + return; + } + } + + if (window.fmExternal && window.fmExternal.isDevelopment) { + setIsDevMode(true); + } + } + useEffect(() => { if (view && routePaths[view]) { navigate(routePaths[view]); @@ -65,9 +79,7 @@ export const App: React.FunctionComponent = ({ }, [view]); useEffect(() => { - if (window.fmExternal && window.fmExternal.isDevelopment) { - setIsDevMode(true); - } + checkDevMode(); }, []); if (!settings) { @@ -87,7 +99,7 @@ export const App: React.FunctionComponent = ({ fallback={} onError={(error: Error, componentStack: string, eventId: string) => { Messenger.send( - DashboardMessage.logError, + GeneralCommands.toVSCode.logging.error, `Event ID: ${eventId} Message: ${error.message} diff --git a/src/dashboardWebView/components/Common/DateField.tsx b/src/dashboardWebView/components/Common/DateField.tsx index 737d94ed..1c260f8c 100644 --- a/src/dashboardWebView/components/Common/DateField.tsx +++ b/src/dashboardWebView/components/Common/DateField.tsx @@ -36,7 +36,7 @@ export const DateField: React.FunctionComponent = ({ } return ( - + {dateValue} ); diff --git a/src/dashboardWebView/components/Common/ItemSelection.tsx b/src/dashboardWebView/components/Common/ItemSelection.tsx new file mode 100644 index 00000000..716b6e23 --- /dev/null +++ b/src/dashboardWebView/components/Common/ItemSelection.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import useSelectedItems from '../../hooks/useSelectedItems'; +import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react'; +import { useMemo } from 'react'; + +export interface IItemSelectionProps { + filePath: string; + show?: boolean; +} + +export const ItemSelection: React.FunctionComponent = ({ + filePath, + show +}: React.PropsWithChildren) => { + const { onMultiSelect, selectedFiles } = useSelectedItems(); + + const cssNames = useMemo(() => { + if (show) { + return 'block'; + } + return `${selectedFiles.includes(filePath) ? 'block' : 'hidden'} absolute top-2 left-2`; + }, [show, selectedFiles]); + + return ( +
+ ) => { + e.stopPropagation(); + onMultiSelect(filePath); + }} + checked={selectedFiles.includes(filePath)} /> +
+ ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Common/Spinner.tsx b/src/dashboardWebView/components/Common/Spinner.tsx index 6659732b..ab24795d 100644 --- a/src/dashboardWebView/components/Common/Spinner.tsx +++ b/src/dashboardWebView/components/Common/Spinner.tsx @@ -20,7 +20,7 @@ export const Spinner: React.FunctionComponent = ( { type === 'initPages' && ( -
+
{l10n.t(LocalizationKey.loadingInitPages)}
diff --git a/src/dashboardWebView/components/Contents/ContentActions.tsx b/src/dashboardWebView/components/Contents/ContentActions.tsx index 85bfdc06..e22d8715 100644 --- a/src/dashboardWebView/components/Contents/ContentActions.tsx +++ b/src/dashboardWebView/components/Contents/ContentActions.tsx @@ -1,23 +1,25 @@ -import { Messenger, messageHandler } from '@estruyf/vscode/dist/client'; -import { EyeIcon, GlobeEuropeAfricaIcon, CommandLineIcon, TrashIcon, EllipsisVerticalIcon, LanguageIcon } from '@heroicons/react/24/outline'; +import { messageHandler } from '@estruyf/vscode/dist/client'; +import { EyeIcon, GlobeEuropeAfricaIcon, TrashIcon, LanguageIcon, EllipsisHorizontalIcon } from '@heroicons/react/24/outline'; import * as React from 'react'; -import { CustomScript, I18nConfig, ScriptType } from '../../../models'; +import { CustomScript, I18nConfig } from '../../../models'; import { DashboardMessage } from '../../DashboardMessage'; -import { QuickAction } from '../Menu'; -import { Alert } from '../Modals/Alert'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { SettingsSelector } from '../../state'; +import { SelectedItemActionAtom, SettingsSelector } from '../../state'; import { COMMAND_NAME, GeneralCommands } from '../../../constants'; import { PinIcon } from '../Icons/PinIcon'; import { PinnedItemsAtom } from '../../state/atom/PinnedItems'; -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; +import { RenameIcon } from '../../../components/icons/RenameIcon'; +import { openOnWebsite } from '../../utils'; +import { CustomActions } from './CustomActions'; +import { TranslationMenu } from './TranslationMenu'; export interface IContentActionsProps { - title: string; path: string; relPath: string; + contentType: string; scripts: CustomScript[] | undefined; listView?: boolean; locale?: I18nConfig; @@ -32,9 +34,9 @@ export interface IContentActionsProps { } export const ContentActions: React.FunctionComponent = ({ - title, path, relPath, + contentType, scripts, onOpen, listView, @@ -42,8 +44,8 @@ export const ContentActions: React.FunctionComponent = ({ translations, locale }: React.PropsWithChildren) => { + const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom); const [pinnedItems, setPinnedItems] = useRecoilState(PinnedItemsAtom); - const [showDeletionAlert, setShowDeletionAlert] = React.useState(false); const settings = useRecoilValue(SettingsSelector); const onView = (e: React.MouseEvent) => { @@ -51,30 +53,19 @@ export const ContentActions: React.FunctionComponent = ({ onOpen(); }; - const onDelete = (e: React.MouseEvent) => { + const onDelete = React.useCallback((e: React.MouseEvent) => { e.stopPropagation(); - setShowDeletionAlert(true); - }; - - const onDeleteConfirm = () => { - if (path) { - Messenger.send(DashboardMessage.deleteFile, path); - } - setShowDeletionAlert(false); - }; + setSelectedItemAction({ path, action: 'delete' }); + }, [path]); - const onOpenFile = (filePath: string) => { - messageHandler.send(DashboardMessage.openFile, filePath); - } + const onRename = React.useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + messageHandler.send(DashboardMessage.rename, path); + }, [path]) - const openOnWebsite = React.useCallback((e: React.MouseEvent) => { + const onOpenWebsite = React.useCallback((e: React.MouseEvent) => { e.stopPropagation(); - if (settings?.websiteUrl && path) { - Messenger.send(GeneralCommands.toVSCode.openOnWebsite, { - websiteUrl: settings.websiteUrl, - filePath: path - }); - } + openOnWebsite(settings?.websiteUrl, path); }, [settings?.websiteUrl, path]); const pinItem = React.useCallback((e: React.MouseEvent) => { @@ -91,14 +82,6 @@ export const ContentActions: React.FunctionComponent = ({ }) }, [path]); - const runCustomScript = React.useCallback( - (e: React.MouseEvent, script: CustomScript) => { - e.stopPropagation(); - Messenger.send(DashboardMessage.runCustomScript, { script, path }); - }, - [path] - ); - const runCommand = React.useCallback((commandId: string) => { messageHandler.send(GeneralCommands.toVSCode.runCommand, { command: commandId, @@ -110,103 +93,22 @@ export const ContentActions: React.FunctionComponent = ({ return pinnedItems.includes(relPath); }, [pinnedItems, relPath]); - const customScriptActions = React.useMemo(() => { - return (scripts || []) - .filter( - (script) => - (script.type === undefined || script.type === ScriptType.Content) && - !script.bulk && - !script.hidden - ) - .map((script) => ( - runCustomScript(e, script)}> - - {script.title} - - )); - }, [scripts]); - - const translationsMenu = React.useMemo(() => { - if (!locale || !translations || Object.keys(translations).length === 0) { - return null; - } - - const crntLocale = translations[locale.locale]; - const otherLocales = Object.entries(translations).filter(([key]) => key !== locale.locale); - - if (otherLocales.length === 0) { - return null; - } - - return ( - - - - {l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsMenu)} - - - - - onOpenFile(crntLocale.path)}> - {crntLocale.locale.title || crntLocale.locale.locale} - - - - - { - otherLocales.map(([key, value]) => ( - onOpenFile(value.path)} - > - {value.locale.title || value.locale.locale} - - )) - } - - - - ); - }, [translations, locale, isDefaultLocale]); - return ( <>
- {!listView && ( -
- - - - { - settings?.websiteUrl && ( - - - ) - } - - - -
- )} - - + {l10n.t(LocalizationKey.dashboardContentsContentActionsActionMenuButtonTitle)} - @@ -220,9 +122,14 @@ export const ContentActions: React.FunctionComponent = ({ {l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)} + + + {l10n.t(LocalizationKey.commonRename)} + + { settings?.websiteUrl && ( - + {l10n.t(LocalizationKey.commonOpenOnWebsite)} @@ -238,9 +145,15 @@ export const ContentActions: React.FunctionComponent = ({ ) } - {translationsMenu} + - {customScriptActions} + @@ -252,17 +165,6 @@ export const ContentActions: React.FunctionComponent = ({
- - {showDeletionAlert && ( - setShowDeletionAlert(false)} - trigger={onDeleteConfirm} - /> - )} ); }; diff --git a/src/dashboardWebView/components/Contents/Contents.tsx b/src/dashboardWebView/components/Contents/Contents.tsx index 0db5a4df..45a20f20 100644 --- a/src/dashboardWebView/components/Contents/Contents.tsx +++ b/src/dashboardWebView/components/Contents/Contents.tsx @@ -1,16 +1,21 @@ import * as React from 'react'; -import { useRecoilValue } from 'recoil'; +import * as l10n from '@vscode/l10n'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { Page } from '../../models'; -import { LoadingAtom, SettingsSelector } from '../../state'; +import { LoadingAtom, SelectedItemActionAtom, SettingsSelector } from '../../state'; import { Overview } from './Overview'; import { Spinner } from '../Common/Spinner'; import { SponsorMsg } from '../Layout/SponsorMsg'; import usePages from '../../hooks/usePages'; -import { useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { Messenger } from '@estruyf/vscode/dist/client'; import { DashboardMessage } from '../../DashboardMessage'; import { TelemetryEvent } from '../../../constants'; import { PageLayout } from '../Layout/PageLayout'; +import { FilesProvider } from '../../providers/FilesProvider'; +import { Alert } from '../Modals/Alert'; +import { LocalizationKey } from '../../../localization'; +import { deletePage } from '../../utils'; export interface IContentsProps { pages: Page[]; @@ -22,9 +27,38 @@ export const Contents: React.FunctionComponent = ({ const loading = useRecoilValue(LoadingAtom); const settings = useRecoilValue(SettingsSelector); const { pageItems } = usePages(pages); + const [showDeletionAlert, setShowDeletionAlert] = React.useState(false); + const [page, setPage] = useState(undefined); + const [selectedItemAction, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom); const pageFolders = [...new Set(pageItems.map((page) => page.fmFolder))]; + const onDismiss = useCallback(() => { + setShowDeletionAlert(false); + setSelectedItemAction(undefined); + }, []); + + const onDeleteConfirm = useCallback(() => { + if (page) { + deletePage(page.fmFilePath); + } + setShowDeletionAlert(false); + setSelectedItemAction(undefined); + }, [page]); + + useEffect(() => { + if (selectedItemAction && selectedItemAction.path && selectedItemAction.action === 'delete') { + const page = pageItems.find((p) => p.fmFilePath === selectedItemAction.path); + + if (page) { + setPage(page); + setShowDeletionAlert(true); + } + + setSelectedItemAction(undefined); + } + }, [pageItems, selectedItemAction]); + useEffect(() => { Messenger.send(DashboardMessage.sendTelemetry, { event: TelemetryEvent.webviewContentsView @@ -32,18 +66,31 @@ export const Contents: React.FunctionComponent = ({ }, []); return ( - -
- {loading ? : } -
- - - - Content metrics -
+ + +
+ {loading ? : } +
+ + + + Content metrics + + {showDeletionAlert && page && ( + + )} +
+
); }; diff --git a/src/dashboardWebView/components/Contents/CustomActions.tsx b/src/dashboardWebView/components/Contents/CustomActions.tsx new file mode 100644 index 00000000..3a1c9e22 --- /dev/null +++ b/src/dashboardWebView/components/Contents/CustomActions.tsx @@ -0,0 +1,89 @@ +import * as React from 'react'; +import * as l10n from '@vscode/l10n'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; +import { CommandLineIcon } from '@heroicons/react/24/outline'; +import { CommandLineIcon as CommandLineIconSolid } from '@heroicons/react/24/solid'; +import { runCustomScript } from '../../utils'; +import { CustomScript, ScriptType } from '../../../models'; +import { LocalizationKey } from '../../../localization'; + +export interface ICustomActionsProps { + filePath: string; + contentType: string; + scripts: CustomScript[] | undefined; + showTrigger?: boolean; +} + +export const CustomActions: React.FunctionComponent = ({ + filePath, + contentType, + scripts, + showTrigger = false, +}: React.PropsWithChildren) => { + + const onRunCustomScript = React.useCallback( + (e: React.MouseEvent, script: CustomScript) => { + e.stopPropagation(); + runCustomScript(script, filePath); + }, + [filePath] + ); + + const customScripts = React.useMemo(() => { + return (scripts || []).filter((script: CustomScript) => { + if (script.contentTypes && script.contentTypes.length > 0) { + return script.contentTypes.includes(contentType); + } + + return true; + }); + }, [scripts, contentType]); + + const customActions = React.useMemo(() => { + if (!customScripts || customScripts.length === 0) { + return null; + } + + return ( + (customScripts || []) + .filter( + (script) => + (script.type === undefined || script.type === ScriptType.Content) && + !script.bulk && + !script.hidden + ) + .map((script) => ( + onRunCustomScript(e, script)}> + + {script.title} + + )) + ); + }, [customScripts, onRunCustomScript]); + + if (!customActions || customActions.length === 0) { + return null; + } + + if (showTrigger) { + return ( + + + {l10n.t(LocalizationKey.commonOpenCustomActions)} + + + + {customActions} + + + ); + } + + return <>{customActions}; +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Contents/FooterActions.tsx b/src/dashboardWebView/components/Contents/FooterActions.tsx new file mode 100644 index 00000000..cf75a6ab --- /dev/null +++ b/src/dashboardWebView/components/Contents/FooterActions.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import * as l10n from '@vscode/l10n'; +import { QuickAction } from '../Menu'; +import { EyeIcon, GlobeEuropeAfricaIcon, TrashIcon } from '@heroicons/react/24/solid'; +import { LocalizationKey } from '../../../localization'; +import { openFile, openOnWebsite } from '../../utils'; +import { useRecoilState } from 'recoil'; +import { SelectedItemActionAtom } from '../../state'; +import { CustomScript } from '../../../models'; +import { CustomActions } from './CustomActions'; + +export interface IFooterActionsProps { + filePath: string; + contentType: string; + websiteUrl?: string; + scripts?: CustomScript[]; +} + +export const FooterActions: React.FunctionComponent = ({ + filePath, + contentType, + websiteUrl, + scripts +}: React.PropsWithChildren) => { + const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom); + + return ( +
+ {/* */} + + openFile(filePath)}> + {l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)} + + + { + websiteUrl && ( + openOnWebsite(websiteUrl, filePath)}> + {l10n.t(LocalizationKey.commonOpenOnWebsite)} + + ) + } + + + + setSelectedItemAction({ path: filePath, action: 'delete' })}> + {l10n.t(LocalizationKey.commonDelete)} + +
+ ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Contents/Item.tsx b/src/dashboardWebView/components/Contents/Item.tsx index ba1edd16..a6750b97 100644 --- a/src/dashboardWebView/components/Contents/Item.tsx +++ b/src/dashboardWebView/components/Contents/Item.tsx @@ -1,10 +1,8 @@ import { useRecoilValue } from 'recoil'; import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon'; -import { DashboardMessage } from '../../DashboardMessage'; import { Page } from '../../models/Page'; import { SettingsSelector, ViewSelector } from '../../state'; import { DateField } from '../Common/DateField'; -import { Messenger } from '@estruyf/vscode/dist/client'; import { DashboardViewType } from '../../models'; import { ContentActions } from './ContentActions'; import { useMemo } from 'react'; @@ -13,10 +11,14 @@ import * as React from 'react'; import useExtensibility from '../../hooks/useExtensibility'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; -import { useNavigate } from 'react-router-dom'; -import { routePaths } from '../..'; import useCard from '../../hooks/useCard'; import { I18nLabel } from './I18nLabel'; +import { ItemSelection } from '../Common/ItemSelection'; +import { openFile } from '../../utils'; +import { FooterActions } from './FooterActions'; +import useSelectedItems from '../../hooks/useSelectedItems'; +import { cn } from '../../../utils/cn'; +import { Tags } from './Tags'; export interface IItemProps extends Page { } @@ -25,12 +27,12 @@ const PREVIEW_IMAGE_FIELD = 'fmPreviewImage'; export const Item: React.FunctionComponent = ({ ...pageData }: React.PropsWithChildren) => { + const { selectedFiles } = useSelectedItems(); const view = useRecoilValue(ViewSelector); const settings = useRecoilValue(SettingsSelector); const draftField = useMemo(() => settings?.draftField, [settings]); const cardFields = useMemo(() => settings?.dashboardState?.contents?.cardFields, [settings?.dashboardState?.contents?.cardFields]); const { escapedTitle, escapedDescription } = useCard(pageData, settings?.dashboardState?.contents?.cardFields); - const navigate = useNavigate(); const { titleHtml, descriptionHtml, dateHtml, statusHtml, tagsHtml, imageHtml, footerHtml } = useExtensibility({ fmFilePath: pageData.fmFilePath, date: pageData.date, @@ -40,9 +42,11 @@ export const Item: React.FunctionComponent = ({ pageData }); - const openFile = () => { - Messenger.send(DashboardMessage.openFile, pageData.fmFilePath); - }; + const isSelected = useMemo(() => selectedFiles.includes(pageData.fmFilePath), [selectedFiles, pageData.fmFilePath]); + + const onOpenFile = React.useCallback(() => { + openFile(pageData.fmFilePath); + }, [pageData.fmFilePath]); const tags: string[] | undefined = useMemo(() => { if (!settings?.dashboardState?.contents?.tags) { @@ -91,9 +95,9 @@ export const Item: React.FunctionComponent = ({ return ( dateHtml ? ( -
+
) : ( - cardFields?.date && pageData.date ? : null + cardFields?.date && pageData.date ? : null ) ) }, [dateHtml, cardFields?.date, pageData]); @@ -106,12 +110,12 @@ export const Item: React.FunctionComponent = ({ return (
  • + +
    { (statusPlaceholder || datePlaceholder) && ( -
    - {statusPlaceholder} - {datePlaceholder} +
    +
    {statusPlaceholder}
    +
    {datePlaceholder}
    ) } @@ -192,27 +198,7 @@ export const Item: React.FunctionComponent = ({ tagsHtml ? (
    ) : ( - tags && tags.length > 0 && ( -
    - {tags.map( - (tag, index) => tag && ( - - ) - )} -
    - ) + ) }
    @@ -222,6 +208,12 @@ export const Item: React.FunctionComponent = ({
    ) } + +
  • ); @@ -232,18 +224,20 @@ export const Item: React.FunctionComponent = ({ className={`px-5 cursor-pointer w-full text-left grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8 py-2 border-b hover:bg-opacity-70 border-[var(--frontmatter-border)] hover:bg-[var(--vscode-sideBar-background)]`} >
    + +
    diff --git a/src/dashboardWebView/components/Contents/PinnedItem.tsx b/src/dashboardWebView/components/Contents/PinnedItem.tsx index 62a5ac42..21e86a3e 100644 --- a/src/dashboardWebView/components/Contents/PinnedItem.tsx +++ b/src/dashboardWebView/components/Contents/PinnedItem.tsx @@ -2,27 +2,32 @@ import * as React from 'react'; import { Page } from '../../models'; import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon'; import { ContentActions } from './ContentActions'; -import { DashboardMessage } from '../../DashboardMessage'; -import { messageHandler } from '@estruyf/vscode/dist/client'; import useCard from '../../hooks/useCard'; import { SettingsSelector } from '../../state'; import { useRecoilValue } from 'recoil'; +import { ItemSelection } from '../Common/ItemSelection'; +import { openFile } from '../../utils'; +import useSelectedItems from '../../hooks/useSelectedItems'; +import { cn } from '../../../utils/cn'; export interface IPinnedItemProps extends Page { } export const PinnedItem: React.FunctionComponent = ({ ...pageData }: React.PropsWithChildren) => { + const { selectedFiles } = useSelectedItems(); const settings = useRecoilValue(SettingsSelector); const { escapedTitle } = useCard(pageData, settings?.dashboardState?.contents?.cardFields); - const openFile = React.useCallback(() => { - messageHandler.send(DashboardMessage.openFile, pageData.fmFilePath); + const isSelected = React.useMemo(() => selectedFiles.includes(pageData.fmFilePath), [selectedFiles, pageData.fmFilePath]); + + const onOpenFile = React.useCallback(() => { + openFile(pageData.fmFilePath); }, [pageData.fmFilePath]); return ( -
  • - - + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Contents/Tags.tsx b/src/dashboardWebView/components/Contents/Tags.tsx new file mode 100644 index 00000000..e1fe48d9 --- /dev/null +++ b/src/dashboardWebView/components/Contents/Tags.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { Tag } from './Tag'; + +export interface ITagsProps { + values?: string[]; + tagField?: string | null | undefined; +} + +export const Tags: React.FunctionComponent = ({ + values, + tagField +}: React.PropsWithChildren) => { + if (!values || values.length === 0) { + return null; + } + + return ( +
    + {values.map( + (tag, index) => tag && ( + + ) + )} +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Contents/TranslationMenu.tsx b/src/dashboardWebView/components/Contents/TranslationMenu.tsx new file mode 100644 index 00000000..476763ee --- /dev/null +++ b/src/dashboardWebView/components/Contents/TranslationMenu.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import * as l10n from '@vscode/l10n'; +import { DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '../../../components/shadcn/Dropdown'; +import { LanguageIcon } from '@heroicons/react/24/outline'; +import { openFile } from '../../utils/MessageHandlers'; +import { I18nConfig } from '../../../models'; +import { LocalizationKey } from '../../../localization'; + +export interface ITranslationMenuProps { + isDefaultLocale?: boolean; + locale?: I18nConfig; + translations?: { + [locale: string]: { + locale: I18nConfig; + path: string; + }; + }; +} + +export const TranslationMenu: React.FunctionComponent = ({ + isDefaultLocale, + locale, + translations, +}: React.PropsWithChildren) => { + + const otherLocales = React.useMemo(() => { + if (!translations) { + return []; + } + + return Object.entries(translations).filter(([key]) => key !== locale?.locale); + }, [translations]); + + const crntLocale = React.useMemo(() => { + if (!locale?.locale || !translations || !translations[locale.locale]) { + return null; + } + + return translations[locale.locale]; + }, [translations, locale]); + + + if (!locale || !translations || Object.keys(translations).length === 0) { + return null; + } + + if (otherLocales.length === 0 || !crntLocale) { + return null; + } + + return ( + + + + {l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsMenu)} + + + + + openFile(crntLocale.path)}> + {crntLocale.locale.title || crntLocale.locale.locale} + + + + + { + otherLocales.map(([key, value]) => ( + openFile(value.path)} + > + {value.locale.title || value.locale.locale} + + )) + } + + + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/DataView.tsx b/src/dashboardWebView/components/DataView/DataView.tsx index 5e73bd5d..b12a75b5 100644 --- a/src/dashboardWebView/components/DataView/DataView.tsx +++ b/src/dashboardWebView/components/DataView/DataView.tsx @@ -17,7 +17,7 @@ import { Container } from './SortableContainer'; import { SortableItem } from './SortableItem'; import { ChevronRightIcon, CircleStackIcon } from '@heroicons/react/24/outline'; import { DataType } from '../../../models/DataType'; -import { TelemetryEvent } from '../../../constants'; +import { TelemetryEvent, WEBSITE_LINKS } from '../../../constants'; import { NavigationItem } from '../Layout'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; @@ -265,7 +265,7 @@ export const DataView: React.FunctionComponent = (

    {l10n.t(LocalizationKey.dashboardDataViewDataViewGetStartedLink)} diff --git a/src/dashboardWebView/components/Header/ActionsBar.tsx b/src/dashboardWebView/components/Header/ActionsBar.tsx new file mode 100644 index 00000000..af034c42 --- /dev/null +++ b/src/dashboardWebView/components/Header/ActionsBar.tsx @@ -0,0 +1,272 @@ +import * as React from 'react'; +import { NavigationType, Page } from '../../models'; +import { CommandLineIcon, PencilIcon, TrashIcon, ChevronDownIcon, XMarkIcon, EyeIcon, LanguageIcon } from '@heroicons/react/24/outline'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { MultiSelectedItemsAtom, SelectedItemActionAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state'; +import { ActionsBarItem } from './ActionsBarItem'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../../../localization'; +import { Alert } from '../Modals/Alert'; +import { messageHandler } from '@estruyf/vscode/dist/client'; +import { DashboardMessage } from '../../DashboardMessage'; +import { CustomScript, ScriptType } from '../../../models'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; +import { useFilesContext } from '../../providers/FilesProvider'; +import { COMMAND_NAME, GeneralCommands } from '../../../constants'; +import { RenameIcon } from '../../../components/icons/RenameIcon'; +import { openFile } from '../../utils'; + +export interface IActionsBarProps { + view: NavigationType; +} + +export const ActionsBar: React.FunctionComponent = ({ + view +}: React.PropsWithChildren) => { + const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom); + const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom); + const [showAlert, setShowAlert] = React.useState(false); + const selectedFolder = useRecoilValue(SelectedMediaFolderSelector); + const settings = useRecoilValue(SettingsSelector); + const { files } = useFilesContext(); + + const viewFile = React.useCallback(() => { + if (selectedFiles.length === 1) { + if (view === NavigationType.Contents) { + openFile(selectedFiles[0]); + } else if (view === NavigationType.Media) { + setSelectedItemAction({ path: selectedFiles[0], action: 'view' }) + } + } + }, [selectedFiles]); + + const onDeleteConfirm = React.useCallback(() => { + for (const file of selectedFiles) { + if (file) { + if (view === NavigationType.Contents) { + messageHandler.send(DashboardMessage.deleteFile, file); + } else if (view === NavigationType.Media) { + messageHandler.send(DashboardMessage.deleteMedia, { + file: file, + folder: selectedFolder + }); + } + } + } + setSelectedFiles([]); + setShowAlert(false); + }, [selectedFiles]); + + const runCustomScript = React.useCallback((script: CustomScript) => { + for (const file of selectedFiles) { + messageHandler.send(DashboardMessage.runCustomScript, { + script, + path: file + }); + } + }, [selectedFiles]); + + const languageActions = React.useMemo(() => { + const actions: React.ReactNode[] = []; + + if (view === NavigationType.Contents && files.length > 0 && selectedFiles.length === 1) { + const selectedItem = selectedFiles[0]; + const page = ((files || []) as Page[]).find((f: Page) => f.fmFilePath === selectedItem); + + if (page?.fmLocale) { + const locale = page.fmLocale; + const translations = page.fmTranslations; + + actions.push( + { + messageHandler.send(GeneralCommands.toVSCode.runCommand, { + command: COMMAND_NAME.i18n.create, + args: selectedItem + }) + }}> + + {l10n.t(LocalizationKey.commonTranslate)} + + ) + + if (translations && Object.keys(translations).length > 0) { + const crntLocale = translations[locale.locale]; + const otherLocales = Object.entries(translations).filter(([key]) => key !== locale.locale); + + if (otherLocales.length > 0) { + actions.push( + + + + {l10n.t(LocalizationKey.commonLanguages)} + + + + + + openFile(crntLocale.path)}> + {crntLocale.locale.title || crntLocale.locale.locale} + + + + + { + otherLocales.map(([key, value]) => ( + openFile(value.path)} + > + {value.locale.title || value.locale.locale} + + )) + } + + + ) + } + } + } + } + + return actions; + }, [files, selectedFiles]); + + const customScriptActions = React.useMemo(() => { + if (!settings?.scripts) { + return null; + } + + const { scripts } = settings; + let crntScripts: CustomScript[] = []; + if (view === NavigationType.Contents) { + crntScripts = (scripts || []) + .filter((script) => (script.type === undefined || script.type === ScriptType.Content) && !script.bulk && !script.hidden); + } else if (view === NavigationType.Media) { + crntScripts = (scripts || []) + .filter((script) => script.type === ScriptType.MediaFile && !script.hidden); + } + + if (crntScripts.length > 0) { + return ( + + + + {l10n.t(LocalizationKey.commonScripts)} + + + + + { + crntScripts.map((script) => ( + runCustomScript(script)} + > + + {script.title} + + )) + } + + + ); + } + + return null; + }, [view, settings?.scripts, selectedFiles]); + + return ( + <> +

    + + {showAlert && ( + setShowAlert(false)} + trigger={onDeleteConfirm} + /> + )} + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/ActionsBarItem.tsx b/src/dashboardWebView/components/Header/ActionsBarItem.tsx new file mode 100644 index 00000000..91a34e43 --- /dev/null +++ b/src/dashboardWebView/components/Header/ActionsBarItem.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { cn } from '../../../utils/cn'; + +export interface IActionsBarItemProps { + className?: string; + disabled?: boolean; + onClick?: () => void; +} + +export const ActionsBarItem: React.FunctionComponent = ({ + children, + className, + disabled, + onClick +}: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/Breadcrumb.tsx b/src/dashboardWebView/components/Header/Breadcrumb.tsx index e3eb9540..e3ff65c6 100644 --- a/src/dashboardWebView/components/Header/Breadcrumb.tsx +++ b/src/dashboardWebView/components/Header/Breadcrumb.tsx @@ -4,24 +4,25 @@ import * as React from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { HOME_PAGE_NAVIGATION_ID } from '../../../constants'; import { parseWinPath } from '../../../helpers/parseWinPath'; -import { SearchAtom, SelectedMediaFolderAtom, SettingsAtom } from '../../state'; +import { SearchAtom, SettingsAtom } from '../../state'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import useMediaFolder from '../../hooks/useMediaFolder'; export interface IBreadcrumbProps { } export const Breadcrumb: React.FunctionComponent = ( _: React.PropsWithChildren ) => { - const [selectedFolder, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom); + const { selectedFolder, updateFolder } = useMediaFolder(); const [, setSearchValue] = useRecoilState(SearchAtom); const [folders, setFolders] = React.useState([]); const settings = useRecoilValue(SettingsAtom); - const updateFolder = (folder: string) => { + const updateMediaFolder = React.useCallback((folder: string) => { setSearchValue(''); - setSelectedFolder(folder); - }; + updateFolder(folder); + }, [updateFolder, setSearchValue]); React.useEffect(() => { if (!settings) { @@ -79,11 +80,11 @@ export const Breadcrumb: React.FunctionComponent = ( }, [selectedFolder, settings]); return ( -
      +
      1. diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index bf1a45e2..997b9ba3 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -6,7 +6,7 @@ import { DashboardMessage } from '../../DashboardMessage'; import { Grouping } from '.'; import { ViewSwitch } from './ViewSwitch'; import { useRecoilValue, useResetRecoilState } from 'recoil'; -import { GroupingSelector, SortingAtom } from '../../state'; +import { GroupingSelector, MultiSelectedItemsAtom, SortingAtom } from '../../state'; import { Messenger } from '@estruyf/vscode/dist/client'; import { ClearFilters } from './ClearFilters'; import { MediaHeaderTop } from '../Media/MediaHeaderTop'; @@ -18,8 +18,7 @@ import { ArrowTopRightOnSquareIcon, BoltIcon, PlusIcon } from '@heroicons/react/ import { HeartIcon } from '@heroicons/react/24/solid'; import { useLocation, useNavigate } from 'react-router-dom'; import { routePaths } from '../..'; -import { useEffect, useMemo } from 'react'; -import { SyncButton } from './SyncButton'; +import { useMemo } from 'react'; import { Pagination } from './Pagination'; import { GroupOption } from '../../constants/GroupOption'; import usePagination from '../../hooks/usePagination'; @@ -32,6 +31,8 @@ import { SettingsLink } from '../SettingsView/SettingsLink'; import { Link } from '../Common/Link'; import { SPONSOR_LINK } from '../../../constants'; import { Filters } from './Filters'; +import { ActionsBar } from './ActionsBar'; +import { RefreshDashboardData } from './RefreshDashboardData'; export interface IHeaderProps { header?: React.ReactNode; @@ -51,6 +52,7 @@ export const Header: React.FunctionComponent = ({ }: React.PropsWithChildren) => { const grouping = useRecoilValue(GroupingSelector); const resetSorting = useResetRecoilState(SortingAtom); + const resetSelectedItems = useResetRecoilState(MultiSelectedItemsAtom); const location = useLocation(); const navigate = useNavigate(); const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination); @@ -70,6 +72,7 @@ export const Header: React.FunctionComponent = ({ const updateView = (view: NavigationType) => { navigate(routePaths[view]); resetSorting(); + resetSelectedItems(); }; const runBulkScript = (script: CustomScript) => { @@ -122,10 +125,10 @@ export const Header: React.FunctionComponent = ({ return (
        -
        +
        -
        +
        { @@ -160,19 +163,19 @@ export const Header: React.FunctionComponent = ({ {location.pathname === routePaths.contents && ( <> -
        - - -
        - {/* */} - +
        +
        + +
        + +
        @@ -186,7 +189,7 @@ export const Header: React.FunctionComponent = ({
        @@ -208,6 +211,8 @@ export const Header: React.FunctionComponent = ({
        )} + + )} @@ -216,6 +221,8 @@ export const Header: React.FunctionComponent = ({ + + )} diff --git a/src/dashboardWebView/components/Header/Navigation.tsx b/src/dashboardWebView/components/Header/Navigation.tsx index 7ccc7d6b..99c75259 100644 --- a/src/dashboardWebView/components/Header/Navigation.tsx +++ b/src/dashboardWebView/components/Header/Navigation.tsx @@ -63,7 +63,7 @@ export const Navigation: React.FunctionComponent = ({ }, [settings?.draftField?.type, tabInfo]); return ( -
        )} + +
        )} -
        + +
        = ({ snippets={mediaSnippets} scripts={settings?.scripts} insertIntoArticle={insertIntoArticle} - insertSnippet={insertSnippet} - showUpdateMedia={updateMetadata} - showMediaDetails={() => setShowDetails(true)} + showUpdateMedia={() => setSelectedItemAction({ + path: media.fsPath, + action: 'edit' + })} + showMediaDetails={() => setSelectedItemAction({ + path: media.fsPath, + action: 'view' + })} processSnippet={processSnippet} onDelete={() => setShowAlert(true)} /> -

        +

        {basename(parseWinPath(media.fsPath) || '')}

        - {!isImageFile && media.metadata.title && ( + {!isImage && media.metadata.title && (

        - + {l10n.t(LocalizationKey.dashboardMediaCommonTitle)}: - {media.metadata.title} + {media.metadata.title}

        )} {media.metadata.caption && (

        - + {l10n.t(LocalizationKey.dashboardMediaCommonCaption)}: - {media.metadata.caption} + {media.metadata.caption}

        )} {!media.metadata.caption && media.metadata.alt && (

        - + {l10n.t(LocalizationKey.dashboardMediaCommonAlt)}: - {media.metadata.alt} + {media.metadata.alt}

        )} {(media?.size || media?.dimensions) && (

        - + {l10n.t(LocalizationKey.dashboardMediaCommonSize)}: - - {getMediaDetails()} + + {mediaDetails}

        )}
        + + setShowAlert(true)} />
      2. {showSnippetSelection && ( @@ -459,29 +405,10 @@ export const Item: React.FunctionComponent = ({ )} - {showDetails && ( - setShowForm(true)} - onEditClose={() => setShowForm(false)} - onDismiss={() => { - setShowDetails(false); - setShowForm(false); - }} - /> - )} - {showAlert && ( setShowAlert(false)} diff --git a/src/dashboardWebView/components/Media/ItemMenu.tsx b/src/dashboardWebView/components/Media/ItemMenu.tsx index 6f55b9b9..78c13729 100644 --- a/src/dashboardWebView/components/Media/ItemMenu.tsx +++ b/src/dashboardWebView/components/Media/ItemMenu.tsx @@ -1,13 +1,14 @@ import * as React from 'react'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; -import { QuickAction } from '../Menu'; -import { ClipboardIcon, CodeBracketIcon, CommandLineIcon, EllipsisVerticalIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; -import { CustomScript, MediaInfo, ScriptType, Snippet, ViewData } from '../../../models'; +import { ClipboardIcon, CodeBracketIcon, EllipsisHorizontalIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; +import { CustomScript, MediaInfo, Snippet, ViewData } from '../../../models'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; import { messageHandler } from '@estruyf/vscode/dist/client'; import { DashboardMessage } from '../../DashboardMessage'; import { parseWinPath } from '../../../helpers/parseWinPath'; +import { copyToClipboard } from '../../utils'; +import { CustomActions } from './CustomActions'; export interface IItemMenuProps { media: MediaInfo; @@ -17,7 +18,6 @@ export interface IItemMenuProps { snippets: Snippet[]; scripts?: CustomScript[]; insertIntoArticle: () => void; - insertSnippet: () => void; showUpdateMedia: () => void; showMediaDetails: () => void; processSnippet: (snippet: Snippet) => void; @@ -32,26 +32,16 @@ export const ItemMenu: React.FunctionComponent = ({ snippets, scripts, insertIntoArticle, - insertSnippet, showUpdateMedia, showMediaDetails, processSnippet, onDelete, }: React.PropsWithChildren) => { - const copyToClipboard = React.useCallback(() => { - if (relPath) { - messageHandler.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || ''); - } + const onCopyToClipboard = React.useCallback(() => { + copyToClipboard(parseWinPath(relPath) || ''); }, [relPath]); - const runCustomScript = React.useCallback((script: CustomScript) => { - messageHandler.send(DashboardMessage.runCustomScript, { - script, - path: media.fsPath - }); - }, [media]); - const revealMedia = React.useCallback(() => { messageHandler.send(DashboardMessage.revealMedia, { file: media.fsPath, @@ -59,81 +49,22 @@ export const ItemMenu: React.FunctionComponent = ({ }); }, [selectedFolder]); - const customScriptActions = React.useMemo(() => { - return (scripts || []) - .filter((script) => script.type === ScriptType.MediaFile && !script.hidden) - .map((script) => ( - runCustomScript(script)} - > - - {script.title} - - )); - }, [scripts]); - return (
        -
        +
        -
        - - - - - - - {viewData?.filePath ? ( - <> - - - - {viewData?.position && snippets.length > 0 && ( - - - )} - - ) : ( - <> - { - relPath && ( - - - ) - } - - )} - - - -
        - {l10n.t(LocalizationKey.commonMenu)} - + + + {l10n.t(LocalizationKey.commonView)} + + {l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)} @@ -160,21 +91,19 @@ export const ItemMenu: React.FunctionComponent = ({ )) } - - {customScriptActions} ) : ( - <> - - - {l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)} - - - {customScriptActions} - + + + {l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)} + ) } + + {l10n.t(LocalizationKey.dashboardMediaItemMenuItemRevealMedia)} diff --git a/src/dashboardWebView/components/Media/Media.tsx b/src/dashboardWebView/components/Media/Media.tsx index 06e61e6a..a0d75761 100644 --- a/src/dashboardWebView/components/Media/Media.tsx +++ b/src/dashboardWebView/components/Media/Media.tsx @@ -27,6 +27,8 @@ import { basename, extname, join } from 'path'; import { MediaInfo } from '../../../models'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { MediaItemPanel } from './MediaItemPanel'; +import { FilesProvider } from '../../providers/FilesProvider'; export interface IMediaProps { } @@ -162,111 +164,115 @@ export const Media: React.FunctionComponent = ( }); return ( - -
        - {viewData?.data?.filePath && ( -
        -

        {l10n.t(LocalizationKey.dashboardMediaMediaDescription)}

        -

        - {l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)} -

        -
        - )} - - {isDragActive && ( -
        - -

        - {selectedFolder - ? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder) - : l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')} -

        -
        - )} - - {allMedia.length === 0 && folders.length === 0 && !loading && ( -
        -
        - - -

        - {l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)} + + +

        + {viewData?.data?.filePath && ( +
        +

        {l10n.t(LocalizationKey.dashboardMediaMediaDescription)}

        +

        + {l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)}

        -
        - )} - - {contentFolders && - contentFolders.length > 0 && - contentFolders.map( - (group, idx) => - group.folders && - group.folders.length > 0 && ( -
        -

        - {l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: {group.title} -

        - - - {group.folders.map((folder) => ( - - ))} - -
        - ) )} - {publicFolders && publicFolders.length > 0 && ( -
        - {contentFolders && contentFolders.length > 0 && ( -

        - {l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)} - {currentStaticFolder && ( - - : {currentStaticFolder} - - )} -

        - )} + {isDragActive && ( +
        + +

        + {selectedFolder + ? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder) + : l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')} +

        +
        + )} - - {publicFolders.map((folder) => ( - +
        + - ))} - -
        - )} - - - {allMedia.map((file, idx) => ( - - ))} - -
        - - {loading && } - - - - - - Media metrics - + +

        + {l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)} +

        +
        +
        + )} + + {contentFolders && + contentFolders.length > 0 && + contentFolders.map( + (group, idx) => + group.folders && + group.folders.length > 0 && ( +
        +

        + {l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: {group.title} +

        + + + {group.folders.map((folder) => ( + + ))} + +
        + ) + )} + + {publicFolders && publicFolders.length > 0 && ( +
        + {contentFolders && contentFolders.length > 0 && ( +

        + {l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)} + {currentStaticFolder && ( + + : {currentStaticFolder} + + )} +

        + )} + + + {publicFolders.map((folder) => ( + + ))} + +
        + )} + + + {allMedia.map((file, idx) => ( + + ))} + +
        + + + + {loading && } + + + + + + Media metrics +
        + ); }; diff --git a/src/dashboardWebView/components/Media/MediaHeaderTop.tsx b/src/dashboardWebView/components/Media/MediaHeaderTop.tsx index 4e4af72d..8aaa6974 100644 --- a/src/dashboardWebView/components/Media/MediaHeaderTop.tsx +++ b/src/dashboardWebView/components/Media/MediaHeaderTop.tsx @@ -81,14 +81,14 @@ export const MediaHeaderTop: React.FunctionComponent< return ( ); }; diff --git a/src/dashboardWebView/components/Media/MediaItemPanel.tsx b/src/dashboardWebView/components/Media/MediaItemPanel.tsx new file mode 100644 index 00000000..06ab8896 --- /dev/null +++ b/src/dashboardWebView/components/Media/MediaItemPanel.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import { useRecoilState } from 'recoil'; +import { SelectedItemActionAtom } from '../../state'; +import { MediaInfo } from '../../../models'; +import { DetailsSlideOver } from './DetailsSlideOver'; +import useMediaInfo from '../../hooks/useMediaInfo'; + +export interface IMediaItemPanelProps { + allMedia: MediaInfo[]; +} + +export const MediaItemPanel: React.FunctionComponent = ({ allMedia }: React.PropsWithChildren) => { + const [crntPath, setCrntPath] = useState(''); + const [media, setMedia] = useState(undefined); + const [showForm, setShowForm] = useState(false); + const [showDetails, setShowDetails] = useState(false); + const [selectedItemAction, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom); + const { mediaFolder, mediaSize, mediaDimensions, isImage, isVideo } = useMediaInfo(media); + + const onDismiss = () => { + setShowDetails(false); + setShowForm(false); + setMedia(undefined); + }; + + useEffect(() => { + if (selectedItemAction && selectedItemAction.path) { + const mediaFile = allMedia.find((m) => m.fsPath === selectedItemAction.path); + setMedia(mediaFile); + setCrntPath(selectedItemAction.path); + + if (selectedItemAction.action === 'edit') { + setShowForm(true); + setShowDetails(true); + } else if (selectedItemAction.action === 'view') { + setShowForm(false); + setShowDetails(true); + } + + setSelectedItemAction(undefined); + } else if (crntPath) { + const mediaFile = allMedia.find((m) => m.fsPath === crntPath); + setMedia(mediaFile); + } + }, [allMedia, selectedItemAction, crntPath]); + + if (showDetails && media) { + return ( + setShowForm(true)} + onEditClose={() => setShowForm(false)} + onDismiss={onDismiss} + /> + ); + } + + return null; +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Menu/MenuButton.tsx b/src/dashboardWebView/components/Menu/MenuButton.tsx index 90a13a2b..6868e438 100644 --- a/src/dashboardWebView/components/Menu/MenuButton.tsx +++ b/src/dashboardWebView/components/Menu/MenuButton.tsx @@ -14,7 +14,7 @@ export const MenuButton: React.FunctionComponent = ({ disabled }: React.PropsWithChildren) => { return ( -
        +
        {label}:
        diff --git a/src/dashboardWebView/components/Menu/QuickAction.tsx b/src/dashboardWebView/components/Menu/QuickAction.tsx index 60ff2076..2f8a5e9b 100644 --- a/src/dashboardWebView/components/Menu/QuickAction.tsx +++ b/src/dashboardWebView/components/Menu/QuickAction.tsx @@ -18,7 +18,7 @@ export const QuickAction: React.FunctionComponent = ({ type="button" title={title} onClick={onClick} - className={cn(`px-2 group inline-flex justify-center text-sm font-medium text-[var(--vscode-foreground)] hover:text-[var(--frontmatter-button-hoverBackground)]`, className)} + className={cn(`px-2 group inline-flex justify-center text-sm font-medium text-[var(--frontmatter-text)] hover:text-[var(--frontmatter-button-hoverBackground)]`, className)} > {children} {title} diff --git a/src/dashboardWebView/components/SnippetsView/Item.tsx b/src/dashboardWebView/components/SnippetsView/Item.tsx index 8d4bbc5a..1ef5ae50 100644 --- a/src/dashboardWebView/components/SnippetsView/Item.tsx +++ b/src/dashboardWebView/components/SnippetsView/Item.tsx @@ -25,6 +25,7 @@ import { NewForm } from './NewForm'; import SnippetForm, { SnippetFormHandle } from './SnippetForm'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { openFile } from '../../utils'; export interface IItemProps { snippetKey: string; @@ -65,7 +66,7 @@ export const Item: React.FunctionComponent = ({ }; const showFile = useCallback(() => { - Messenger.send(DashboardMessage.openFile, snippet.sourcePath); + openFile(snippet.sourcePath); }, [snippet]); const onOpenEdit = useCallback(() => { @@ -163,7 +164,7 @@ export const Item: React.FunctionComponent = ({ <>
      3. - +

        = ({

      4. -

        {snippet.description}

        +

        {snippet.description}

        {showInsertDialog && ( diff --git a/src/dashboardWebView/components/SnippetsView/NewForm.tsx b/src/dashboardWebView/components/SnippetsView/NewForm.tsx index 479ec2ff..36f2f02a 100644 --- a/src/dashboardWebView/components/SnippetsView/NewForm.tsx +++ b/src/dashboardWebView/components/SnippetsView/NewForm.tsx @@ -1,6 +1,6 @@ import { Messenger } from '@estruyf/vscode/dist/client'; import * as React from 'react'; -import { GeneralCommands } from '../../../constants'; +import { GeneralCommands, WEBSITE_LINKS } from '../../../constants'; import { SnippetInput } from './SnippetInput'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; @@ -30,7 +30,7 @@ export const NewForm: React.FunctionComponent = ({ const openLink = () => { Messenger.send( GeneralCommands.toVSCode.openLink, - 'https://frontmatter.codes/docs/snippets#placeholders' + WEBSITE_LINKS.docs.snippetsPlaceholders ); }; @@ -110,7 +110,7 @@ export const NewForm: React.FunctionComponent = ({ > {l10n.t(LocalizationKey.dashboardSnippetsViewNewFormSnippetInputIsMediaSnippetCheckboxLabel)} -

        +

        {l10n.t(LocalizationKey.dashboardSnippetsViewNewFormSnippetInputIsMediaSnippetCheckboxDescription)}

        diff --git a/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx b/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx index 804091dc..41c6006b 100644 --- a/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx +++ b/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx @@ -8,6 +8,7 @@ import { DashboardMessage } from '../../DashboardMessage'; import { SettingsAtom, ViewDataSelector } from '../../state'; import { SnippetInputField } from './SnippetInputField'; import { SNIPPET } from '../../../constants/Snippet'; +import { GeneralCommands } from '../../../constants'; export interface ISnippetFormProps { snippetKey?: string; @@ -144,22 +145,17 @@ ${snippetBody} const allFields: SnippetField[] = []; const snippetFields = snippet.fields || []; - // Loop over all fields to check if they are present in the snippet - console.log('placeholders', placeholders); - console.log('snippetFields', snippetFields); - for await (const field of snippetFields) { - console.log('field', field); const idx = placeholders.findIndex((fieldName) => fieldName === field.name); if (idx > -1) { try { const value = await insertPlaceholderValues(field.default || ''); - console.log('value', value); allFields.push({ ...field, value }); } catch (e) { + messageHandler.send(GeneralCommands.toVSCode.logging.error, `SnippetForm: ${(e as Error).message}`); console.log('Error', (e as Error).message) } } diff --git a/src/dashboardWebView/components/SnippetsView/Snippets.tsx b/src/dashboardWebView/components/SnippetsView/Snippets.tsx index 5fdf2f9e..a545dd82 100644 --- a/src/dashboardWebView/components/SnippetsView/Snippets.tsx +++ b/src/dashboardWebView/components/SnippetsView/Snippets.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { FeatureFlag } from '../../../components/features/FeatureFlag'; -import { FEATURE_FLAG } from '../../../constants'; +import { FEATURE_FLAG, WEBSITE_LINKS } from '../../../constants'; import { TelemetryEvent } from '../../../constants/TelemetryEvent'; import { SnippetParser } from '../../../helpers/SnippetParser'; import { DashboardMessage } from '../../DashboardMessage'; @@ -146,7 +146,7 @@ export const Snippets: React.FunctionComponent = (

        {l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsReadMore)} diff --git a/src/dashboardWebView/components/TaxonomyView/TaxonomyTagging.tsx b/src/dashboardWebView/components/TaxonomyView/TaxonomyTagging.tsx index 92cf5001..c3e5cb12 100644 --- a/src/dashboardWebView/components/TaxonomyView/TaxonomyTagging.tsx +++ b/src/dashboardWebView/components/TaxonomyView/TaxonomyTagging.tsx @@ -16,6 +16,7 @@ import { Messenger, messageHandler } from '@estruyf/vscode/dist/client'; import { DashboardMessage } from '../../DashboardMessage'; import { ExtensionState } from '../../../constants'; import { LinkButton } from '../Common/LinkButton'; +import { openFile } from '../../utils'; export interface ITaxonomyTaggingProps { taxonomy: string | null; @@ -125,7 +126,7 @@ export const TaxonomyTagging: React.FunctionComponent = ( }, [untaggedPages, pageMappings.tagged]); const onFileView = (filePath: string) => { - Messenger.send(DashboardMessage.openFile, filePath); + openFile(filePath); } React.useEffect(() => { diff --git a/src/dashboardWebView/hooks/useMedia.tsx b/src/dashboardWebView/hooks/useMedia.tsx index 5e27d04e..fbf45652 100644 --- a/src/dashboardWebView/hooks/useMedia.tsx +++ b/src/dashboardWebView/hooks/useMedia.tsx @@ -12,12 +12,12 @@ import { MediaTotalAtom, PageAtom, SearchAtom, - SelectedMediaFolderAtom, SettingsAtom } from '../state'; import Fuse from 'fuse.js'; import usePagination from './usePagination'; import { usePrevious } from '../../panelWebView/hooks/usePrevious'; +import useMediaFolder from './useMediaFolder'; const fuseOptions: Fuse.IFuseOptions = { keys: [ @@ -35,7 +35,7 @@ export default function useMedia() { // const page = useRecoilValue(PageAtom); const [page, setPage] = useRecoilState(PageAtom); const [searchedMedia, setSearchedMedia] = useState([]); - const [, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom); + const { updateFolder } = useMediaFolder(); const [, setTotal] = useRecoilState(MediaTotalAtom); const [, setFolders] = useRecoilState(MediaFoldersAtom); const [, setAllContentFolders] = useRecoilState(AllContentFoldersAtom); @@ -79,7 +79,7 @@ export default function useMedia() { setMedia(payload.media); setTotal(payload.total); setFolders(payload.folders); - setSelectedFolder(payload.selectedFolder); + updateFolder(payload.selectedFolder); if (search) { searchMedia(search, payload.media); } else { diff --git a/src/dashboardWebView/hooks/useMediaFolder.tsx b/src/dashboardWebView/hooks/useMediaFolder.tsx new file mode 100644 index 00000000..5f8a736a --- /dev/null +++ b/src/dashboardWebView/hooks/useMediaFolder.tsx @@ -0,0 +1,17 @@ +import { useRecoilState } from 'recoil'; +import { MultiSelectedItemsAtom, SelectedMediaFolderAtom } from '../state'; + +export default function useMediaFolder() { + const [selectedFolder, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom); + const [, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom); + + const updateFolder = (folder: string) => { + setSelectedFolder(folder); + setSelectedFiles([]); + }; + + return { + selectedFolder, + updateFolder + }; +} \ No newline at end of file diff --git a/src/dashboardWebView/hooks/useMediaInfo.tsx b/src/dashboardWebView/hooks/useMediaInfo.tsx new file mode 100644 index 00000000..9820884b --- /dev/null +++ b/src/dashboardWebView/hooks/useMediaInfo.tsx @@ -0,0 +1,91 @@ +import { useMemo } from 'react'; +import { MediaInfo } from '../../models'; +import { dirname } from 'path'; +import { useRecoilValue } from 'recoil'; +import { SettingsSelector } from '../state'; +import { parseWinPath } from '../../helpers/parseWinPath'; + +export default function useMediaInfo(media?: MediaInfo) { + const settings = useRecoilValue(SettingsSelector); + + const mediaFolder = useMemo(() => { + if (settings?.wsFolder && media?.fsPath) { + let relPath = media.fsPath.split(settings.wsFolder).pop(); + + if (settings.staticFolder && relPath) { + relPath = relPath.split(settings.staticFolder).pop(); + } + + return dirname(parseWinPath(relPath) || ''); + } + return ''; + }, [media?.fsPath, settings?.staticFolder, settings?.wsFolder]); + + const mediaSize = useMemo(() => { + if (media?.size) { + const size = media.size / (1024 * 1024); + if (size > 1) { + return `${size.toFixed(2)} MB`; + } else { + return `${(size * 1024).toFixed(2)} KB`; + } + } + + return ''; + }, [media]); + + const mediaDimensions = useMemo(() => { + if (media?.dimensions) { + return `${media.dimensions.width} x ${media.dimensions.height}`; + } + return ''; + }, [media]); + + const mediaDetails = useMemo(() => { + let sizeDetails = []; + + if (mediaDimensions) { + sizeDetails.push(mediaDimensions); + } + + if (mediaSize) { + sizeDetails.push(mediaSize); + } + + return sizeDetails.join(' - '); + }, [mediaDimensions, mediaSize]); + + const isVideo = useMemo(() => { + if (media?.mimeType?.startsWith('video/')) { + return true; + } + return false; + }, [media]); + + const isAudio = useMemo(() => { + if (media?.mimeType?.startsWith('audio/')) { + return true; + } + return false; + }, [media]); + + const isImage = useMemo(() => { + if ( + media?.mimeType?.startsWith('image/') && + !media?.mimeType?.startsWith('image/vnd.adobe.photoshop') + ) { + return true; + } + return false; + }, [media]); + + return { + mediaFolder, + mediaSize, + mediaDimensions, + mediaDetails, + isVideo, + isAudio, + isImage + }; +} \ No newline at end of file diff --git a/src/dashboardWebView/hooks/useMessages.tsx b/src/dashboardWebView/hooks/useMessages.tsx index 612a43e4..cbb0b6d6 100644 --- a/src/dashboardWebView/hooks/useMessages.tsx +++ b/src/dashboardWebView/hooks/useMessages.tsx @@ -15,8 +15,11 @@ import { Messenger } from '@estruyf/vscode/dist/client'; import { EventData } from '@estruyf/vscode/dist/models'; import { NavigationType } from '../models'; import { GeneralCommands } from '../../constants'; +import { useNavigate } from 'react-router-dom'; +import { routePaths } from '..'; export default function useMessages() { + const navigate = useNavigate(); const [loading, setLoading] = useRecoilState(LoadingAtom); const [pages, setPages] = useState([]); const [settings, setSettings] = useRecoilState(SettingsAtom); @@ -36,14 +39,19 @@ export default function useMessages() { setViewData(message.payload); if (message.payload?.type === NavigationType.Media) { setView(NavigationType.Media); + navigate(routePaths[NavigationType.Media]); } else if (message.payload?.type === NavigationType.Contents) { setView(NavigationType.Contents); + navigate(routePaths[NavigationType.Contents]); } else if (message.payload?.type === NavigationType.Data) { setView(NavigationType.Data); + navigate(routePaths[NavigationType.Data]); } else if (message.payload?.type === NavigationType.Taxonomy) { setView(NavigationType.Taxonomy); + navigate(routePaths[NavigationType.Taxonomy]); } else if (message.payload?.type === NavigationType.Snippets) { setView(NavigationType.Snippets); + navigate(routePaths[NavigationType.Snippets]); } break; case DashboardCommand.settings: diff --git a/src/dashboardWebView/hooks/useSelectedItems.tsx b/src/dashboardWebView/hooks/useSelectedItems.tsx new file mode 100644 index 00000000..9694efa8 --- /dev/null +++ b/src/dashboardWebView/hooks/useSelectedItems.tsx @@ -0,0 +1,20 @@ +import { useCallback } from 'react'; +import { useRecoilState } from 'recoil'; +import { MultiSelectedItemsAtom } from '../state'; + +export default function useSelectedItems() { + const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom); + + const onMultiSelect = useCallback((filePath: string) => { + if (selectedFiles.includes(filePath)) { + setSelectedFiles(selectedFiles.filter((file) => file !== filePath)); + } else { + setSelectedFiles([...selectedFiles, filePath]); + } + }, [selectedFiles]); + + return { + selectedFiles, + onMultiSelect + }; +} \ No newline at end of file diff --git a/src/dashboardWebView/index.tsx b/src/dashboardWebView/index.tsx index 4628c94c..24a93426 100644 --- a/src/dashboardWebView/index.tsx +++ b/src/dashboardWebView/index.tsx @@ -12,6 +12,7 @@ import { Chatbot } from './components/Chatbot/Chatbot'; import { updateCssVariables } from './utils'; import { I10nProvider } from './providers/I10nProvider'; import { SentryInit } from '../utils/sentryInit'; +import { WEBSITE_LINKS } from '../constants'; declare const acquireVsCodeApi: () => { getState: () => T; @@ -51,7 +52,8 @@ export const routePaths: { [name: string]: string } = { }; const mutationObserver = new MutationObserver((_, __) => { - updateCssVariables(); + const darkMode = document.body.classList.contains('vscode-dark'); + updateCssVariables(darkMode); }); const elm = document.querySelector('#app'); @@ -66,7 +68,7 @@ if (elm) { const webviewUrl = elm?.getAttribute('data-webview-url'); const isCrashDisabled = elm?.getAttribute('data-is-crash-disabled'); - updateCssVariables(); + updateCssVariables(document.body.classList.contains('vscode-dark')); mutationObserver.observe(document.body, { childList: false, attributes: true }); if (isProd === 'true' && isCrashDisabled === 'false') { @@ -91,7 +93,7 @@ if (elm) { render( diff --git a/src/dashboardWebView/providers/FilesProvider.tsx b/src/dashboardWebView/providers/FilesProvider.tsx new file mode 100644 index 00000000..64639e85 --- /dev/null +++ b/src/dashboardWebView/providers/FilesProvider.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import { Page } from '../models'; +import { MediaInfo } from '../../models'; + +interface IFilesProviderProps { + files: Page[] | MediaInfo[]; +} + +const FilesContext = React.createContext(undefined); + +const FilesProvider: React.FunctionComponent = ({ files, children }: React.PropsWithChildren) => { + return ( + + {children} + + ) +}; + +const useFilesContext = (): IFilesProviderProps => { + const loadFunc = React.useContext(FilesContext); + + if (loadFunc === undefined) { + throw new Error('useFilesContext must be used within the FilesProvider'); + } + + return loadFunc; +}; + +FilesContext.displayName = 'FilesContext'; +FilesProvider.displayName = 'FilesProvider'; + +export { FilesProvider, useFilesContext }; \ No newline at end of file diff --git a/src/dashboardWebView/state/atom/MultiSelectedItemsAtom.ts b/src/dashboardWebView/state/atom/MultiSelectedItemsAtom.ts new file mode 100644 index 00000000..a89ea790 --- /dev/null +++ b/src/dashboardWebView/state/atom/MultiSelectedItemsAtom.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const MultiSelectedItemsAtom = atom({ + key: 'MultiSelectedItemsAtom', + default: [] +}); diff --git a/src/dashboardWebView/state/atom/SelectedItemActionAtom.ts b/src/dashboardWebView/state/atom/SelectedItemActionAtom.ts new file mode 100644 index 00000000..39526d72 --- /dev/null +++ b/src/dashboardWebView/state/atom/SelectedItemActionAtom.ts @@ -0,0 +1,12 @@ +import { atom } from 'recoil'; + +export const SelectedItemActionAtom = atom< + | { + path: string; + action: 'view' | 'edit' | 'delete'; + } + | undefined +>({ + key: 'SelectedItemActionAtom', + default: undefined +}); diff --git a/src/dashboardWebView/state/atom/index.ts b/src/dashboardWebView/state/atom/index.ts index de507e13..d899d96f 100644 --- a/src/dashboardWebView/state/atom/index.ts +++ b/src/dashboardWebView/state/atom/index.ts @@ -14,10 +14,12 @@ export * from './LocalesAtom'; export * from './MediaFoldersAtom'; export * from './MediaTotalAtom'; export * from './ModeAtom'; +export * from './MultiSelectedItemsAtom'; export * from './PageAtom'; export * from './PinnedItems'; export * from './SearchAtom'; export * from './SearchReadyAtom'; +export * from './SelectedItemActionAtom'; export * from './SelectedMediaFolderAtom'; export * from './SettingsAtom'; export * from './SortingAtom'; diff --git a/src/dashboardWebView/styles.css b/src/dashboardWebView/styles.css index 45f3bde0..0c830d85 100644 --- a/src/dashboardWebView/styles.css +++ b/src/dashboardWebView/styles.css @@ -411,7 +411,7 @@ } .question { - @apply relative ml-auto mr-3 w-5/6 rounded-full rounded-br-none bg-teal-900 py-2 px-4 text-whisper-500; + @apply relative ml-auto mr-3 w-5/6 rounded-full rounded-br-none bg-teal-900 px-4 py-2 text-whisper-500; &:after { --size: 1rem; diff --git a/src/dashboardWebView/utils/MessageHandlers.ts b/src/dashboardWebView/utils/MessageHandlers.ts new file mode 100644 index 00000000..f792e07a --- /dev/null +++ b/src/dashboardWebView/utils/MessageHandlers.ts @@ -0,0 +1,47 @@ +import { messageHandler } from '@estruyf/vscode/dist/client'; +import { DashboardMessage } from '../DashboardMessage'; +import { GeneralCommands } from '../../constants'; +import { CustomScript } from '../../models'; + +export const openFile = (filePath?: string) => { + if (!filePath) { + return; + } + + messageHandler.send(DashboardMessage.openFile, filePath); +}; + +export const deletePage = (filePath?: string) => { + if (!filePath) { + return; + } + + messageHandler.send(DashboardMessage.deleteFile, filePath); +}; + +export const openOnWebsite = (websiteUrl?: string, filePath?: string) => { + if (!websiteUrl) { + return; + } + + messageHandler.send(GeneralCommands.toVSCode.openOnWebsite, { + websiteUrl, + filePath + }); +}; + +export const copyToClipboard = (value: string) => { + if (!value) { + return; + } + + messageHandler.send(DashboardMessage.copyToClipboard, value); +}; + +export const runCustomScript = (script: CustomScript, path: string) => { + if (!script) { + return; + } + + messageHandler.send(DashboardMessage.runCustomScript, { script, path }); +}; diff --git a/src/dashboardWebView/utils/darkenColor.ts b/src/dashboardWebView/utils/darkenColor.ts new file mode 100644 index 00000000..552d1af9 --- /dev/null +++ b/src/dashboardWebView/utils/darkenColor.ts @@ -0,0 +1,71 @@ +export const darkenColor = (color: string | undefined, percentage: number) => { + if (!color) { + return color; + } + + // Remove any whitespace and convert to lowercase + color = color.trim().toLowerCase(); + + // Check if the color is in hex format + if (color.startsWith('#')) { + // Remove the "#" symbol + color = color.slice(1); + + // Convert the color to rgb format + const hexToRgb = (hex: string) => { + const bigint = parseInt(hex, 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + return [r, g, b]; + }; + + const [r, g, b] = hexToRgb(color); + + // Calculate the darkened color + const darkenValue = Math.round(255 * (percentage / 100)); + const darkenedR = Math.max(r - darkenValue, 0); + const darkenedG = Math.max(g - darkenValue, 0); + const darkenedB = Math.max(b - darkenValue, 0); + + // Convert the darkened color back to hex format + const rgbToHex = (r: number, g: number, b: number) => { + const toHex = (c: number) => { + const hex = c.toString(16); + return hex.length === 1 ? '0' + hex : hex; + }; + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; + }; + + return rgbToHex(darkenedR, darkenedG, darkenedB); + } + + // Check if the color is in rgb or rgba format + if (color.startsWith('rgb')) { + // Extract the RGB values + const rgbValues = color.match(/\d+/g); + + if (rgbValues) { + const [r, g, b] = rgbValues.map(Number); + + // Calculate the darkened color + const darkenValue = Math.round(255 * (percentage / 100)); + const darkenedR = Math.max(r - darkenValue, 0); + const darkenedG = Math.max(g - darkenValue, 0); + const darkenedB = Math.max(b - darkenValue, 0); + + // Check if the color is in rgba format + if (color.startsWith('rgba')) { + // Extract the alpha value + const alphaMatch = color.match(/[\d\.]+(?=\))/); + const alpha = alphaMatch ? Number(alphaMatch[0]) : 1; + + return `rgba(${darkenedR}, ${darkenedG}, ${darkenedB}, ${alpha})`; + } + + return `rgb(${darkenedR}, ${darkenedG}, ${darkenedB})`; + } + } + + return color; +}; diff --git a/src/dashboardWebView/utils/index.ts b/src/dashboardWebView/utils/index.ts index 9c6fc58a..e8fd1757 100644 --- a/src/dashboardWebView/utils/index.ts +++ b/src/dashboardWebView/utils/index.ts @@ -1,3 +1,6 @@ +export * from './MessageHandlers'; +export * from './darkenColor'; export * from './getRelPath'; +export * from './opacityColor'; export * from './preserveColor'; export * from './updateCssVariables'; diff --git a/src/dashboardWebView/utils/opacityColor.ts b/src/dashboardWebView/utils/opacityColor.ts new file mode 100644 index 00000000..f5cd7978 --- /dev/null +++ b/src/dashboardWebView/utils/opacityColor.ts @@ -0,0 +1,19 @@ +export const opacityColor = (color: string | undefined, opacity: number) => { + if (color) { + if (color.startsWith('#')) { + return `${color}${Math.round(opacity * 255) + .toString(16) + .padStart(2, '0')}`; + } else if (color.startsWith('rgba')) { + const splits = color.split(','); + splits.pop(); + return `${splits.join(', ')}, ${opacity})`; + } else if (color.startsWith('rgb')) { + return `${color.replace('rgb', 'rgba').replace(')', `, ${opacity})`)}`; + } else { + return color; + } + } + + return color; +}; diff --git a/src/dashboardWebView/utils/updateCssVariables.ts b/src/dashboardWebView/utils/updateCssVariables.ts index 4175fa06..e28d07c1 100644 --- a/src/dashboardWebView/utils/updateCssVariables.ts +++ b/src/dashboardWebView/utils/updateCssVariables.ts @@ -1,6 +1,6 @@ -import { preserveColor } from './preserveColor'; +import { darkenColor, opacityColor, preserveColor } from '.'; -export const updateCssVariables = () => { +export const updateCssVariables = (isDarkTheme: boolean = true) => { const styles = getComputedStyle(document.documentElement); // Lightbox @@ -51,8 +51,24 @@ export const updateCssVariables = () => { 'var(--vscode-list-activeSelectionForeground)' ); + // Navigation + const tabActiveForeground = styles.getPropertyValue('--vscode-tab-activeForeground'); + document.documentElement.style.setProperty( + '--frontmatter-nav-active', + preserveColor(tabActiveForeground) || 'var(--vscode-tab-activeForeground)' + ); + document.documentElement.style.setProperty( + '--frontmatter-nav-inactive', + opacityColor(tabActiveForeground, 0.6) || 'var(--vscode-tab-inactiveForeground)' + ); + // Borders + const borderColor = styles.getPropertyValue('--vscode-panel-border'); document.documentElement.style.setProperty('--frontmatter-border', 'var(--vscode-panel-border)'); + document.documentElement.style.setProperty( + '--frontmatter-border-preserve', + preserveColor(borderColor) || 'var(--vscode-panel-border)' + ); // Other colors which should be preserved (no opacity) const buttonBackground = styles.getPropertyValue('--vscode-button-background'); @@ -70,4 +86,16 @@ export const updateCssVariables = () => { preserveColor(buttonHoverBackground) || 'var(--vscode-button-hoverBackground)' ); } + + // Darken the background of a color + const sideBarBg = styles.getPropertyValue('--vscode-sideBar-background'); + document.documentElement.style.setProperty( + '--frontmatter-sideBar-background', + darkenColor(sideBarBg, 2) || 'var(--vscode-sideBar-background)' + ); + + document.documentElement.style.setProperty( + '--frontmatter-border-active', + darkenColor(borderColor, isDarkTheme ? -30 : 30) || 'var(--vscode-activityBar-activeBorder)' + ); }; diff --git a/src/extension.ts b/src/extension.ts index 44fbfa71..d47a78b4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -140,15 +140,8 @@ export async function activate(context: vscode.ExtensionContext) { const remap = vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap); - const setLastModifiedDate = vscode.commands.registerCommand( - COMMAND_NAME.setLastModifiedDate, - Article.setLastModifiedDate - ); - - const generateSlug = vscode.commands.registerCommand( - COMMAND_NAME.generateSlug, - Article.updateSlug - ); + // Register all the article commands + Article.registerCommands(subscriptions); subscriptions.push( vscode.commands.registerCommand(COMMAND_NAME.initTemplate, () => @@ -291,16 +284,6 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand(COMMAND_NAME.chatbot, () => Chatbot.open(extensionPath)) ); - // Inserting an image in Markdown - subscriptions.push( - vscode.commands.registerCommand(COMMAND_NAME.insertMedia, Article.insertMedia) - ); - - // Inserting a snippet in Markdown - subscriptions.push( - vscode.commands.registerCommand(COMMAND_NAME.insertSnippet, Article.insertSnippet) - ); - // Create the editor experience for bulk scripts subscriptions.push( vscode.workspace.registerTextDocumentContentProvider( @@ -340,8 +323,6 @@ export async function activate(context: vscode.ExtensionContext) { createCategory, exportTaxonomy, remap, - setLastModifiedDate, - generateSlug, createFromTemplate, createTemplate, registerFolder, diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index ead20979..26129596 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -36,7 +36,7 @@ import { import { format, parse } from 'date-fns'; import { Notifications } from './Notifications'; import { Article } from '../commands'; -import { join, parse as parseFile } from 'path'; +import { dirname, join, parse as parseFile } from 'path'; import { EditorHelper } from '@estruyf/vscode'; import sanitize from '../helpers/Sanitize'; import { Field, ContentType as IContentType } from '../models'; @@ -183,6 +183,52 @@ export class ArticleHelper { } } + /** + * Renames a file. + * @param filePath - The path of the file to be renamed. + */ + public static async rename(filePath: string) { + filePath = parseWinPath(filePath); + const fileUri = Uri.file(filePath); + const file = workspace.openTextDocument(fileUri); + if (!file) { + Notifications.error(l10n.t(LocalizationKey.commandsArticleRenameFileNotExistsError)); + return; + } + + const folderPath = dirname(fileUri.fsPath); + const fileName = parseFile(filePath).base; + const fileNameWithoutExt = parseFile(filePath).name; + const fileExtension = parseFile(filePath).ext; + const newFileName = await window.showInputBox({ + title: l10n.t(LocalizationKey.commandsArticleRenameFileNameTitle, fileName), + prompt: l10n.t(LocalizationKey.commandsArticleRenameFileNamePrompt), + value: fileNameWithoutExt, + ignoreFocusOut: true, + validateInput: async (value) => { + try { + const newFileUri = Uri.joinPath(Uri.file(folderPath), `${value}${fileExtension}`); + const exists = await workspace.fs.readFile(newFileUri); + if (exists && value !== fileNameWithoutExt) { + return l10n.t(LocalizationKey.commandsArticleRenameFileExistsError, value); + } + } catch (e) { + // File does not exist + } + return undefined; + } + }); + + if (!newFileName) { + return; + } + + const newFileUri = Uri.joinPath(Uri.file(folderPath), `${newFileName}${fileExtension}`); + await workspace.fs.rename(fileUri, newFileUri, { + overwrite: true + }); + } + /** * Generate the update to be applied to the article. * @param article @@ -694,15 +740,17 @@ export class ArticleHelper { * Get the details of the current article * @returns */ - public static async getDetails(filePath: string) { + public static async getDetails( + filePath: string + ): Promise

        { const baseUrl = Settings.get(SETTING_SITE_BASEURL); if (!filePath) { - return null; + return 'nofilepath'; } const document = await workspace.openTextDocument(filePath); if (!ArticleHelper.isSupportedFile(document)) { - return null; + return 'notsupported'; } const article = await ArticleHelper.getFrontMatterByPath(filePath); @@ -758,7 +806,7 @@ export class ArticleHelper { }; } - return null; + return 'nodata'; } /** diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index bc9ba5cb..edf63f65 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -1,7 +1,7 @@ import { Settings } from './SettingsHelper'; import { CommandType, EnvironmentType } from './../models/PanelSettings'; import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettings'; -import { window, env as vscodeEnv, ProgressLocation } from 'vscode'; +import { window, env as vscodeEnv, ProgressLocation, Uri, commands } from 'vscode'; import { ArticleHelper, Logger, Telemetry } from '.'; import { Folders, WORKSPACE_PLACEHOLDER } from '../commands/Folders'; import { exec, execSync } from 'child_process'; @@ -301,6 +301,11 @@ export class CustomScript { l10n.t(LocalizationKey.helpersCustomScriptShowOutputFrontMatterSuccess, script.title) ); } + } else if (data.fmAction) { + if (data.fmAction === 'open' && data.fmPath) { + const uri = data.fmPath.startsWith(`http`) ? data.fmPath : Uri.file(data.fmPath); + commands.executeCommand('vscode.open', uri); + } } else { Logger.error(`No frontmatter found.`); throw new Error(`No frontmatter found.`); diff --git a/src/helpers/Telemetry.ts b/src/helpers/Telemetry.ts index 12dc9fda..f40dfd24 100644 --- a/src/helpers/Telemetry.ts +++ b/src/helpers/Telemetry.ts @@ -1,8 +1,11 @@ import { workspace } from 'vscode'; import { Extension, Settings } from '.'; -import { EXTENSION_BETA_ID, EXTENSION_ID, SETTING_TELEMETRY_DISABLE } from '../constants'; - -const METRICS_URL = 'https://frontmatter.codes/api/metrics'; +import { + EXTENSION_BETA_ID, + EXTENSION_ID, + SETTING_TELEMETRY_DISABLE, + WEBSITE_LINKS +} from '../constants'; export class Telemetry { private static instance: Telemetry; @@ -77,7 +80,7 @@ export class Telemetry { // Set a new timeout instance.timeout = setTimeout(async () => { - await fetch(METRICS_URL, { + await fetch(WEBSITE_LINKS.api.metrics, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/src/hooks/useDarkMode.tsx b/src/hooks/useDarkMode.tsx index 3453ee52..c8d1d5b1 100644 --- a/src/hooks/useDarkMode.tsx +++ b/src/hooks/useDarkMode.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useEffect } from 'react'; export default function useDarkMode() { const setTheme = (elm: HTMLElement) => { diff --git a/src/listeners/dashboard/LogListener.ts b/src/listeners/dashboard/LogListener.ts deleted file mode 100644 index f4879b7b..00000000 --- a/src/listeners/dashboard/LogListener.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DashboardMessage } from '../../dashboardWebView/DashboardMessage'; -import { Logger } from '../../helpers'; -import { PostMessageData } from '../../models'; -import { BaseListener } from './BaseListener'; - -export class LogListener extends BaseListener { - /** - * Process the messages for the dashboard views - * @param msg - */ - public static process(msg: PostMessageData) { - super.process(msg); - - switch (msg.command) { - case DashboardMessage.logError: - Logger.error(msg.payload); - break; - } - } -} diff --git a/src/listeners/dashboard/PagesListener.ts b/src/listeners/dashboard/PagesListener.ts index 8133f093..685b928d 100644 --- a/src/listeners/dashboard/PagesListener.ts +++ b/src/listeners/dashboard/PagesListener.ts @@ -54,6 +54,9 @@ export class PagesListener extends BaseListener { case DashboardMessage.deleteFile: this.deletePage(msg.payload); break; + case DashboardMessage.rename: + ArticleHelper.rename(msg.payload); + break; } } diff --git a/src/listeners/dashboard/index.ts b/src/listeners/dashboard/index.ts index 230a62c6..26727ef8 100644 --- a/src/listeners/dashboard/index.ts +++ b/src/listeners/dashboard/index.ts @@ -8,6 +8,5 @@ export * from './SettingsListener'; export * from './SnippetListener'; export * from './TelemetryListener'; export * from './TaxonomyListener'; -export * from './LogListener'; export * from './LocalizationListener'; export * from './SsgListener'; diff --git a/src/listeners/general/BaseListener.ts b/src/listeners/general/BaseListener.ts index 3340dac6..93d51e49 100644 --- a/src/listeners/general/BaseListener.ts +++ b/src/listeners/general/BaseListener.ts @@ -25,6 +25,15 @@ export abstract class BaseListener { commands.executeCommand(command, args); } break; + case GeneralCommands.toVSCode.logging.info: + Logger.info(msg.payload); + break; + case GeneralCommands.toVSCode.logging.warn: + Logger.warning(msg.payload); + break; + case GeneralCommands.toVSCode.logging.error: + Logger.error(msg.payload); + break; } } diff --git a/src/listeners/panel/DataListener.ts b/src/listeners/panel/DataListener.ts index 071b1926..1436c274 100644 --- a/src/listeners/panel/DataListener.ts +++ b/src/listeners/panel/DataListener.ts @@ -28,7 +28,7 @@ import { SETTING_TAXONOMY_CONTENT_TYPES } from '../../constants'; import { Article, Preview } from '../../commands'; -import { ParsedFrontMatter } from '../../parsers'; +import { FrontMatterParser, ParsedFrontMatter } from '../../parsers'; import { Field, Mode, PostMessageData, ContentType as IContentType } from '../../models'; import { encodeEmoji, fieldWhenClause } from '../../utils'; import { PanelProvider } from '../../panelWebView/PanelProvider'; @@ -198,9 +198,37 @@ export class DataListener extends BaseListener { try { if (filePath) { articleDetails = await ArticleHelper.getDetails(filePath); + + if (!articleDetails || articleDetails === 'nodata') { + try { + const contents = await ArticleHelper.getContents(filePath); + if (contents) { + FrontMatterParser.fromFile(contents); + } + } catch (e) { + this.sendMsg(Command.metadata, { + fmError: l10n.t( + LocalizationKey.listenersPanelDataListenerPushMetadataFrontMatterError + ), + fmErrorMessage: (e as Error).message + }); + return; + } + } else if (articleDetails === 'notsupported' || articleDetails === 'nofilepath') { + // No file or invalid file format + this.sendMsg(Command.metadata, undefined); + return; + } + } else { + this.sendMsg(Command.metadata, undefined); + return; } } catch (e) { - Logger.error(`DataListener::pushMetadata: ${(e as Error).message}`); + if (filePath) { + Logger.error(`DataListener::pushMetadata: ${(e as Error).message}`); + } + this.sendMsg(Command.metadata, undefined); + return; } if (articleDetails) { diff --git a/src/localization/localization.enum.ts b/src/localization/localization.enum.ts index 4d3f8fa4..ec1086a7 100644 --- a/src/localization/localization.enum.ts +++ b/src/localization/localization.enum.ts @@ -151,6 +151,30 @@ export enum LocalizationKey { * Open: {0} */ commonOpenWithValue = 'common.openWithValue', + /** + * Open custom actions + */ + commonOpenCustomActions = 'common.openCustomActions', + /** + * View + */ + commonView = 'common.view', + /** + * Translate + */ + commonTranslate = 'common.translate', + /** + * Languages + */ + commonLanguages = 'common.languages', + /** + * Scripts + */ + commonScripts = 'common.scripts', + /** + * Rename + */ + commonRename = 'common.rename', /** * Loading content */ @@ -252,7 +276,7 @@ export enum LocalizationKey { */ settingsIntegrationsViewDeeplIntputLabel = 'settings.integrationsView.deepl.intput.label', /** - * Enter your Azure Translator API key + * Enter your Deepl API key */ settingsIntegrationsViewDeeplIntputPlaceholder = 'settings.integrationsView.deepl.intput.placeholder', /** @@ -483,6 +507,18 @@ export enum LocalizationKey { * All */ dashboardFiltersLanguageFilterAll = 'dashboard.filters.languageFilter.all', + /** + * {0} selected + */ + dashboardHeaderActionsBarItemsSelected = 'dashboard.header.actionsBar.itemsSelected', + /** + * Delete selected files + */ + dashboardHeaderActionsBarAlertDeleteTitle = 'dashboard.header.actionsBar.alertDelete.title', + /** + * Are you sure you want to delete the selected files? + */ + dashboardHeaderActionsBarAlertDeleteDescription = 'dashboard.header.actionsBar.alertDelete.description', /** * Home */ @@ -644,7 +680,7 @@ export enum LocalizationKey { */ dashboardHeaderTabsSnippets = 'dashboard.header.tabs.snippets', /** - * data + * Data */ dashboardHeaderTabsData = 'dashboard.header.tabs.data', /** @@ -739,6 +775,14 @@ export enum LocalizationKey { * Create new folder */ dashboardMediaFolderCreationFolderCreate = 'dashboard.media.folderCreation.folder.create', + /** + * Content directory + */ + dashboardMediaFolderItemContentDirectory = 'dashboard.media.folderItem.contentDirectory', + /** + * Public directory + */ + dashboardMediaFolderItemPublicDirectory = 'dashboard.media.folderItem.publicDirectory', /** * Insert image */ @@ -1464,6 +1508,10 @@ export enum LocalizationKey { * Metadata */ panelMetadataTitle = 'panel.metadata.title', + /** + * Check the problems view for more information + */ + panelMetadataFocusProblems = 'panel.metadata.focusProblems', /** * Other actions */ @@ -1644,6 +1692,22 @@ export enum LocalizationKey { * Failed to rename file: {0} */ commandsArticleUpdateSlugError = 'commands.article.updateSlug.error', + /** + * The file did not exist + */ + commandsArticleRenameFileNotExistsError = 'commands.article.rename.fileNotExists.error', + /** + * A file with the name "{0}" already exists + */ + commandsArticleRenameFileExistsError = 'commands.article.rename.fileExists.error', + /** + * Rename: {0} + */ + commandsArticleRenameFileNameTitle = 'commands.article.rename.fileName.title', + /** + * File name + */ + commandsArticleRenameFileNamePrompt = 'commands.article.rename.fileName.prompt', /** * Cache cleared */ @@ -2480,6 +2544,10 @@ export enum LocalizationKey { * Couldn't find data file entries */ listenersPanelDataListenerGetDataFileEntriesNoDataFilesError = 'listeners.panel.dataListener.getDataFileEntries.noDataFiles.error', + /** + * Something went wrong while parsing your front matter. Please check the contents of your file. + */ + listenersPanelDataListenerPushMetadataFrontMatterError = 'listeners.panel.dataListener.pushMetadata.frontMatter.error', /** * No active editor */ diff --git a/src/panelWebView/ViewPanel.tsx b/src/panelWebView/ViewPanel.tsx index 75bf377c..307117d2 100644 --- a/src/panelWebView/ViewPanel.tsx +++ b/src/panelWebView/ViewPanel.tsx @@ -86,7 +86,7 @@ export const ViewPanel: React.FunctionComponent = ( return ; } - if (!metadata || Object.keys(metadata || {}).length === 0) { + if (!metadata) { return ; } diff --git a/src/panelWebView/components/ArticleDetails.tsx b/src/panelWebView/components/ArticleDetails.tsx index 1a14e9fa..0fc8cbdb 100644 --- a/src/panelWebView/components/ArticleDetails.tsx +++ b/src/panelWebView/components/ArticleDetails.tsx @@ -1,14 +1,7 @@ import * as React from 'react'; -import { - VsTable, - VsTableBody, - VsTableHeader, - VsTableHeaderCell, - VsTableRow, - VsTableCell -} from './VscodeComponents'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; +import { VSCodeTable, VSCodeTableBody, VSCodeTableCell, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable'; export interface IArticleDetailsProps { details: { @@ -32,52 +25,55 @@ const ArticleDetails: React.FunctionComponent = ({

        {l10n.t(LocalizationKey.panelArticleDetailsTitle)}

        - - - - {l10n.t(LocalizationKey.panelArticleDetailsType)} - - - {l10n.t(LocalizationKey.panelArticleDetailsTotal)} - - - + + + + + {l10n.t(LocalizationKey.panelArticleDetailsType)} + + + {l10n.t(LocalizationKey.panelArticleDetailsTotal)} + + + + + {details?.headings !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsHeadings)} - {details.headings} - + + {l10n.t(LocalizationKey.panelArticleDetailsHeadings)} + {details.headings} + )} {details?.paragraphs !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsParagraphs)} - {details.paragraphs} - + + {l10n.t(LocalizationKey.panelArticleDetailsParagraphs)} + {details.paragraphs} + )} {details?.internalLinks !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsInternalLinks)} - {details.internalLinks} - + + {l10n.t(LocalizationKey.panelArticleDetailsInternalLinks)} + {details.internalLinks} + )} {details?.externalLinks !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsExternalLinks)} - {details.externalLinks} - + + {l10n.t(LocalizationKey.panelArticleDetailsExternalLinks)} + {details.externalLinks} + )} {details?.images !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsImages)} - {details.images} - + + {l10n.t(LocalizationKey.panelArticleDetailsImages)} + {details.images} + )} - - + +
        ); }; diff --git a/src/panelWebView/components/BaseView.tsx b/src/panelWebView/components/BaseView.tsx index 5821bdbd..48315cd2 100644 --- a/src/panelWebView/components/BaseView.tsx +++ b/src/panelWebView/components/BaseView.tsx @@ -12,6 +12,7 @@ import * as l10n from "@vscode/l10n" import { LocalizationKey } from '../../localization'; import { InitializeAction } from './InitializeAction'; import { Actions } from './Actions'; +import { CustomView } from './CustomView'; export interface IBaseViewProps { settings: PanelSettings | undefined; @@ -56,6 +57,8 @@ const BaseView: React.FunctionComponent = ({ <> + + diff --git a/src/panelWebView/components/Collapsible.tsx b/src/panelWebView/components/Collapsible.tsx index bad7f993..5f68ba6a 100644 --- a/src/panelWebView/components/Collapsible.tsx +++ b/src/panelWebView/components/Collapsible.tsx @@ -66,7 +66,7 @@ const Collapsible: React.FunctionComponent = ({ return ( -
        +
        {children}
        diff --git a/src/panelWebView/components/ContentType/ContentTypeValidator.tsx b/src/panelWebView/components/ContentType/ContentTypeValidator.tsx index cf6bf63b..96649bf8 100644 --- a/src/panelWebView/components/ContentType/ContentTypeValidator.tsx +++ b/src/panelWebView/components/ContentType/ContentTypeValidator.tsx @@ -5,9 +5,9 @@ import { useMemo } from 'react'; import { Field } from '../../../models'; import { CommandToCode } from '../../CommandToCode'; import { IMetadata } from '../Metadata'; -import { VsLabel } from '../VscodeComponents'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { VSCodeLabel } from '../VSCode'; export interface IContentTypeValidatorProps { fields: Field[]; @@ -50,7 +50,7 @@ export const ContentTypeValidator: React.FunctionComponent - +
        -
        + {l10n.t(LocalizationKey.panelContentTypeContentTypeValidatorHint).split(`\n`).map(s => (

        {s}

        ))} diff --git a/src/panelWebView/components/CustomView/CustomView.tsx b/src/panelWebView/components/CustomView/CustomView.tsx index 9ac0bf73..d3616af6 100644 --- a/src/panelWebView/components/CustomView/CustomView.tsx +++ b/src/panelWebView/components/CustomView/CustomView.tsx @@ -4,7 +4,7 @@ import { CustomPanelViewResult } from '../../../models'; import { Collapsible } from '../Collapsible'; export interface ICustomViewProps { - metadata: any; + metadata?: any; } export const CustomView: React.FunctionComponent = ({ metadata }: React.PropsWithChildren) => { diff --git a/src/panelWebView/components/DataBlock/DataBlockRecords.tsx b/src/panelWebView/components/DataBlock/DataBlockRecords.tsx index 69073a41..a04093aa 100644 --- a/src/panelWebView/components/DataBlock/DataBlockRecords.tsx +++ b/src/panelWebView/components/DataBlock/DataBlockRecords.tsx @@ -1,12 +1,12 @@ -import { RectangleStackIcon, PlusIcon } from '@heroicons/react/24/outline'; +import { RectangleStackIcon } from '@heroicons/react/24/outline'; import * as React from 'react'; -import { VsLabel } from '../VscodeComponents'; import { DataBlockRecord } from '.'; import { SortableContainer, SortEnd } from 'react-sortable-hoc'; import { useCallback } from 'react'; import { FieldGroup } from '../../../models'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { VSCodeLabel } from '../VSCode'; export interface IDataBlockRecordsProps { fieldGroups?: FieldGroup[]; @@ -52,7 +52,7 @@ export const DataBlockRecords = ({ return (
        - +
        @@ -61,7 +61,7 @@ export const DataBlockRecords = ({
        -
        + {records.map((v: any, idx: number) => ( diff --git a/src/panelWebView/components/ErrorBoundary/FieldBoundary.tsx b/src/panelWebView/components/ErrorBoundary/FieldBoundary.tsx index 8b6715af..6f921c65 100644 --- a/src/panelWebView/components/ErrorBoundary/FieldBoundary.tsx +++ b/src/panelWebView/components/ErrorBoundary/FieldBoundary.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import * as Sentry from '@sentry/react'; -import { VsLabel } from '../VscodeComponents'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { VSCodeLabel } from '../VSCode'; export interface IFieldBoundaryProps { fieldName: string; @@ -34,11 +34,11 @@ export default class FieldBoundary extends React.Component< if (this.state.hasError) { return (
        - +
        {this.props.fieldName}
        -
        +
        {l10n.t(LocalizationKey.panelErrorBoundaryFieldBoundaryLabel)} diff --git a/src/panelWebView/components/Fields/DataFileField.tsx b/src/panelWebView/components/Fields/DataFileField.tsx index 4fffdba4..6d6c390b 100644 --- a/src/panelWebView/components/Fields/DataFileField.tsx +++ b/src/panelWebView/components/Fields/DataFileField.tsx @@ -166,7 +166,7 @@ export const DataFileField: React.FunctionComponent = ({
          = ({ }, [icon]); return ( - +
          {Icon} @@ -33,6 +33,6 @@ export const FieldTitle: React.FunctionComponent = ({ {actionElement}
          - + ); }; diff --git a/src/panelWebView/components/TagPicker.tsx b/src/panelWebView/components/Fields/TagPicker.tsx similarity index 95% rename from src/panelWebView/components/TagPicker.tsx rename to src/panelWebView/components/Fields/TagPicker.tsx index 005f0ea4..119fa3e3 100644 --- a/src/panelWebView/components/TagPicker.tsx +++ b/src/panelWebView/components/Fields/TagPicker.tsx @@ -1,21 +1,21 @@ import * as React from 'react'; -import { Tags } from './Tags'; -import { usePrevious } from '../hooks/usePrevious'; -import { CommandToCode } from '../CommandToCode'; -import { TagType } from '../TagType'; +import { Tags } from '../Tags'; +import { usePrevious } from '../../hooks/usePrevious'; +import { CommandToCode } from '../../CommandToCode'; +import { TagType } from '../../TagType'; import Downshift from 'downshift'; -import { AddIcon } from './Icons/AddIcon'; -import { BlockFieldData, CustomTaxonomyData } from '../../models'; +import { AddIcon } from '../Icons/AddIcon'; +import { BlockFieldData, CustomTaxonomyData } from '../../../models'; import { useCallback, useEffect, useMemo } from 'react'; import { messageHandler, Messenger } from '@estruyf/vscode/dist/client'; -import { FieldMessage } from './Fields/FieldMessage'; -import { FieldTitle } from './Fields/FieldTitle'; +import { FieldMessage } from '../Fields/FieldMessage'; +import { FieldTitle } from '../Fields/FieldTitle'; import { useRecoilValue } from 'recoil'; -import { PanelSettingsAtom } from '../state'; +import { PanelSettingsAtom } from '../../state'; import { SparklesIcon } from '@heroicons/react/24/outline'; import * as l10n from '@vscode/l10n'; -import { LocalizationKey } from '../../localization'; -import useDropdownStyle from '../hooks/useDropdownStyle'; +import { LocalizationKey } from '../../../localization'; +import useDropdownStyle from '../../hooks/useDropdownStyle'; export interface ITagPickerProps { type: TagType; diff --git a/src/panelWebView/components/Fields/WrapperField.tsx b/src/panelWebView/components/Fields/WrapperField.tsx index 00dbbad5..fa068006 100644 --- a/src/panelWebView/components/Fields/WrapperField.tsx +++ b/src/panelWebView/components/Fields/WrapperField.tsx @@ -11,7 +11,6 @@ import { ListUnorderedIcon } from '../Icons/ListUnorderedIcon'; import { TagIcon } from '../Icons/TagIcon'; import { JsonField } from '../JsonField'; import { IMetadata } from '../Metadata'; -import { TagPicker } from '../TagPicker'; import { ChoiceField, DataFileField, @@ -27,7 +26,8 @@ import { PreviewImageValue, NumberField, CustomField, - FieldCollection + FieldCollection, + TagPicker } from '.'; import { fieldWhenClause } from '../../../utils/fieldWhenClause'; import { ContentTypeRelationshipField } from './ContentTypeRelationshipField'; diff --git a/src/panelWebView/components/Fields/index.ts b/src/panelWebView/components/Fields/index.ts index 35a8ebd5..208e8cde 100644 --- a/src/panelWebView/components/Fields/index.ts +++ b/src/panelWebView/components/Fields/index.ts @@ -16,6 +16,7 @@ export * from './PreviewImage'; export * from './PreviewImageField'; export * from './RequiredAsterix'; export * from './SlugField'; +export * from './TagPicker'; export * from './TextField'; export * from './Toggle'; export * from './WrapperField'; diff --git a/src/panelWebView/components/FileList.tsx b/src/panelWebView/components/FileList.tsx index 203d3776..d60165b8 100644 --- a/src/panelWebView/components/FileList.tsx +++ b/src/panelWebView/components/FileList.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { FileInfo } from '../../models'; import { FileItem } from './FileItem'; -import { VsLabel } from './VscodeComponents'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; +import { VSCodeLabel } from './VSCode'; export interface IFileListProps { folderName: string; @@ -22,9 +22,9 @@ const FileList: React.FunctionComponent = ({ return (
          - + {folderName} - {files.length === 1 ? l10n.t(LocalizationKey.panelFileListLabelSingular) : l10n.t(LocalizationKey.panelFileListLabelPlural)}: {totalFiles} - +
            {files && diff --git a/src/panelWebView/components/FolderAndFiles.tsx b/src/panelWebView/components/FolderAndFiles.tsx index ee484911..eaae1155 100644 --- a/src/panelWebView/components/FolderAndFiles.tsx +++ b/src/panelWebView/components/FolderAndFiles.tsx @@ -2,9 +2,9 @@ import * as React from 'react'; import { FolderInfo } from '../../models'; import { Collapsible } from './Collapsible'; import { FileList } from './FileList'; -import { VsLabel } from './VscodeComponents'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; +import { VSCodeLabel } from './VSCode'; export interface IFolderAndFilesProps { data: FolderInfo[] | undefined; @@ -35,9 +35,9 @@ const FolderAndFiles: React.FunctionComponent = ({ />
          ) : isBase ? ( - + {folder.title}: {folder.files} {folder.files > 1 ? l10n.t(LocalizationKey.panelFileListLabelPlural) : l10n.t(LocalizationKey.panelFileListLabelSingular)} - + ) : null}
          ))} diff --git a/src/panelWebView/components/GlobalSettings.tsx b/src/panelWebView/components/GlobalSettings.tsx index 7873cfb8..9819c312 100644 --- a/src/panelWebView/components/GlobalSettings.tsx +++ b/src/panelWebView/components/GlobalSettings.tsx @@ -3,12 +3,12 @@ import { PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; import { useDebounce } from '../../hooks/useDebounce'; import { Collapsible } from './Collapsible'; -import { VsLabel } from './VscodeComponents'; import useStartCommand from '../hooks/useStartCommand'; import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react'; import { Messenger } from '@estruyf/vscode/dist/client'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; +import { VSCodeLabel } from './VSCode'; export interface IGlobalSettingsProps { settings: PanelSettings | undefined; @@ -78,25 +78,25 @@ const GlobalSettings: React.FunctionComponent = ({ title={l10n.t(LocalizationKey.panelGlobalSettingsTitle)} >
          - + {l10n.t(LocalizationKey.panelGlobalSettingsActionModifiedDateLabel)} - + {l10n.t(LocalizationKey.panelGlobalSettingsActionModifiedDateDescription)}
          - + {l10n.t(LocalizationKey.panelGlobalSettingsActionFrontMatterLabel)} - + {l10n.t(LocalizationKey.panelGlobalSettingsActionFrontMatterDescription)}
          - + {l10n.t(LocalizationKey.panelGlobalSettingsActionPreviewLabel)} - + = ({ />
          - + {l10n.t(LocalizationKey.panelGlobalSettingsActionServerLabel)} - + = ( - props: React.PropsWithChildren -) => { - return ( - - - - ); -}; diff --git a/src/panelWebView/components/Icons/PageIcon.tsx b/src/panelWebView/components/Icons/PageIcon.tsx new file mode 100644 index 00000000..4932cfb0 --- /dev/null +++ b/src/panelWebView/components/Icons/PageIcon.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +export interface IPageIconProps { + className?: string; +} + +export const PageIcon: React.FunctionComponent = ({ + className +}: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/panelWebView/components/Icons/WarningIcon.tsx b/src/panelWebView/components/Icons/WarningIcon.tsx deleted file mode 100644 index f76ede02..00000000 --- a/src/panelWebView/components/Icons/WarningIcon.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from 'react'; - -export interface IWarningIconProps {} - -export const WarningIcon: React.FunctionComponent = ( - props: React.PropsWithChildren -) => { - return ( - - - - ); -}; diff --git a/src/panelWebView/components/Icons/index.ts b/src/panelWebView/components/Icons/index.ts new file mode 100644 index 00000000..f02ef818 --- /dev/null +++ b/src/panelWebView/components/Icons/index.ts @@ -0,0 +1,20 @@ +export * from './AddIcon'; +export * from './ArchiveIcon'; +export * from './BranchIcon'; +export * from './BugIcon'; +export * from './CenterIcon'; +export * from './FileIcon'; +export * from './FolderOpenedIcon'; +export * from './FrontMatterIcon'; +export * from './GitHubIcon'; +export * from './HeartIcon'; +export * from './ListUnorderedIcon'; +export * from './MarkdownIcon'; +export * from './PageIcon'; +export * from './RocketIcon'; +export * from './SettingsIcon'; +export * from './SymbolKeywordIcon'; +export * from './TagIcon'; +export * from './TemplateIcon'; +export * from './ToggleIcon'; +export * from './WritingIcon'; diff --git a/src/panelWebView/components/JsonField/JsonField.tsx b/src/panelWebView/components/JsonField/JsonField.tsx index fe92e843..86954575 100644 --- a/src/panelWebView/components/JsonField/JsonField.tsx +++ b/src/panelWebView/components/JsonField/JsonField.tsx @@ -2,10 +2,10 @@ import * as React from 'react'; import { useState, useMemo, useCallback, useEffect } from 'react'; import { Field, PanelSettings } from '../../../models'; import { PencilIcon } from '@heroicons/react/24/outline'; -import { VsLabel } from '../VscodeComponents'; import { JsonFieldRecords, JsonFieldForm, JsonFieldSelector } from '.'; import { SortEnd } from 'react-sortable-hoc'; import { arrayMoveImmutable } from 'array-move'; +import { VSCodeLabel } from '../VSCode'; export interface IJsonFieldProps { label: string; @@ -101,12 +101,12 @@ export const JsonField: React.FunctionComponent = ({ return (
          - +
          {' '} {label}
          -
          + - +
          @@ -44,7 +44,7 @@ export const JsonFieldRecords = ({
          - + {records.map((v: any, idx: number) => ( diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index fd220f09..843e498d 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -8,7 +8,7 @@ import useContentType from '../../hooks/useContentType'; import { WrapperField } from './Fields/WrapperField'; import { ContentTypeValidator } from './ContentType/ContentTypeValidator'; import { FeatureFlag } from '../../components/features/FeatureFlag'; -import { FEATURE_FLAG } from '../../constants'; +import { FEATURE_FLAG, GeneralCommands } from '../../constants'; import { Messenger } from '@estruyf/vscode/dist/client'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; @@ -33,6 +33,12 @@ const Metadata: React.FunctionComponent = ({ }: React.PropsWithChildren) => { const contentType = useContentType(settings, metadata); + const focusProblems = () => { + Messenger.send(GeneralCommands.toVSCode.runCommand, { + command: `workbench.panel.markers.view.focus` + }); + } + const onSendUpdate = React.useCallback((field: string | undefined, value: any, parents: string[]) => { if (!field) { return; @@ -93,7 +99,20 @@ const Metadata: React.FunctionComponent = ({ - {allFields} + { + metadata.fmError && metadata.fmErrorMessage ? ( +
          +

          {metadata.fmError}

          + + +
          + ) : allFields + } ); }; diff --git a/src/panelWebView/components/SeoDetails.tsx b/src/panelWebView/components/SeoDetails.tsx index ab13f486..0ec6ffea 100644 --- a/src/panelWebView/components/SeoDetails.tsx +++ b/src/panelWebView/components/SeoDetails.tsx @@ -1,14 +1,7 @@ import * as React from 'react'; -import { - VsTable, - VsTableBody, - VsTableHeader, - VsTableHeaderCell, - VsTableRow, - VsTableCell -} from './VscodeComponents'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; +import { VSCodeTable, VSCodeTableBody, VSCodeTableCell, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable'; export interface ISeoDetailsProps { allowedLength: number; @@ -23,30 +16,33 @@ const SeoDetails: React.FunctionComponent = ( ) => { const { allowedLength, title, value, valueTitle, noValidation } = props; - const validate = () => { + const validate = React.useMemo(() => { if (noValidation) { return ''; } return value <= allowedLength ? 'valid' : 'not-valid'; - }; + }, [value, allowedLength, noValidation]); return ( -
          +

          {title}

          - - - {valueTitle} - {l10n.t(LocalizationKey.panelSeoDetailsRecommended)} - - - - {value} - {allowedLength} - - - + + + + {valueTitle} + {l10n.t(LocalizationKey.panelSeoDetailsRecommended)} + + + + + + {value} + {allowedLength} + + +
          ); }; diff --git a/src/panelWebView/components/SeoFieldInfo.tsx b/src/panelWebView/components/SeoFieldInfo.tsx index f8814bba..8d15a1e4 100644 --- a/src/panelWebView/components/SeoFieldInfo.tsx +++ b/src/panelWebView/components/SeoFieldInfo.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { ValidInfo } from './ValidInfo'; -import { VsTableCell, VsTableRow } from './VscodeComponents'; +import { VSCodeTableCell, VSCodeTableRow } from './VSCode/VSCodeTable'; export interface ISeoFieldInfoProps { title: string; @@ -16,15 +16,11 @@ const SeoFieldInfo: React.FunctionComponent = ({ isValid }: React.PropsWithChildren) => { return ( - - {title} - - {value}/{recommendation} - - - {isValid !== undefined ? : -} - - + + {title} + {value}/{recommendation} + {isValid !== undefined ? : -} + ); }; diff --git a/src/panelWebView/components/SeoKeywordInfo.tsx b/src/panelWebView/components/SeoKeywordInfo.tsx index bcb567ea..38418233 100644 --- a/src/panelWebView/components/SeoKeywordInfo.tsx +++ b/src/panelWebView/components/SeoKeywordInfo.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { ValidInfo } from './ValidInfo'; -import { VsTableCell, VsTableRow } from './VscodeComponents'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; +import { VSCodeTableCell, VSCodeTableRow } from './VSCode/VSCodeTable'; export interface ISeoKeywordInfoProps { keyword: string; @@ -71,22 +71,22 @@ const SeoKeywordInfo: React.FunctionComponent = ({ } return ( - - {keyword} - -
          + + {keyword} + +
          -
          +
          -
          +
          = ({ } />
          -
          +
          - {headings && headings.length > 0 &&
          {checkHeadings()}
          } - {wordCount &&
          {density()}
          } - - + {headings && headings.length > 0 && +
          {checkHeadings()}
          } + {wordCount && +
          {density()}
          } + + ); }; diff --git a/src/panelWebView/components/SeoKeywords.tsx b/src/panelWebView/components/SeoKeywords.tsx index 0a2ca00f..094a73ed 100644 --- a/src/panelWebView/components/SeoKeywords.tsx +++ b/src/panelWebView/components/SeoKeywords.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { SeoKeywordInfo } from './SeoKeywordInfo'; -import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell } from './VscodeComponents'; import { ErrorBoundary } from '@sentry/react'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; +import { VSCodeTable, VSCodeTableBody, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable'; export interface ISeoKeywordsProps { keywords: string[] | null; @@ -54,16 +54,19 @@ const SeoKeywords: React.FunctionComponent = ({

          {l10n.t(LocalizationKey.panelSeoKeywordsTitle)}

          - - - - {l10n.t(LocalizationKey.panelSeoKeywordsHeaderKeyword)} - - - {l10n.t(LocalizationKey.panelSeoKeywordsHeaderDetails)} - - - + + + + + {l10n.t(LocalizationKey.panelSeoKeywordsHeaderKeyword)} + + + {l10n.t(LocalizationKey.panelSeoKeywordsHeaderDetails)} + + + + + {validateKeywords().map((keyword, index) => { return ( }> @@ -71,11 +74,11 @@ const SeoKeywords: React.FunctionComponent = ({ ); })} - - + + {data.wordCount && ( -
          +
          {l10n.t(LocalizationKey.panelSeoKeywordsDensity)}
          )} diff --git a/src/panelWebView/components/SeoStatus.tsx b/src/panelWebView/components/SeoStatus.tsx index 28677c0b..55b6b38d 100644 --- a/src/panelWebView/components/SeoStatus.tsx +++ b/src/panelWebView/components/SeoStatus.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { useEffect } from 'react'; import { SEO } from '../../models/PanelSettings'; import { TagType } from '../TagType'; import { ArticleDetails } from './ArticleDetails'; @@ -8,10 +7,10 @@ import FieldBoundary from './ErrorBoundary/FieldBoundary'; import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon'; import { SeoFieldInfo } from './SeoFieldInfo'; import { SeoKeywords } from './SeoKeywords'; -import { TagPicker } from './TagPicker'; -import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell } from './VscodeComponents'; +import { TagPicker } from './Fields/TagPicker'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; +import { VSCodeTable, VSCodeTableBody, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable'; export interface ISeoStatusProps { seo: SEO; @@ -27,90 +26,61 @@ const SeoStatus: React.FunctionComponent = ({ unsetFocus }: React.PropsWithChildren) => { const { title, slug } = data; - const [isOpen, setIsOpen] = React.useState(true); - const tableRef = React.useRef(); - const pushUpdate = React.useRef((value: boolean) => { - setTimeout(() => { - setIsOpen(value); - }, 10); - }).current; const { descriptionField, titleField } = seo; - // Workaround for lit components not updating render - useEffect(() => { - setTimeout(() => { - let height = 0; - - tableRef.current?.childNodes.forEach((elm: any) => { - height += elm.clientHeight; - }); - - if (height > 0 && tableRef.current) { - tableRef.current.style.height = `${height}px`; - } - }, 10); - }, [title, data[titleField], data[descriptionField], data?.articleDetails?.wordCount]); - - const renderContent = () => { - if (!isOpen) { - return null; - } - + const tableContent = React.useMemo(() => { return (

          {l10n.t(LocalizationKey.panelSeoStatusTitle)}

          - - - - {l10n.t(LocalizationKey.panelSeoStatusHeaderProperty)} - - - {l10n.t(LocalizationKey.panelSeoStatusHeaderLength)} - - - {l10n.t(LocalizationKey.panelSeoStatusHeaderValid)} - - - - {data[titleField] && seo.title > 0 && ( + + + + {l10n.t(LocalizationKey.panelSeoStatusHeaderProperty)} + {l10n.t(LocalizationKey.panelSeoStatusHeaderLength)} + {l10n.t(LocalizationKey.panelSeoStatusHeaderValid)} + + + + + {data[titleField] && seo.title > 0 ? ( - )} + ) : null} - {slug && seo.slug > 0 && ( + {slug && seo.slug > 0 ? ( - )} + ) : null} - {data[descriptionField] && seo.description > 0 && ( + {data[descriptionField] && seo.description > 0 ? ( - )} + ) : null} - {seo.content > 0 && data?.articleDetails?.wordCount > 0 && ( + {seo.content > 0 && data?.articleDetails?.wordCount > 0 ? ( - )} - - + ) : null} + +
          = ({
          ); - }; + }, [data, seo, focusElm, unsetFocus]); return ( - + {!title && !data[descriptionField] ? (

          @@ -150,7 +120,7 @@ const SeoStatus: React.FunctionComponent = ({

          ) : ( - renderContent() + tableContent )}
          ); diff --git a/src/panelWebView/components/VSCode/VSCodeLabel.tsx b/src/panelWebView/components/VSCode/VSCodeLabel.tsx new file mode 100644 index 00000000..6dcee08a --- /dev/null +++ b/src/panelWebView/components/VSCode/VSCodeLabel.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; + +export interface IVSCodeLabelProps { } + +export const VSCodeLabel: React.FunctionComponent = ({ + children +}: React.PropsWithChildren) => { + const DEFAULT_LINE_HEIGHT = 16; + const DEFAULT_FONT_SIZE = 13; + + const INPUT_LINE_HEIGHT_RATIO = DEFAULT_LINE_HEIGHT / DEFAULT_FONT_SIZE; + + return ( + + ); +}; \ No newline at end of file diff --git a/src/panelWebView/components/VSCode/VSCodeTable.tsx b/src/panelWebView/components/VSCode/VSCodeTable.tsx new file mode 100644 index 00000000..4dee145b --- /dev/null +++ b/src/panelWebView/components/VSCode/VSCodeTable.tsx @@ -0,0 +1,103 @@ +import * as React from "react" +import { cn } from "../../../utils/cn" + +const VSCodeTable = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
          + + +)) +VSCodeTable.displayName = "VSCodeTable" + +const VSCodeTableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +VSCodeTableHeader.displayName = "VSCodeTableHeader" + +const VSCodeTableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +VSCodeTableBody.displayName = "VSCodeTableBody" + +const VSCodeTableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +VSCodeTableFooter.displayName = "VSCodeTableFooter" + +const VSCodeTableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +VSCodeTableRow.displayName = "VSCodeTableRow" + +const VSCodeTableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
          +)) +VSCodeTableHead.displayName = "VSCodeTableHead" + +const VSCodeTableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +VSCodeTableCell.displayName = "VSCodeTableCell" + +export { + VSCodeTable, + VSCodeTableHeader, + VSCodeTableBody, + VSCodeTableFooter, + VSCodeTableHead, + VSCodeTableRow, + VSCodeTableCell, +} \ No newline at end of file diff --git a/src/panelWebView/components/VSCode/index.ts b/src/panelWebView/components/VSCode/index.ts new file mode 100644 index 00000000..a6f343b3 --- /dev/null +++ b/src/panelWebView/components/VSCode/index.ts @@ -0,0 +1 @@ +export * from './VSCodeLabel'; diff --git a/src/panelWebView/components/ValidInfo.tsx b/src/panelWebView/components/ValidInfo.tsx index 8f23e1a8..2e817775 100644 --- a/src/panelWebView/components/ValidInfo.tsx +++ b/src/panelWebView/components/ValidInfo.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import { CheckIcon } from './Icons/CheckIcon'; -import { WarningIcon } from './Icons/WarningIcon'; +import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'; export interface IValidInfoProps { label?: string; @@ -14,13 +13,9 @@ const ValidInfo: React.FunctionComponent = ({ return ( <> {isValid ? ( - - - + ) : ( - - - + )} {label && {label}} diff --git a/src/panelWebView/components/VscodeComponents.ts b/src/panelWebView/components/VscodeComponents.ts index a3128e4d..5fbfc822 100644 --- a/src/panelWebView/components/VscodeComponents.ts +++ b/src/panelWebView/components/VscodeComponents.ts @@ -1,11 +1,5 @@ import { wrapWc } from 'wc-react'; // @bendera/vscode-webview-elements -export const VsTable = wrapWc(`vscode-table`); -export const VsTableHeader = wrapWc(`vscode-table-header`); -export const VsTableHeaderCell = wrapWc(`vscode-table-header-cell`); -export const VsTableBody = wrapWc(`vscode-table-body`); -export const VsTableRow = wrapWc(`vscode-table-row`); -export const VsTableCell = wrapWc(`vscode-table-cell`); export const VsCollapsible = wrapWc(`vscode-collapsible`); -export const VsLabel = wrapWc(`vscode-label`); +// export const VsLabel = wrapWc(`vscode-label`); diff --git a/src/panelWebView/index.tsx b/src/panelWebView/index.tsx index 613ddbbd..5b5a7c70 100644 --- a/src/panelWebView/index.tsx +++ b/src/panelWebView/index.tsx @@ -9,14 +9,8 @@ import { SentryInit } from '../utils/sentryInit'; import './styles.css'; // require('@vscode/codicons/dist/codicon.css'); -import '@bendera/vscode-webview-elements/dist/vscode-table.js'; -import '@bendera/vscode-webview-elements/dist/vscode-table-header.js'; -import '@bendera/vscode-webview-elements/dist/vscode-table-header-cell.js'; -import '@bendera/vscode-webview-elements/dist/vscode-table-body.js'; -import '@bendera/vscode-webview-elements/dist/vscode-table-row.js'; -import '@bendera/vscode-webview-elements/dist/vscode-table-cell.js'; -import '@bendera/vscode-webview-elements/dist/vscode-collapsible.js'; -import '@bendera/vscode-webview-elements/dist/vscode-label.js'; +import '@vscode-elements/elements/dist/vscode-collapsible/index.js'; +// import '@bendera/vscode-webview-elements/dist/vscode-label/index.js'; // import '@bendera/vscode-webview-elements/dist/vscode-checkbox.js'; // import '@vscode/webview-ui-toolkit/dist/esm/checkbox'; diff --git a/src/services/SponsorAI.ts b/src/services/SponsorAI.ts index 0820be49..1bf7a9b2 100644 --- a/src/services/SponsorAI.ts +++ b/src/services/SponsorAI.ts @@ -1,11 +1,15 @@ -import { SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from '../constants'; +import { + SETTING_SEO_DESCRIPTION_LENGTH, + SETTING_SEO_TITLE_LENGTH, + WEBSITE_LINKS +} from '../constants'; import { Logger, Notifications, Settings, TaxonomyHelper } from '../helpers'; import { TagType } from '../panelWebView/TagType'; import { TaxonomyType } from '../models'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; -const AI_URL = 'https://frontmatter.codes/api/ai'; +const AI_URL = WEBSITE_LINKS.api.ai; // const AI_URL = 'http://localhost:3000/api/ai'; export class SponsorAi {