mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
731212264c | ||
|
|
3a8dcbe22f | ||
|
|
6cbe76096b | ||
|
|
039170eae5 | ||
|
|
14ddd8b53c | ||
|
|
b60aadc4a3 | ||
|
|
79ee6607ca | ||
|
|
fe9fbe899d | ||
|
|
a196c4192a | ||
|
|
0f1085756c | ||
|
|
83f103b991 | ||
|
|
13f5446163 | ||
|
|
96c496caba | ||
|
|
7c2a59615f | ||
|
|
0ea972e4f3 | ||
|
|
d1eb252380 | ||
|
|
3e038c58a3 | ||
|
|
2cb72a607b | ||
|
|
2f7d8e5816 | ||
|
|
1337b21789 |
1
.frontmatter/database/pinnedItemsDb.json
Normal file
1
.frontmatter/database/pinnedItemsDb.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,5 +1,34 @@
|
||||
# Change Log
|
||||
|
||||
## [9.4.0] - 2023-xx-xx
|
||||
|
||||
### ✨ New features
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#273](https://github.com/estruyf/vscode-front-matter/issues/273): Allow single value arrays to be set as a string with the `singleValueAsString` field property
|
||||
- [#686](https://github.com/estruyf/vscode-front-matter/issues/686): Allow script authors to ask questions during script execution
|
||||
- [#688](https://github.com/estruyf/vscode-front-matter/issues/688): Allow to show the scheduled articles in the content dashboard (filter and group)
|
||||
|
||||
### ⚡️ Optimizations
|
||||
|
||||
- Dashboard layout grid optimizations
|
||||
- Added the content-type name to the metadata section in the panel
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#685](https://github.com/estruyf/vscode-front-matter/issues/685): Fix when using non-string values in the tag picker
|
||||
- [#691](https://github.com/estruyf/vscode-front-matter/issues/691): Silent authentication retrieval for GitHub sponsors
|
||||
- [#694](https://github.com/estruyf/vscode-front-matter/issues/694): Start terminal session from the folder where the `frontmatter.json` file is located
|
||||
- [#696](https://github.com/estruyf/vscode-front-matter/issues/696): Close the local server terminal on restart
|
||||
- [#699](https://github.com/estruyf/vscode-front-matter/issues/699): Changing border theme variable for the dashboard header
|
||||
|
||||
## [9.3.1] - 2023-10-27
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#697](https://github.com/estruyf/vscode-front-matter/issues/697): Fix missing localization key
|
||||
|
||||
## [9.3.0] - 2023-10-06 - [Release notes](https://beta.frontmatter.codes/updates/v9.3.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"common.refreshSettings": "Refresh settings",
|
||||
"common.pin": "Pin",
|
||||
"common.unpin": "Unpin",
|
||||
"common.noResults": "No results",
|
||||
|
||||
"settings.view.common": "Common",
|
||||
"settings.view.contentFolders": "Content folders",
|
||||
@@ -84,6 +85,7 @@
|
||||
|
||||
"dashboard.contents.status.draft": "Draft",
|
||||
"dashboard.contents.status.published": "Published",
|
||||
"dashboard.contents.status.scheduled": "Scheduled",
|
||||
|
||||
"dashboard.dataView.dataForm.modify": "Modify the data",
|
||||
"dashboard.dataView.dataForm.add": "Add new data",
|
||||
@@ -122,6 +124,7 @@
|
||||
|
||||
"dashboard.header.navigation.allArticles": "All articles",
|
||||
"dashboard.header.navigation.published": "Published",
|
||||
"dashboard.header.navigation.scheduled": "Scheduled",
|
||||
"dashboard.header.navigation.draft": "In draft",
|
||||
|
||||
"dashboard.header.header.createContent": "Create content",
|
||||
|
||||
34
package-lock.json
generated
34
package-lock.json
generated
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "9.3.0",
|
||||
"version": "9.4.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "9.3.0",
|
||||
"version": "9.4.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "^1.1.0",
|
||||
"@headlessui/react": "1.5.0",
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@heroicons/react": "1.0.4",
|
||||
"@iarna/toml": "2.2.3",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
@@ -348,9 +348,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@headlessui/react": {
|
||||
"version": "1.5.0",
|
||||
"version": "1.7.17",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz",
|
||||
"integrity": "sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"client-only": "^0.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -2960,6 +2964,12 @@
|
||||
"node": ">= 4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/clipboardy": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz",
|
||||
@@ -13314,9 +13324,13 @@
|
||||
}
|
||||
},
|
||||
"@headlessui/react": {
|
||||
"version": "1.5.0",
|
||||
"version": "1.7.17",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz",
|
||||
"integrity": "sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"requires": {
|
||||
"client-only": "^0.0.1"
|
||||
}
|
||||
},
|
||||
"@heroicons/react": {
|
||||
"version": "1.0.4",
|
||||
@@ -15272,6 +15286,12 @@
|
||||
"source-map": "~0.6.0"
|
||||
}
|
||||
},
|
||||
"client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"dev": true
|
||||
},
|
||||
"clipboardy": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz",
|
||||
|
||||
284
package.json
284
package.json
@@ -3,14 +3,15 @@
|
||||
"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": "9.3.0",
|
||||
"version": "9.4.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"
|
||||
@@ -70,7 +71,8 @@
|
||||
"**/.frontmatter/config/*.json": "jsonc"
|
||||
}
|
||||
},
|
||||
"keybindings": [{
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "frontMatter.dashboard",
|
||||
"key": "alt+d"
|
||||
},
|
||||
@@ -88,19 +90,23 @@
|
||||
}
|
||||
],
|
||||
"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%",
|
||||
@@ -168,7 +174,8 @@
|
||||
"frontMatter.content.defaultFileType": {
|
||||
"type": "string",
|
||||
"default": "md",
|
||||
"oneOf": [{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"md",
|
||||
"mdx"
|
||||
@@ -184,7 +191,8 @@
|
||||
"frontMatter.content.defaultSorting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"oneOf": [{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"LastModifiedAsc",
|
||||
"LastModifiedDesc",
|
||||
@@ -536,7 +544,8 @@
|
||||
"command": {
|
||||
"$id": "#scriptCommand",
|
||||
"type": "string",
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"node",
|
||||
"bash",
|
||||
@@ -743,7 +752,8 @@
|
||||
"title",
|
||||
"file"
|
||||
],
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -797,7 +807,8 @@
|
||||
"id",
|
||||
"path"
|
||||
],
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -993,7 +1004,7 @@
|
||||
"frontMatter.panel.actions.disabled": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"markdownDescription": "%setting.frontMatter.panel.actions.disabeld.markdownDescription%",
|
||||
"markdownDescription": "%setting.frontMatter.panel.actions.disabled.markdownDescription%",
|
||||
"enum": [
|
||||
"openDashboard",
|
||||
"createContent",
|
||||
@@ -1185,7 +1196,8 @@
|
||||
"default": "",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%",
|
||||
"not": {
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1260,6 +1272,11 @@
|
||||
"default": 0,
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyLimit.description%"
|
||||
},
|
||||
"singleValueAsString": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.singleValueAsString.description%"
|
||||
},
|
||||
"isPublishDate": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
@@ -1374,7 +1391,8 @@
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"allOf": [{
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -1574,48 +1592,51 @@
|
||||
"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": {
|
||||
@@ -1628,7 +1649,8 @@
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%",
|
||||
"not": {
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1814,7 +1836,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [{
|
||||
"commands": [
|
||||
{
|
||||
"command": "frontMatter.project.switch",
|
||||
"title": "%command.frontMatter.project.switch%",
|
||||
"category": "Front Matter",
|
||||
@@ -2131,12 +2154,15 @@
|
||||
"category": "Front Matter"
|
||||
}
|
||||
],
|
||||
"submenus": [{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}],
|
||||
"submenus": [
|
||||
{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"editor/title": [{
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "frontMatter.markup.heading",
|
||||
"group": "navigation@-133",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
@@ -2217,11 +2243,14 @@
|
||||
"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"
|
||||
@@ -2237,7 +2266,8 @@
|
||||
"group": "frontmatter@3"
|
||||
}
|
||||
],
|
||||
"commandPalette": [{
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "frontMatter.init",
|
||||
"when": "frontMatterCanInit"
|
||||
},
|
||||
@@ -2382,7 +2412,8 @@
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
}
|
||||
],
|
||||
"view/title": [{
|
||||
"view/title": [
|
||||
{
|
||||
"command": "frontMatter.chatbot",
|
||||
"group": "navigation@0",
|
||||
"when": "view == frontMatter.explorer"
|
||||
@@ -2414,52 +2445,57 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"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"
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"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.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": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
{
|
||||
"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:*",
|
||||
@@ -2490,7 +2526,7 @@
|
||||
"@actions/core": "^1.10.0",
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "^1.1.0",
|
||||
"@headlessui/react": "1.5.0",
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@heroicons/react": "1.0.4",
|
||||
"@iarna/toml": "2.2.3",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
@@ -2596,4 +2632,4 @@
|
||||
"vsce": {
|
||||
"dependencies": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
"setting.frontMatter.media.defaultSorting.markdownDescription": "Specify the default sorting option for the media dashboard. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.media.defaultsorting)",
|
||||
"setting.frontMatter.media.supportedMimeTypes.markdownDescription": "Specify the mime types to support for the media files. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes)",
|
||||
"setting.frontMatter.panel.freeform.markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform)",
|
||||
"setting.frontMatter.panel.actions.disabled.markdownDescription": "Specify the actions you want to disable in the panel. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled)",
|
||||
"setting.frontMatter.preview.host.markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host)",
|
||||
"setting.frontMatter.preview.pathName.markdownDescription": "Specify the path you want to add after the host and before your slug. This can be used for instance to include the year/month like: `yyyy/MM`. The date will be generated based on the article its date field value. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.pathname)",
|
||||
"setting.frontMatter.site.baseURL.markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.site.baseurl)",
|
||||
@@ -190,6 +191,7 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.numberOptions.properties.max.description": "The maximum value",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.numberOptions.properties.step.description": "The step value",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyLimit.description": "Limit the number of taxonomies to select. Set to 0 to allow unlimited.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.singleValueAsString.description": "Specify if you want to store a single array value as a string instead of an array.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPublishDate.description": "Specify if the field is the publish date field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isModifiedDate.description": "Specify if the field is the modified date field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.dataFileId.description": "Specify the ID of the data file to use for this field",
|
||||
|
||||
@@ -127,7 +127,7 @@ export class Article {
|
||||
* @param article
|
||||
*/
|
||||
public static updateDate(article: ParsedFrontMatter) {
|
||||
article.data = ArticleHelper.updateDates(article.data);
|
||||
article.data = ArticleHelper.updateDates(article);
|
||||
return article;
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ export class Article {
|
||||
}
|
||||
|
||||
let filePrefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
filePrefix = ArticleHelper.getFilePrefix(filePrefix, editor.document.uri.fsPath, contentType);
|
||||
|
||||
const titleField = 'title';
|
||||
@@ -393,7 +393,7 @@ export class Article {
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
const contentType =
|
||||
article && article.data ? ArticleHelper.getContentType(article.data) : DEFAULT_CONTENT_TYPE;
|
||||
article && article.data ? ArticleHelper.getContentType(article) : DEFAULT_CONTENT_TYPE;
|
||||
|
||||
const position = editor.selection.active;
|
||||
const selectionText = editor.document.getText(editor.selection);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { commands } from 'vscode';
|
||||
import { COMMAND_NAME, ExtensionState } from '../constants';
|
||||
import { Extension, Notifications } from '../helpers';
|
||||
import { Extension, Logger, Notifications } from '../helpers';
|
||||
|
||||
export class Cache {
|
||||
public static async registerCommands() {
|
||||
@@ -29,6 +29,8 @@ export class Cache {
|
||||
|
||||
if (showNotification) {
|
||||
Notifications.info('Cache cleared');
|
||||
} else {
|
||||
Logger.info('Cache cleared');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +361,10 @@ export class Folders {
|
||||
*/
|
||||
public static get(): ContentFolder[] {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
|
||||
let folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
|
||||
|
||||
// Filter out folders without a path
|
||||
folders = folders.filter((f) => f.path);
|
||||
|
||||
const contentFolders = folders.map((folder) => {
|
||||
if (!folder.title) {
|
||||
|
||||
@@ -213,7 +213,7 @@ export class Preview {
|
||||
* @returns
|
||||
*/
|
||||
public static async getContentSlug(
|
||||
article: ParsedFrontMatter | null,
|
||||
article: ParsedFrontMatter | null | undefined,
|
||||
filePath?: string
|
||||
): Promise<string | undefined> {
|
||||
if (!filePath) {
|
||||
@@ -230,7 +230,7 @@ export class Preview {
|
||||
|
||||
let contentType: ContentType | undefined = undefined;
|
||||
if (article?.data) {
|
||||
contentType = ArticleHelper.getContentType(article.data);
|
||||
contentType = ArticleHelper.getContentType(article);
|
||||
}
|
||||
|
||||
// Check if there is a pathname defined on content folder level
|
||||
|
||||
@@ -165,11 +165,15 @@ export class Template {
|
||||
newFilePath
|
||||
);
|
||||
|
||||
frontMatter = Article.updateDate(frontMatter);
|
||||
const article = Article.updateDate(frontMatter);
|
||||
|
||||
if (!article) {
|
||||
return;
|
||||
}
|
||||
|
||||
await writeFileAsync(
|
||||
newFilePath,
|
||||
ArticleHelper.stringifyFrontMatter(frontMatter.content, frontMatter.data),
|
||||
ArticleHelper.stringifyFrontMatter(article.content, article.data),
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
|
||||
|
||||
return (
|
||||
<PageLayout folders={pageFolders} totalPages={pageItems.length}>
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto pb-6 px-4">
|
||||
<div className="w-full flex-grow max-w-full mx-auto pb-6 px-4">
|
||||
{loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} />}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
<img
|
||||
src={`${pageData[PREVIEW_IMAGE_FIELD]}`}
|
||||
alt={escapedTitle || ""}
|
||||
className="absolute inset-0 h-full w-full object-cover group-hover:brightness-75"
|
||||
className="absolute inset-0 h-full w-full object-cover object-left-top group-hover:brightness-75"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
@@ -110,7 +110,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
statusHtml ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: statusHtml }} />
|
||||
) : (
|
||||
cardFields?.state && draftField && draftField.name && <Status draft={pageData[draftField.name]} />
|
||||
cardFields?.state && draftField && draftField.name && <Status draft={pageData[draftField.name]} published={pageData.fmPublished} />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
<DateField value={pageData.date} />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
{draftField && draftField.name && <Status draft={pageData[draftField.name]} />}
|
||||
{draftField && draftField.name && <Status draft={pageData[draftField.name]} published={pageData.fmPublished} />}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const List: React.FunctionComponent<IListProps> = ({
|
||||
|
||||
let className = '';
|
||||
if (view === DashboardViewType.Grid) {
|
||||
className = `grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8`;
|
||||
className = `grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4`;
|
||||
} else if (view === DashboardViewType.List) {
|
||||
className = `-mx-4`;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,44 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
[grouping]
|
||||
);
|
||||
|
||||
const { groupKeys, groupedPages } = useMemo(() => {
|
||||
if (grouping === GroupOption.none) {
|
||||
return { groupKeys: [], groupedPages: {} };
|
||||
}
|
||||
|
||||
let groupedPages = groupBy(pages, grouping === GroupOption.Year ? 'fmYear' : 'fmDraft');
|
||||
let groupKeys = Object.keys(groupedPages);
|
||||
|
||||
if (grouping === GroupOption.Year) {
|
||||
groupKeys = groupKeys.sort((a, b) => {
|
||||
return parseInt(b) - parseInt(a);
|
||||
});
|
||||
} else if (grouping === GroupOption.Draft && settings?.draftField?.type !== 'choice') {
|
||||
const isInverted = settings?.draftField?.invert;
|
||||
const allPublished: Page[] = groupedPages['Published'] || [];
|
||||
const allDrafts: Page[] = groupedPages['Draft'] || [];
|
||||
|
||||
if (allPublished.length > 0) {
|
||||
const drafts = !isInverted ? allDrafts : allPublished;
|
||||
const published = (!isInverted ? allPublished : allDrafts).filter((page) => !page.fmPublished || page.fmPublished <= Date.now());
|
||||
const scheduled = (!isInverted ? allPublished : allDrafts).filter((page) => page.fmPublished && page.fmPublished > Date.now());
|
||||
|
||||
delete groupedPages["Published"];
|
||||
delete groupedPages["Draft"];
|
||||
|
||||
groupKeys = ['Scheduled', ...groupKeys];
|
||||
groupedPages = {
|
||||
"Scheduled": scheduled,
|
||||
"Published": published,
|
||||
"Draft": drafts,
|
||||
...groupedPages,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { groupKeys, groupedPages };
|
||||
}, [pages, grouping, settings?.draftField]);
|
||||
|
||||
React.useEffect(() => {
|
||||
messageHandler.request<string[]>(DashboardMessage.getPinnedItems).then((items) => {
|
||||
setIsReady(true);
|
||||
@@ -99,40 +137,33 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
}
|
||||
|
||||
if (grouping !== GroupOption.none) {
|
||||
const groupedPages = groupBy(pages, grouping === GroupOption.Year ? 'fmYear' : 'fmDraft');
|
||||
let groupKeys = Object.keys(groupedPages);
|
||||
|
||||
if (grouping === GroupOption.Year) {
|
||||
groupKeys = groupKeys.sort((a, b) => {
|
||||
return parseInt(b) - parseInt(a);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{groupKeys.map((groupId, idx) => (
|
||||
<Disclosure key={groupId} as={`div`} className={`w-full`} defaultOpen>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className={`mb-4 ${idx !== 0 ? 'mt-8' : ''}`}>
|
||||
<h2 className={`text-2xl font-bold flex items-center`}>
|
||||
<ChevronRightIcon
|
||||
className={`w-8 h-8 mr-1 ${open ? 'transform rotate-90' : ''}`}
|
||||
/>
|
||||
{groupName(groupId, groupedPages)}
|
||||
</h2>
|
||||
</Disclosure.Button>
|
||||
groupedPages[groupId].length > 0 && (
|
||||
<Disclosure key={groupId} as={`div`} className={`w-full`} defaultOpen>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className={`mb-4 ${idx !== 0 ? 'mt-8' : ''}`}>
|
||||
<h2 className={`text-2xl font-bold flex items-center`}>
|
||||
<ChevronRightIcon
|
||||
className={`w-8 h-8 mr-1 ${open ? 'transform rotate-90' : ''}`}
|
||||
/>
|
||||
{groupName(groupId, groupedPages)}
|
||||
</h2>
|
||||
</Disclosure.Button>
|
||||
|
||||
<Disclosure.Panel>
|
||||
<List>
|
||||
{groupedPages[groupId].map((page: Page) => (
|
||||
<Item key={`${page.slug}-${idx}`} {...page} />
|
||||
))}
|
||||
</List>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
<Disclosure.Panel>
|
||||
<List>
|
||||
{groupedPages[groupId].map((page: Page) => (
|
||||
<Item key={`${page.slug}-${idx}`} {...page} />
|
||||
))}
|
||||
</List>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -7,15 +7,18 @@ import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface IStatusProps {
|
||||
draft: boolean | string;
|
||||
published: number | null | undefined;
|
||||
}
|
||||
|
||||
export const Status: React.FunctionComponent<IStatusProps> = ({
|
||||
draft
|
||||
draft,
|
||||
published
|
||||
}: React.PropsWithChildren<IStatusProps>) => {
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const tabInfo = useRecoilValue(TabInfoAtom);
|
||||
|
||||
const draftField = useMemo(() => settings?.draftField, [settings]);
|
||||
const isFuture = useMemo(() => published ? published > Date.now() : false, [published]);
|
||||
|
||||
const draftValue = useMemo(() => {
|
||||
if (draftField && draftField.type === 'choice') {
|
||||
@@ -31,7 +34,7 @@ export const Status: React.FunctionComponent<IStatusProps> = ({
|
||||
if (draftValue) {
|
||||
return (
|
||||
<span
|
||||
className={`inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs text-[var(--vscode-badge-foreground)] bg-[var(--vscode-badge-background)]`}
|
||||
className={`inline-block px-1 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-[0.7rem] text-[var(--vscode-badge-foreground)] bg-[var(--vscode-badge-background)]`}
|
||||
>
|
||||
{draftValue}
|
||||
</span>
|
||||
@@ -48,16 +51,21 @@ export const Status: React.FunctionComponent<IStatusProps> = ({
|
||||
return (
|
||||
<span
|
||||
className={`draft__status
|
||||
inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs
|
||||
inline-block px-1 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-[0.7rem]
|
||||
${draftValue ?
|
||||
'bg-[var(--vscode-statusBarItem-errorBackground)] text-[var(--vscode-statusBarItem-errorForeground)]' :
|
||||
'bg-[var(--vscode-badge-background)] text-[var(--vscode-badge-foreground)]'
|
||||
isFuture ?
|
||||
'bg-[var(--vscode-statusBarItem-warningBackground)] text-[var(--vscode-statusBarItem-warningForeground)]' :
|
||||
'bg-[var(--vscode-badge-background)] text-[var(--vscode-badge-foreground)]'
|
||||
}`}
|
||||
>
|
||||
{
|
||||
draftValue ?
|
||||
l10n.t(LocalizationKey.dashboardContentsStatusDraft) :
|
||||
l10n.t(LocalizationKey.dashboardContentsStatusPublished)
|
||||
l10n.t(LocalizationKey.dashboardContentsStatusDraft) : (
|
||||
isFuture ?
|
||||
l10n.t(LocalizationKey.dashboardContentsStatusScheduled) :
|
||||
l10n.t(LocalizationKey.dashboardContentsStatusPublished)
|
||||
)
|
||||
}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,6 @@ import { JSONSchemaBridge } from 'uniforms-bridge-json-schema';
|
||||
import { AutoFields, AutoForm, ErrorsField } from '../../../components/uniforms-frontmatter';
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import { DataFormControls } from './DataFormControls';
|
||||
import useThemeColors from '../../hooks/useThemeColors';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
@@ -24,7 +23,6 @@ export const DataForm: React.FunctionComponent<IDataFormProps> = ({
|
||||
}: React.PropsWithChildren<IDataFormProps>) => {
|
||||
const [bridge, setBridge] = useState<JSONSchemaBridge | null>(null);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const { getColors } = useThemeColors();
|
||||
|
||||
const ajv = new Ajv({
|
||||
allErrors: true,
|
||||
@@ -81,11 +79,11 @@ export const DataForm: React.FunctionComponent<IDataFormProps> = ({
|
||||
<ErrorBoundary>
|
||||
<div className="autoform">
|
||||
{model ? (
|
||||
<h2 className={getColors(`text - gray - 500 dark: text - whisper - 900`, `text - [var(--frontmatter - secondary - text)]`)}>
|
||||
<h2 className={`text-[var(--frontmatter-secondary-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataFormModify)}
|
||||
</h2>
|
||||
) : (
|
||||
<h2 className={getColors(`text - gray - 500 dark: text - whisper - 900`, `text - [var(--frontmatter - secondary - text)]`)}>
|
||||
<h2 className={`text-[var(--frontmatter-secondary-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataFormAdd)}
|
||||
</h2>
|
||||
)}
|
||||
|
||||
@@ -39,7 +39,7 @@ export const FilterInput: React.FunctionComponent<IFilterInputProps> = ({
|
||||
name="search"
|
||||
className={`block w-full py-2 pl-10 pr-3 sm:text-sm appearance-none disabled:opacity-50 rounded ${getColors(
|
||||
'bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none',
|
||||
'bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-editorWidget-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent'
|
||||
'bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-input-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent'
|
||||
)
|
||||
}`}
|
||||
placeholder={placeholder || l10n.t(LocalizationKey.commonSearch)}
|
||||
|
||||
@@ -25,7 +25,6 @@ import { Pagination } from './Pagination';
|
||||
import { GroupOption } from '../../constants/GroupOption';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
import { PaginationStatus } from './PaginationStatus';
|
||||
import useThemeColors from '../../hooks/useThemeColors';
|
||||
import { Navigation } from './Navigation';
|
||||
import { ProjectSwitcher } from './ProjectSwitcher';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
@@ -55,7 +54,6 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination);
|
||||
const { getColors } = useThemeColors();
|
||||
|
||||
const createContent = () => {
|
||||
Messenger.send(DashboardMessage.createContent);
|
||||
@@ -144,16 +142,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
}, [location.search]);
|
||||
|
||||
return (
|
||||
<div className={`w-full sticky top-0 z-20 ${getColors(
|
||||
`bg-gray-100 dark:bg-vulcan-500`,
|
||||
`bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]`
|
||||
)
|
||||
}`}>
|
||||
<div className={`mb-0 border-b flex justify-between ${getColors(
|
||||
`bg-gray-100 dark:bg-vulcan-500 border-gray-200 dark:border-vulcan-300`,
|
||||
`bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--vscode-editorWidget-border)]`
|
||||
)
|
||||
}`}>
|
||||
<div className={`w-full sticky top-0 z-20 bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]`}>
|
||||
<div className={`mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<Tabs onNavigate={updateView} />
|
||||
|
||||
<div className='flex'>
|
||||
@@ -180,8 +170,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`px-4 flex flex-row items-center border-b justify-between ${getColors(`border-gray-200 dark:border-vulcan-100`, `border-[var(--vscode-editorWidget-border)]`)
|
||||
}`}>
|
||||
<div className={`px-4 flex flex-row items-center border-b justify-between border-[var(--frontmatter-border)]`}>
|
||||
<div>
|
||||
<Navigation totalPages={totalPages || 0} />
|
||||
</div>
|
||||
@@ -192,8 +181,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end border-b space-x-4 lg:space-x-6 xl:space-x-8 ${getColors(`bg-gray-200 border-gray-300 dark:bg-vulcan-400 dark:border-vulcan-100`, `bg-[var(--vscode-panel-background)] border-[var(--vscode-editorWidget-border)]`)
|
||||
}`}
|
||||
className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end border-b space-x-4 lg:space-x-6 xl:space-x-8 bg-[var(--vscode-panel-background)] border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<ClearFilters />
|
||||
|
||||
@@ -222,8 +210,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
(totalPages || 0) > pageSetNr &&
|
||||
(!grouping || grouping === GroupOption.none) && (
|
||||
<div
|
||||
className={`px-4 flex justify-between py-2 border-b ${getColors(`border-gray-300 dark:border-vulcan-100`, `border-[var(--vscode-editorWidget-border)]`)
|
||||
}`}
|
||||
className={`px-4 flex justify-between py-2 border-b border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<PaginationStatus totalPages={totalPages || 0} />
|
||||
|
||||
|
||||
@@ -51,6 +51,11 @@ export const Navigation: React.FunctionComponent<INavigationProps> = ({
|
||||
|
||||
if (settings?.draftField?.type === 'boolean' && tabInfo && Object.keys(tabInfo).length > 1) {
|
||||
crntTabs.push({ name: l10n.t(LocalizationKey.dashboardHeaderNavigationPublished), id: Tab.Published });
|
||||
|
||||
if (tabInfo.scheduled) {
|
||||
crntTabs.push({ name: l10n.t(LocalizationKey.dashboardHeaderNavigationScheduled), id: Tab.Scheduled });
|
||||
}
|
||||
|
||||
crntTabs.push({ name: l10n.t(LocalizationKey.dashboardHeaderNavigationDraft), id: Tab.Draft });
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({
|
||||
name="search"
|
||||
className={`block w-full py-2 pl-10 pr-3 sm:text-sm appearance-none disabled:opacity-50 rounded ${getColors(
|
||||
'bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none',
|
||||
'bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-editorWidget-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent'
|
||||
'bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-input-border, --vscode-editorWidget-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent'
|
||||
)
|
||||
}`}
|
||||
placeholder={placeholder || l10n.t(LocalizationKey.commonSearch)}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const PageLayout: React.FunctionComponent<IPageLayoutProps> = ({
|
||||
<div
|
||||
className={
|
||||
contentClass ||
|
||||
'w-full flex justify-between flex-col flex-grow max-w-7xl mx-auto pt-6 px-4'
|
||||
'w-full flex justify-between flex-col flex-grow mx-auto pt-6 px-4 max-w-full xl:max-w-[90%]'
|
||||
}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -14,12 +14,11 @@ export const DetailsInput: React.FunctionComponent<IDetailsInputProps> = ({ valu
|
||||
return (
|
||||
<textarea
|
||||
rows={3}
|
||||
className={`py-1 px-2 sm:text-sm border w-full ${
|
||||
getColors(
|
||||
'bg-white dark:bg-vulcan-300 border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none',
|
||||
'bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-editorWidget-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent'
|
||||
)
|
||||
}`}
|
||||
className={`py-1 px-2 sm:text-sm border w-full ${getColors(
|
||||
'bg-white dark:bg-vulcan-300 border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none',
|
||||
'bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-input-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent'
|
||||
)
|
||||
}`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
@@ -28,12 +27,11 @@ export const DetailsInput: React.FunctionComponent<IDetailsInputProps> = ({ valu
|
||||
|
||||
return (
|
||||
<input
|
||||
className={`py-1 px-2 sm:text-sm border w-full ${
|
||||
getColors(
|
||||
'bg-white dark:bg-vulcan-300 border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none',
|
||||
'bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-editorWidget-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent'
|
||||
)
|
||||
}`}
|
||||
className={`py-1 px-2 sm:text-sm border w-full ${getColors(
|
||||
'bg-white dark:bg-vulcan-300 border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none',
|
||||
'bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-input-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent'
|
||||
)
|
||||
}`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
@@ -8,12 +8,12 @@ export const List: React.FunctionComponent<IListProps> = ({
|
||||
gap,
|
||||
children
|
||||
}: React.PropsWithChildren<IListProps>) => {
|
||||
const gapClass = gap !== undefined ? `gap-y-${gap}` : `gap-y-8`;
|
||||
const gapClass = gap !== undefined ? `gap-y-${gap}` : ``;
|
||||
|
||||
return (
|
||||
<ul
|
||||
role="list"
|
||||
className={`grid grid-cols-2 gap-x-4 ${gapClass} sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8`}
|
||||
className={`grid gap-4 ${gapClass} grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5`}
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
|
||||
@@ -105,7 +105,7 @@ export const NewForm: React.FunctionComponent<INewFormProps> = ({
|
||||
onChange={(e) => onMediaSnippetUpdate(e.currentTarget.checked)}
|
||||
className={`h-4 w-4 rounded ${getColors(
|
||||
`focus:ring-teal-500 text-teal-600 border-gray-300 dark:border-vulcan-50`,
|
||||
`focus:ring-[var(--frontmatter-button-background)] text-[var(--frontmatter-button-background)] border-[var(--vscode-editorWidget-border)]`
|
||||
`focus:ring-[var(--frontmatter-button-background)] text-[var(--frontmatter-button-background)] border-[var(--frontmatter-border)]`
|
||||
)
|
||||
}`}
|
||||
/>
|
||||
|
||||
@@ -37,7 +37,7 @@ export const FilterInput: React.FunctionComponent<IFilterInputProps> = ({
|
||||
<input
|
||||
type="text"
|
||||
name="filter"
|
||||
className={`block w-full py-2 pl-10 pr-3 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-editorWidget-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent`}
|
||||
className={`block w-full py-2 pl-10 pr-3 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-[var(--vscode-input-border)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-[var(--vscode-focusBorder)] focus:outline-1 focus:outline-offset-0 focus:shadow-none focus:border-transparent`}
|
||||
placeholder={placeholder || ""}
|
||||
value={value}
|
||||
onChange={(e) => onChange && onChange(e.target.value)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export enum Tab {
|
||||
All = 'all',
|
||||
Published = 'published',
|
||||
Draft = 'draft'
|
||||
Draft = 'draft',
|
||||
Scheduled = 'scheduled'
|
||||
}
|
||||
|
||||
@@ -152,23 +152,32 @@ export default function usePages(pages: Page[]) {
|
||||
|
||||
const usesDraft = crntPages.some(x => typeof x[draftFieldName] !== 'undefined');
|
||||
if (usesDraft) {
|
||||
const drafts = crntPages.filter(
|
||||
const allDrafts = crntPages.filter(
|
||||
(page) => page[draftFieldName] == true || page[draftFieldName] === 'true'
|
||||
);
|
||||
const published = crntPages.filter(
|
||||
const allPublished = crntPages.filter(
|
||||
(page) =>
|
||||
page[draftFieldName] == false ||
|
||||
page[draftFieldName] === 'false' ||
|
||||
typeof page[draftFieldName] === 'undefined'
|
||||
);
|
||||
|
||||
draftTypes[Tab.Draft] = draftField?.invert ? published.length : drafts.length;
|
||||
draftTypes[Tab.Published] = draftField?.invert ? drafts.length : published.length;
|
||||
// If the invert is set, the drafts become published and vice versa
|
||||
const isInverted = draftField?.invert;
|
||||
const drafts = !isInverted ? allDrafts : allPublished;
|
||||
const published = (!isInverted ? allPublished : allDrafts).filter((page) => !page.fmPublished || page.fmPublished <= Date.now());
|
||||
const scheduled = (!isInverted ? allPublished : allDrafts).filter((page) => page.fmPublished && page.fmPublished > Date.now());
|
||||
|
||||
draftTypes[Tab.Draft] = drafts.length;
|
||||
draftTypes[Tab.Published] = published.length;
|
||||
draftTypes[Tab.Scheduled] = scheduled.length;
|
||||
|
||||
if (tab === Tab.Published) {
|
||||
crntPages = draftField?.invert ? drafts : published;
|
||||
crntPages = published;
|
||||
} else if (tab === Tab.Draft) {
|
||||
crntPages = draftField?.invert ? published : drafts;
|
||||
crntPages = drafts;
|
||||
} else if (tab === Tab.Scheduled) {
|
||||
crntPages = scheduled;
|
||||
} else {
|
||||
crntPages = crntPages;
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ if (elm) {
|
||||
});
|
||||
|
||||
Sentry.setTag("type", "dashboard");
|
||||
if (document.body.getAttribute(`data - vscode - theme - id`)) {
|
||||
if (document.body.getAttribute(`data-vscode-theme-id`)) {
|
||||
Sentry.setTag("theme", document.body.getAttribute(`data-vscode-theme-id`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@
|
||||
select {
|
||||
color: var(--vscode-input-foreground);
|
||||
background-color: var(--vscode-input-background);
|
||||
border: 1px solid var(--vscode-editorWidget-border);
|
||||
border: 1px solid var(--vscode-panel-border);
|
||||
font-size: var(--vscode-font-size);
|
||||
@apply rounded;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
Chatbot
|
||||
} from './commands';
|
||||
import { join } from 'path';
|
||||
import { Terminal } from './services';
|
||||
|
||||
let pageUpdateDebouncer: { (fnc: any, time: number): void };
|
||||
let editDebounce: { (fnc: any, time: number): void };
|
||||
@@ -58,6 +59,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure the terminal windows are closed
|
||||
Terminal.closeLocalServerTerminal();
|
||||
|
||||
if (!extension.checkIfExtensionCanRun()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -60,9 +60,19 @@ export class ArticleHelper {
|
||||
*
|
||||
* @param document The document to parse.
|
||||
*/
|
||||
public static getFrontMatterFromDocument(document: vscode.TextDocument) {
|
||||
public static getFrontMatterFromDocument(
|
||||
document: vscode.TextDocument
|
||||
): ParsedFrontMatter | undefined {
|
||||
const fileContents = document.getText();
|
||||
return ArticleHelper.parseFile(fileContents, document.fileName);
|
||||
const article = ArticleHelper.parseFile(fileContents, document.fileName);
|
||||
if (!article) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...article,
|
||||
path: document.uri.fsPath
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,7 +89,10 @@ export class ArticleHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
return article;
|
||||
return {
|
||||
...article,
|
||||
path: editor.document.uri.fsPath
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +101,15 @@ export class ArticleHelper {
|
||||
*/
|
||||
public static async getFrontMatterByPath(filePath: string) {
|
||||
const file = await readFileAsync(filePath, { encoding: 'utf-8' });
|
||||
return ArticleHelper.parseFile(file, filePath);
|
||||
const article = ArticleHelper.parseFile(file, filePath);
|
||||
if (!article) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...article,
|
||||
path: filePath
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,7 +261,7 @@ export class ArticleHelper {
|
||||
/**
|
||||
* Get date from front matter
|
||||
*/
|
||||
public static getDate(article: ParsedFrontMatter | null) {
|
||||
public static getDate(article: ParsedFrontMatter | null | undefined) {
|
||||
if (!article || !article.data) {
|
||||
return;
|
||||
}
|
||||
@@ -270,7 +291,7 @@ export class ArticleHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
const articleCt = ArticleHelper.getContentType(article.data);
|
||||
const articleCt = ArticleHelper.getContentType(article);
|
||||
const pubDateField = articleCt.fields.find((f) => f.isPublishDate);
|
||||
|
||||
return (
|
||||
@@ -290,7 +311,7 @@ export class ArticleHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
const articleCt = ArticleHelper.getContentType(article.data);
|
||||
const articleCt = ArticleHelper.getContentType(article);
|
||||
const modDateField = articleCt.fields.find((f) => f.isModifiedDate);
|
||||
|
||||
return (
|
||||
@@ -312,16 +333,38 @@ export class ArticleHelper {
|
||||
* Retrieve the content type of the current file
|
||||
* @param updatedMetadata
|
||||
*/
|
||||
public static getContentType(metadata: { [field: string]: string }): IContentType {
|
||||
public static getContentType(article: ParsedFrontMatter): IContentType {
|
||||
const contentTypes = ArticleHelper.getContentTypes();
|
||||
|
||||
if (!contentTypes || !metadata) {
|
||||
if (!contentTypes || !article.data) {
|
||||
return DEFAULT_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
let contentType = contentTypes.find(
|
||||
(ct) => ct.name === (metadata.type || DEFAULT_CONTENT_TYPE_NAME)
|
||||
);
|
||||
let contentType: IContentType | undefined = undefined;
|
||||
|
||||
// Get content type by type name in the front matter
|
||||
if (article.data.type) {
|
||||
contentType = contentTypes.find((ct) => ct.name === article.data.type);
|
||||
} else if (!contentType && article.path) {
|
||||
// Get the content type by the folder name
|
||||
let folders = Folders.get();
|
||||
let parsedPath = parseWinPath(article.path);
|
||||
let pageFolderMatches = folders.filter(
|
||||
(folder) => parsedPath && folder.path && parsedPath.includes(folder.path)
|
||||
);
|
||||
|
||||
// Sort by longest path
|
||||
pageFolderMatches = pageFolderMatches.sort((a, b) => b.path.length - a.path.length);
|
||||
if (
|
||||
pageFolderMatches.length > 0 &&
|
||||
pageFolderMatches[0].contentTypes &&
|
||||
pageFolderMatches[0].contentTypes.length === 1
|
||||
) {
|
||||
const contentTypeName = pageFolderMatches[0].contentTypes[0];
|
||||
contentType = contentTypes.find((ct) => ct.name === contentTypeName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!contentType) {
|
||||
contentType = contentTypes.find((ct) => ct.name === DEFAULT_CONTENT_TYPE_NAME);
|
||||
}
|
||||
@@ -343,17 +386,17 @@ export class ArticleHelper {
|
||||
* Update all dates in the metadata
|
||||
* @param metadata
|
||||
*/
|
||||
public static updateDates(metadata: { [field: string]: string }) {
|
||||
const contentType = ArticleHelper.getContentType(metadata);
|
||||
public static updateDates(article: ParsedFrontMatter) {
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
const dateFields = contentType.fields.filter((field) => field.type === 'datetime');
|
||||
|
||||
for (const dateField of dateFields) {
|
||||
if (typeof metadata[dateField.name] !== 'undefined') {
|
||||
metadata[dateField.name] = Article.formatDate(new Date(), dateField.dateFormat);
|
||||
if (typeof article?.data[dateField.name] !== 'undefined') {
|
||||
article.data[dateField.name] = Article.formatDate(new Date(), dateField.dateFormat);
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
return article.data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,18 +49,18 @@ export class ContentType {
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
public static getDraftStatus(data: { [field: string]: any }) {
|
||||
const contentType = ArticleHelper.getContentType(data);
|
||||
public static getDraftStatus(article: ParsedFrontMatter) {
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
const draftSetting = ContentType.getDraftField();
|
||||
|
||||
const draftField = contentType.fields.find((f) => f.type === 'draft');
|
||||
|
||||
let fieldValue = null;
|
||||
|
||||
if (draftField) {
|
||||
fieldValue = data[draftField.name];
|
||||
} else if (draftSetting && data && data[draftSetting.name]) {
|
||||
fieldValue = data[draftSetting.name];
|
||||
if (draftField && article?.data) {
|
||||
fieldValue = article?.data[draftField.name];
|
||||
} else if (draftSetting && article?.data && article?.data[draftSetting.name]) {
|
||||
fieldValue = article?.data[draftSetting.name];
|
||||
}
|
||||
|
||||
if (draftSetting && fieldValue !== null) {
|
||||
@@ -282,7 +282,7 @@ export class ContentType {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = ArticleHelper.getContentType(content?.data);
|
||||
const contentType = ArticleHelper.getContentType(content);
|
||||
const updatedFields = ContentType.generateFields(content.data, contentType.fields);
|
||||
|
||||
const contentTypes = ContentType.getAll() || [];
|
||||
@@ -492,7 +492,7 @@ export class ContentType {
|
||||
* Find the required fields
|
||||
*/
|
||||
public static findEmptyRequiredFields(article: ParsedFrontMatter): Field[][] | undefined {
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
if (!contentType) {
|
||||
return;
|
||||
}
|
||||
@@ -793,7 +793,7 @@ export class ContentType {
|
||||
}
|
||||
|
||||
let templatePath = contentType.template;
|
||||
let templateData: ParsedFrontMatter | null = null;
|
||||
let templateData: ParsedFrontMatter | null | undefined = null;
|
||||
if (templatePath) {
|
||||
templatePath = Folders.getAbsFilePath(templatePath);
|
||||
templateData = await ArticleHelper.getFrontMatterByPath(templatePath);
|
||||
|
||||
@@ -71,7 +71,7 @@ export class CustomScript {
|
||||
path: string | null = null
|
||||
): Promise<void> {
|
||||
let articlePath: string | null = path;
|
||||
let article: ParsedFrontMatter | null = null;
|
||||
let article: ParsedFrontMatter | null | undefined = null;
|
||||
|
||||
if (!path) {
|
||||
const editor = window.activeTextEditor;
|
||||
@@ -214,7 +214,7 @@ export class CustomScript {
|
||||
*/
|
||||
private static async runScript(
|
||||
wsPath: string,
|
||||
article: ParsedFrontMatter | null,
|
||||
article: ParsedFrontMatter | null | undefined,
|
||||
contentPath: string,
|
||||
script: ICustomScript
|
||||
): Promise<string | null> {
|
||||
@@ -223,7 +223,7 @@ export class CustomScript {
|
||||
if (os.type() === 'Windows_NT') {
|
||||
const jsonData = JSON.stringify(article?.data);
|
||||
|
||||
if (script.command && script.command.toLowerCase() === "powershell") {
|
||||
if (script.command && script.command.toLowerCase() === 'powershell') {
|
||||
articleData = `'${jsonData.replace(/"/g, `\\"`)}'`;
|
||||
} else {
|
||||
articleData = `"${jsonData.replace(/"/g, `\\"`)}"`;
|
||||
@@ -323,55 +323,97 @@ export class CustomScript {
|
||||
wsPath: string,
|
||||
args: string
|
||||
): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const osType = os.type();
|
||||
const osType = os.type();
|
||||
|
||||
// Check the command to use
|
||||
let command = script.nodeBin || 'node';
|
||||
if (script.command && script.command !== CommandType.Node) {
|
||||
command = script.command;
|
||||
// Check the command to use
|
||||
let command = script.nodeBin || 'node';
|
||||
if (script.command && script.command !== CommandType.Node) {
|
||||
command = script.command;
|
||||
}
|
||||
|
||||
let scriptPath = join(wsPath, script.script);
|
||||
if (script.script.includes(WORKSPACE_PLACEHOLDER)) {
|
||||
scriptPath = Folders.getAbsFilePath(script.script);
|
||||
}
|
||||
|
||||
// Check if there is an environments overwrite required
|
||||
if (script.environments) {
|
||||
let crntType: EnvironmentType | null = null;
|
||||
if (osType === 'Windows_NT') {
|
||||
crntType = 'windows';
|
||||
} else if (osType === 'Darwin') {
|
||||
crntType = 'macos';
|
||||
} else {
|
||||
crntType = 'linux';
|
||||
}
|
||||
|
||||
let scriptPath = join(wsPath, script.script);
|
||||
if (script.script.includes(WORKSPACE_PLACEHOLDER)) {
|
||||
scriptPath = Folders.getAbsFilePath(script.script);
|
||||
}
|
||||
|
||||
// Check if there is an environments overwrite required
|
||||
if (script.environments) {
|
||||
let crntType: EnvironmentType | null = null;
|
||||
if (osType === 'Windows_NT') {
|
||||
crntType = 'windows';
|
||||
} else if (osType === 'Darwin') {
|
||||
crntType = 'macos';
|
||||
} else {
|
||||
crntType = 'linux';
|
||||
}
|
||||
|
||||
const environment = script.environments.find((e) => e.type === crntType);
|
||||
if (environment && environment.script && environment.command) {
|
||||
if (await CustomScript.validateCommand(environment.command)) {
|
||||
command = environment.command;
|
||||
scriptPath = join(wsPath, environment.script);
|
||||
if (environment.script.includes(WORKSPACE_PLACEHOLDER)) {
|
||||
scriptPath = Folders.getAbsFilePath(environment.script);
|
||||
}
|
||||
const environment = script.environments.find((e) => e.type === crntType);
|
||||
if (environment && environment.script && environment.command) {
|
||||
if (await CustomScript.validateCommand(environment.command)) {
|
||||
command = environment.command;
|
||||
scriptPath = join(wsPath, environment.script);
|
||||
if (environment.script.includes(WORKSPACE_PLACEHOLDER)) {
|
||||
scriptPath = Folders.getAbsFilePath(environment.script);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(await existsAsync(scriptPath))) {
|
||||
reject(new Error(`Script not found: ${scriptPath}`));
|
||||
return;
|
||||
if (!(await existsAsync(scriptPath))) {
|
||||
throw new Error(`Script not found: ${scriptPath}`);
|
||||
}
|
||||
|
||||
if (osType === 'Windows_NT' && command.toLowerCase() === 'powershell') {
|
||||
command = `${command} -File`;
|
||||
}
|
||||
|
||||
const fullScript = `${command} "${scriptPath}" ${args}`;
|
||||
Logger.info(`Executing: ${fullScript}`);
|
||||
|
||||
const output: string = await CustomScript.executeScriptAsync(fullScript, wsPath);
|
||||
|
||||
try {
|
||||
const data = JSON.parse(output);
|
||||
if (data.questions) {
|
||||
const answers: string[] = [];
|
||||
|
||||
for (const question of data.questions) {
|
||||
if (question.name && question.message) {
|
||||
const answer = await window.showInputBox({
|
||||
prompt: question.message,
|
||||
value: question.default || '',
|
||||
ignoreFocusOut: true,
|
||||
title: question.message
|
||||
});
|
||||
|
||||
if (answer) {
|
||||
answers.push(`${question.name}="${answer}"`);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (answers.length > 0) {
|
||||
const newScript = `${fullScript} ${answers.join(' ')}`;
|
||||
return await CustomScript.executeScriptAsync(newScript, wsPath);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Not a JSON
|
||||
}
|
||||
|
||||
if (osType === 'Windows_NT' && command.toLowerCase() === "powershell") {
|
||||
command = `${command} -File`;
|
||||
}
|
||||
|
||||
const fullScript = `${command} "${scriptPath}" ${args}`;
|
||||
Logger.info(`Executing: ${fullScript}`);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute script async
|
||||
* @param fullScript
|
||||
* @param wsPath
|
||||
* @returns
|
||||
*/
|
||||
private static async executeScriptAsync(fullScript: string, wsPath: string): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
exec(fullScript, { cwd: wsPath }, (error, stdout) => {
|
||||
if (error) {
|
||||
reject(error.message);
|
||||
|
||||
@@ -32,6 +32,7 @@ import { ContentFolder, Snippet, TaxonomyType } from '../models';
|
||||
import { Notifications } from './Notifications';
|
||||
import { Settings } from './SettingsHelper';
|
||||
import { TaxonomyHelper } from './TaxonomyHelper';
|
||||
import { Cache } from '../commands/Cache';
|
||||
|
||||
export class Extension {
|
||||
private static instance: Extension;
|
||||
@@ -104,6 +105,9 @@ export class Extension {
|
||||
});
|
||||
|
||||
this.setVersion(installedVersion);
|
||||
|
||||
// Reset the cache
|
||||
Cache.clear(false);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -380,7 +380,7 @@ export class MediaHelpers {
|
||||
|
||||
const article = editor ? ArticleHelper.getFrontMatter(editor) : null;
|
||||
const articleCt =
|
||||
article && article.data ? ArticleHelper.getContentType(article.data) : DEFAULT_CONTENT_TYPE;
|
||||
article && article.data ? ArticleHelper.getContentType(article) : DEFAULT_CONTENT_TYPE;
|
||||
|
||||
const absImgPath = join(parseWinPath(wsFolder?.fsPath || ''), relPath);
|
||||
const fileDir = parseWinPath(dirname(filePath));
|
||||
|
||||
@@ -305,7 +305,7 @@ export class TaxonomyHelper {
|
||||
if (mdFile) {
|
||||
try {
|
||||
const article = FrontMatterParser.fromFile(mdFile);
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
|
||||
let fieldNames: string[] = this.getFieldsHierarchy(taxonomyType, contentType);
|
||||
|
||||
@@ -415,7 +415,7 @@ export class TaxonomyHelper {
|
||||
if (mdFile) {
|
||||
try {
|
||||
const article = FrontMatterParser.fromFile(mdFile);
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
|
||||
let oldFieldNames: string[] = this.getFieldsHierarchy(oldType, contentType);
|
||||
let newFieldNames: string[] = this.getFieldsHierarchy(newType, contentType, true);
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function useContentType(
|
||||
|
||||
// Get the content type by the folder name
|
||||
const pageFolders = settings.contentFolders;
|
||||
let pageFolderMatches = pageFolders.filter((folder) => metadata.filePath.includes(folder.path));
|
||||
let pageFolderMatches = pageFolders.filter((folder) => metadata.filePath && folder.path && metadata.filePath.includes(folder.path));
|
||||
|
||||
// Sort by longest path
|
||||
pageFolderMatches = pageFolderMatches.sort((a, b) => b.path.length - a.path.length);
|
||||
|
||||
@@ -119,7 +119,7 @@ export class PagesListener extends BaseListener {
|
||||
if (!article) {
|
||||
return;
|
||||
}
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
|
||||
Logger.info(`Deleting file: ${path}`);
|
||||
|
||||
@@ -240,7 +240,10 @@ export class PagesListener extends BaseListener {
|
||||
* @param pages
|
||||
*/
|
||||
private static async createSearchIndex(pages: Page[]) {
|
||||
const pagesIndex = Fuse.createIndex(['title', 'slug', 'description', 'fmBody', 'type'], pages);
|
||||
const pagesIndex = Fuse.createIndex(
|
||||
['title', 'slug', 'description', 'fmBody', 'type', 'fmContentType'],
|
||||
pages
|
||||
);
|
||||
await Extension.getInstance().setState(
|
||||
ExtensionState.Dashboard.Pages.Index,
|
||||
pagesIndex.toJSON(),
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Folders } from '../../commands/Folders';
|
||||
import { Command } from '../../panelWebView/Command';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { authentication, commands, ThemeIcon, window } from 'vscode';
|
||||
import { authentication, commands, window } from 'vscode';
|
||||
import { ArticleHelper, ContentType, Extension, Logger, Settings } from '../../helpers';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
@@ -23,12 +23,12 @@ import { encodeEmoji } from '../../utils';
|
||||
import { PanelProvider } from '../../panelWebView/PanelProvider';
|
||||
import { MessageHandlerData } from '@estruyf/vscode';
|
||||
import { SponsorAi } from '../../services/SponsorAI';
|
||||
import { Terminal } from '../../services';
|
||||
|
||||
const FILE_LIMIT = 10;
|
||||
|
||||
export class DataListener extends BaseListener {
|
||||
private static lastMetadataUpdate: any = {};
|
||||
private static readonly terminalName: string = 'Local server';
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
@@ -254,7 +254,7 @@ export class DataListener extends BaseListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
|
||||
if (!value && field !== titleField && contentType.clearEmpty) {
|
||||
value = undefined;
|
||||
@@ -374,7 +374,7 @@ export class DataListener extends BaseListener {
|
||||
) {
|
||||
let parentObj = data;
|
||||
let allParents = Object.assign([], parents);
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
let selectedIndexes: number[] = [];
|
||||
if (blockData?.selectedIndex) {
|
||||
if (typeof blockData.selectedIndex === 'string') {
|
||||
@@ -509,27 +509,7 @@ export class DataListener extends BaseListener {
|
||||
*/
|
||||
private static openTerminalWithCommand(command: string) {
|
||||
if (command) {
|
||||
let localServerTerminal = DataListener.findServerTerminal();
|
||||
if (localServerTerminal) {
|
||||
localServerTerminal.dispose();
|
||||
}
|
||||
|
||||
if (
|
||||
!localServerTerminal ||
|
||||
(localServerTerminal && localServerTerminal.state.isInteractedWith === true)
|
||||
) {
|
||||
localServerTerminal = window.createTerminal({
|
||||
name: this.terminalName,
|
||||
iconPath: new ThemeIcon('server-environment'),
|
||||
message: `Starting local server`
|
||||
});
|
||||
}
|
||||
|
||||
if (localServerTerminal) {
|
||||
localServerTerminal.sendText(command);
|
||||
localServerTerminal.show(false);
|
||||
}
|
||||
|
||||
Terminal.openLocalServerTerminal(command);
|
||||
this.sendMsg(Command.serverStarted, true);
|
||||
}
|
||||
}
|
||||
@@ -538,11 +518,7 @@ export class DataListener extends BaseListener {
|
||||
* Stop the local server
|
||||
*/
|
||||
private static stopServer() {
|
||||
const localServerTerminal = DataListener.findServerTerminal();
|
||||
if (localServerTerminal) {
|
||||
localServerTerminal.dispose();
|
||||
}
|
||||
|
||||
Terminal.closeLocalServerTerminal();
|
||||
this.sendMsg(Command.serverStarted, false);
|
||||
}
|
||||
|
||||
@@ -554,22 +530,10 @@ export class DataListener extends BaseListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const localServerTerminal = DataListener.findServerTerminal();
|
||||
const localServerTerminal = Terminal.findLocalServerTerminal();
|
||||
this.sendRequest(command, requestId, !!localServerTerminal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the server terminal
|
||||
* @returns
|
||||
*/
|
||||
private static findServerTerminal() {
|
||||
let terminals = window.terminals;
|
||||
if (terminals) {
|
||||
const localServerTerminal = terminals.find((t) => t.name === DataListener.terminalName);
|
||||
return localServerTerminal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the placeholder
|
||||
* @param field
|
||||
|
||||
@@ -36,7 +36,7 @@ export class FieldsListener extends BaseListener {
|
||||
|
||||
PagesListener.getPagesData(false, async (pages) => {
|
||||
const fuseOptions: Fuse.IFuseOptions<Page> = {
|
||||
keys: [{ name: 'type', weight: 1 }]
|
||||
keys: [{ name: 'fmContentType', weight: 1 }]
|
||||
};
|
||||
|
||||
const pagesIndex = await Extension.getInstance().getState<Fuse.FuseIndex<Page>>(
|
||||
@@ -48,7 +48,7 @@ export class FieldsListener extends BaseListener {
|
||||
const results = fuse.search({
|
||||
$and: [
|
||||
{
|
||||
type
|
||||
fmContentType: type
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ export class TaxonomyListener extends BaseListener {
|
||||
msg.payload?.fieldName,
|
||||
msg.payload?.values || [],
|
||||
msg.payload?.parents || [],
|
||||
typeof msg.payload?.renderAsString !== 'undefined' ? msg.payload?.renderAsString : false,
|
||||
msg.payload?.blockData
|
||||
);
|
||||
break;
|
||||
@@ -36,6 +37,7 @@ export class TaxonomyListener extends BaseListener {
|
||||
msg.payload?.fieldName,
|
||||
msg.payload?.values || [],
|
||||
msg.payload?.parents || [],
|
||||
typeof msg.payload?.renderAsString !== 'undefined' ? msg.payload?.renderAsString : false,
|
||||
msg.payload?.blockData
|
||||
);
|
||||
break;
|
||||
@@ -134,6 +136,7 @@ export class TaxonomyListener extends BaseListener {
|
||||
fieldName: string,
|
||||
values: string[],
|
||||
parents: string[],
|
||||
renderAsString: boolean,
|
||||
blockData?: BlockFieldData
|
||||
) {
|
||||
const editor = window.activeTextEditor;
|
||||
@@ -145,7 +148,17 @@ export class TaxonomyListener extends BaseListener {
|
||||
if (article && article.data) {
|
||||
const parentObj = DataListener.getParentObject(article.data, article, parents, blockData);
|
||||
|
||||
parentObj[fieldName] = values || [];
|
||||
if (renderAsString) {
|
||||
if (values.length === 0) {
|
||||
parentObj[fieldName] = '';
|
||||
} else if (values.length === 1) {
|
||||
parentObj[fieldName] = values[0];
|
||||
} else {
|
||||
parentObj[fieldName] = values || [];
|
||||
}
|
||||
} else {
|
||||
parentObj[fieldName] = values || [];
|
||||
}
|
||||
ArticleHelper.update(editor, article);
|
||||
DataListener.pushMetadata(article!.data);
|
||||
}
|
||||
@@ -174,7 +187,18 @@ export class TaxonomyListener extends BaseListener {
|
||||
data.blockData
|
||||
);
|
||||
|
||||
parentObj[data.name] = data.options || [];
|
||||
if (data.renderAsString) {
|
||||
if (!data.options || data.options.length === 0) {
|
||||
parentObj[data.name] = '';
|
||||
} else if (data.options.length === 1) {
|
||||
parentObj[data.name] = data.options[0];
|
||||
} else {
|
||||
parentObj[data.name] = data.options || [] || [];
|
||||
}
|
||||
} else {
|
||||
parentObj[data.name] = data.options || [];
|
||||
}
|
||||
|
||||
ArticleHelper.update(editor, article);
|
||||
DataListener.pushMetadata(article!.data);
|
||||
}
|
||||
|
||||
@@ -111,6 +111,10 @@ export enum LocalizationKey {
|
||||
* Unpin
|
||||
*/
|
||||
commonUnpin = 'common.unpin',
|
||||
/**
|
||||
* No results
|
||||
*/
|
||||
commonNoResults = 'common.noResults',
|
||||
/**
|
||||
* Common
|
||||
*/
|
||||
@@ -283,6 +287,10 @@ export enum LocalizationKey {
|
||||
* Published
|
||||
*/
|
||||
dashboardContentsStatusPublished = 'dashboard.contents.status.published',
|
||||
/**
|
||||
* Scheduled
|
||||
*/
|
||||
dashboardContentsStatusScheduled = 'dashboard.contents.status.scheduled',
|
||||
/**
|
||||
* Modify the data
|
||||
*/
|
||||
@@ -391,6 +399,10 @@ export enum LocalizationKey {
|
||||
* Published
|
||||
*/
|
||||
dashboardHeaderNavigationPublished = 'dashboard.header.navigation.published',
|
||||
/**
|
||||
* Scheduled
|
||||
*/
|
||||
dashboardHeaderNavigationScheduled = 'dashboard.header.navigation.scheduled',
|
||||
/**
|
||||
* In draft
|
||||
*/
|
||||
|
||||
@@ -6,5 +6,6 @@ export interface CustomTaxonomyData {
|
||||
options?: string[] | undefined;
|
||||
option?: string | undefined;
|
||||
parents?: string[];
|
||||
renderAsString?: boolean;
|
||||
blockData?: BlockFieldData;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ export interface Field {
|
||||
fieldGroup?: string | string[];
|
||||
dataType?: string | string[];
|
||||
taxonomyLimit?: number;
|
||||
singleValueAsString?: boolean;
|
||||
fileExtensions?: string[];
|
||||
editable?: boolean;
|
||||
required?: boolean;
|
||||
|
||||
@@ -6,21 +6,23 @@ import { LocalizationKey } from '../../../localization';
|
||||
export interface IChoiceButtonProps {
|
||||
title: string;
|
||||
value: string;
|
||||
className?: string;
|
||||
onClick: (value: string) => void;
|
||||
}
|
||||
|
||||
export const ChoiceButton: React.FunctionComponent<IChoiceButtonProps> = ({
|
||||
title,
|
||||
value,
|
||||
className,
|
||||
onClick
|
||||
}: React.PropsWithChildren<IChoiceButtonProps>) => {
|
||||
return (
|
||||
<button
|
||||
title={l10n.t(LocalizationKey.commonRemoveValue, title)}
|
||||
className="metadata_field__choice__button"
|
||||
className={`metadata_field__choice__button text-left ${className || ""}`}
|
||||
onClick={() => onClick(value)}
|
||||
>
|
||||
{title}
|
||||
<span>{title}</span>
|
||||
<XIcon className={`metadata_field__choice__button_icon`} />
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { ChevronDownIcon, DocumentAddIcon } from '@heroicons/react/outline';
|
||||
import Downshift from 'downshift';
|
||||
import { Combobox, Transition } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { BaseFieldProps } from '../../../models';
|
||||
import { ChoiceButton } from './ChoiceButton';
|
||||
import { FieldTitle } from './FieldTitle';
|
||||
import { FieldMessage } from './FieldMessage';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { Fragment, useCallback, useEffect, useMemo } from 'react';
|
||||
import { Page } from '../../../dashboardWebView/models';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client/webview';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { ChevronDownIcon, DocumentAddIcon } from '@heroicons/react/outline';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { FieldTitle } from './FieldTitle';
|
||||
import { FieldMessage } from './FieldMessage';
|
||||
import { ChoiceButton } from './ChoiceButton';
|
||||
import useDropdownStyle from '../../hooks/useDropdownStyle';
|
||||
|
||||
export interface IContentTypeRelationshipFieldProps extends BaseFieldProps<string | string[]> {
|
||||
@@ -34,34 +34,25 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
|
||||
const [choices, setChoices] = React.useState<string[]>([]);
|
||||
const [pages, setPages] = React.useState<Page[]>([]);
|
||||
const [crntSelected, setCrntSelected] = React.useState<string | string[] | null>(value);
|
||||
const dsRef = React.useRef<Downshift<string> | null>(null);
|
||||
const [filter, setFilter] = React.useState<string | undefined>(undefined);
|
||||
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
||||
const { getDropdownStyle } = useDropdownStyle(inputRef as any);
|
||||
const { getDropdownStyle } = useDropdownStyle(inputRef as any, '6px');
|
||||
|
||||
const onValueChange = (txtValue: string) => {
|
||||
if (multiSelect) {
|
||||
const crntValue = typeof crntSelected === 'string' ? [crntSelected] : crntSelected;
|
||||
const newValue = [...((crntValue || []) as string[]), txtValue];
|
||||
const uniqueValues = [...new Set(newValue)];
|
||||
setCrntSelected(uniqueValues);
|
||||
onChange(uniqueValues);
|
||||
} else {
|
||||
setCrntSelected(txtValue);
|
||||
onChange(txtValue);
|
||||
}
|
||||
};
|
||||
|
||||
const removeSelected = (txtValue: string) => {
|
||||
if (multiSelect) {
|
||||
const newValue = [...(crntSelected || [])].filter((v) => v !== txtValue);
|
||||
setCrntSelected(newValue);
|
||||
onChange(newValue);
|
||||
} else {
|
||||
setCrntSelected('');
|
||||
onChange('');
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Check the required state
|
||||
*/
|
||||
const showRequiredState = useMemo(() => {
|
||||
return (
|
||||
required && ((crntSelected instanceof Array && crntSelected.length === 0) || !crntSelected)
|
||||
);
|
||||
}, [required, crntSelected]);
|
||||
|
||||
/**
|
||||
* Retrieve the value based on the field setting
|
||||
* @param value
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
const getValue = (value: Page, type: string = "path") => {
|
||||
if (type === 'path') {
|
||||
return value.fmRelFilePath || value.fmFilePath;
|
||||
@@ -70,7 +61,10 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
|
||||
return `${value[type]}`;
|
||||
};
|
||||
|
||||
const getChoiceValue = React.useCallback((value: string) => {
|
||||
/**
|
||||
* Retrieve choice value to display
|
||||
*/
|
||||
const getChoiceValue = useCallback((value: string) => {
|
||||
const choice = pages.find(
|
||||
(p: Page) => getValue(p, contentTypeValue) === value
|
||||
);
|
||||
@@ -79,35 +73,80 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
|
||||
return choice.title;
|
||||
}
|
||||
return '';
|
||||
}, [choices, contentTypeValue]);
|
||||
}, [pages, choices, contentTypeValue]);
|
||||
|
||||
/**
|
||||
* On selecting an option
|
||||
* @param txtValue
|
||||
*/
|
||||
const onSelect = (option: string) => {
|
||||
setFilter(undefined);
|
||||
|
||||
if (multiSelect) {
|
||||
if (option) {
|
||||
const crntValue = typeof crntSelected === 'string' ? [crntSelected] : crntSelected;
|
||||
const newValue = [...((crntValue || []) as string[]), option];
|
||||
const uniqueValues = [...new Set(newValue)];
|
||||
setCrntSelected(uniqueValues);
|
||||
onChange(uniqueValues);
|
||||
}
|
||||
} else if (option) {
|
||||
setCrntSelected(option);
|
||||
onChange(option);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a selected value
|
||||
* @param txtValue
|
||||
*/
|
||||
const removeSelected = useCallback((txtValue: string) => {
|
||||
if (multiSelect) {
|
||||
const newValue = [...(crntSelected || [])].filter((v) => v !== txtValue);
|
||||
setCrntSelected(newValue);
|
||||
onChange(newValue);
|
||||
} else {
|
||||
setCrntSelected('');
|
||||
onChange('');
|
||||
}
|
||||
}, [multiSelect, crntSelected, onChange]);
|
||||
|
||||
/**
|
||||
* Retrieve the available choices
|
||||
*/
|
||||
const availableChoices = useMemo(() => {
|
||||
return pages.filter((page: Page) => {
|
||||
const value = contentTypeValue === "slug" ? page.slug : page.fmFilePath;
|
||||
|
||||
let toShow = true;
|
||||
|
||||
if (typeof crntSelected === 'string') {
|
||||
return crntSelected !== `${value}` && !value.includes(crntSelected);
|
||||
toShow = crntSelected !== `${value}` && !value.includes(crntSelected);
|
||||
} else if (crntSelected instanceof Array) {
|
||||
const selected = crntSelected.filter((v) => v === `${value}` || value.includes(v));
|
||||
return selected.length === 0;
|
||||
toShow = selected.length === 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (toShow && filter) {
|
||||
return page.title.toLowerCase().includes(filter);
|
||||
}
|
||||
|
||||
return toShow;
|
||||
});
|
||||
}, [choices, crntSelected, multiSelect, contentTypeValue]);
|
||||
|
||||
const showRequiredState = useMemo(() => {
|
||||
return (
|
||||
required && ((crntSelected instanceof Array && crntSelected.length === 0) || !crntSelected)
|
||||
);
|
||||
}, [required, crntSelected]);
|
||||
}, [choices, crntSelected, multiSelect, contentTypeValue, filter]);
|
||||
|
||||
/**
|
||||
* Retrieve the selected value
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (crntSelected !== value) {
|
||||
setCrntSelected(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
/**
|
||||
* Retrieve the pages based on the content type
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (contentTypeName) {
|
||||
setLoading(true);
|
||||
@@ -137,83 +176,94 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Downshift
|
||||
ref={dsRef}
|
||||
onSelect={(selected) => onValueChange(selected || '')}
|
||||
itemToString={(item) => (item ? item : '')}
|
||||
>
|
||||
{({ getToggleButtonProps, getItemProps, getMenuProps, isOpen, getRootProps }) => (
|
||||
<div
|
||||
{...getRootProps(undefined, { suppressRefError: true })}
|
||||
ref={inputRef}
|
||||
className={`metadata_field__choice`}
|
||||
>
|
||||
<button
|
||||
{...getToggleButtonProps({
|
||||
className: `metadata_field__choice__toggle`,
|
||||
disabled: availableChoices.length === 0
|
||||
})}
|
||||
>
|
||||
<span>{`Select ${label}`}</span>
|
||||
<ChevronDownIcon className="icon" />
|
||||
</button>
|
||||
<Combobox
|
||||
value={crntSelected}
|
||||
onChange={onSelect}
|
||||
>
|
||||
{({ open }) => (
|
||||
<div className="relative">
|
||||
|
||||
<ul
|
||||
className={`field_dropdown metadata_field__choice_list ${isOpen ? 'open' : 'closed'}`}
|
||||
style={{
|
||||
bottom: getDropdownStyle(isOpen)
|
||||
}}
|
||||
{...getMenuProps()}
|
||||
>
|
||||
{
|
||||
availableChoices.map((choice: Page, index) => (
|
||||
<li
|
||||
{...getItemProps({
|
||||
key: getValue(choice, contentTypeValue),
|
||||
index,
|
||||
item: getValue(choice, contentTypeValue),
|
||||
})}
|
||||
>
|
||||
{choice.title || (
|
||||
<span className={`metadata_field__choice_list__item`}>
|
||||
{l10n.t(LocalizationKey.commonClearValue)}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<div className="relative w-full cursor-default overflow-hidden text-left focus:outline-none border border-solid border-[var(--vscode-inputValidation-infoBorder)] rounded">
|
||||
<Combobox.Input
|
||||
className="w-full border-none py-2 pl-3 pr-10 leading-5 focus:ring-0 text-[var(--vscode-input-foreground)]"
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
value={filter || ""}
|
||||
placeholder={l10n.t(LocalizationKey.panelFieldsChoiceFieldSelect, label)}
|
||||
ref={inputRef} />
|
||||
|
||||
<Combobox.Button
|
||||
className="absolute inset-y-0 right-0 flex items-center w-8 bg-inherit rounded-none text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-button-foreground)]">
|
||||
<ChevronDownIcon
|
||||
className="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Combobox.Button>
|
||||
</div>
|
||||
)}
|
||||
</Downshift>
|
||||
|
||||
<FieldMessage
|
||||
name={label.toLowerCase()}
|
||||
description={description}
|
||||
showRequired={showRequiredState}
|
||||
/>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
afterLeave={() => setFilter('')}
|
||||
>
|
||||
<Combobox.Options
|
||||
className="field_dropdown absolute max-h-60 w-full shadow-lg overflow-auto py-1 px-0 space-y-1 text-base focus:outline-none z-50 bg-[var(--vscode-dropdown-background)] text-[var(--vscode-dropdown-foreground)] border border-solid border-[var(--vscode-dropdown-border)]"
|
||||
style={{
|
||||
bottom: getDropdownStyle(open)
|
||||
}}
|
||||
>
|
||||
{availableChoices.map((choice) => (
|
||||
<Combobox.Option
|
||||
key={choice.fmFilePath}
|
||||
value={getValue(choice, contentTypeValue)}
|
||||
className={({ active }) => `py-[var(--input-padding-vertical)] px-[var(--input-padding-horizontal)] list-none cursor-pointer hover:text-[var(--vscode-button-foreground)] hover:bg-[var(--vscode-button-hoverBackground)] ${active ? "text-[var(--vscode-button-foreground)] bg-[var(--vscode-button-hoverBackground)] " : ""}`}>
|
||||
{choice.title}
|
||||
</Combobox.Option>
|
||||
))}
|
||||
|
||||
{crntSelected instanceof Array
|
||||
? crntSelected.map((value: string) => (
|
||||
<ChoiceButton
|
||||
key={value}
|
||||
value={value}
|
||||
title={getChoiceValue(value)}
|
||||
onClick={removeSelected}
|
||||
/>
|
||||
))
|
||||
: crntSelected && (
|
||||
<ChoiceButton
|
||||
key={crntSelected}
|
||||
value={crntSelected}
|
||||
title={getChoiceValue(crntSelected)}
|
||||
onClick={removeSelected}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{availableChoices.length === 0 ? (
|
||||
<div className="relative cursor-default select-none text-center">
|
||||
{l10n.t(LocalizationKey.commonNoResults)}
|
||||
</div>
|
||||
) : null}
|
||||
</Combobox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Combobox>
|
||||
)
|
||||
}
|
||||
|
||||
<FieldMessage
|
||||
name={label.toLowerCase()}
|
||||
description={description}
|
||||
showRequired={showRequiredState}
|
||||
/>
|
||||
|
||||
{
|
||||
pages.length > 0 && (
|
||||
crntSelected instanceof Array
|
||||
? crntSelected.map((value: string) => (
|
||||
<ChoiceButton
|
||||
key={value}
|
||||
value={value}
|
||||
className='w-full mr-0 flex justify-between'
|
||||
title={getChoiceValue(value)}
|
||||
onClick={removeSelected}
|
||||
/>
|
||||
))
|
||||
: crntSelected && (
|
||||
<ChoiceButton
|
||||
key={crntSelected}
|
||||
value={crntSelected}
|
||||
className='w-full mr-0 flex justify-between'
|
||||
title={getChoiceValue(crntSelected)}
|
||||
onClick={removeSelected}
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
};
|
||||
@@ -311,6 +311,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
parents={parentFields}
|
||||
blockData={blockData}
|
||||
limit={field.taxonomyLimit}
|
||||
renderAsString={field.singleValueAsString}
|
||||
required={!!field.required}
|
||||
/>
|
||||
</FieldBoundary>
|
||||
@@ -335,6 +336,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
parents={parentFields}
|
||||
blockData={blockData}
|
||||
limit={field.taxonomyLimit}
|
||||
renderAsString={field.singleValueAsString}
|
||||
required={!!field.required}
|
||||
/>
|
||||
</FieldBoundary>
|
||||
@@ -356,6 +358,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
parents={parentFields}
|
||||
blockData={blockData}
|
||||
limit={field.taxonomyLimit}
|
||||
renderAsString={field.singleValueAsString}
|
||||
required={!!field.required}
|
||||
/>
|
||||
</FieldBoundary>
|
||||
|
||||
@@ -89,7 +89,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Collapsible id={`tags`} title={l10n.t(LocalizationKey.panelMetadataTitle)} className={`inherit z-20`}>
|
||||
<Collapsible id={`tags`} title={`${l10n.t(LocalizationKey.panelMetadataTitle)}${contentType?.name ? ` (${contentType?.name})` : ""}`} className={`inherit z-20`}>
|
||||
<FeatureFlag features={features || []} flag={FEATURE_FLAG.panel.contentType}>
|
||||
<ContentTypeValidator fields={contentType?.fields || []} metadata={metadata} />
|
||||
</FeatureFlag>
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface ITagPickerProps {
|
||||
taxonomyId?: string;
|
||||
limit?: number;
|
||||
required?: boolean;
|
||||
renderAsString?: boolean;
|
||||
}
|
||||
|
||||
const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
@@ -53,7 +54,8 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
parents,
|
||||
blockData,
|
||||
limit,
|
||||
required
|
||||
required,
|
||||
renderAsString
|
||||
}: React.PropsWithChildren<ITagPickerProps>) => {
|
||||
const [selected, setSelected] = React.useState<string[]>([]);
|
||||
const [inputValue, setInputValue] = React.useState<string>('');
|
||||
@@ -96,11 +98,12 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
* Send an update to VSCode
|
||||
* @param values
|
||||
*/
|
||||
const sendUpdate = (values: string[]) => {
|
||||
const sendUpdate = useCallback((values: string[]) => {
|
||||
if (type === TagType.tags) {
|
||||
Messenger.send(CommandToCode.updateTags, {
|
||||
fieldName,
|
||||
values,
|
||||
renderAsString,
|
||||
parents,
|
||||
blockData
|
||||
});
|
||||
@@ -108,6 +111,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
Messenger.send(CommandToCode.updateCategories, {
|
||||
fieldName,
|
||||
values,
|
||||
renderAsString,
|
||||
parents,
|
||||
blockData
|
||||
});
|
||||
@@ -121,11 +125,12 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
id: taxonomyId,
|
||||
name: fieldName,
|
||||
options: values,
|
||||
renderAsString,
|
||||
parents,
|
||||
blockData
|
||||
} as CustomTaxonomyData);
|
||||
}
|
||||
};
|
||||
}, [renderAsString]);
|
||||
|
||||
/**
|
||||
* Triggers the focus to the input when command is executed
|
||||
@@ -145,7 +150,10 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
if (selectedItem) {
|
||||
let value = selectedItem || '';
|
||||
|
||||
const item = options.find((o) => o?.toLowerCase() === selectedItem?.toLowerCase());
|
||||
const item = options.find((o) => {
|
||||
o = typeof o === 'string' ? o : `${o}`;
|
||||
return o?.toLowerCase() === value?.toLowerCase();
|
||||
});
|
||||
if (item) {
|
||||
value = item;
|
||||
}
|
||||
@@ -174,8 +182,12 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
* @param inputValue
|
||||
*/
|
||||
const filterList = (option: string, inputValue: string | null) => {
|
||||
if (typeof option !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
!selected.includes(option) && option.toLowerCase().includes((inputValue || '').toLowerCase())
|
||||
option && !selected.includes(option) && option.toLowerCase().includes((inputValue || '').toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
@@ -201,7 +213,10 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
for (let crntValue of values) {
|
||||
crntValue = crntValue.trim();
|
||||
if (crntValue) {
|
||||
const item = options.find((o) => o?.toLowerCase() === crntValue?.toLowerCase());
|
||||
const item = options.find((o) => {
|
||||
o = typeof o === 'string' ? o : `${o}`;
|
||||
return o?.toLowerCase() === crntValue?.toLowerCase();
|
||||
});
|
||||
if (item) {
|
||||
newValues.push(item);
|
||||
} else if (freeform) {
|
||||
@@ -279,6 +294,15 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
);
|
||||
}, [settings?.aiEnabled, label, type]);
|
||||
|
||||
const sortedSelectedTags = useMemo(() => {
|
||||
return (selected || []).sort((a: string, b: string) => {
|
||||
const aString = typeof a === 'string' ? a : `${a}`;
|
||||
const bString = typeof b === 'string' ? b : `${b}`;
|
||||
|
||||
return aString?.toLowerCase() < bString?.toLowerCase() ? -1 : 1;
|
||||
});
|
||||
}, [selected]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
triggerFocus();
|
||||
@@ -400,9 +424,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
/>
|
||||
|
||||
<Tags
|
||||
values={(selected || []).sort((a: string, b: string) =>
|
||||
a?.toLowerCase() < b?.toLowerCase() ? -1 : 1
|
||||
)}
|
||||
values={sortedSelectedTags}
|
||||
onRemove={onRemove}
|
||||
onCreate={onCreate}
|
||||
options={options}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export default function useDropdownStyle(inputRef: React.MutableRefObject<HTMLInputElement | null>) {
|
||||
const bottomStyle = "calc(100% - 38px)";
|
||||
export default function useDropdownStyle(inputRef: React.MutableRefObject<HTMLInputElement | null>, inputHeight?: string) {
|
||||
const bottomStyle = `calc(100% - ${inputHeight || '38px'})`;
|
||||
const listItemHeight = 28;
|
||||
|
||||
const getDropdownStyle = useCallback((isOpen) => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as Sentry from '@sentry/react';
|
||||
import { Integrations } from '@sentry/tracing';
|
||||
import { SENTRY_LINK, SentryIgnore } from '../constants';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import './styles.css';
|
||||
|
||||
// require('@vscode/codicons/dist/codicon.css');
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
/* Animations */
|
||||
@keyframes spin {
|
||||
from {
|
||||
@@ -969,7 +972,7 @@ vscode-divider {
|
||||
|
||||
/* Divider */
|
||||
.divider {
|
||||
background: var(--divider-background);
|
||||
background: var(--vscode-panel-border);
|
||||
}
|
||||
|
||||
/* Git actions */
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface Format {
|
||||
export interface ParsedFrontMatter {
|
||||
data: { [key: string]: any };
|
||||
content: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
export class FrontMatterParser {
|
||||
|
||||
@@ -21,7 +21,7 @@ export class Credentials {
|
||||
* prompting the user to sign in.
|
||||
* */
|
||||
const session = await authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, {
|
||||
createIfNone: false
|
||||
silent: true
|
||||
});
|
||||
|
||||
if (session) {
|
||||
@@ -62,8 +62,13 @@ export class Credentials {
|
||||
* Note that this can throw if the user clicks cancel.
|
||||
*/
|
||||
const session = await authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, {
|
||||
createIfNone: true
|
||||
silent: true
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
throw new Error('No GitHub authentication session available.');
|
||||
}
|
||||
|
||||
this.octokit = new Octokit.Octokit({
|
||||
auth: session.accessToken
|
||||
});
|
||||
|
||||
@@ -171,7 +171,7 @@ export class PagesParser {
|
||||
|
||||
const dateField = ArticleHelper.getPublishDateField(article) || DefaultFields.PublishingDate;
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
let dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
const ctDateField = ContentType.findFieldByName(contentType.fields, dateField);
|
||||
if (ctDateField && ctDateField.dateFormat) {
|
||||
@@ -211,7 +211,7 @@ export class PagesParser {
|
||||
fmRelFileWsPath: FilesHelper.absToRelPath(filePath),
|
||||
fmRelFilePath: parseWinPath(filePath).replace(wsFolder?.fsPath || '', ''),
|
||||
fmFileName: fileName,
|
||||
fmDraft: ContentType.getDraftStatus(article?.data),
|
||||
fmDraft: ContentType.getDraftStatus(article),
|
||||
fmModified: modifiedFieldValue ? modifiedFieldValue : fileMtime,
|
||||
fmPublished: dateFieldValue ? dateFieldValue.getTime() : null,
|
||||
fmYear: dateFieldValue ? dateFieldValue.getFullYear() : null,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { workspace } from 'vscode';
|
||||
import { workspace, window, ThemeIcon, TerminalOptions } from 'vscode';
|
||||
import * as os from 'os';
|
||||
import { Folders } from '../commands';
|
||||
|
||||
interface ShellSetting {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export class Terminal {
|
||||
public static readonly terminalName: string = 'Local server';
|
||||
|
||||
/**
|
||||
* Return the shell path for the current platform
|
||||
*/
|
||||
@@ -22,6 +25,68 @@ export class Terminal {
|
||||
return shellPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new local server terminal
|
||||
*/
|
||||
public static openLocalServerTerminal(command: string) {
|
||||
let localServerTerminal = Terminal.findLocalServerTerminal();
|
||||
if (localServerTerminal) {
|
||||
localServerTerminal.dispose();
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!localServerTerminal ||
|
||||
(localServerTerminal && localServerTerminal.state.isInteractedWith === true)
|
||||
) {
|
||||
const terminalOptions: TerminalOptions = {
|
||||
name: Terminal.terminalName,
|
||||
iconPath: new ThemeIcon('server-environment'),
|
||||
message: `Starting local server`
|
||||
};
|
||||
|
||||
// Check if workspace
|
||||
if (workspace.workspaceFolders && workspace.workspaceFolders.length > 1) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (wsFolder) {
|
||||
terminalOptions.cwd = wsFolder;
|
||||
}
|
||||
}
|
||||
|
||||
localServerTerminal = window.createTerminal(terminalOptions);
|
||||
}
|
||||
|
||||
if (localServerTerminal) {
|
||||
localServerTerminal.sendText(command);
|
||||
localServerTerminal.show(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close local server terminal
|
||||
*/
|
||||
public static closeLocalServerTerminal() {
|
||||
const localServerTerminal = Terminal.findLocalServerTerminal();
|
||||
if (localServerTerminal) {
|
||||
localServerTerminal.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the server terminal
|
||||
* @returns
|
||||
*/
|
||||
public static findLocalServerTerminal() {
|
||||
let terminals = window.terminals;
|
||||
if (terminals) {
|
||||
const localServerTerminal = terminals.find((t) => t.name === Terminal.terminalName);
|
||||
return localServerTerminal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the automation profile for the current platform
|
||||
* @returns
|
||||
|
||||
8
tailwind.panel.js
Normal file
8
tailwind.panel.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const defaultConfig = require("./tailwind.config")
|
||||
|
||||
module.exports = {
|
||||
...defaultConfig,
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,21 @@ const config = [{
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader', 'postcss-loader']
|
||||
use: ['style-loader', 'css-loader', {
|
||||
loader: "postcss-loader",
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {
|
||||
config: path.resolve(__dirname, '../tailwind.panel.js')
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
}]
|
||||
},
|
||||
{
|
||||
test: /\.m?js/,
|
||||
|
||||
Reference in New Issue
Block a user