Compare commits
231 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
feff69d969 | ||
|
|
a34d77242a | ||
|
|
1b24c1277d | ||
|
|
e6750205be | ||
|
|
1da8bf3f8b | ||
|
|
90c60b6a40 | ||
|
|
ee79f89c7f | ||
|
|
0668d48fd5 | ||
|
|
d046f73d16 | ||
|
|
f144d713d1 | ||
|
|
d31c403bdc | ||
|
|
35a0327387 | ||
|
|
9b39649bde | ||
|
|
3d857463f0 | ||
|
|
0428642a2f | ||
|
|
5182a9ae1a | ||
|
|
ab3686b3b5 | ||
|
|
c5b7b7845d | ||
|
|
2f13c335ed | ||
|
|
f219ac721f | ||
|
|
0149885289 | ||
|
|
cb80a10de2 | ||
|
|
092eb0fd2a | ||
|
|
24f79d9d3f | ||
|
|
d667b19716 | ||
|
|
4d38a0881a | ||
|
|
1478d0bd53 | ||
|
|
00a9e59bc2 | ||
|
|
2b2843234e | ||
|
|
71449aa5cc | ||
|
|
87f293a3f6 | ||
|
|
6da3ddb5dd | ||
|
|
87e80ccfe9 | ||
|
|
af9865d91b | ||
|
|
e576b6e8a4 | ||
|
|
c9f4c8f94e | ||
|
|
6ef63da973 | ||
|
|
2d85b0a554 | ||
|
|
41cbcc4c46 | ||
|
|
6c94880497 | ||
|
|
1d75c6b6b8 | ||
|
|
338bc022f9 | ||
|
|
2451730311 | ||
|
|
22cdaabe28 | ||
|
|
5a69240178 | ||
|
|
b81ef077f6 | ||
|
|
cb72b53653 | ||
|
|
050a513b48 | ||
|
|
481d1f56e4 | ||
|
|
229238703a | ||
|
|
074de212ce | ||
|
|
98e4490318 | ||
|
|
c79dc3c41f | ||
|
|
b45abe88ac | ||
|
|
2bac596e3d | ||
|
|
15a6ea8d8d | ||
|
|
72d6f8263a | ||
|
|
7549b3d989 | ||
|
|
ca628b37de | ||
|
|
db120b5c45 | ||
|
|
2973f5d27b | ||
|
|
075c7ad350 | ||
|
|
23625ed8fa | ||
|
|
9bd06909c8 | ||
|
|
1b45c2af13 | ||
|
|
b4bbf6c6fb | ||
|
|
a5b8d9668d | ||
|
|
490210da00 | ||
|
|
6878d440b1 | ||
|
|
81fa0a7d0f | ||
|
|
b7d4e547a1 | ||
|
|
2b2e256b8f | ||
|
|
7a7b31c2ae | ||
|
|
dea5eb1053 | ||
|
|
df3144ea73 | ||
|
|
a170cc3ad9 | ||
|
|
35b3813022 | ||
|
|
f0472fe89b | ||
|
|
7bdb2fa025 | ||
|
|
5ba7ad449a | ||
|
|
145441d55e | ||
|
|
62ff3419c1 | ||
|
|
34aee134d6 | ||
|
|
5899120d87 | ||
|
|
816a2fefe7 | ||
|
|
1f64e59917 | ||
|
|
5e54334fb9 | ||
|
|
d354af306f | ||
|
|
045cece0ce | ||
|
|
506012200f | ||
|
|
4ad2f0d495 | ||
|
|
b00e3cfd4b | ||
|
|
bb05489872 | ||
|
|
ede4d417bd | ||
|
|
928072fa27 | ||
|
|
6a313fcc8a | ||
|
|
725f7f4915 | ||
|
|
b9cb0ea16d | ||
|
|
61b80795a4 | ||
|
|
8daaa23774 | ||
|
|
8a0d308ceb | ||
|
|
e6a7a9aae7 | ||
|
|
f3943bd846 | ||
|
|
d2ae94df34 | ||
|
|
f799613b1a | ||
|
|
3f057a01d8 | ||
|
|
3ad5136735 | ||
|
|
e201ce6f83 | ||
|
|
383a3a7d4c | ||
|
|
8261f1de1b | ||
|
|
5b38e6fa56 | ||
|
|
717f34bc85 | ||
|
|
47fb2a90a9 | ||
|
|
85a7221895 | ||
|
|
9618a89528 | ||
|
|
14f0af2754 | ||
|
|
ebe248670d | ||
|
|
511960c4a9 | ||
|
|
31fd1f93ce | ||
|
|
6625b69170 | ||
|
|
9e8533fbb8 | ||
|
|
9c9cbb7dcb | ||
|
|
079a13e161 | ||
|
|
69c1e587d0 | ||
|
|
3996252531 | ||
|
|
4fddda65e6 | ||
|
|
5916344092 | ||
|
|
b96722dd69 | ||
|
|
263ccab311 | ||
|
|
3571af82c7 | ||
|
|
c60520c0ff | ||
|
|
b473431eae | ||
|
|
cbf434f741 | ||
|
|
04c401207f | ||
|
|
7291e6aac6 | ||
|
|
a7aab96f0e | ||
|
|
f500749644 | ||
|
|
47e59bc54c | ||
|
|
8902e25021 | ||
|
|
33093e1eb4 | ||
|
|
d36178c44f | ||
|
|
15b09ccc75 | ||
|
|
dffa6c87a0 | ||
|
|
c4a1caee09 | ||
|
|
1d9f07b86d | ||
|
|
a794a95bb8 | ||
|
|
40a56f6057 | ||
|
|
82353f7b64 | ||
|
|
82a22da90a | ||
|
|
380e40ea05 | ||
|
|
2bedb23341 | ||
|
|
1110b76364 | ||
|
|
d19e632f80 | ||
|
|
4e040b5f7a | ||
|
|
7a2a0934c2 | ||
|
|
d3eb7b223c | ||
|
|
f74eec954f | ||
|
|
864c4e7aa6 | ||
|
|
5667906caf | ||
|
|
2fe7c524e7 | ||
|
|
5cc83526ad | ||
|
|
76b5e99a08 | ||
|
|
7d5505d421 | ||
|
|
d97a11f8b5 | ||
|
|
0590cec684 | ||
|
|
b4cdc4feb9 | ||
|
|
986fd95524 | ||
|
|
f51fec5fb9 | ||
|
|
8198ce2af3 | ||
|
|
defffc4c8e | ||
|
|
8f47cbfb0b | ||
|
|
0be91c17d0 | ||
|
|
fae7ab8417 | ||
|
|
df239f2cc0 | ||
|
|
70099dc97f | ||
|
|
c11be0e3ec | ||
|
|
f8b7870180 | ||
|
|
c58a5c62d9 | ||
|
|
ce92444bf2 | ||
|
|
b2709ebffd | ||
|
|
2b20cf9d24 | ||
|
|
f4a499ad0f | ||
|
|
4494b158c0 | ||
|
|
3416e55264 | ||
|
|
9b53e31cd5 | ||
|
|
f49b93b042 | ||
|
|
a005930c14 | ||
|
|
9dea1ee6ed | ||
|
|
1ea0999d17 | ||
|
|
2e6a466ba5 | ||
|
|
fbcd430dc6 | ||
|
|
2bd910db47 | ||
|
|
8158c9a483 | ||
|
|
4622fbe757 | ||
|
|
97a635c2de | ||
|
|
e8c67c75fd | ||
|
|
6151ecb4c1 | ||
|
|
1d8c192c07 | ||
|
|
014911b7a9 | ||
|
|
7b2d7b8aa5 | ||
|
|
caceed2d4c | ||
|
|
476ec6c2fd | ||
|
|
5374edfa01 | ||
|
|
6a0cac9dfb | ||
|
|
b525a6a211 | ||
|
|
c295761560 | ||
|
|
6154164b4b | ||
|
|
05ce2d3537 | ||
|
|
544f24bcba | ||
|
|
1c354ed976 | ||
|
|
9b9bf1bfbe | ||
|
|
29d5f02d10 | ||
|
|
9bc2fbc141 | ||
|
|
cac009b773 | ||
|
|
bf1639cac7 | ||
|
|
5f28e145c4 | ||
|
|
8199ab964e | ||
|
|
14c050e34b | ||
|
|
b96411d1f9 | ||
|
|
87c469a6c7 | ||
|
|
c29aef03f0 | ||
|
|
9eaf94de7a | ||
|
|
f5e7526fae | ||
|
|
579e4925c8 | ||
|
|
4a4c558d9d | ||
|
|
9bb34850e2 | ||
|
|
c6e37532bc | ||
|
|
221f962beb | ||
|
|
17566279e2 | ||
|
|
4e7488414d | ||
|
|
c206817431 |
7
.github/ISSUE_TEMPLATE/feedback_request.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
name: Feedback
|
||||
about: Tell more on what you think
|
||||
title: 'Feedback: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
7
.github/workflows/release-beta.yml
vendored
@@ -11,9 +11,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: 16
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Install the dependencies
|
||||
@@ -25,3 +25,6 @@ jobs:
|
||||
- name: Publish
|
||||
run: npx vsce publish -p ${{ secrets.VSCE_PAT }} --baseImagesUrl https://raw.githubusercontent.com/estruyf/vscode-front-matter/dev
|
||||
|
||||
- name: Publish to open-vsx.org
|
||||
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}
|
||||
|
||||
|
||||
6
.github/workflows/release.yml
vendored
@@ -12,9 +12,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: 16
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Install the dependencies
|
||||
@@ -26,3 +26,5 @@ jobs:
|
||||
- name: Publish
|
||||
run: npx vsce publish -p ${{ secrets.VSCE_PAT }}
|
||||
|
||||
- name: Publish to open-vsx.org
|
||||
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}
|
||||
@@ -17,4 +17,9 @@ postcss.config.js
|
||||
.templates
|
||||
.github
|
||||
scripts
|
||||
.all-contributorsrc
|
||||
.all-contributorsrc
|
||||
assets/v2.*
|
||||
assets/v3.*
|
||||
assets/v4.*
|
||||
assets/sponsors
|
||||
dist/*.html
|
||||
161
CHANGELOG.md
@@ -1,5 +1,166 @@
|
||||
# Change Log
|
||||
|
||||
## [5.9.0] - 2022-01-01 - 🎇🎆
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- Fixing the spinner which overlaps the global navigation bar
|
||||
- Quick actions added for media files (edit, delete, insert markdown, insert snippet)
|
||||
- [#199](https://github.com/estruyf/vscode-front-matter/issues/199): Search media files in the currently selected folder
|
||||
- [#211](https://github.com/estruyf/vscode-front-matter/issues/211): Replace text selection on media inserts
|
||||
- [#212](https://github.com/estruyf/vscode-front-matter/issues/212): Create folder watchers for content folders. When new content gets created, the dashboard updates.
|
||||
- [#213](https://github.com/estruyf/vscode-front-matter/issues/213): New media folder overview design
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#210](https://github.com/estruyf/vscode-front-matter/issues/210): Fix for adding media files with uppercase file extensions
|
||||
- [#214](https://github.com/estruyf/vscode-front-matter/issues/214): Fix for opening markdown file after creating it for the specified content type
|
||||
|
||||
## [5.8.0] - 2021-12-21 - 🎄
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- Refactoring of the WebView logic to new message handlers
|
||||
- Optimized the `getMedia` call from the webview
|
||||
- Keep the dashboard its context when switching tabs
|
||||
- [#205](https://github.com/estruyf/vscode-front-matter/issues/205): Define a logging level setting
|
||||
- [#206](https://github.com/estruyf/vscode-front-matter/issues/206): Add front matter issues to the diagnostic tab
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#207](https://github.com/estruyf/vscode-front-matter/issues/207): Fix the quick picks for content creation
|
||||
- [#208](https://github.com/estruyf/vscode-front-matter/issues/208): Fix for the collapse sections action so that it is not available everywhere, but only on the Front Matter panel
|
||||
|
||||
## [5.7.0] - 2021-12-07 - [Release Notes](https://beta.frontmatter.codes/updates/v5.7.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#188](https://github.com/estruyf/vscode-front-matter/issues/188): Support for `.markdown` files added to the dashboard
|
||||
- [#190](https://github.com/estruyf/vscode-front-matter/issues/190): Diagnostic output for the extension
|
||||
- [#194](https://github.com/estruyf/vscode-front-matter/issues/194): WYSIWYG controls added for markdown files + configuration to enable/disable the functionality
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#191](https://github.com/estruyf/vscode-front-matter/issues/191): Fix beta settings page
|
||||
- [#200](https://github.com/estruyf/vscode-front-matter/issues/200): Fix last modified date sorting for media files
|
||||
- [#201](https://github.com/estruyf/vscode-front-matter/issues/201): Fix overflow issue with the media filename
|
||||
- [#202](https://github.com/estruyf/vscode-front-matter/issues/202): Fix checkbox label color for light themes
|
||||
|
||||
## [5.6.0] - 2021-11-23
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- Updated camera icon from VS Code to media icon
|
||||
- Updated the media card actions to show it within a menu. This will give a better experience with custom scripts.
|
||||
- [#97](https://github.com/estruyf/vscode-front-matter/issues/97): Custom Script support for media files and folders
|
||||
- [#178](https://github.com/estruyf/vscode-front-matter/issues/178): Sorting added to the media dashboard
|
||||
- [#179](https://github.com/estruyf/vscode-front-matter/issues/179): Updated the `open dashboard` icon to make it easier to spot it
|
||||
- [#180](https://github.com/estruyf/vscode-front-matter/issues/180): Added `{filename}` as placeholder for media snippets
|
||||
- [#181](https://github.com/estruyf/vscode-front-matter/issues/181): Support for custom taxonomy fields added
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#183](https://github.com/estruyf/vscode-front-matter/issues/183): Fix type error on the `frontMatter.content.sorting` setting
|
||||
|
||||
## [5.5.0] - 2021-11-15
|
||||
|
||||
As from this version onwards, the extension will be published to [open-vsx.org](https://open-vsx.org/).
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#173](https://github.com/estruyf/vscode-front-matter/issues/173): Allow to specify your own sorting for the content dashboard
|
||||
- [#174](https://github.com/estruyf/vscode-front-matter/issues/174): Added option to exclude sub-directories from page/markdown content retrieval
|
||||
|
||||
## [5.4.0] - 2021-11-05
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#166](https://github.com/estruyf/vscode-front-matter/issues/166): Added preview button to the panel base view
|
||||
- [#167](https://github.com/estruyf/vscode-front-matter/issues/167): Allow to set the preview path per content type
|
||||
|
||||
## [5.3.1] - 2021-10-29
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#163](https://github.com/estruyf/vscode-front-matter/issues/163): Setting workspace state instead of global state for the media view
|
||||
|
||||
## [5.3.0] - 2021-10-28 - [Release Notes](https://beta.frontmatter.codes/updates/v5.3.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#158](https://github.com/estruyf/vscode-front-matter/issues/158): Add support for non-boolean draft/publish status fields
|
||||
- [#159](https://github.com/estruyf/vscode-front-matter/issues/159): Enhancements to SEO checks: Slug check, keyword details, more article information
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- Value check when generating slug from title
|
||||
- Fix for date time formatting with `DD` and `YYYY` tokens
|
||||
- Fix in tag space replacing when object is passed
|
||||
|
||||
## [5.2.0] - 2021-10-19
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#151](https://github.com/estruyf/vscode-front-matter/issues/151): Detect which site-generator or framework is used
|
||||
- [#152](https://github.com/estruyf/vscode-front-matter/issues/152): Automatically set setting based on the used site-generator or framework
|
||||
- [#154](https://github.com/estruyf/vscode-front-matter/issues/154): Bulk script support added
|
||||
- [#155](https://github.com/estruyf/vscode-front-matter/issues/155): Fallback image added for the images shown in the editor panel
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#153](https://github.com/estruyf/vscode-front-matter/issues/153): Support old date formatting for date-fns
|
||||
- [#156](https://github.com/estruyf/vscode-front-matter/issues/156): Fix for uploading media files into a new folder
|
||||
|
||||
## [5.1.1] - 2021-10-14
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#149](https://github.com/estruyf/vscode-front-matter/issues/149): Fix panel rendering when incorrect type for keywords is provided
|
||||
|
||||
## [5.1.0] - 2021-10-13
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#141](https://github.com/estruyf/vscode-front-matter/issues/141): Allow content creation for page bundles or single files
|
||||
- [#145](https://github.com/estruyf/vscode-front-matter/issues/145): Moved folder registration settings to `frontmatter.json` file
|
||||
- [#147](https://github.com/estruyf/vscode-front-matter/issues/147): Error boundary added for metadata fields
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- Rendered more hooks than during the previous render in `FileList`
|
||||
- [#142](https://github.com/estruyf/vscode-front-matter/issues/142): Fix for unknown tags where it throws an error
|
||||
- [#143](https://github.com/estruyf/vscode-front-matter/issues/143): Fix for duplicate values in the file list
|
||||
- [#144](https://github.com/estruyf/vscode-front-matter/issues/144): Fix for `toISOString` does not exist on object
|
||||
- [#146](https://github.com/estruyf/vscode-front-matter/issues/146): Date parsing logic added with fallbacks
|
||||
|
||||
## [5.0.0] - 2021-10-07 - [Release Notes](https://beta.frontmatter.codes/updates/v5.0.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- [#113](https://github.com/estruyf/vscode-front-matter/issues/113): Integrating a local DB for media metadata (caption, alt)
|
||||
- [#132](https://github.com/estruyf/vscode-front-matter/issues/132): Major changes to the media dashboard which allows you to navigate through all folders
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#110](https://github.com/estruyf/vscode-front-matter/issues/110): Add support for workspaces with multiple folders
|
||||
- [#117](https://github.com/estruyf/vscode-front-matter/issues/117): Allow to specify a singleline of text in the metadata fields
|
||||
- [#119](https://github.com/estruyf/vscode-front-matter/issues/119): Multi-select support for choice fields
|
||||
- [#121](https://github.com/estruyf/vscode-front-matter/issues/121): Choice fields support ID/title objects as well as a regular string
|
||||
- [#122](https://github.com/estruyf/vscode-front-matter/issues/122): Update the filenames of your media
|
||||
- [#124](https://github.com/estruyf/vscode-front-matter/issues/124): Add new `isPreviewImage` property to the content type field to specify custom preview images
|
||||
- [#126](https://github.com/estruyf/vscode-front-matter/issues/126): Create new content from the available content types
|
||||
- [#127](https://github.com/estruyf/vscode-front-matter/issues/127): Title bar action added to open the dashboard
|
||||
- [#128](https://github.com/estruyf/vscode-front-matter/issues/128): Support for multi-select on image fields added
|
||||
- [#131](https://github.com/estruyf/vscode-front-matter/issues/131): Folder creation support added on media dashboard
|
||||
- [#134](https://github.com/estruyf/vscode-front-matter/issues/134): On startup, the extension checks if local settings can be promoted
|
||||
- [#135](https://github.com/estruyf/vscode-front-matter/issues/135): `Hidden` property added for field configuration
|
||||
- [#137](https://github.com/estruyf/vscode-front-matter/issues/137): Ask to move the `.templates` folder to the new `.frontmatter` folder
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#120](https://github.com/estruyf/vscode-front-matter/issues/120): Choice and number field not updating when set manually in front matter of the file
|
||||
- [#133](https://github.com/estruyf/vscode-front-matter/issues/133): Fix for overriding default content type settings
|
||||
|
||||
## [4.0.1] - 2021-09-24
|
||||
|
||||
- [#114](https://github.com/estruyf/vscode-front-matter/issues/114): Fix for categories/tags provided as string in YAML
|
||||
|
||||
@@ -48,6 +48,10 @@ Our main extension features are:
|
||||
|
||||
> If you see something missing in your article creation flow, please feel free to reach out.
|
||||
|
||||
**Version 5**
|
||||
|
||||
The new media dashboard redesign got introduced + support for setting metadata on media files [v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0).
|
||||
|
||||
**Version 4**
|
||||
|
||||
Support for Team level settings, content-types, and image support. Get to know more at: [v4.0.0 release notes](https://frontmatter.codes/updates/v4_0_0).
|
||||
@@ -96,18 +100,31 @@ If you have the courage to test out the beta features, we made available a beta
|
||||
|
||||
## 👉 Contributors 🤘
|
||||
|
||||
<a href="https://github.com/estruyf/vscode-front-matter/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=estruyf/vscode-front-matter" />
|
||||
</a>
|
||||
<p align="center">
|
||||
<a href="https://github.com/estruyf/vscode-front-matter/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=estruyf/vscode-front-matter" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 🖤 Backers & Sponsors 👇 🤘
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/timschps" title="Tim Schaeps">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/13098307" />
|
||||
</a>
|
||||
<a href="https://github.com/zivbk1" title="Bryan Klein">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/6154767" />
|
||||
</a>
|
||||
<a href="https://github.com/flikteoh" title="FlikTeoh">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/1472065" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
## 🖤 Sponsors
|
||||
|
||||
<p align="center">
|
||||
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
|
||||
<img src="assets/sponsors/powered-by-vercel.png" />
|
||||
<img src="assets/sponsors/powered-by-vercel.png" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
31
README.md
@@ -46,6 +46,10 @@ Our main extension features are:
|
||||
|
||||
> If you see something missing in your article creation flow, please feel free to reach out.
|
||||
|
||||
**Version 5**
|
||||
|
||||
The new media dashboard redesign got introduced + support for setting metadata on media files [v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0).
|
||||
|
||||
**Version 4**
|
||||
|
||||
Support for Team level settings, content-types, and image support. Get to know more at: [v4.0.0 release notes](https://frontmatter.codes/updates/v4_0_0).
|
||||
@@ -94,18 +98,31 @@ If you have the courage to test out the beta features, we made available a beta
|
||||
|
||||
## 👉 Contributors 🤘
|
||||
|
||||
<a href="https://github.com/estruyf/vscode-front-matter/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=estruyf/vscode-front-matter" />
|
||||
</a>
|
||||
<p align="center">
|
||||
<a href="https://github.com/estruyf/vscode-front-matter/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=estruyf/vscode-front-matter" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 🖤 Backers & Sponsors 👇 🤘
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/timschps" title="Tim Schaeps">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/13098307" />
|
||||
</a>
|
||||
<a href="https://github.com/zivbk1" title="Bryan Klein">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/6154767" />
|
||||
</a>
|
||||
<a href="https://github.com/flikteoh" title="FlikTeoh">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/1472065" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
## 🖤 Sponsors
|
||||
|
||||
<p align="center">
|
||||
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
|
||||
<img src="assets/sponsors/powered-by-vercel.png" />
|
||||
<img src="assets/sponsors/powered-by-vercel.png" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,40 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 28 28" style="enable-background:new 0 0 28 28;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{enable-background:new ;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:none;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;}
|
||||
.st3{fill:none;}
|
||||
</style>
|
||||
<g class="st0">
|
||||
<path class="st1" d="M4,11.4H2.2V2.9h3.2v2H4v1.2h1.3V8H4V11.4z"/>
|
||||
<path class="st1" d="M10.9,11.4H9l-0.9-3c0-0.1,0-0.1,0-0.2C8,8.1,8,8,7.9,7.8v0.6v3H6.1V2.9H8c0.8,0,1.4,0.2,1.9,0.6
|
||||
c0.5,0.5,0.8,1.3,0.8,2.2c0,1-0.4,1.7-1.1,2.1L10.9,11.4z M8,6.8h0.1c0.2,0,0.4-0.1,0.5-0.3C8.7,6.3,8.8,6,8.8,5.7
|
||||
c0-0.6-0.3-1-0.8-1l0,0V6.8z"/>
|
||||
<path class="st1" d="M16.5,7.2c0,1.3-0.2,2.4-0.7,3.2c-0.5,0.8-1.1,1.2-1.8,1.2s-1.2-0.3-1.7-0.9c-0.6-0.8-0.9-2-0.9-3.5
|
||||
s0.3-2.7,0.9-3.5c0.5-0.6,1-0.9,1.7-0.9c0.8,0,1.4,0.4,1.9,1.2C16.2,4.7,16.5,5.8,16.5,7.2z M14.6,7.2c0-1.5-0.2-2.3-0.7-2.3
|
||||
c-0.2,0-0.4,0.2-0.5,0.6s-0.2,0.9-0.2,1.7c0,0.7,0.1,1.3,0.2,1.7c0.1,0.4,0.3,0.6,0.5,0.6s0.4-0.2,0.5-0.6
|
||||
C14.5,8.4,14.6,7.9,14.6,7.2z"/>
|
||||
<path class="st1" d="M17.2,11.4V2.9H19l0.9,3C20,6,20,6.2,20.1,6.5c0.1,0.2,0.1,0.5,0.2,0.8L20.5,8c-0.1-0.7-0.1-1.4-0.2-1.9
|
||||
s-0.1-1-0.1-1.3V2.9H22v8.5h-1.7l-0.9-3.1c-0.1-0.3-0.2-0.6-0.3-0.9S19,6.8,18.9,6.6c0,0.6,0.1,1.1,0.1,1.6c0,0.4,0,0.8,0,1.2v2.2
|
||||
h-1.8V11.4z"/>
|
||||
<path class="st1" d="M25.3,11.4h-1.8V4.9h-1v-2h3.9v2h-1.1V11.4z"/>
|
||||
</g>
|
||||
<rect class="st2" width="28" height="28"/>
|
||||
<g class="st0">
|
||||
<path class="st1" d="M2.9,17h0.9l0.6,3c0.1,0.4,0.2,0.8,0.2,1.2c0.1,0.4,0.1,0.8,0.2,1.2c0-0.1,0-0.1,0-0.1v-0.1L5,21.3l0.1-0.8
|
||||
L5.2,20l0.6-3h0.9l0.7,7.5h-1l-0.2-2.6c0-0.1,0-0.2,0-0.3s0-0.2,0-0.2v-1v-0.9l0,0c0,0,0,0,0-0.1v0.2c0,0.2,0,0.3-0.1,0.5
|
||||
s0,0.2-0.1,0.3l-0.1,0.7v0.3l-0.6,3.3H4.5l-0.6-2.8c-0.1-0.4-0.2-0.8-0.2-1.1c-0.1-0.4-0.1-0.8-0.2-1.2l-0.3,5.2h-1L2.9,17z"/>
|
||||
<path class="st1" d="M9.3,17h0.8l1.6,7.5h-1L10.4,23H8.9l-0.3,1.5h-1L9.3,17z M10.3,22.2L10,21c-0.1-0.8-0.3-1.7-0.4-2.6
|
||||
c0,0.5-0.1,0.9-0.2,1.4c-0.1,0.5-0.2,1-0.3,1.5l-0.2,1L10.3,22.2L10.3,22.2z"/>
|
||||
<path class="st1" d="M11.5,17h3.3v0.9h-1.1v6.7h-1v-6.7h-1.2V17z"/>
|
||||
<path class="st1" d="M14.8,17h3.3v0.9H17v6.7h-1v-6.7h-1.2V17z"/>
|
||||
<path class="st1" d="M18.7,17h2.7v0.9h-1.7v2.4h1.5v0.9h-1.5v2.6h1.7v0.9h-2.7V17z"/>
|
||||
<path class="st1" d="M22.3,17h1.3c0.6,0,1,0.1,1.2,0.4c0.3,0.3,0.5,0.9,0.5,1.6c0,0.5-0.1,1-0.3,1.3c-0.2,0.3-0.4,0.5-0.8,0.6
|
||||
l1.4,3.7h-1l-1.4-3.7v3.7h-1L22.3,17L22.3,17z M23.3,20.3c0.4,0,0.7-0.1,0.8-0.3c0.2-0.2,0.2-0.5,0.2-0.9c0-0.2,0-0.4-0.1-0.6
|
||||
s-0.1-0.3-0.2-0.4s-0.2-0.2-0.3-0.2c-0.1,0-0.3-0.1-0.4-0.1h-0.2v2.5H23.3z"/>
|
||||
</g>
|
||||
<rect x="-33.5" y="14" class="st3" width="8.6" height="14"/>
|
||||
</svg>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1249.98 1249.98"><rect x="25" y="25" width="1199.98" height="1199.98" style="fill:none;stroke:#ffffff;stroke-miterlimit:10;stroke-width:50px"/><path d="M171.89,489.56H95.38V127.21H230.6V212.4H171.89v52.8h54.68v81.91H171.89Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M461.79,489.56H379l-37.8-129.08c-.37-2.18-1-5.08-1.93-8.68s-2.05-7.9-3.39-12.91l.55,23.94V489.56H260.33V127.21h78.34q51.75,0,77.43,26.05,32.65,33.32,32.66,94.81,0,65.71-43.85,90.82ZM336.84,295H342q13.21,0,22-12.91t8.81-32.86q0-40.59-33.21-40.6h-2.75Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M691.68,309.56q0,82.85-29.54,134.71-29.35,51.63-76.51,51.63-41.82,0-71.74-39.66Q476.29,406,476.28,305.57q0-96.23,39.26-147.15,29.18-37.78,69.18-37.79,49,0,78,51.17T691.68,309.56Zm-79.44.7q0-98.32-27.16-98.33-13.58,0-21.65,25.81-7.89,23.94-7.89,70.41,0,45.77,7.43,71t20.65,25.23q13.57,0,20.91-24.88Q612.24,354.62,612.24,310.26Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M724.34,489.56V127.21h73l38.35,127.2q3.1,11.27,7.06,25.81t8.72,33.56l7.88,31.92Q855.17,298.52,853,265t-2.2-56.33V127.21h73V489.56h-73l-38.53-133.3q-6.06-21.35-10.92-40t-8.53-35.56q2.38,38.26,3.49,66.65t1.1,49.76v92.46Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M1062.31,489.56H985.8V214H943.6V127.21h162.56V214h-43.85Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M122.7,730.59h35.82l27.36,133.72q5,25.05,9.16,50.2t7.55,52.74q.39-3.6.6-5.62a25.33,25.33,0,0,1,.4-2.87l5.84-37.56,5.23-35.66L219.29,862l24.35-131.39h36.22l28.57,327.72h-40l-7-111.22q-.41-8.49-.71-14.64c-.2-4.11-.3-7.5-.3-10.19l-1.81-43.94-1-40.33c0-.28,0-.88-.1-1.8s-.17-2.16-.3-3.72l-1,6.58q-1.61,11.69-2.91,20.38t-2.32,14.65L245.65,904l-2,11.25-26.16,143.06H189.3L164.75,934.78q-5-24.4-8.95-49.56t-7.14-52.75l-12.08,225.84H97.14Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M395.56,730.59h32.6l66.6,327.72H453.31l-11.67-63.89H380.06l-11.87,63.89H327.94Zm40,229.66L426.35,908q-9.27-53.28-15.1-113.77Q408.43,823.78,404,854t-10.46,64.2l-7.65,42Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M496.17,730.59H632.4v38.63H585.51v289.09h-41V769.22H496.17Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M639,730.59H775.26v38.63H728.38v289.09h-41V769.22H639Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M806.65,730.59H917.93V768H848.5V871.74h61.58V909.1H848.5V1021h69.43v37.35H806.65Z" transform="translate(24 24)" style="fill:#ffffff"/><path d="M964.61,730.59h55.13q34.21,0,50.91,17.19,21.13,22.29,21.13,68.14,0,35.24-11.16,56.56t-31.9,26.43l57.15,159.4h-42.46l-57-160.46v160.46H964.61Zm41.85,145.18q24.35,0,34.41-11.88t10.06-40.12a138.46,138.46,0,0,0-2.11-26.11q-2.11-10.81-6.64-17.61a27.08,27.08,0,0,0-11.67-10,41.58,41.58,0,0,0-17-3.18h-7Z" transform="translate(24 24)" style="fill:#ffffff"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 108 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M4,11.4H2.2V2.9H5.4v2H4V6.1H5.3V8H4Z" transform="translate(1 1)" fill="#01aeb7"/><path d="M10.9,11.4H9l-.9-3V8.2C8,8.1,8,8,7.9,7.8v3.6H6.1V2.9H8a2.88,2.88,0,0,1,1.9.6,3.11,3.11,0,0,1,.8,2.2A2.25,2.25,0,0,1,9.6,7.8ZM8,6.8h.1a.55.55,0,0,0,.5-.3,1.88,1.88,0,0,0,.2-.8c0-.6-.3-1-.8-1H8Z" transform="translate(1 1)" fill="#01aeb7"/><path d="M16.5,7.2a6.08,6.08,0,0,1-.7,3.2A2.14,2.14,0,0,1,14,11.6a2.09,2.09,0,0,1-1.7-.9,5.84,5.84,0,0,1-.9-3.5,5.84,5.84,0,0,1,.9-3.5A2.09,2.09,0,0,1,14,2.8,2.16,2.16,0,0,1,15.9,4,8.24,8.24,0,0,1,16.5,7.2Zm-1.9,0c0-1.5-.2-2.3-.7-2.3-.2,0-.4.2-.5.6a6.53,6.53,0,0,0-.2,1.7,7.18,7.18,0,0,0,.2,1.7c.1.4.3.6.5.6s.4-.2.5-.6A7.93,7.93,0,0,0,14.6,7.2Z" transform="translate(1 1)" fill="#01aeb7"/><path d="M17.2,11.4V2.9H19l.9,3c.1.1.1.3.2.6s.1.5.2.8l.2.7c-.1-.7-.1-1.4-.2-1.9a6.64,6.64,0,0,1-.1-1.3V2.9H22v8.5H20.3l-.9-3.1-.3-.9c-.1-.3-.1-.6-.2-.8,0,.6.1,1.1.1,1.6v3.4H17.2Z" transform="translate(1 1)" fill="#01aeb7"/><path d="M25.3,11.4H23.5V4.9h-1v-2h3.9v2H25.3Z" transform="translate(1 1)" fill="#01aeb7"/><rect x="1" y="1" width="28" height="28" fill="none" stroke="#01aeb7" stroke-miterlimit="10" stroke-width="2"/><path d="M2.9,17h.9l.6,3a5,5,0,0,1,.2,1.2c.1.4.1.8.2,1.2v-.2l.2-.9.1-.8.1-.5.6-3h.9l.7,7.5h-1l-.2-2.6V19.5h0v.1a.9.9,0,0,1-.1.5c-.1.2,0,.2-.1.3l-.1.7v.3l-.6,3.3H4.5l-.6-2.8a5.16,5.16,0,0,1-.2-1.1c-.1-.4-.1-.8-.2-1.2l-.3,5.2h-1Z" transform="translate(1 1)" fill="#01aeb7"/><path d="M9.3,17h.8l1.6,7.5h-1L10.4,23H8.9l-.3,1.5h-1Zm1,5.2L10,21c-.1-.8-.3-1.7-.4-2.6a6.75,6.75,0,0,1-.2,1.4l-.3,1.5-.2,1h1.4Z" transform="translate(1 1)" fill="#01aeb7"/><path d="M11.5,17h3.3v.9H13.7v6.7h-1V17.9H11.5Z" transform="translate(1 1)" fill="#01aeb7"/><path d="M14.8,17h3.3v.9H17v6.7H16V17.9H14.8Z" transform="translate(1 1)" fill="#01aeb7"/><path d="M18.7,17h2.7v.9H19.7v2.4h1.5v.9H19.7v2.6h1.7v.9H18.7Z" transform="translate(1 1)" fill="#01aeb7"/><path d="M22.3,17h1.3c.6,0,1,.1,1.2.4a2.35,2.35,0,0,1,.5,1.6,2.5,2.5,0,0,1-.3,1.3,1.24,1.24,0,0,1-.8.6l1.4,3.7h-1l-1.4-3.7v3.7h-1V17Zm1,3.3c.4,0,.7-.1.8-.3s.2-.5.2-.9a1.27,1.27,0,0,0-.1-.6c-.1-.2-.1-.3-.2-.4s-.2-.2-.3-.2-.3-.1-.4-.1h-.2v2.5Z" transform="translate(1 1)" fill="#01aeb7"/></svg>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1249.98 1249.98"><rect x="25" y="25" width="1199.98" height="1199.98" style="fill:none;stroke:#AD0670;stroke-miterlimit:10;stroke-width:50px"/><path d="M171.89,489.56H95.38V127.21H230.6V212.4H171.89v52.8h54.68v81.91H171.89Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M461.79,489.56H379l-37.8-129.08c-.37-2.18-1-5.08-1.93-8.68s-2.05-7.9-3.39-12.91l.55,23.94V489.56H260.33V127.21h78.34q51.75,0,77.43,26.05,32.65,33.32,32.66,94.81,0,65.71-43.85,90.82ZM336.84,295H342q13.21,0,22-12.91t8.81-32.86q0-40.59-33.21-40.6h-2.75Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M691.68,309.56q0,82.85-29.54,134.71-29.35,51.63-76.51,51.63-41.82,0-71.74-39.66Q476.29,406,476.28,305.57q0-96.23,39.26-147.15,29.18-37.78,69.18-37.79,49,0,78,51.17T691.68,309.56Zm-79.44.7q0-98.32-27.16-98.33-13.58,0-21.65,25.81-7.89,23.94-7.89,70.41,0,45.77,7.43,71t20.65,25.23q13.57,0,20.91-24.88Q612.24,354.62,612.24,310.26Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M724.34,489.56V127.21h73l38.35,127.2q3.1,11.27,7.06,25.81t8.72,33.56l7.88,31.92Q855.17,298.52,853,265t-2.2-56.33V127.21h73V489.56h-73l-38.53-133.3q-6.06-21.35-10.92-40t-8.53-35.56q2.38,38.26,3.49,66.65t1.1,49.76v92.46Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M1062.31,489.56H985.8V214H943.6V127.21h162.56V214h-43.85Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M122.7,730.59h35.82l27.36,133.72q5,25.05,9.16,50.2t7.55,52.74q.39-3.6.6-5.62a25.33,25.33,0,0,1,.4-2.87l5.84-37.56,5.23-35.66L219.29,862l24.35-131.39h36.22l28.57,327.72h-40l-7-111.22q-.41-8.49-.71-14.64c-.2-4.11-.3-7.5-.3-10.19l-1.81-43.94-1-40.33c0-.28,0-.88-.1-1.8s-.17-2.16-.3-3.72l-1,6.58q-1.61,11.69-2.91,20.38t-2.32,14.65L245.65,904l-2,11.25-26.16,143.06H189.3L164.75,934.78q-5-24.4-8.95-49.56t-7.14-52.75l-12.08,225.84H97.14Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M395.56,730.59h32.6l66.6,327.72H453.31l-11.67-63.89H380.06l-11.87,63.89H327.94Zm40,229.66L426.35,908q-9.27-53.28-15.1-113.77Q408.43,823.78,404,854t-10.46,64.2l-7.65,42Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M496.17,730.59H632.4v38.63H585.51v289.09h-41V769.22H496.17Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M639,730.59H775.26v38.63H728.38v289.09h-41V769.22H639Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M806.65,730.59H917.93V768H848.5V871.74h61.58V909.1H848.5V1021h69.43v37.35H806.65Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M964.61,730.59h55.13q34.21,0,50.91,17.19,21.13,22.29,21.13,68.14,0,35.24-11.16,56.56t-31.9,26.43l57.15,159.4h-42.46l-57-160.46v160.46H964.61Zm41.85,145.18q24.35,0,34.41-11.88t10.06-40.12a138.46,138.46,0,0,0-2.11-26.11q-2.11-10.81-6.64-17.61a27.08,27.08,0,0,0-11.67-10,41.58,41.58,0,0,0-17-3.18h-7Z" transform="translate(24 24)" style="fill:#AD0670"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -1,44 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 28 28" style="color:#ad0670" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st1 {
|
||||
fill: none;
|
||||
stroke: #ad0670;
|
||||
stroke-width: 2;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
<g class="st0">
|
||||
<path fill="currentcolor" d="M4,11.4H2.2V2.9h3.2v2H4v1.2h1.3V8H4V11.4z" />
|
||||
<path fill="currentcolor" d="M10.9,11.4H9l-0.9-3c0-0.1,0-0.1,0-0.2C8,8.1,8,8,7.9,7.8l0,0.6v3H6.1V2.9H8c0.8,0,1.4,0.2,1.9,0.6
|
||||
c0.5,0.5,0.8,1.3,0.8,2.2c0,1-0.4,1.7-1.1,2.1L10.9,11.4z M8,6.8h0.1c0.2,0,0.4-0.1,0.5-0.3C8.7,6.3,8.8,6,8.8,5.7
|
||||
c0-0.6-0.3-1-0.8-1H8V6.8z" />
|
||||
<path fill="currentcolor" d="M16.5,7.2c0,1.3-0.2,2.4-0.7,3.2c-0.5,0.8-1.1,1.2-1.8,1.2c-0.7,0-1.2-0.3-1.7-0.9c-0.6-0.8-0.9-2-0.9-3.5
|
||||
c0-1.5,0.3-2.7,0.9-3.5c0.5-0.6,1-0.9,1.7-0.9c0.8,0,1.4,0.4,1.9,1.2C16.2,4.7,16.5,5.8,16.5,7.2z M14.6,7.2c0-1.5-0.2-2.3-0.7-2.3
|
||||
c-0.2,0-0.4,0.2-0.5,0.6c-0.1,0.4-0.2,0.9-0.2,1.7c0,0.7,0.1,1.3,0.2,1.7c0.1,0.4,0.3,0.6,0.5,0.6c0.2,0,0.4-0.2,0.5-0.6
|
||||
C14.5,8.4,14.6,7.9,14.6,7.2z" />
|
||||
<path fill="currentcolor" d="M17.2,11.4V2.9H19l0.9,3C20,6,20,6.2,20.1,6.5c0.1,0.2,0.1,0.5,0.2,0.8L20.5,8c-0.1-0.7-0.1-1.4-0.2-1.9s-0.1-1-0.1-1.3
|
||||
V2.9H22v8.5h-1.7l-0.9-3.1c-0.1-0.3-0.2-0.6-0.3-0.9s-0.1-0.6-0.2-0.8c0,0.6,0.1,1.1,0.1,1.6c0,0.4,0,0.8,0,1.2v2.2H17.2z" />
|
||||
<path fill="currentcolor" d="M25.3,11.4h-1.8V4.9h-1v-2h3.9v2h-1.1V11.4z" />
|
||||
</g>
|
||||
|
||||
<rect class="st1" width="28" height="28" />
|
||||
|
||||
<g class="st0">
|
||||
<path fill="currentcolor" d="M2.9,17h0.9L4.4,20c0.1,0.4,0.2,0.8,0.2,1.2c0.1,0.4,0.1,0.8,0.2,1.2c0-0.1,0-0.1,0-0.1c0,0,0-0.1,0-0.1L5,21.3l0.1-0.8
|
||||
L5.2,20l0.6-3h0.9l0.7,7.5h-1l-0.2-2.6c0-0.1,0-0.2,0-0.3s0-0.2,0-0.2l0-1l0-0.9c0,0,0,0,0,0c0,0,0,0,0-0.1l0,0.2
|
||||
c0,0.2,0,0.3-0.1,0.5s0,0.2-0.1,0.3l-0.1,0.7l0,0.3l-0.6,3.3H4.5l-0.6-2.8c-0.1-0.4-0.2-0.8-0.2-1.1c-0.1-0.4-0.1-0.8-0.2-1.2
|
||||
l-0.3,5.2h-1L2.9,17z" />
|
||||
<path fill="currentcolor" d="M9.3,17h0.8l1.6,7.5h-1L10.4,23H8.9l-0.3,1.5h-1L9.3,17z M10.3,22.2L10,21c-0.1-0.8-0.3-1.7-0.4-2.6c0,0.5-0.1,0.9-0.2,1.4
|
||||
c-0.1,0.5-0.2,1-0.3,1.5l-0.2,1H10.3z" />
|
||||
<path fill="currentcolor" d="M11.5,17h3.3v0.9h-1.1v6.7h-1v-6.7h-1.2V17z" />
|
||||
<path fill="currentcolor" d="M14.8,17h3.3v0.9H17v6.7h-1v-6.7h-1.2V17z" />
|
||||
<path fill="currentcolor" d="M18.7,17h2.7v0.9h-1.7v2.4h1.5v0.9h-1.5v2.6h1.7v0.9h-2.7V17z" />
|
||||
<path fill="currentcolor" d="M22.3,17h1.3c0.6,0,1,0.1,1.2,0.4c0.3,0.3,0.5,0.9,0.5,1.6c0,0.5-0.1,1-0.3,1.3c-0.2,0.3-0.4,0.5-0.8,0.6l1.4,3.7h-1
|
||||
l-1.4-3.7v3.7h-1V17z M23.3,20.3c0.4,0,0.7-0.1,0.8-0.3c0.2-0.2,0.2-0.5,0.2-0.9c0-0.2,0-0.4-0.1-0.6s-0.1-0.3-0.2-0.4
|
||||
s-0.2-0.2-0.3-0.2c-0.1,0-0.3-0.1-0.4-0.1h-0.2V20.3z" />
|
||||
</g>
|
||||
</svg>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1249.98 1249.98"><rect x="25" y="25" width="1199.98" height="1199.98" style="fill:none;stroke:#AD0670;stroke-miterlimit:10;stroke-width:50px"/><path d="M171.89,489.56H95.38V127.21H230.6V212.4H171.89v52.8h54.68v81.91H171.89Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M461.79,489.56H379l-37.8-129.08c-.37-2.18-1-5.08-1.93-8.68s-2.05-7.9-3.39-12.91l.55,23.94V489.56H260.33V127.21h78.34q51.75,0,77.43,26.05,32.65,33.32,32.66,94.81,0,65.71-43.85,90.82ZM336.84,295H342q13.21,0,22-12.91t8.81-32.86q0-40.59-33.21-40.6h-2.75Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M691.68,309.56q0,82.85-29.54,134.71-29.35,51.63-76.51,51.63-41.82,0-71.74-39.66Q476.29,406,476.28,305.57q0-96.23,39.26-147.15,29.18-37.78,69.18-37.79,49,0,78,51.17T691.68,309.56Zm-79.44.7q0-98.32-27.16-98.33-13.58,0-21.65,25.81-7.89,23.94-7.89,70.41,0,45.77,7.43,71t20.65,25.23q13.57,0,20.91-24.88Q612.24,354.62,612.24,310.26Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M724.34,489.56V127.21h73l38.35,127.2q3.1,11.27,7.06,25.81t8.72,33.56l7.88,31.92Q855.17,298.52,853,265t-2.2-56.33V127.21h73V489.56h-73l-38.53-133.3q-6.06-21.35-10.92-40t-8.53-35.56q2.38,38.26,3.49,66.65t1.1,49.76v92.46Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M1062.31,489.56H985.8V214H943.6V127.21h162.56V214h-43.85Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M122.7,730.59h35.82l27.36,133.72q5,25.05,9.16,50.2t7.55,52.74q.39-3.6.6-5.62a25.33,25.33,0,0,1,.4-2.87l5.84-37.56,5.23-35.66L219.29,862l24.35-131.39h36.22l28.57,327.72h-40l-7-111.22q-.41-8.49-.71-14.64c-.2-4.11-.3-7.5-.3-10.19l-1.81-43.94-1-40.33c0-.28,0-.88-.1-1.8s-.17-2.16-.3-3.72l-1,6.58q-1.61,11.69-2.91,20.38t-2.32,14.65L245.65,904l-2,11.25-26.16,143.06H189.3L164.75,934.78q-5-24.4-8.95-49.56t-7.14-52.75l-12.08,225.84H97.14Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M395.56,730.59h32.6l66.6,327.72H453.31l-11.67-63.89H380.06l-11.87,63.89H327.94Zm40,229.66L426.35,908q-9.27-53.28-15.1-113.77Q408.43,823.78,404,854t-10.46,64.2l-7.65,42Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M496.17,730.59H632.4v38.63H585.51v289.09h-41V769.22H496.17Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M639,730.59H775.26v38.63H728.38v289.09h-41V769.22H639Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M806.65,730.59H917.93V768H848.5V871.74h61.58V909.1H848.5V1021h69.43v37.35H806.65Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M964.61,730.59h55.13q34.21,0,50.91,17.19,21.13,22.29,21.13,68.14,0,35.24-11.16,56.56t-31.9,26.43l57.15,159.4h-42.46l-57-160.46v160.46H964.61Zm41.85,145.18q24.35,0,34.41-11.88t10.06-40.12a138.46,138.46,0,0,0-2.11-26.11q-2.11-10.81-6.64-17.61a27.08,27.08,0,0,0-11.67-10,41.58,41.58,0,0,0-17-3.18h-7Z" transform="translate(24 24)" style="fill:#AD0670"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.9 KiB |
BIN
assets/frontmatter-short-min.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
16
assets/frontmatter-short-min.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#02AEB7;stroke-width:50;stroke-miterlimit:10;}
|
||||
.st1{fill:#02AEB7;}
|
||||
</style>
|
||||
<rect x="25" y="25" class="st0" width="1200" height="1200"/>
|
||||
<path class="st1" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path class="st1" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
|
||||
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
|
||||
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
|
||||
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
|
||||
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 5.7 KiB |
1
assets/frontmatter-teal-min.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1249.98 1249.98"><rect x="25" y="25" width="1199.98" height="1199.98" style="fill:none;stroke:#02aeb7;stroke-miterlimit:10;stroke-width:50px"/><path d="M171.89,489.56H95.38V127.21H230.6V212.4H171.89v52.8h54.68v81.91H171.89Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M461.79,489.56H379l-37.8-129.08c-.37-2.18-1-5.08-1.93-8.68s-2.05-7.9-3.39-12.91l.55,23.94V489.56H260.33V127.21h78.34q51.75,0,77.43,26.05,32.65,33.32,32.66,94.81,0,65.71-43.85,90.82ZM336.84,295H342q13.21,0,22-12.91t8.81-32.86q0-40.59-33.21-40.6h-2.75Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M691.68,309.56q0,82.85-29.54,134.71-29.35,51.63-76.51,51.63-41.82,0-71.74-39.66Q476.29,406,476.28,305.57q0-96.23,39.26-147.15,29.18-37.78,69.18-37.79,49,0,78,51.17T691.68,309.56Zm-79.44.7q0-98.32-27.16-98.33-13.58,0-21.65,25.81-7.89,23.94-7.89,70.41,0,45.77,7.43,71t20.65,25.23q13.57,0,20.91-24.88Q612.24,354.62,612.24,310.26Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M724.34,489.56V127.21h73l38.35,127.2q3.1,11.27,7.06,25.81t8.72,33.56l7.88,31.92Q855.17,298.52,853,265t-2.2-56.33V127.21h73V489.56h-73l-38.53-133.3q-6.06-21.35-10.92-40t-8.53-35.56q2.38,38.26,3.49,66.65t1.1,49.76v92.46Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M1062.31,489.56H985.8V214H943.6V127.21h162.56V214h-43.85Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M122.7,730.59h35.82l27.36,133.72q5,25.05,9.16,50.2t7.55,52.74q.39-3.6.6-5.62a25.33,25.33,0,0,1,.4-2.87l5.84-37.56,5.23-35.66L219.29,862l24.35-131.39h36.22l28.57,327.72h-40l-7-111.22q-.41-8.49-.71-14.64c-.2-4.11-.3-7.5-.3-10.19l-1.81-43.94-1-40.33c0-.28,0-.88-.1-1.8s-.17-2.16-.3-3.72l-1,6.58q-1.61,11.69-2.91,20.38t-2.32,14.65L245.65,904l-2,11.25-26.16,143.06H189.3L164.75,934.78q-5-24.4-8.95-49.56t-7.14-52.75l-12.08,225.84H97.14Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M395.56,730.59h32.6l66.6,327.72H453.31l-11.67-63.89H380.06l-11.87,63.89H327.94Zm40,229.66L426.35,908q-9.27-53.28-15.1-113.77Q408.43,823.78,404,854t-10.46,64.2l-7.65,42Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M496.17,730.59H632.4v38.63H585.51v289.09h-41V769.22H496.17Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M639,730.59H775.26v38.63H728.38v289.09h-41V769.22H639Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M806.65,730.59H917.93V768H848.5V871.74h61.58V909.1H848.5V1021h69.43v37.35H806.65Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M964.61,730.59h55.13q34.21,0,50.91,17.19,21.13,22.29,21.13,68.14,0,35.24-11.16,56.56t-31.9,26.43l57.15,159.4h-42.46l-57-160.46v160.46H964.61Zm41.85,145.18q24.35,0,34.41-11.88t10.06-40.12a138.46,138.46,0,0,0-2.11-26.11q-2.11-10.81-6.64-17.61a27.08,27.08,0,0,0-11.67-10,41.58,41.58,0,0,0-17-3.18h-7Z" transform="translate(24 24)" style="fill:#02aeb7"/></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
@@ -1,39 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 28 28" style="enable-background:new 0 0 28 28;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{enable-background:new ;}
|
||||
.st1{fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
|
||||
.st2{fill:none;}
|
||||
</style>
|
||||
<g class="st0">
|
||||
<path d="M4,11.4H2.2V2.9h3.2v2H4v1.2h1.3V8H4V11.4z"/>
|
||||
<path d="M10.9,11.4H9l-0.9-3c0-0.1,0-0.1,0-0.2C8,8.1,8,8,7.9,7.8l0,0.6v3H6.1V2.9H8c0.8,0,1.4,0.2,1.9,0.6
|
||||
c0.5,0.5,0.8,1.3,0.8,2.2c0,1-0.4,1.7-1.1,2.1L10.9,11.4z M8,6.8h0.1c0.2,0,0.4-0.1,0.5-0.3C8.7,6.3,8.8,6,8.8,5.7
|
||||
c0-0.6-0.3-1-0.8-1H8V6.8z"/>
|
||||
<path d="M16.5,7.2c0,1.3-0.2,2.4-0.7,3.2c-0.5,0.8-1.1,1.2-1.8,1.2c-0.7,0-1.2-0.3-1.7-0.9c-0.6-0.8-0.9-2-0.9-3.5
|
||||
c0-1.5,0.3-2.7,0.9-3.5c0.5-0.6,1-0.9,1.7-0.9c0.8,0,1.4,0.4,1.9,1.2C16.2,4.7,16.5,5.8,16.5,7.2z M14.6,7.2c0-1.5-0.2-2.3-0.7-2.3
|
||||
c-0.2,0-0.4,0.2-0.5,0.6c-0.1,0.4-0.2,0.9-0.2,1.7c0,0.7,0.1,1.3,0.2,1.7c0.1,0.4,0.3,0.6,0.5,0.6c0.2,0,0.4-0.2,0.5-0.6
|
||||
C14.5,8.4,14.6,7.9,14.6,7.2z"/>
|
||||
<path d="M17.2,11.4V2.9H19l0.9,3C20,6,20,6.2,20.1,6.5c0.1,0.2,0.1,0.5,0.2,0.8L20.5,8c-0.1-0.7-0.1-1.4-0.2-1.9s-0.1-1-0.1-1.3
|
||||
V2.9H22v8.5h-1.7l-0.9-3.1c-0.1-0.3-0.2-0.6-0.3-0.9s-0.1-0.6-0.2-0.8c0,0.6,0.1,1.1,0.1,1.6c0,0.4,0,0.8,0,1.2v2.2H17.2z"/>
|
||||
<path d="M25.3,11.4h-1.8V4.9h-1v-2h3.9v2h-1.1V11.4z"/>
|
||||
</g>
|
||||
<rect class="st1" width="28" height="28"/>
|
||||
<g class="st0">
|
||||
<path d="M2.9,17h0.9L4.4,20c0.1,0.4,0.2,0.8,0.2,1.2c0.1,0.4,0.1,0.8,0.2,1.2c0-0.1,0-0.1,0-0.1c0,0,0-0.1,0-0.1L5,21.3l0.1-0.8
|
||||
L5.2,20l0.6-3h0.9l0.7,7.5h-1l-0.2-2.6c0-0.1,0-0.2,0-0.3s0-0.2,0-0.2l0-1l0-0.9c0,0,0,0,0,0c0,0,0,0,0-0.1l0,0.2
|
||||
c0,0.2,0,0.3-0.1,0.5s0,0.2-0.1,0.3l-0.1,0.7l0,0.3l-0.6,3.3H4.5l-0.6-2.8c-0.1-0.4-0.2-0.8-0.2-1.1c-0.1-0.4-0.1-0.8-0.2-1.2
|
||||
l-0.3,5.2h-1L2.9,17z"/>
|
||||
<path d="M9.3,17h0.8l1.6,7.5h-1L10.4,23H8.9l-0.3,1.5h-1L9.3,17z M10.3,22.2L10,21c-0.1-0.8-0.3-1.7-0.4-2.6c0,0.5-0.1,0.9-0.2,1.4
|
||||
c-0.1,0.5-0.2,1-0.3,1.5l-0.2,1H10.3z"/>
|
||||
<path d="M11.5,17h3.3v0.9h-1.1v6.7h-1v-6.7h-1.2V17z"/>
|
||||
<path d="M14.8,17h3.3v0.9H17v6.7h-1v-6.7h-1.2V17z"/>
|
||||
<path d="M18.7,17h2.7v0.9h-1.7v2.4h1.5v0.9h-1.5v2.6h1.7v0.9h-2.7V17z"/>
|
||||
<path d="M22.3,17h1.3c0.6,0,1,0.1,1.2,0.4c0.3,0.3,0.5,0.9,0.5,1.6c0,0.5-0.1,1-0.3,1.3c-0.2,0.3-0.4,0.5-0.8,0.6l1.4,3.7h-1
|
||||
l-1.4-3.7v3.7h-1V17z M23.3,20.3c0.4,0,0.7-0.1,0.8-0.3c0.2-0.2,0.2-0.5,0.2-0.9c0-0.2,0-0.4-0.1-0.6s-0.1-0.3-0.2-0.4
|
||||
s-0.2-0.2-0.3-0.2c-0.1,0-0.3-0.1-0.4-0.1h-0.2V20.3z"/>
|
||||
</g>
|
||||
<rect x="-33.5" y="14" class="st2" width="8.6" height="14"/>
|
||||
</svg>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1249.98 1249.98"><rect x="25" y="25" width="1199.98" height="1199.98" style="fill:none;stroke:#000000;stroke-miterlimit:10;stroke-width:50px"/><path d="M171.89,489.56H95.38V127.21H230.6V212.4H171.89v52.8h54.68v81.91H171.89Z" transform="translate(24 24)" style="fill:#000000"/><path d="M461.79,489.56H379l-37.8-129.08c-.37-2.18-1-5.08-1.93-8.68s-2.05-7.9-3.39-12.91l.55,23.94V489.56H260.33V127.21h78.34q51.75,0,77.43,26.05,32.65,33.32,32.66,94.81,0,65.71-43.85,90.82ZM336.84,295H342q13.21,0,22-12.91t8.81-32.86q0-40.59-33.21-40.6h-2.75Z" transform="translate(24 24)" style="fill:#000000"/><path d="M691.68,309.56q0,82.85-29.54,134.71-29.35,51.63-76.51,51.63-41.82,0-71.74-39.66Q476.29,406,476.28,305.57q0-96.23,39.26-147.15,29.18-37.78,69.18-37.79,49,0,78,51.17T691.68,309.56Zm-79.44.7q0-98.32-27.16-98.33-13.58,0-21.65,25.81-7.89,23.94-7.89,70.41,0,45.77,7.43,71t20.65,25.23q13.57,0,20.91-24.88Q612.24,354.62,612.24,310.26Z" transform="translate(24 24)" style="fill:#000000"/><path d="M724.34,489.56V127.21h73l38.35,127.2q3.1,11.27,7.06,25.81t8.72,33.56l7.88,31.92Q855.17,298.52,853,265t-2.2-56.33V127.21h73V489.56h-73l-38.53-133.3q-6.06-21.35-10.92-40t-8.53-35.56q2.38,38.26,3.49,66.65t1.1,49.76v92.46Z" transform="translate(24 24)" style="fill:#000000"/><path d="M1062.31,489.56H985.8V214H943.6V127.21h162.56V214h-43.85Z" transform="translate(24 24)" style="fill:#000000"/><path d="M122.7,730.59h35.82l27.36,133.72q5,25.05,9.16,50.2t7.55,52.74q.39-3.6.6-5.62a25.33,25.33,0,0,1,.4-2.87l5.84-37.56,5.23-35.66L219.29,862l24.35-131.39h36.22l28.57,327.72h-40l-7-111.22q-.41-8.49-.71-14.64c-.2-4.11-.3-7.5-.3-10.19l-1.81-43.94-1-40.33c0-.28,0-.88-.1-1.8s-.17-2.16-.3-3.72l-1,6.58q-1.61,11.69-2.91,20.38t-2.32,14.65L245.65,904l-2,11.25-26.16,143.06H189.3L164.75,934.78q-5-24.4-8.95-49.56t-7.14-52.75l-12.08,225.84H97.14Z" transform="translate(24 24)" style="fill:#000000"/><path d="M395.56,730.59h32.6l66.6,327.72H453.31l-11.67-63.89H380.06l-11.87,63.89H327.94Zm40,229.66L426.35,908q-9.27-53.28-15.1-113.77Q408.43,823.78,404,854t-10.46,64.2l-7.65,42Z" transform="translate(24 24)" style="fill:#000000"/><path d="M496.17,730.59H632.4v38.63H585.51v289.09h-41V769.22H496.17Z" transform="translate(24 24)" style="fill:#000000"/><path d="M639,730.59H775.26v38.63H728.38v289.09h-41V769.22H639Z" transform="translate(24 24)" style="fill:#000000"/><path d="M806.65,730.59H917.93V768H848.5V871.74h61.58V909.1H848.5V1021h69.43v37.35H806.65Z" transform="translate(24 24)" style="fill:#000000"/><path d="M964.61,730.59h55.13q34.21,0,50.91,17.19,21.13,22.29,21.13,68.14,0,35.24-11.16,56.56t-31.9,26.43l57.15,159.4h-42.46l-57-160.46v160.46H964.61Zm41.85,145.18q24.35,0,34.41-11.88t10.06-40.12a138.46,138.46,0,0,0-2.11-26.11q-2.11-10.81-6.64-17.61a27.08,27.08,0,0,0-11.67-10,41.58,41.58,0,0,0-17-3.18h-7Z" transform="translate(24 24)" style="fill:#000000"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.9 KiB |
9
assets/icons/blockquote-dark.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 15h15" />
|
||||
<path d="M21 19h-15" />
|
||||
<path d="M15 11h6" />
|
||||
<path d="M21 7h-6" />
|
||||
<path d="M9 9h1a1 1 0 1 1 -1 1v-2.5a2 2 0 0 1 2 -2" />
|
||||
<path d="M3 9h1a1 1 0 1 1 -1 1v-2.5a2 2 0 0 1 2 -2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 449 B |
9
assets/icons/blockquote-light.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 15h15" />
|
||||
<path d="M21 19h-15" />
|
||||
<path d="M15 11h6" />
|
||||
<path d="M21 7h-6" />
|
||||
<path d="M9 9h1a1 1 0 1 1 -1 1v-2.5a2 2 0 0 1 2 -2" />
|
||||
<path d="M3 9h1a1 1 0 1 1 -1 1v-2.5a2 2 0 0 1 2 -2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 449 B |
5
assets/icons/bold-dark.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 5h6a3.5 3.5 0 0 1 0 7h-6z" />
|
||||
<path d="M13 12h1a3.5 3.5 0 0 1 0 7h-7v-7" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 329 B |
5
assets/icons/bold-light.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 5h6a3.5 3.5 0 0 1 0 7h-6z" />
|
||||
<path d="M13 12h1a3.5 3.5 0 0 1 0 7h-7v-7" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 329 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="32px" height="32px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#F3EFF5">
|
||||
<svg width="32px" height="32px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#C5C5C5">
|
||||
<path d="M9 9H4v1h5V9z" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3l1-1h7l1 1v7l-1 1h-2v2l-1 1H3l-1-1V6l1-1h2V3zm1 2h4l1 1v4h2V3H6v2zm4 1H3v7h7V6z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 277 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="32px" height="32px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentcolor">
|
||||
<svg width="32px" height="32px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#424242">
|
||||
<path d="M9 9H4v1h5V9z" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3l1-1h7l1 1v7l-1 1h-2v2l-1 1H3l-1-1V6l1-1h2V3zm1 2h4l1 1v4h2V3H6v2zm4 1H3v7h7V6z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 277 B |
6
assets/icons/code-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<polyline points="7 8 3 12 7 16" />
|
||||
<polyline points="17 8 21 12 17 16" />
|
||||
<line x1="14" y1="4" x2="10" y2="20" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 358 B |
6
assets/icons/code-light.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<polyline points="7 8 3 12 7 16" />
|
||||
<polyline points="17 8 21 12 17 16" />
|
||||
<line x1="14" y1="4" x2="10" y2="20" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 358 B |
7
assets/icons/codeblock-dark.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 12h6" />
|
||||
<path d="M12 9v6" />
|
||||
<path d="M6 19a2 2 0 0 1 -2 -2v-4l-1 -1l1 -1v-4a2 2 0 0 1 2 -2" />
|
||||
<path d="M18 19a2 2 0 0 0 2 -2v-4l1 -1l-1 -1v-4a2 2 0 0 0 -2 -2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 422 B |
7
assets/icons/codeblock-light.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 12h6" />
|
||||
<path d="M12 9v6" />
|
||||
<path d="M6 19a2 2 0 0 1 -2 -2v-4l-1 -1l1 -1v-4a2 2 0 0 1 2 -2" />
|
||||
<path d="M18 19a2 2 0 0 0 2 -2v-4l1 -1l-1 -1v-4a2 2 0 0 0 -2 -2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 422 B |
1
assets/icons/frontmatter-dark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M4,11.4H2.2V2.9H5.4v2H4V6.1H5.3V8H4Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M10.9,11.4H9l-.9-3V8.2C8,8.1,8,8,7.9,7.8v3.6H6.1V2.9H8a2.88,2.88,0,0,1,1.9.6,3.11,3.11,0,0,1,.8,2.2A2.25,2.25,0,0,1,9.6,7.8ZM8,6.8h.1a.55.55,0,0,0,.5-.3,1.88,1.88,0,0,0,.2-.8c0-.6-.3-1-.8-1H8Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M16.5,7.2a6.08,6.08,0,0,1-.7,3.2A2.14,2.14,0,0,1,14,11.6a2.09,2.09,0,0,1-1.7-.9,5.84,5.84,0,0,1-.9-3.5,5.84,5.84,0,0,1,.9-3.5A2.09,2.09,0,0,1,14,2.8,2.16,2.16,0,0,1,15.9,4,8.24,8.24,0,0,1,16.5,7.2Zm-1.9,0c0-1.5-.2-2.3-.7-2.3-.2,0-.4.2-.5.6a6.53,6.53,0,0,0-.2,1.7,7.18,7.18,0,0,0,.2,1.7c.1.4.3.6.5.6s.4-.2.5-.6A7.93,7.93,0,0,0,14.6,7.2Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M17.2,11.4V2.9H19l.9,3c.1.1.1.3.2.6s.1.5.2.8l.2.7c-.1-.7-.1-1.4-.2-1.9a6.64,6.64,0,0,1-.1-1.3V2.9H22v8.5H20.3l-.9-3.1-.3-.9c-.1-.3-.1-.6-.2-.8,0,.6.1,1.1.1,1.6v3.4H17.2Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M25.3,11.4H23.5V4.9h-1v-2h3.9v2H25.3Z" transform="translate(1 1)" fill="#C5C5C5"/><rect x="1" y="1" width="28" height="28" fill="none" stroke="#C5C5C5" stroke-miterlimit="10" stroke-width="2"/><path d="M2.9,17h.9l.6,3a5,5,0,0,1,.2,1.2c.1.4.1.8.2,1.2v-.2l.2-.9.1-.8.1-.5.6-3h.9l.7,7.5h-1l-.2-2.6V19.5h0v.1a.9.9,0,0,1-.1.5c-.1.2,0,.2-.1.3l-.1.7v.3l-.6,3.3H4.5l-.6-2.8a5.16,5.16,0,0,1-.2-1.1c-.1-.4-.1-.8-.2-1.2l-.3,5.2h-1Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M9.3,17h.8l1.6,7.5h-1L10.4,23H8.9l-.3,1.5h-1Zm1,5.2L10,21c-.1-.8-.3-1.7-.4-2.6a6.75,6.75,0,0,1-.2,1.4l-.3,1.5-.2,1h1.4Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M11.5,17h3.3v.9H13.7v6.7h-1V17.9H11.5Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M14.8,17h3.3v.9H17v6.7H16V17.9H14.8Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M18.7,17h2.7v.9H19.7v2.4h1.5v.9H19.7v2.6h1.7v.9H18.7Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M22.3,17h1.3c.6,0,1,.1,1.2.4a2.35,2.35,0,0,1,.5,1.6,2.5,2.5,0,0,1-.3,1.3,1.24,1.24,0,0,1-.8.6l1.4,3.7h-1l-1.4-3.7v3.7h-1V17Zm1,3.3c.4,0,.7-.1.8-.3s.2-.5.2-.9a1.27,1.27,0,0,0-.1-.6c-.1-.2-.1-.3-.2-.4s-.2-.2-.3-.2-.3-.1-.4-.1h-.2v2.5Z" transform="translate(1 1)" fill="#C5C5C5"/></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
1
assets/icons/frontmatter-light.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M4,11.4H2.2V2.9H5.4v2H4V6.1H5.3V8H4Z" transform="translate(1 1)" fill="#424242"/><path d="M10.9,11.4H9l-.9-3V8.2C8,8.1,8,8,7.9,7.8v3.6H6.1V2.9H8a2.88,2.88,0,0,1,1.9.6,3.11,3.11,0,0,1,.8,2.2A2.25,2.25,0,0,1,9.6,7.8ZM8,6.8h.1a.55.55,0,0,0,.5-.3,1.88,1.88,0,0,0,.2-.8c0-.6-.3-1-.8-1H8Z" transform="translate(1 1)" fill="#424242"/><path d="M16.5,7.2a6.08,6.08,0,0,1-.7,3.2A2.14,2.14,0,0,1,14,11.6a2.09,2.09,0,0,1-1.7-.9,5.84,5.84,0,0,1-.9-3.5,5.84,5.84,0,0,1,.9-3.5A2.09,2.09,0,0,1,14,2.8,2.16,2.16,0,0,1,15.9,4,8.24,8.24,0,0,1,16.5,7.2Zm-1.9,0c0-1.5-.2-2.3-.7-2.3-.2,0-.4.2-.5.6a6.53,6.53,0,0,0-.2,1.7,7.18,7.18,0,0,0,.2,1.7c.1.4.3.6.5.6s.4-.2.5-.6A7.93,7.93,0,0,0,14.6,7.2Z" transform="translate(1 1)" fill="#424242"/><path d="M17.2,11.4V2.9H19l.9,3c.1.1.1.3.2.6s.1.5.2.8l.2.7c-.1-.7-.1-1.4-.2-1.9a6.64,6.64,0,0,1-.1-1.3V2.9H22v8.5H20.3l-.9-3.1-.3-.9c-.1-.3-.1-.6-.2-.8,0,.6.1,1.1.1,1.6v3.4H17.2Z" transform="translate(1 1)" fill="#424242"/><path d="M25.3,11.4H23.5V4.9h-1v-2h3.9v2H25.3Z" transform="translate(1 1)" fill="#424242"/><rect x="1" y="1" width="28" height="28" fill="none" stroke="#424242" stroke-miterlimit="10" stroke-width="2"/><path d="M2.9,17h.9l.6,3a5,5,0,0,1,.2,1.2c.1.4.1.8.2,1.2v-.2l.2-.9.1-.8.1-.5.6-3h.9l.7,7.5h-1l-.2-2.6V19.5h0v.1a.9.9,0,0,1-.1.5c-.1.2,0,.2-.1.3l-.1.7v.3l-.6,3.3H4.5l-.6-2.8a5.16,5.16,0,0,1-.2-1.1c-.1-.4-.1-.8-.2-1.2l-.3,5.2h-1Z" transform="translate(1 1)" fill="#424242"/><path d="M9.3,17h.8l1.6,7.5h-1L10.4,23H8.9l-.3,1.5h-1Zm1,5.2L10,21c-.1-.8-.3-1.7-.4-2.6a6.75,6.75,0,0,1-.2,1.4l-.3,1.5-.2,1h1.4Z" transform="translate(1 1)" fill="#424242"/><path d="M11.5,17h3.3v.9H13.7v6.7h-1V17.9H11.5Z" transform="translate(1 1)" fill="#424242"/><path d="M14.8,17h3.3v.9H17v6.7H16V17.9H14.8Z" transform="translate(1 1)" fill="#424242"/><path d="M18.7,17h2.7v.9H19.7v2.4h1.5v.9H19.7v2.6h1.7v.9H18.7Z" transform="translate(1 1)" fill="#424242"/><path d="M22.3,17h1.3c.6,0,1,.1,1.2.4a2.35,2.35,0,0,1,.5,1.6,2.5,2.5,0,0,1-.3,1.3,1.24,1.24,0,0,1-.8.6l1.4,3.7h-1l-1.4-3.7v3.7h-1V17Zm1,3.3c.4,0,.7-.1.8-.3s.2-.5.2-.9a1.27,1.27,0,0,0-.1-.6c-.1-.2-.1-.3-.2-.4s-.2-.2-.3-.2-.3-.1-.4-.1h-.2v2.5Z" transform="translate(1 1)" fill="#424242"/></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
12
assets/icons/frontmatter-short-dark.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve">
|
||||
<rect x="25" y="25" fill="none" stroke="#ffffff" stroke-width="50" stroke-miterlimit="10" width="1200" height="1200"/>
|
||||
<path fill="#ffffff" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path fill="#ffffff" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
|
||||
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
|
||||
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
|
||||
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
|
||||
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
12
assets/icons/frontmatter-short-light.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve">
|
||||
<rect x="25" y="25" fill="none" stroke="#424242" stroke-width="50" stroke-miterlimit="10" width="1200" height="1200"/>
|
||||
<path fill="#424242" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path fill="#424242" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
|
||||
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
|
||||
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
|
||||
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
|
||||
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
11
assets/icons/frontmatter-small-dark.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve" width="16" height="16">
|
||||
<path fill="#C5C5C5" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path fill="#C5C5C5" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
|
||||
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
|
||||
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
|
||||
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
|
||||
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
11
assets/icons/frontmatter-small-light.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve" width="16" height="16">
|
||||
<path fill="#424242" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path fill="#424242" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
|
||||
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
|
||||
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
|
||||
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
|
||||
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
11
assets/icons/frontmatter-small-teal.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve" width="16" height="16">
|
||||
<path fill="#02aeb7" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path fill="#02aeb7" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
|
||||
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
|
||||
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
|
||||
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
|
||||
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
10
assets/icons/heading-dark.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 12h10" />
|
||||
<path d="M7 4v16" />
|
||||
<path d="M17 4v16" />
|
||||
<path d="M15 20h4" />
|
||||
<path d="M15 4h4" />
|
||||
<path d="M5 20h4" />
|
||||
<path d="M5 4h4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 400 B |
10
assets/icons/heading-light.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 12h10" />
|
||||
<path d="M7 4v16" />
|
||||
<path d="M17 4v16" />
|
||||
<path d="M15 20h4" />
|
||||
<path d="M15 4h4" />
|
||||
<path d="M5 20h4" />
|
||||
<path d="M5 4h4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 400 B |
6
assets/icons/italic-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<line x1="11" y1="5" x2="17" y2="5" />
|
||||
<line x1="7" y1="19" x2="13" y2="19" />
|
||||
<line x1="14" y1="5" x2="10" y2="19" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 362 B |
6
assets/icons/italic-light.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<line x1="11" y1="5" x2="17" y2="5" />
|
||||
<line x1="7" y1="19" x2="13" y2="19" />
|
||||
<line x1="14" y1="5" x2="10" y2="19" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 362 B |
3
assets/icons/media-dark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#C5C5C5" width="24" height="24">
|
||||
<path fillRule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 269 B |
3
assets/icons/media-light.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#424242" width="24" height="24">
|
||||
<path fillRule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 269 B |
7
assets/icons/options-dark.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<line x1="8" y1="12" x2="8" y2="12.01" />
|
||||
<line x1="12" y1="12" x2="12" y2="12.01" />
|
||||
<line x1="16" y1="12" x2="16" y2="12.01" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
7
assets/icons/options-light.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<line x1="8" y1="12" x2="8" y2="12.01" />
|
||||
<line x1="12" y1="12" x2="12" y2="12.01" />
|
||||
<line x1="16" y1="12" x2="16" y2="12.01" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
8
assets/icons/ordered-list-dark.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list-numbers" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M11 6h9" />
|
||||
<path d="M11 12h9" />
|
||||
<path d="M12 18h8" />
|
||||
<path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4" />
|
||||
<path d="M6 10v-6l-2 2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 451 B |
8
assets/icons/ordered-list-light.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list-numbers" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M11 6h9" />
|
||||
<path d="M11 12h9" />
|
||||
<path d="M12 18h8" />
|
||||
<path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4" />
|
||||
<path d="M6 10v-6l-2 2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 451 B |
5
assets/icons/strikethrough-dark.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 5v9a5 5 0 0 0 10 0v-9" />
|
||||
<path d="M4 12h16" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 301 B |
5
assets/icons/strikethrough-light.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 5v9a5 5 0 0 0 10 0v-9" />
|
||||
<path d="M4 12h16" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 301 B |
9
assets/icons/unordered-list-dark.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<line x1="9" y1="6" x2="20" y2="6" />
|
||||
<line x1="9" y1="12" x2="20" y2="12" />
|
||||
<line x1="9" y1="18" x2="20" y2="18" />
|
||||
<line x1="5" y1="6" x2="5" y2="6.01" />
|
||||
<line x1="5" y1="12" x2="5" y2="12.01" />
|
||||
<line x1="5" y1="18" x2="5" y2="18.01" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 533 B |
9
assets/icons/unordered-list-light.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<line x1="9" y1="6" x2="20" y2="6" />
|
||||
<line x1="9" y1="12" x2="20" y2="12" />
|
||||
<line x1="9" y1="18" x2="20" y2="18" />
|
||||
<line x1="5" y1="6" x2="5" y2="6.01" />
|
||||
<line x1="5" y1="12" x2="5" y2="12.01" />
|
||||
<line x1="5" y1="18" x2="5" y2="18.01" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 533 B |
@@ -356,8 +356,18 @@
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.table__cell__seo_details {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.table__cell__validation {
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.table__cell__validation div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.table__cell__validation .valid {
|
||||
@@ -368,6 +378,15 @@
|
||||
color: #E6AF2E;
|
||||
}
|
||||
|
||||
.table__cell__validation div span + span {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.seo__status__note {
|
||||
font-size: 10px;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
/* Fields */
|
||||
.field__toggle {
|
||||
position: relative;
|
||||
@@ -437,6 +456,26 @@ input:checked + .field__toggle__slider:before {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.metadata_field__error {
|
||||
color: var(--vscode-errorForeground);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metadata_field__error button {
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
background-color: var(--vscode-button-secondaryBackground);
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.metadata_field__error button:hover {
|
||||
background-color: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
.metadata_field__input, .metadata_field__input:focus,
|
||||
.metadata_field__textarea, .metadata_field__textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
@@ -451,17 +490,92 @@ input:checked + .field__toggle__slider:before {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.metadata_field__choice {
|
||||
.metadata_field__choice__toggle {
|
||||
color: var(--vscode-input-placeholderForeground);
|
||||
border: 1px solid var(--vscode-inputValidation-infoBorder) !important;
|
||||
outline: none !important;
|
||||
width: 100%;
|
||||
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
|
||||
color: var(--vscode-input-foreground);
|
||||
background-color: var(--vscode-input-background);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.metadata_field__choice__toggle:hover,
|
||||
.metadata_field__choice__toggle:focus,
|
||||
.metadata_field__choice__toggle:active,
|
||||
.metadata_field__choice__toggle:disabled {
|
||||
background-color: var(--vscode-input-background);
|
||||
}
|
||||
|
||||
.metadata_field__choice::placeholde {
|
||||
color: var(--vscode-input-placeholderForeground);
|
||||
.metadata_field__choice__toggle span {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.metadata_field__choice__toggle svg.icon {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
margin-left: .25rem;
|
||||
|
||||
position: absolute;
|
||||
right: .25rem;
|
||||
}
|
||||
|
||||
.metadata_field__choice_list {
|
||||
width: 90%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
list-style: none;
|
||||
overflow: auto;
|
||||
max-height: 200px;
|
||||
|
||||
color: var(--vscode-dropdown-foreground);
|
||||
background-color: var(--vscode-dropdown-background);
|
||||
}
|
||||
|
||||
.metadata_field__choice_list.open {
|
||||
border: 1px solid rgba(0, 0, 0, .9);
|
||||
}
|
||||
|
||||
.metadata_field__choice_list li {
|
||||
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.metadata_field__choice_list li:active {
|
||||
color: var(--vscode-button-foreground);
|
||||
background-color: var(--vscode-button-background);
|
||||
}
|
||||
|
||||
.metadata_field__choice_list li[aria-selected="true"] {
|
||||
color: var(--vscode-button-foreground);
|
||||
background-color: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
|
||||
.metadata_field__choice_list li[aria-disabled="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.metadata_field__choice_list__item {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.metadata_field__choice__button {
|
||||
margin-top: .5rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.metadata_field__choice__button_icon {
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.metadata_field__datetime {
|
||||
@@ -481,12 +595,56 @@ input:checked + .field__toggle__slider:before {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.metadata_field__datetime > button:hover {
|
||||
background-color: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
.metadata_field__multiple_images {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.metadata_field__preview_image img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-height: 16rem;
|
||||
}
|
||||
|
||||
.metadata_field__preview_image__button {
|
||||
background-color: transparent;
|
||||
border: 2px dashed var(--vscode-button-background);
|
||||
padding: 1.5rem;
|
||||
filter: brightness(85%);
|
||||
}
|
||||
|
||||
.metadata_field__preview_image__button:hover {
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
filter: brightness(100%);
|
||||
}
|
||||
|
||||
.metadata_field__preview_image__button svg {
|
||||
color: var(--vscode-foreground);
|
||||
display: block;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.metadata_field__preview_image__button span {
|
||||
color: var(--vscode-foreground);
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
.metadata_field__preview_image__preview {
|
||||
background-color: var(--vscode-button-secondaryBackground);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.metadata_field__preview_image__remove {
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
color: var(--vscode-inputValidation-errorForeground);
|
||||
|
||||
20635
package-lock.json
generated
612
package.json
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "vscode-front-matter-beta",
|
||||
"displayName": "Front Matter",
|
||||
"description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
|
||||
"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, Hexo, NextJs, Gatsby, and many more...",
|
||||
"icon": "assets/frontmatter-teal-128x128.png",
|
||||
"version": "4.0.1",
|
||||
"version": "5.9.0",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"vscode": "^1.51.0"
|
||||
"vscode": "^1.63.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
@@ -66,7 +66,7 @@
|
||||
"onCommand:frontMatter.insertImage",
|
||||
"onView:frontMatter.explorer"
|
||||
],
|
||||
"main": "./dist/extension",
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
@@ -97,10 +97,66 @@
|
||||
"markdownDescription": "Specify if you want to automatically update the modified date of your article/page. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.autoupdatedate)",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.defaultSorting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"LastModifiedAsc",
|
||||
"LastModifiedDesc",
|
||||
"FileNameAsc",
|
||||
"FileNameDesc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"markdownDescription": "Specify the default sorting option for the content dashboard. You can use one of the values from the enum or define your own ID. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.defaultSorting)",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.draftField": {
|
||||
"type": "object",
|
||||
"markdownDescription": "Define the draft field you want to use to manage your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.draftfield)",
|
||||
"default": {
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"boolean",
|
||||
"choice"
|
||||
],
|
||||
"description": "Type of the draft field you want to use"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the field to use"
|
||||
},
|
||||
"choices": {
|
||||
"type": "array",
|
||||
"description": "List of choices for the field",
|
||||
"items": {
|
||||
"type": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.fmHighlight": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"markdownDescription": "Specify if you want to highlight the Front Matter in the Markdown file. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.fmhighlight)",
|
||||
"markdownDescription": "Specify if you want to highlight the Front Matter in the Markdown file. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.fmhighlight)",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.pageFolders": {
|
||||
@@ -117,6 +173,11 @@
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path of the folder"
|
||||
},
|
||||
"excludeSubdir": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Exclude sub-directories"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -133,6 +194,58 @@
|
||||
"markdownDescription": "Specify the folder name where all your assets are located. For instance in Hugo this is the `static` folder. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.publicfolder)",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.sorting": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"markdownDescription": "Define the sorting options for your dashboard content. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.sorting)",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the sorting option. This will be used for the storing the last used sorting option or the default option."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Name of the sorting label"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the metadata field to sort by"
|
||||
},
|
||||
"order": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"description": "Order of the sorting"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "string",
|
||||
"enum": [
|
||||
"string",
|
||||
"date"
|
||||
],
|
||||
"description": "Type of the field value"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"title",
|
||||
"name",
|
||||
"order"
|
||||
]
|
||||
},
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.wysiwyg": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"markdownDescription": "Specifies if you want to enable/disable the What You See, Is What You Get (WYSIWYG) markdown controls. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.wysiwyg)",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.custom.scripts": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
@@ -151,6 +264,32 @@
|
||||
"nodeBin": {
|
||||
"type": "string",
|
||||
"description": "Path to the node executable. This is required when using NVM, so that there is no confusion of which node version to use."
|
||||
},
|
||||
"bulk": {
|
||||
"type": "boolean",
|
||||
"description": "Run the script for all content files"
|
||||
},
|
||||
"output": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"editor",
|
||||
"notification"
|
||||
],
|
||||
"description": "Define where you want to output your script output. Default is a notification, but you can specify to show it in an editor panel."
|
||||
},
|
||||
"outputType": {
|
||||
"type": "string",
|
||||
"description": "The type of output for the editor panel. Can be used to change it to 'markdown' for example"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "content",
|
||||
"enum": [
|
||||
"content",
|
||||
"mediaFolder",
|
||||
"mediaFile"
|
||||
],
|
||||
"description": "The type for which the script will be used."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -164,7 +303,7 @@
|
||||
"frontMatter.dashboard.mediaSnippet": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"markdownDescription": "Specify the a snippet for your custom media insert markup. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.dashboard.mediaSnippet)",
|
||||
"markdownDescription": "Specify the a snippet for your custom media insert markup. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.mediasnippet)",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The parts of your snippet. Use `{mediaUrl}` as placeholder where the path of the image needs to be inserted."
|
||||
@@ -180,6 +319,23 @@
|
||||
"markdownDescription": "Specify if you want to open the dashboard when you start VS Code. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.openonstart)",
|
||||
"scope": "Dashboard"
|
||||
},
|
||||
"frontMatter.framework.id": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"markdownDescription": "Specify the ID of your static site generator or framework you are using for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.framework.id)"
|
||||
},
|
||||
"frontMatter.media.defaultSorting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"enum": [
|
||||
"LastModifiedAsc",
|
||||
"LastModifiedDesc",
|
||||
"FileNameAsc",
|
||||
"FileNameDesc"
|
||||
],
|
||||
"markdownDescription": "Specify the default sorting option for the media dashboard. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.media.defaultsorting)",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.panel.freeform": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
@@ -198,6 +354,12 @@
|
||||
"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#frontmatter.preview.pathname)",
|
||||
"scope": "Site preview"
|
||||
},
|
||||
"frontMatter.site.baseURL": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.site.baseurl)",
|
||||
"scope": "Site"
|
||||
},
|
||||
"frontMatter.taxonomy.alignFilename": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
@@ -214,7 +376,7 @@
|
||||
},
|
||||
"frontMatter.taxonomy.commaSeparatedFields": {
|
||||
"type": "array",
|
||||
"markdownDescription": "Specify the fields names that Front Matter should treat as a comma-separated array. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.taxonomy.commaSeparatedFields)",
|
||||
"markdownDescription": "Specify the fields names that Front Matter should treat as a comma-separated array. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.commaSeparatedFields)",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "Name of the fields you want to use as comma-separated arrays."
|
||||
@@ -226,7 +388,7 @@
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"markdownDescription": "Specify the type of contents you want to use for your articles/pages/etc. Make sure the `type` is correctly set in your front matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.taxonomy.contentTypes)",
|
||||
"markdownDescription": "Specify the type of contents you want to use for your articles/pages/etc. Make sure the `type` is correctly set in your front matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.contentTypes)",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "Define the content types you want to use in Front Matter.",
|
||||
@@ -251,8 +413,10 @@
|
||||
"boolean",
|
||||
"image",
|
||||
"choice",
|
||||
"taxonomy",
|
||||
"tags",
|
||||
"categories"
|
||||
"categories",
|
||||
"draft"
|
||||
],
|
||||
"description": "Define the type of field"
|
||||
},
|
||||
@@ -268,16 +432,100 @@
|
||||
"type": "array",
|
||||
"description": "Define your choices",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
],
|
||||
"description": "The choice ID"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The choice title"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"single": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Is a single line field"
|
||||
},
|
||||
"multiple": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do you allow to select multiple values?"
|
||||
},
|
||||
"isPreviewImage": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specify if the image field can be used as preview. Be aware, you can only have one preview image per content type."
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do you want to hide the field from the metadata section?"
|
||||
},
|
||||
"taxonomyId": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The ID of your taxonomy field"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "taxonomy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"taxonomyId"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "choice"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"choices"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pageBundle": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specify if you want to create a folder when creating new content."
|
||||
},
|
||||
"previewPath": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
],
|
||||
"default": null,
|
||||
"description": "Defines a custom preview path for the content type."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -289,6 +537,7 @@
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
@@ -306,7 +555,7 @@
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"title": "Article preview",
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
@@ -330,6 +579,32 @@
|
||||
],
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.customTaxonomy": {
|
||||
"type": "array",
|
||||
"markdownDescription": "Specify the custom taxonomy field data. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.tags)",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "ID for your taxonomy field"
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"description": "Options from which you can pick",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"id",
|
||||
"options"
|
||||
]
|
||||
},
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.dateField": {
|
||||
"type": "string",
|
||||
"default": "date",
|
||||
@@ -393,6 +668,12 @@
|
||||
"markdownDescription": "Specifies the optimal description length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seodescriptionlength)",
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.seoSlugLength": {
|
||||
"type": "number",
|
||||
"default": 75,
|
||||
"markdownDescription": "Specifies the optimal slug length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seoSlugLength)",
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.seoTitleLength": {
|
||||
"type": "number",
|
||||
"default": 60,
|
||||
@@ -419,7 +700,7 @@
|
||||
},
|
||||
"frontMatter.templates.folder": {
|
||||
"type": "string",
|
||||
"default": ".templates",
|
||||
"default": ".frontmatter/templates",
|
||||
"markdownDescription": "Specify the folder to use for your article templates. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.templates.folder)",
|
||||
"scope": "Templates"
|
||||
},
|
||||
@@ -428,6 +709,24 @@
|
||||
"default": "yyyy-MM-dd",
|
||||
"markdownDescription": "Specify the prefix you want to add for your new article filenames. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.templates.prefix)",
|
||||
"scope": "Templates"
|
||||
},
|
||||
"frontMatter.global.notifications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"info",
|
||||
"warning",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"default": [
|
||||
"info",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"markdownDescription": "Specifies the notifications you want to see. By default, all notifications types will be shown. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.global.notifications)",
|
||||
"scope": "Templates"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -475,7 +774,7 @@
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.generateSlug",
|
||||
"title": "Generate slug based on article title",
|
||||
"title": "Generate slug based on content title",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
@@ -490,9 +789,12 @@
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertImage",
|
||||
"title": "Insert image into article",
|
||||
"title": "Insert image into your content",
|
||||
"category": "Front matter",
|
||||
"icon": "$(device-camera)"
|
||||
"icon": {
|
||||
"dark": "/assets/icons/media-dark.svg",
|
||||
"light": "/assets/icons/media-light.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertTags",
|
||||
@@ -501,17 +803,39 @@
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.createContent",
|
||||
"title": "New article from template",
|
||||
"title": "Create new content from defined content type or template",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard",
|
||||
"title": "Open dashboard",
|
||||
"category": "Front matter"
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"dark": "/assets/icons/frontmatter-small-dark.svg",
|
||||
"light": "/assets/icons/frontmatter-small-light.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.media",
|
||||
"title": "Open media dashboard",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"dark": "/assets/icons/frontmatter-small-dark.svg",
|
||||
"light": "/assets/icons/frontmatter-small-light.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.close",
|
||||
"title": "Close dashboard",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"dark": "/assets/icons/frontmatter-small-teal.svg",
|
||||
"light": "/assets/icons/frontmatter-small-teal.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.preview",
|
||||
"title": "Preview article",
|
||||
"title": "Preview content",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
@@ -528,14 +852,179 @@
|
||||
"command": "frontMatter.setLastModifiedDate",
|
||||
"title": "Set lastmod date",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.bold",
|
||||
"title": "Bold",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/bold-light.svg",
|
||||
"dark": "assets/icons/bold-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.italic",
|
||||
"title": "Italic",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/italic-light.svg",
|
||||
"dark": "assets/icons/italic-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.strikethrough",
|
||||
"title": "Strikethrough",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/strikethrough-light.svg",
|
||||
"dark": "assets/icons/strikethrough-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.code",
|
||||
"title": "Code",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/code-light.svg",
|
||||
"dark": "assets/icons/code-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.codeblock",
|
||||
"title": "Codeblock",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/codeblock-light.svg",
|
||||
"dark": "assets/icons/codeblock-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.blockquote",
|
||||
"title": "Codeblock",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/blockquote-light.svg",
|
||||
"dark": "assets/icons/blockquote-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.heading",
|
||||
"title": "Heading",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/heading-light.svg",
|
||||
"dark": "assets/icons/heading-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.unorderedlist",
|
||||
"title": "Unordered list",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/unordered-list-light.svg",
|
||||
"dark": "assets/icons/unordered-list-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.orderedlist",
|
||||
"title": "Ordered list",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/ordered-list-light.svg",
|
||||
"dark": "assets/icons/ordered-list-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.tasklist",
|
||||
"title": "Task list",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.options",
|
||||
"title": "Other markup options",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/options-light.svg",
|
||||
"dark": "assets/icons/options-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.diagnostics",
|
||||
"title": "Diagnostic logging",
|
||||
"category": "Front matter"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"editor/title": [
|
||||
{
|
||||
"when": "resourceLangId == markdown",
|
||||
"command": "frontMatter.markup.heading",
|
||||
"group": "navigation@-132",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.bold",
|
||||
"group": "navigation@-131",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.italic",
|
||||
"group": "navigation@-130",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.strikethrough",
|
||||
"group": "navigation@-129",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.blockquote",
|
||||
"group": "navigation@-128",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertImage",
|
||||
"group": "navigation"
|
||||
"group": "navigation@-127",
|
||||
"when": "resourceLangId == markdown"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.options",
|
||||
"group": "navigation@-126",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.orderedlist",
|
||||
"group": "1_markup@1",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.unorderedlist",
|
||||
"group": "1_markup@2",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.tasklist",
|
||||
"group": "1_markup@3",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.code",
|
||||
"group": "1_markup@4",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.codeblock",
|
||||
"group": "1_markup@5",
|
||||
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard",
|
||||
"group": "navigation@-98",
|
||||
"when": "frontMatter:enabled == true && frontMatter:dashboard:open == false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.close",
|
||||
"group": "navigation@-98",
|
||||
"when": "frontMatter:enabled == true && frontMatter:dashboard:open == true"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
@@ -583,12 +1072,60 @@
|
||||
{
|
||||
"command": "frontMatter.createFromTemplate",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.close",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.bold",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.italic",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.strikethrough",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.code",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.codeblock",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.blockquote",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.heading",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.unorderedlist",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.orderedlist",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.tasklist",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.options",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "frontMatter.collapseSections",
|
||||
"group": "navigation"
|
||||
"group": "frontmatter-explorer"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -603,9 +1140,15 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run clean && webpack --mode production",
|
||||
"build:ext": "npm run clean && webpack --mode development",
|
||||
"dev:ext": "npm run clean && webpack --mode development --watch",
|
||||
"dev:ext": "npm run clean && npm-run-all --parallel watch:*",
|
||||
"vscode:prepublish": "npm run clean && npm-run-all --parallel prod:*",
|
||||
"build:ext": "npm run clean && npm-run-all --parallel dev:build:*",
|
||||
"watch:ext": "webpack --mode development --watch --config ./webpack/extension.config.js",
|
||||
"watch:dashboard": "webpack serve --mode development --config ./webpack/dashboard.config.js",
|
||||
"dev:build:ext": "webpack --mode development --config ./webpack/extension.config.js",
|
||||
"dev:build:dashboard": "webpack --mode development --config ./webpack/dashboard.config.js",
|
||||
"prod:ext": "webpack --mode production --config ./webpack/extension.config.js",
|
||||
"prod:dashboard": "webpack --mode production --config ./webpack/dashboard.config.js",
|
||||
"test-compile": "tsc -p ./",
|
||||
"clean": "rimraf dist",
|
||||
"start:site": "cd ./docs && npm run dev"
|
||||
@@ -616,6 +1159,8 @@
|
||||
"@headlessui/react": "^1.4.1",
|
||||
"@heroicons/react": "1.0.4",
|
||||
"@iarna/toml": "2.2.3",
|
||||
"@sentry/react": "^6.13.3",
|
||||
"@sentry/tracing": "^6.13.3",
|
||||
"@tailwindcss/forms": "^0.3.3",
|
||||
"@types/glob": "7.1.3",
|
||||
"@types/js-yaml": "3.12.1",
|
||||
@@ -625,8 +1170,10 @@
|
||||
"@types/react": "17.0.0",
|
||||
"@types/react-datepicker": "^4.1.7",
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/vscode": "1.51.0",
|
||||
"@types/vscode": "^1.63.0",
|
||||
"@vscode/codicons": "0.0.20",
|
||||
"@vscode/webview-ui-toolkit": "^0.8.1",
|
||||
"@webpack-cli/serve": "^1.6.0",
|
||||
"autoprefixer": "^10.3.2",
|
||||
"css-loader": "5.2.7",
|
||||
"date-fns": "2.23.0",
|
||||
@@ -636,8 +1183,13 @@
|
||||
"gray-matter": "4.0.2",
|
||||
"html-loader": "1.3.2",
|
||||
"html-webpack-plugin": "4.5.0",
|
||||
"image-size": "^1.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lodash.uniqby": "4.7.0",
|
||||
"mdast-util-from-markdown": "1.0.0",
|
||||
"node-json-db": "^1.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "^8.3.6",
|
||||
"postcss-loader": "4.3.0",
|
||||
"react": "17.0.1",
|
||||
@@ -651,11 +1203,11 @@
|
||||
"ts-loader": "8.0.3",
|
||||
"tslint": "6.1.3",
|
||||
"typescript": "4.0.2",
|
||||
"url-join-ts": "^1.0.5",
|
||||
"wc-react": "github:estruyf/wc-react",
|
||||
"webpack": "4.44.2",
|
||||
"webpack-cli": "3.3.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docsearch/js": "^3.0.0-alpha.40"
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX } from './../constants/settings';
|
||||
import { isValidFile } from './../helpers/isValidFile';
|
||||
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX, CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX } from './../constants';
|
||||
import * as vscode from 'vscode';
|
||||
import { TaxonomyType } from "../models";
|
||||
import { CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX } from "../constants/settings";
|
||||
import { format } from "date-fns";
|
||||
import { ArticleHelper, Settings, SlugHelper } from '../helpers';
|
||||
import matter = require('gray-matter');
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { extname, basename } from 'path';
|
||||
import { extname, basename, parse, dirname } from 'path';
|
||||
import { COMMAND_NAME, DefaultFields } from '../constants';
|
||||
import { DashboardData } from '../models/DashboardData';
|
||||
import { ExplorerView } from '../explorerView/ExplorerView';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
|
||||
|
||||
export class Article {
|
||||
@@ -172,7 +174,7 @@ export class Article {
|
||||
|
||||
let newFileName = `${slugName}${ext}`;
|
||||
if (filePrefix && typeof filePrefix === "string") {
|
||||
newFileName = `${format(new Date(), filePrefix)}-${newFileName}`;
|
||||
newFileName = `${format(new Date(), DateHelper.formatUpdate(filePrefix) as string)}-${newFileName}`;
|
||||
}
|
||||
|
||||
const newPath = editor.document.uri.fsPath.replace(fileName, newFileName);
|
||||
@@ -183,7 +185,7 @@ export class Article {
|
||||
await vscode.workspace.fs.rename(editor.document.uri, vscode.Uri.file(newPath), {
|
||||
overwrite: false
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
Notifications.error(`Failed to rename file: ${e?.message || e}`);
|
||||
}
|
||||
}
|
||||
@@ -191,6 +193,31 @@ export class Article {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the slug from the front matter
|
||||
*/
|
||||
public static getSlug() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = parseWinPath(editor.document.fileName);
|
||||
|
||||
if (!isValidFile(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedFile = parse(file);
|
||||
|
||||
if (parsedFile.name.toLowerCase() !== "index") {
|
||||
return parsedFile.name;
|
||||
}
|
||||
|
||||
const folderName = basename(dirname(file));
|
||||
return folderName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the page its draft mode
|
||||
*/
|
||||
@@ -240,13 +267,13 @@ export class Article {
|
||||
/**
|
||||
* Format the date to the defined format
|
||||
*/
|
||||
public static formatDate(dateValue: Date) {
|
||||
public static formatDate(dateValue: Date): string {
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
|
||||
if (dateFormat && typeof dateFormat === "string") {
|
||||
return format(dateValue, dateFormat);
|
||||
return format(dateValue, DateHelper.formatUpdate(dateFormat) as string);
|
||||
} else {
|
||||
return dateValue.toISOString();
|
||||
return typeof dateValue.toISOString === 'function' ? dateValue.toISOString() : dateValue?.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
31
src/commands/Content.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { commands, QuickPickItem, window } from 'vscode';
|
||||
import { COMMAND_NAME } from '../constants';
|
||||
|
||||
export class Content {
|
||||
|
||||
public static async create() {
|
||||
|
||||
const options: QuickPickItem[] = [{
|
||||
label: "Create content by content type",
|
||||
description: "Select if you want to create new content by the available content type(s)"
|
||||
}, {
|
||||
label: "Create content by template",
|
||||
description: "Select if you want to create new content by the available template(s)"
|
||||
} as QuickPickItem];
|
||||
|
||||
const selectedOption = await window.showQuickPick(options, {
|
||||
placeHolder: `Select how you want to create your new content`,
|
||||
canPickMany: false
|
||||
});
|
||||
|
||||
if (selectedOption) {
|
||||
if (selectedOption.label === options[0].label) {
|
||||
commands.executeCommand(COMMAND_NAME.createByContentType);
|
||||
} else {
|
||||
commands.executeCommand(COMMAND_NAME.createByTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,27 @@
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDERS, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET } from './../constants/settings';
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { basename, dirname, extname, join } from "path";
|
||||
import { existsSync, statSync, unlinkSync, writeFileSync } from "fs";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env, Position } from "vscode";
|
||||
import { Settings as SettingsHelper } from '../helpers';
|
||||
import { TaxonomyType } from '../models';
|
||||
import { Folders } from './Folders';
|
||||
import { PagesListener } from './../listeners/PagesListener';
|
||||
import { ExtensionListener } from './../listeners/ExtensionListener';
|
||||
import { SETTINGS_DASHBOARD_OPENONSTART, CONTEXT } from '../constants';
|
||||
import { join } from "path";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from "vscode";
|
||||
import { Logger, Settings as SettingsHelper } from '../helpers';
|
||||
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
|
||||
import { Page } from '../dashboardWebView/models/Page';
|
||||
import { openFileInEditor } from '../helpers/openFileInEditor';
|
||||
import { COMMAND_NAME, EXTENSION_STATE_PAGES_VIEW } from '../constants/Extension';
|
||||
import { Template } from './Template';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { Settings } from '../dashboardWebView/models/Settings';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
import { parseJSON } from 'date-fns';
|
||||
import { ViewType } from '../dashboardWebView/state';
|
||||
import { EditorHelper, WebviewHelper } from '@estruyf/vscode';
|
||||
import { MediaInfo, MediaPaths } from './../models/MediaPaths';
|
||||
import { decodeBase64Image } from '../helpers/decodeBase64Image';
|
||||
import { DefaultFields } from '../constants';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { DashboardData } from '../models/DashboardData';
|
||||
import { ExplorerView } from '../explorerView/ExplorerView';
|
||||
|
||||
import { MediaLibrary } from '../helpers/MediaLibrary';
|
||||
import { DashboardListener, MediaListener, SettingsListener } from '../listeners';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
private static isDisposed: boolean = true;
|
||||
private static media: MediaInfo[] = [];
|
||||
private static timers: { [folder: string]: any } = {};
|
||||
private static _viewData: DashboardData | undefined;
|
||||
private static isDisposed: boolean = true;
|
||||
|
||||
public static get viewData(): DashboardData | undefined {
|
||||
return Dashboard._viewData;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Init the dashboard
|
||||
*/
|
||||
public static async init() {
|
||||
@@ -50,13 +35,17 @@ export class Dashboard {
|
||||
* Open or reveal the dashboard
|
||||
*/
|
||||
public static async open(data?: DashboardData) {
|
||||
MediaLibrary.getInstance();
|
||||
|
||||
Dashboard._viewData = data;
|
||||
|
||||
if (Dashboard.isOpen) {
|
||||
Dashboard.reveal();
|
||||
Dashboard.reveal(!!data);
|
||||
} else {
|
||||
Dashboard.create();
|
||||
}
|
||||
|
||||
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,11 +58,33 @@ export class Dashboard {
|
||||
/**
|
||||
* Reveal the dashboard if it is open
|
||||
*/
|
||||
public static reveal() {
|
||||
public static reveal(hasData: boolean = false) {
|
||||
if (Dashboard.webview) {
|
||||
Dashboard.webview.reveal();
|
||||
|
||||
if (hasData) {
|
||||
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: Dashboard.viewData });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static close() {
|
||||
Dashboard.webview?.dispose();
|
||||
}
|
||||
|
||||
public static reload() {
|
||||
if (Dashboard.isOpen) {
|
||||
Dashboard.webview?.dispose();
|
||||
|
||||
setTimeout(() => {
|
||||
Dashboard.open();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
public static resetViewData() {
|
||||
Dashboard._viewData = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the dashboard webview
|
||||
@@ -87,399 +98,68 @@ export class Dashboard {
|
||||
'FrontMatter Dashboard',
|
||||
ViewColumn.One,
|
||||
{
|
||||
enableScripts: true
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true
|
||||
}
|
||||
);
|
||||
|
||||
Dashboard.isDisposed = false;
|
||||
|
||||
Dashboard.webview.iconPath = {
|
||||
dark: Uri.file(join(extensionUri.fsPath, 'assets/frontmatter-dark.svg')),
|
||||
light: Uri.file(join(extensionUri.fsPath, 'assets/frontmatter.svg'))
|
||||
dark: Uri.file(join(extensionUri.fsPath, 'assets/icons/frontmatter-short-dark.svg')),
|
||||
light: Uri.file(join(extensionUri.fsPath, 'assets/icons/frontmatter-short-light.svg'))
|
||||
};
|
||||
|
||||
Dashboard.webview.webview.html = Dashboard.getWebviewContent(Dashboard.webview.webview, extensionUri);
|
||||
|
||||
Dashboard.webview.onDidChangeViewState(() => {
|
||||
Dashboard.webview.onDidChangeViewState(async () => {
|
||||
if (!this.webview?.visible) {
|
||||
Dashboard._viewData = undefined;
|
||||
const panel = ExplorerView.getInstance(extensionUri);
|
||||
panel.getMediaSelection();
|
||||
|
||||
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: null });
|
||||
}
|
||||
|
||||
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, this.webview?.visible);
|
||||
});
|
||||
|
||||
Dashboard.webview.onDidDispose(() => {
|
||||
Dashboard.webview.onDidDispose(async () => {
|
||||
Dashboard.isDisposed = true;
|
||||
Dashboard._viewData = undefined;
|
||||
const panel = ExplorerView.getInstance(extensionUri);
|
||||
panel.getMediaSelection();
|
||||
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false);
|
||||
});
|
||||
|
||||
SettingsHelper.onConfigChange((global?: any) => {
|
||||
Dashboard.getSettings();
|
||||
SettingsListener.getSettings();
|
||||
});
|
||||
|
||||
Dashboard.webview.webview.onDidReceiveMessage(async (msg) => {
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.getViewType:
|
||||
if (Dashboard._viewData) {
|
||||
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: Dashboard._viewData });
|
||||
}
|
||||
break;
|
||||
case DashboardMessage.getData:
|
||||
Dashboard.getSettings();
|
||||
Dashboard.getPages();
|
||||
break;
|
||||
case DashboardMessage.openFile:
|
||||
openFileInEditor(msg.data);
|
||||
break;
|
||||
case DashboardMessage.createContent:
|
||||
await commands.executeCommand(COMMAND_NAME.createContent);
|
||||
break;
|
||||
case DashboardMessage.updateSetting:
|
||||
Dashboard.updateSetting(msg.data);
|
||||
break;
|
||||
case DashboardMessage.initializeProject:
|
||||
await commands.executeCommand(COMMAND_NAME.init, Dashboard.getSettings);
|
||||
break;
|
||||
case DashboardMessage.reload:
|
||||
if (!Dashboard.isDisposed) {
|
||||
Dashboard.webview?.dispose();
|
||||
setTimeout(() => {
|
||||
Dashboard.open();
|
||||
}, 100);
|
||||
}
|
||||
break;
|
||||
case DashboardMessage.setPageViewType:
|
||||
Extension.getInstance().setState(EXTENSION_STATE_PAGES_VIEW, msg.data);
|
||||
break;
|
||||
case DashboardMessage.getMedia:
|
||||
Dashboard.getMedia(msg?.data?.page, msg?.data?.folder)
|
||||
break;
|
||||
case DashboardMessage.copyToClipboard:
|
||||
env.clipboard.writeText(msg.data);
|
||||
break;
|
||||
case DashboardMessage.refreshMedia:
|
||||
Dashboard.media = [];
|
||||
Dashboard.getMedia(0, msg?.data?.folder);
|
||||
break;
|
||||
case DashboardMessage.uploadMedia:
|
||||
Dashboard.saveFile(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.deleteMedia:
|
||||
Dashboard.deleteFile(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.insertPreviewImage:
|
||||
Dashboard.insertImage(msg?.data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an image into the front matter or contents
|
||||
* @param data
|
||||
*/
|
||||
private static async insertImage(data: any) {
|
||||
if (data?.file && data?.image) {
|
||||
if (!data?.position) {
|
||||
await commands.executeCommand(`workbench.view.extension.frontmatter-explorer`);
|
||||
}
|
||||
|
||||
await EditorHelper.showFile(data.file);
|
||||
Dashboard._viewData = undefined;
|
||||
Logger.info(`Receiving message from webview: ${msg.command}`);
|
||||
|
||||
const extensionUri = Extension.getInstance().extensionPath;
|
||||
const panel = ExplorerView.getInstance(extensionUri);
|
||||
|
||||
if (data?.position) {
|
||||
const editor = window.activeTextEditor;
|
||||
const line = data.position.line;
|
||||
const character = data.position.character;
|
||||
if (line) {
|
||||
await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || ``));
|
||||
}
|
||||
panel.getMediaSelection();
|
||||
} else {
|
||||
panel.getMediaSelection();
|
||||
panel.updateMetadata({field: data.fieldName, value: data.image});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the settings for the dashboard
|
||||
*/
|
||||
private static async getSettings() {
|
||||
const ext = Extension.getInstance();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.settings,
|
||||
data: {
|
||||
beta: ext.isBetaVersion(),
|
||||
wsFolder: wsFolder ? wsFolder.fsPath : '',
|
||||
staticFolder: SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS),
|
||||
folders: Folders.get(),
|
||||
initialized: await Template.isInitialized(),
|
||||
tags: SettingsHelper.getTaxonomy(TaxonomyType.Tag),
|
||||
categories: SettingsHelper.getTaxonomy(TaxonomyType.Category),
|
||||
openOnStart: SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART),
|
||||
versionInfo: ext.getVersion(),
|
||||
pageViewType: await ext.getState<ViewType | undefined>(EXTENSION_STATE_PAGES_VIEW),
|
||||
mediaSnippet: SettingsHelper.get<string[]>(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [],
|
||||
} as Settings
|
||||
DashboardListener.process(msg);
|
||||
ExtensionListener.process(msg);
|
||||
MediaListener.process(msg);
|
||||
PagesListener.process(msg);
|
||||
SettingsListener.process(msg);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a setting from the dashboard
|
||||
* Return the webview
|
||||
* @returns The webview
|
||||
*/
|
||||
private static async updateSetting(data: { name: string, value: any }) {
|
||||
await SettingsHelper.update(data.name, data.value);
|
||||
Dashboard.getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all media files
|
||||
*/
|
||||
private static async getMedia(page: number = 0, folder: string = '') {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const staticFolder = SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS);
|
||||
|
||||
if (Dashboard.media.length === 0) {
|
||||
const contentFolder = Folders.get();
|
||||
let allMedia: MediaInfo[] = [];
|
||||
|
||||
if (staticFolder) {
|
||||
const files = await workspace.findFiles(`${staticFolder || ""}/**/*`);
|
||||
const media = Dashboard.filterMedia(files);
|
||||
|
||||
allMedia = [...media];
|
||||
}
|
||||
|
||||
if (contentFolder && wsFolder) {
|
||||
for (let i = 0; i < contentFolder.length; i++) {
|
||||
const folder = contentFolder[i];
|
||||
const relFolderPath = folder.path.substring(wsFolder.fsPath.length + 1);
|
||||
const files = await workspace.findFiles(`${relFolderPath}/**/*`);
|
||||
const media = Dashboard.filterMedia(files);
|
||||
|
||||
allMedia = [...allMedia, ...media];
|
||||
}
|
||||
}
|
||||
|
||||
allMedia = allMedia.sort((a, b) => {
|
||||
if (b.fsPath < a.fsPath) {
|
||||
return -1;
|
||||
}
|
||||
if (b.fsPath > a.fsPath) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
Dashboard.media = Object.assign([], allMedia);
|
||||
}
|
||||
|
||||
// Filter the media
|
||||
let files: MediaInfo[] = Dashboard.media;
|
||||
if (folder) {
|
||||
files = files.filter(f => f.fsPath.includes(folder));
|
||||
}
|
||||
|
||||
// Retrieve the total after filtering and before the slicing happens
|
||||
const total = files.length;
|
||||
|
||||
// Get media set
|
||||
files = files.slice(page * 16, ((page + 1) * 16));
|
||||
files = files.map((file) => {
|
||||
try {
|
||||
return {
|
||||
...file,
|
||||
stats: statSync(file.fsPath)
|
||||
};
|
||||
} catch (e) {
|
||||
return {...file, stats: undefined};
|
||||
}
|
||||
}).filter(f => f.stats !== undefined);
|
||||
|
||||
const folders = [...new Set(Dashboard.media.map((file) => {
|
||||
let relFolderPath = wsFolder ? file.fsPath.substring(wsFolder.fsPath.length + 1) : file.fsPath;
|
||||
if (staticFolder && relFolderPath.startsWith(staticFolder)) {
|
||||
relFolderPath = relFolderPath.substring(staticFolder.length);
|
||||
}
|
||||
if (relFolderPath?.startsWith('/')) {
|
||||
relFolderPath = relFolderPath.substring(1);
|
||||
}
|
||||
return dirname(relFolderPath);
|
||||
}))];
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.media,
|
||||
data: {
|
||||
media: files,
|
||||
total: total,
|
||||
folders
|
||||
} as MediaPaths
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the markdown pages
|
||||
*/
|
||||
private static async getPages() {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
const descriptionField = SettingsHelper.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
|
||||
const dateField = SettingsHelper.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate;
|
||||
const staticFolder = SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS);
|
||||
|
||||
const folderInfo = await Folders.getInfo();
|
||||
const pages: Page[] = [];
|
||||
|
||||
if (folderInfo) {
|
||||
for (const folder of folderInfo) {
|
||||
for (const file of folder.lastModified) {
|
||||
if (file.fileName.endsWith(`.md`) || file.fileName.endsWith(`.mdx`)) {
|
||||
try {
|
||||
const article = ArticleHelper.getFrontMatterByPath(file.filePath);
|
||||
|
||||
if (article?.data.title) {
|
||||
const page: Page = {
|
||||
...article.data,
|
||||
// FrontMatter properties
|
||||
fmFolder: folder.title,
|
||||
fmModified: file.mtime,
|
||||
fmFilePath: file.filePath,
|
||||
fmFileName: file.fileName,
|
||||
fmDraft: article?.data.draft ? "Draft" : "Published",
|
||||
fmYear: article?.data[dateField] ? parseJSON(article?.data[dateField]).getFullYear() : null,
|
||||
// Make sure these are always set
|
||||
title: article?.data.title,
|
||||
slug: article?.data.slug,
|
||||
date: article?.data[dateField] || "",
|
||||
draft: article?.data.draft,
|
||||
description: article?.data[descriptionField] || "",
|
||||
};
|
||||
|
||||
if (article?.data.preview && wsFolder) {
|
||||
const staticPath = join(wsFolder.fsPath, staticFolder || "", article?.data.preview);
|
||||
const contentFolderPath = join(dirname(file.filePath), article?.data.preview);
|
||||
|
||||
let previewUri = null;
|
||||
if (existsSync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (existsSync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
const preview = Dashboard.webview?.webview.asWebviewUri(previewUri);
|
||||
page.preview = preview?.toString() || "";
|
||||
} else {
|
||||
page.preview = "";
|
||||
}
|
||||
}
|
||||
|
||||
pages.push(page);
|
||||
}
|
||||
} catch (error: any) {
|
||||
Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.pages,
|
||||
data: pages
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the media files
|
||||
*/
|
||||
private static filterMedia(files: Uri[]) {
|
||||
return files.filter(file => {
|
||||
const ext = extname(file.fsPath);
|
||||
return ['.jpg', '.jpeg', '.png', '.gif', '.svg'].includes(ext);
|
||||
}).map((file) => ({
|
||||
fsPath: file.fsPath,
|
||||
vsPath: Dashboard.webview?.webview.asWebviewUri(file).toString(),
|
||||
stats: undefined
|
||||
} as MediaInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the dropped file in the current folder
|
||||
* @param fileData
|
||||
*/
|
||||
private static async saveFile({fileName, contents, folder}: { fileName: string; contents: string; folder: string | null }) {
|
||||
if (fileName && contents) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const staticFolder = SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS);
|
||||
const wsPath = wsFolder ? wsFolder.fsPath : "";
|
||||
let absFolderPath = join(wsPath, staticFolder || "", folder || "");
|
||||
|
||||
if (!existsSync(absFolderPath)) {
|
||||
absFolderPath = join(wsPath, folder || "");
|
||||
}
|
||||
|
||||
if (!existsSync(absFolderPath)) {
|
||||
Notifications.error(`We couldn't find your selected folder.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const staticPath = join(absFolderPath, fileName);
|
||||
const imgData = decodeBase64Image(contents);
|
||||
|
||||
if (imgData) {
|
||||
writeFileSync(staticPath, imgData.data);
|
||||
Notifications.info(`File ${fileName} uploaded to: ${staticFolder}/${folder}`);
|
||||
|
||||
const folderPath = `${staticFolder}/${folder}`;
|
||||
if (Dashboard.timers[folderPath]) {
|
||||
clearTimeout(Dashboard.timers[folderPath]);
|
||||
delete Dashboard.timers[folderPath];
|
||||
}
|
||||
|
||||
Dashboard.timers[folderPath] = setTimeout(() => {
|
||||
Dashboard.media = [];
|
||||
Dashboard.getMedia(0, folder || "");
|
||||
delete Dashboard.timers[folderPath];
|
||||
}, 500);
|
||||
} else {
|
||||
Notifications.error(`Something went wrong uploading ${fileName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the selected file
|
||||
* @param fileData
|
||||
*/
|
||||
private static async deleteFile({ file, page, folder }: { file: string; page: number; folder: string | null; }) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
unlinkSync(file);
|
||||
|
||||
Dashboard.media = [];
|
||||
Dashboard.getMedia(page || 0, folder || "");
|
||||
} catch(err) {
|
||||
Notifications.error(`Something went wrong deleting ${basename(file)}`);
|
||||
}
|
||||
public static getWebview() {
|
||||
return Dashboard.webview?.webview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post data to the dashboard
|
||||
* @param msg
|
||||
*/
|
||||
private static postWebviewMessage(msg: { command: DashboardCommand, data?: any }) {
|
||||
public static postWebviewMessage(msg: { command: DashboardCommand, data?: any }) {
|
||||
if (Dashboard.isDisposed) {
|
||||
return;
|
||||
}
|
||||
@@ -494,27 +174,48 @@ export class Dashboard {
|
||||
* @param webView
|
||||
*/
|
||||
private static getWebviewContent(webView: Webview, extensionPath: Uri): string {
|
||||
const scriptUri = webView.asWebviewUri(Uri.joinPath(extensionPath, 'dist', 'pages.js'));
|
||||
const dashboardFile = "dashboardWebView.js";
|
||||
const localServerUrl = "http://localhost:9000";
|
||||
|
||||
let scriptUri = "";
|
||||
const isProd = Extension.getInstance().isProductionMode;
|
||||
if (isProd) {
|
||||
scriptUri = webView.asWebviewUri(Uri.joinPath(extensionPath, 'dist', dashboardFile)).toString();
|
||||
} else {
|
||||
scriptUri = `${localServerUrl}/${dashboardFile}`;
|
||||
}
|
||||
|
||||
const nonce = WebviewHelper.getNonce();
|
||||
|
||||
const version = Extension.getInstance().getVersion();
|
||||
const ext = Extension.getInstance();
|
||||
const version = ext.getVersion();
|
||||
const isBeta = ext.isBetaVersion();
|
||||
|
||||
const csp = [
|
||||
`default-src 'none';`,
|
||||
`img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline'`,
|
||||
`script-src ${isProd ? `'nonce-${nonce}'` : "http://localhost:9000 http://0.0.0.0:9000"}`,
|
||||
`style-src ${webView.cspSource} 'self' 'unsafe-inline'`,
|
||||
`font-src ${webView.cspSource}`,
|
||||
`connect-src https://o1022172.ingest.sentry.io ${isProd ? `` : "ws://localhost:9000 ws://0.0.0.0:9000 http://localhost:9000 http://0.0.0.0:9000"}`
|
||||
];
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" style="width:100%;height:100%;margin:0;padding:0;">
|
||||
<head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline'; script-src 'nonce-${nonce}'; style-src ${webView.cspSource} 'self' 'unsafe-inline'; font-src ${webView.cspSource}">
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="${csp.join('; ')}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>Front Matter Dashboard</title>
|
||||
</head>
|
||||
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden" class="bg-gray-100 text-vulcan-500 dark:bg-vulcan-500 dark:text-whisper-500">
|
||||
<div id="app" style="width:100%;height:100%;margin:0;padding:0;" ${version.usedVersion ? "" : `data-showWelcome="true"`}></div>
|
||||
<div id="app" data-isProd="${isProd}" data-environment="${isBeta ? "BETA" : "main"}" data-version="${version.usedVersion}" style="width:100%;height:100%;margin:0;padding:0;" ${version.usedVersion ? "" : `data-showWelcome="true"`}></div>
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`dashboard-${version.installedVersion}`}" alt="Daily usage" />
|
||||
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
<script ${isProd ? `nonce="${nonce}"` : ""} src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
63
src/commands/Diagnostics.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Folders } from "./Folders";
|
||||
import { ViewColumn, workspace } from "vscode";
|
||||
import ContentProvider from "../providers/ContentProvider";
|
||||
import { join } from "path";
|
||||
import { ContentFolder } from "../models";
|
||||
|
||||
|
||||
export class Diagnostics {
|
||||
|
||||
public static async show() {
|
||||
const folders = Folders.get();
|
||||
const projectName = Folders.getProjectFolderName();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
const folderData = [];
|
||||
for (const folder of folders) {
|
||||
folderData.push(await Diagnostics.processFolder(folder, projectName));
|
||||
}
|
||||
|
||||
const all = await Diagnostics.allProjectFiles();
|
||||
|
||||
const logging = `# Project name
|
||||
|
||||
${projectName}
|
||||
|
||||
# Folders
|
||||
|
||||
${folders.map(f => `- ${f.title}: "${f.path}"`).join("\n")}
|
||||
|
||||
# Workspace folder
|
||||
|
||||
${wsFolder ? wsFolder.fsPath : "No workspace folder"}
|
||||
|
||||
# Total files
|
||||
|
||||
${all}
|
||||
|
||||
# Folders to search files
|
||||
|
||||
${folderData.join("\n")}
|
||||
`;
|
||||
|
||||
ContentProvider.show(logging, `${projectName} diagnostics`, "markdown", ViewColumn.One);
|
||||
}
|
||||
|
||||
private static async allProjectFiles() {
|
||||
const allFiles = await workspace.findFiles(`**/*.*`);
|
||||
return `Total files found: ${allFiles.length}`;
|
||||
}
|
||||
|
||||
private static async processFolder(folder: ContentFolder, projectName: string) {
|
||||
let projectStart = folder.path.split(projectName).pop();
|
||||
projectStart = projectStart || "";
|
||||
projectStart = projectStart?.replace(/\\/g, '/');
|
||||
projectStart = projectStart?.startsWith('/') ? projectStart.substr(1) : projectStart;
|
||||
|
||||
const mdFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md'));
|
||||
const mdxFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx'));
|
||||
const markdownFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown'));
|
||||
|
||||
return `- Project start length: ${projectStart.length} | Search in: "${join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.*')}" | mdFiles: ${mdFiles.length} | mdxFiles: ${mdxFiles.length} | markdownFiles: ${markdownFiles.length}`;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,85 @@
|
||||
import { SETTINGS_CONTENT_PAGE_FOLDERS } from './../constants/settings';
|
||||
import { Questions } from './../helpers/Questions';
|
||||
import { SETTINGS_CONTENT_PAGE_FOLDERS, SETTINGS_CONTENT_STATIC_FOLDER } from './../constants';
|
||||
import { commands, Uri, workspace, window } from "vscode";
|
||||
import { basename, join } from "path";
|
||||
import { ContentFolder, FileInfo, FolderInfo } from "../models";
|
||||
import uniqBy = require("lodash.uniqby");
|
||||
import { Template } from "./Template";
|
||||
import { Notifications } from "../helpers/Notifications";
|
||||
import { CONTEXT } from "../constants/context";
|
||||
import { Settings } from "../helpers";
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { format } from 'date-fns';
|
||||
import { Dashboard } from './Dashboard';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { MediaHelpers } from '../helpers/MediaHelpers';
|
||||
import { MediaListener, PagesListener } from '../listeners';
|
||||
|
||||
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
|
||||
|
||||
export class Folders {
|
||||
|
||||
/**
|
||||
* Add a media folder
|
||||
* @returns
|
||||
*/
|
||||
public static async addMediaFolder(data?: {selectedFolder?: string}) {
|
||||
let wsFolder = Folders.getWorkspaceFolder();
|
||||
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
|
||||
|
||||
let startPath = "";
|
||||
|
||||
if (data?.selectedFolder) {
|
||||
startPath = data.selectedFolder.replace(parseWinPath(wsFolder?.fsPath || ""), "");
|
||||
} else if (staticFolder) {
|
||||
startPath = `/${staticFolder}`;
|
||||
}
|
||||
|
||||
if (startPath && !startPath.endsWith("/")) {
|
||||
startPath += "/";
|
||||
}
|
||||
|
||||
const folderName = await window.showInputBox({
|
||||
prompt: `Which name would you like to give to your folder (use "/" to create multi-level folders)?`,
|
||||
value: startPath,
|
||||
ignoreFocusOut: true,
|
||||
placeHolder: `${format(new Date(), `yyyy/MM`)}`
|
||||
});
|
||||
|
||||
if (!folderName) {
|
||||
Notifications.warning(`No folder name was specified.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const folders = folderName.split("/").filter(f => f);
|
||||
let parentFolders: string[] = [];
|
||||
|
||||
for (const folder of folders) {
|
||||
const folderPath = join(parseWinPath(wsFolder?.fsPath || ""), parentFolders.join("/"), folder);
|
||||
|
||||
parentFolders.push(folder);
|
||||
|
||||
if (!existsSync(folderPath)) {
|
||||
mkdirSync(folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (Dashboard.isOpen) {
|
||||
MediaHelpers.resetMedia();
|
||||
MediaListener.sendMediaFiles(0, folderName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create content in a registered folder
|
||||
* @returns
|
||||
*/
|
||||
public static async create() {
|
||||
const folders = Folders.get();
|
||||
|
||||
if (!folders || folders.length === 0) {
|
||||
Notifications.warning(`There are no known content locations defined in this project.`);
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedFolder: string | undefined;
|
||||
if (folders.length > 1) {
|
||||
selectedFolder = await window.showQuickPick(folders.map(f => f.title), {
|
||||
placeHolder: `Select where you want to create your content`
|
||||
});
|
||||
} else {
|
||||
selectedFolder = folders[0].title;
|
||||
}
|
||||
|
||||
const selectedFolder = await Questions.SelectContentFolder();
|
||||
if (!selectedFolder) {
|
||||
Notifications.warning(`You didn't select a place where you wanted to create your content.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const folders = Folders.get();
|
||||
const location = folders.find(f => f.title === selectedFolder);
|
||||
if (location) {
|
||||
const folderPath = Folders.getFolderPath(Uri.file(location.path));
|
||||
@@ -115,9 +157,33 @@ export class Folders {
|
||||
*/
|
||||
public static getWorkspaceFolder(): Uri | undefined {
|
||||
const folders = workspace.workspaceFolders;
|
||||
if (folders && folders.length > 0) {
|
||||
|
||||
if (folders && folders.length === 1) {
|
||||
return folders[0].uri;
|
||||
} else if (folders && folders.length > 1) {
|
||||
let projectFolder = undefined;
|
||||
|
||||
for (const folder of folders) {
|
||||
if (!projectFolder && existsSync(join(folder.uri.fsPath, Settings.globalFile))) {
|
||||
projectFolder = folder.uri;
|
||||
}
|
||||
}
|
||||
|
||||
if (!projectFolder) {
|
||||
window.showWorkspaceFolderPick({
|
||||
placeHolder: `Please select the main workspace folder for Front Matter to use.`
|
||||
}).then(selectedFolder => {
|
||||
if (selectedFolder) {
|
||||
Settings.createGlobalFile(selectedFolder.uri);
|
||||
// Full reload to make sure the whole extension is reloaded correctly
|
||||
commands.executeCommand(`workbench.action.reloadWindow`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return projectFolder;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -127,7 +193,6 @@ export class Folders {
|
||||
public static getProjectFolderName(): string {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (wsFolder) {
|
||||
// const projectFolder = wsFolder?.fsPath.split('\\').join('/').split('/').pop();
|
||||
return basename(wsFolder.fsPath);
|
||||
}
|
||||
return "";
|
||||
@@ -145,19 +210,23 @@ export class Folders {
|
||||
try {
|
||||
const projectName = Folders.getProjectFolderName();
|
||||
let projectStart = folder.path.split(projectName).pop();
|
||||
|
||||
if (projectStart) {
|
||||
projectStart = projectStart.replace(/\\/g, '/');
|
||||
projectStart = projectStart.startsWith('/') ? projectStart.substr(1) : projectStart;
|
||||
const mdFiles = await workspace.findFiles(join(projectStart, '**/*.md'));
|
||||
const mdxFiles = await workspace.findFiles(join(projectStart, '**/*.mdx'));
|
||||
let files = [...mdFiles, ...mdxFiles];
|
||||
const mdFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md'));
|
||||
const markdownFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown'));
|
||||
const mdxFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx'));
|
||||
let files = [...mdFiles, ...markdownFiles, ...mdxFiles];
|
||||
if (files) {
|
||||
let fileStats: FileInfo[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const fileName = basename(file.fsPath);
|
||||
|
||||
const stats = await workspace.fs.stat(file);
|
||||
|
||||
fileStats.push({
|
||||
filePath: file.fsPath,
|
||||
fileName,
|
||||
@@ -201,7 +270,7 @@ export class Folders {
|
||||
const folders: ContentFolder[] = Settings.get(SETTINGS_CONTENT_PAGE_FOLDERS) as ContentFolder[];
|
||||
|
||||
return folders.map(folder => ({
|
||||
title: folder.title,
|
||||
...folder,
|
||||
path: Folders.absWsFolder(folder, wsFolder)
|
||||
}));
|
||||
}
|
||||
@@ -212,7 +281,16 @@ export class Folders {
|
||||
*/
|
||||
private static async update(folders: ContentFolder[]) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folders.map(folder => ({ title: folder.title, path: Folders.relWsFolder(folder, wsFolder) })));
|
||||
|
||||
let folderDetails = folders.map(folder => ({
|
||||
...folder,
|
||||
path: Folders.relWsFolder(folder, wsFolder)
|
||||
}));
|
||||
|
||||
await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folderDetails, true);
|
||||
|
||||
// Reinitialize the folder listeners
|
||||
PagesListener.startWatchers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,7 +301,7 @@ export class Folders {
|
||||
*/
|
||||
private static absWsFolder(folder: ContentFolder, wsFolder?: Uri) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, wsFolder?.fsPath || "");
|
||||
let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ""));
|
||||
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
|
||||
return absPath;
|
||||
}
|
||||
@@ -236,7 +314,7 @@ export class Folders {
|
||||
*/
|
||||
private static relWsFolder(folder: ContentFolder, wsFolder?: Uri) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
let absPath = folder.path.replace(wsFolder?.fsPath || "", WORKSPACE_PLACEHOLDER);
|
||||
let absPath = folder.path.replace(parseWinPath(wsFolder?.fsPath || ""), WORKSPACE_PLACEHOLDER);
|
||||
absPath = isWindows ? absPath.split('\\').join('/') : absPath;
|
||||
return absPath;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { SETTING_PREVIEW_HOST, SETTING_PREVIEW_PATHNAME } from './../constants/settings';
|
||||
import { SETTING_PREVIEW_HOST, SETTING_PREVIEW_PATHNAME, CONTEXT } from './../constants';
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { join } from "path";
|
||||
import { commands, env, Uri, ViewColumn, window } from "vscode";
|
||||
import { Settings } from '../helpers';
|
||||
import { PreviewSettings } from '../models';
|
||||
import { format } from 'date-fns';
|
||||
import { CONTEXT } from '../constants/context';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
import { Article } from '.';
|
||||
import { urlJoin } from 'url-join-ts';
|
||||
|
||||
|
||||
export class Preview {
|
||||
@@ -32,12 +34,25 @@ export class Preview {
|
||||
const article = editor ? ArticleHelper.getFrontMatter(editor) : null;
|
||||
let slug = article?.data ? article.data.slug : "";
|
||||
|
||||
if (settings.pathname) {
|
||||
let pathname = settings.pathname;
|
||||
if (article?.data) {
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
if (contentType && contentType.previewPath) {
|
||||
pathname = contentType.previewPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (!slug) {
|
||||
slug = Article.getSlug();
|
||||
}
|
||||
|
||||
if (pathname) {
|
||||
const articleDate = ArticleHelper.getDate(article);
|
||||
|
||||
try {
|
||||
slug = join(format(articleDate || new Date(), settings.pathname), slug);
|
||||
slug = join(format(articleDate || new Date(), DateHelper.formatUpdate(pathname) as string), slug);
|
||||
} catch (error) {
|
||||
slug = join(settings.pathname, slug);
|
||||
slug = join(pathname, slug);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +128,9 @@ export class Preview {
|
||||
</head>
|
||||
<body>
|
||||
<div class="slug">
|
||||
<input type="text" value="${join(localhostUrl.toString(), slug)}" disabled />
|
||||
<input type="text" value="${urlJoin(localhostUrl.toString(), slug || '')}" disabled />
|
||||
</div>
|
||||
<iframe src="${join(localhostUrl.toString(), slug)}" >
|
||||
<iframe src="${urlJoin(localhostUrl.toString(), slug || '')}" >
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
@@ -132,4 +147,4 @@ export class Preview {
|
||||
pathname
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { workspace, Uri } from "vscode";
|
||||
import { CONFIG_KEY, SETTING_TEMPLATES_FOLDER } from "../constants";
|
||||
import { join } from "path";
|
||||
import * as fs from "fs";
|
||||
import { Notifications } from "../helpers/Notifications";
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants/settings';
|
||||
import { SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants';
|
||||
import * as vscode from 'vscode';
|
||||
import { ArticleHelper, SeoHelper, Settings } from '../helpers';
|
||||
import { ExplorerView } from '../explorerView/ExplorerView';
|
||||
import { DefaultFields } from '../constants';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
|
||||
export class StatusListener {
|
||||
|
||||
@@ -15,6 +16,11 @@ export class StatusListener {
|
||||
public static async verify(frontMatterSB: vscode.StatusBarItem, collection: vscode.DiagnosticCollection) {
|
||||
const draftMsg = "in draft";
|
||||
const publishMsg = "to publish";
|
||||
|
||||
const draft = ContentType.getDraftField();
|
||||
if (!draft || draft.type !== "boolean") {
|
||||
frontMatterSB.hide();
|
||||
}
|
||||
|
||||
let editor = vscode.window.activeTextEditor;
|
||||
if (editor && ArticleHelper.isMarkdownFile()) {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { Questions } from './../helpers/Questions';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { SETTING_TEMPLATES_FOLDER, SETTING_TEMPLATES_PREFIX } from '../constants';
|
||||
import { format } from 'date-fns';
|
||||
import sanitize from '../helpers/Sanitize';
|
||||
import { ArticleHelper, Settings } from '../helpers';
|
||||
import { Article } from '.';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { CONTEXT } from '../constants/context';
|
||||
import { CONTEXT } from '../constants';
|
||||
import { Project } from './Project';
|
||||
import { Folders } from './Folders';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
import { ContentType as IContentType } from '../models';
|
||||
import { PagesListener } from '../listeners';
|
||||
|
||||
export class Template {
|
||||
|
||||
@@ -67,7 +69,7 @@ export class Template {
|
||||
["yes", "no"],
|
||||
{
|
||||
canPickMany: false,
|
||||
placeHolder: `Do you want to keep the article its contents for the template?`,
|
||||
placeHolder: `Do you want to keep the contents for the template?`,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -94,7 +96,7 @@ export class Template {
|
||||
*/
|
||||
public static async create(folderPath: string) {
|
||||
const folder = Settings.get<string>(SETTING_TEMPLATES_FOLDER);
|
||||
const prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
const contentTypes = ContentType.getAll();
|
||||
|
||||
if (!folderPath) {
|
||||
Notifications.warning(`Incorrect project folder path retrieved.`);
|
||||
@@ -113,39 +115,33 @@ export class Template {
|
||||
}
|
||||
|
||||
const selectedTemplate = await vscode.window.showQuickPick(templates.map(t => path.basename(t.fsPath)), {
|
||||
placeHolder: `Select the article template to use`
|
||||
placeHolder: `Select the content template to use`
|
||||
});
|
||||
if (!selectedTemplate) {
|
||||
Notifications.warning(`No template selected.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const titleValue = await vscode.window.showInputBox({
|
||||
prompt: `What would you like to use as a title for the new article?`,
|
||||
placeHolder: `Article title`
|
||||
});
|
||||
const titleValue = await Questions.ContentTitle();
|
||||
if (!titleValue) {
|
||||
Notifications.warning(`You did not specify an article title.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the template read
|
||||
const template = templates.find(t => t.fsPath.endsWith(selectedTemplate));
|
||||
if (!template) {
|
||||
Notifications.warning(`Article template could not be found.`);
|
||||
Notifications.warning(`Content template could not be found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const fileExt = path.parse(selectedTemplate).ext;
|
||||
const sanitizedName = sanitize(titleValue.toLowerCase().replace(/ /g, "-"));
|
||||
let newFileName = `${sanitizedName}${fileExt}`;
|
||||
if (prefix && typeof prefix === "string") {
|
||||
newFileName = `${format(new Date(), prefix)}-${newFileName}`;
|
||||
const templateData = ArticleHelper.getFrontMatterByPath(template.fsPath);
|
||||
let contentType: IContentType | undefined;
|
||||
if (templateData && templateData.data && templateData.data.type) {
|
||||
contentType = contentTypes?.find(t => t.name === templateData.data.type);
|
||||
}
|
||||
|
||||
const newFilePath = path.join(folderPath, newFileName);
|
||||
if (fs.existsSync(newFilePath)) {
|
||||
Notifications.warning(`File already exists, please remove it before creating a new one with the same title.`);
|
||||
let newFilePath: string | undefined = ArticleHelper.createContent(contentType, folderPath, titleValue);
|
||||
if (!newFilePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,7 +161,7 @@ export class Template {
|
||||
fmData.title = titleValue;
|
||||
}
|
||||
if (typeof fmData.slug !== "undefined") {
|
||||
fmData.slug = sanitizedName;
|
||||
fmData.slug = ArticleHelper.sanitize(titleValue);
|
||||
}
|
||||
|
||||
frontMatter = Article.updateDate(frontMatter);
|
||||
@@ -180,7 +176,10 @@ export class Template {
|
||||
vscode.window.showTextDocument(txtDoc);
|
||||
}
|
||||
|
||||
Notifications.info(`Your new article has been created.`);
|
||||
Notifications.info(`Your new content has been created.`);
|
||||
|
||||
// Trigger a refresh for the dashboard
|
||||
PagesListener.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
230
src/commands/Wysiwyg.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
import { commands, window, Selection, QuickPickItem } from "vscode";
|
||||
import { COMMAND_NAME, CONTEXT, SETTINGS_CONTENT_WYSIWYG } from "../constants";
|
||||
import { Settings } from "../helpers";
|
||||
|
||||
enum MarkupType {
|
||||
bold = 1,
|
||||
italic,
|
||||
strikethrough,
|
||||
code,
|
||||
codeblock,
|
||||
blockquote,
|
||||
heading,
|
||||
unorderedList,
|
||||
orderedList,
|
||||
taskList
|
||||
}
|
||||
|
||||
export class Wysiwyg {
|
||||
|
||||
/**
|
||||
* Registers the markup commands for the WYSIWYG controls
|
||||
* @param subscriptions
|
||||
* @returns
|
||||
*/
|
||||
public static async registerCommands(subscriptions: any) {
|
||||
|
||||
const wysiwygEnabled = Settings.get(SETTINGS_CONTENT_WYSIWYG);
|
||||
|
||||
if (!wysiwygEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
await commands.executeCommand('setContext', CONTEXT.wysiwyg, true);
|
||||
|
||||
// Surrounding markup
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.bold, () => this.addMarkup(MarkupType.bold)));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.italic, () => this.addMarkup(MarkupType.italic)));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.strikethrough, () => this.addMarkup(MarkupType.strikethrough)));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.code, () => this.addMarkup(MarkupType.code)));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.codeblock, () => this.addMarkup(MarkupType.codeblock)));
|
||||
|
||||
// Prefix markup
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.heading, () => this.addMarkup(MarkupType.heading)));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.blockquote, () => this.addMarkup(MarkupType.blockquote)));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.unorderedlist, () => this.addMarkup(MarkupType.unorderedList)));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.orderedlist, () => this.addMarkup(MarkupType.orderedList)));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.taskList, () => this.addMarkup(MarkupType.taskList)));
|
||||
|
||||
// Options
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.options, async () => {
|
||||
const qpItems: QuickPickItem[] = [
|
||||
{ label: "$(list-unordered) Unordered list", detail: "Add an unordered list", alwaysShow: true, },
|
||||
{ label: "$(list-ordered) Ordered list", detail: "Add an ordered list", alwaysShow: true },
|
||||
{ label: "$(tasklist) Task list", detail: "Add a task list", alwaysShow: true },
|
||||
{ label: "$(code) Code", detail: "Add inline code snippet", alwaysShow: true },
|
||||
{ label: "$(symbol-namespace) Code block", detail: "Add a code block", alwaysShow: true },
|
||||
]
|
||||
|
||||
const option = await window.showQuickPick([ ...qpItems ], {
|
||||
placeHolder: "Which type of markup would you like to insert?",
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: false,
|
||||
});
|
||||
|
||||
if (option) {
|
||||
if (option.label === qpItems[0].label) {
|
||||
await this.addMarkup(MarkupType.unorderedList);
|
||||
} else if (option.label === qpItems[1].label) {
|
||||
await this.addMarkup(MarkupType.orderedList);
|
||||
} else if (option.label === qpItems[2].label) {
|
||||
await this.addMarkup(MarkupType.taskList);
|
||||
} else if (option.label === qpItems[3].label) {
|
||||
await this.addMarkup(MarkupType.code);
|
||||
} else if (option.label === qpItems[4].label) {
|
||||
await this.addMarkup(MarkupType.codeblock);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the markup to the content
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static async addMarkup(type: MarkupType) {
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = editor.selection;
|
||||
const hasTextSelection = !selection.isEmpty;
|
||||
|
||||
const markers = this.getMarkers(type);
|
||||
if (!markers) {
|
||||
return;
|
||||
}
|
||||
|
||||
const crntSelection = selection.active;
|
||||
|
||||
if (hasTextSelection) {
|
||||
// Replace the selection and surround with the markup
|
||||
const selectionText = editor.document.getText(selection);
|
||||
const txt = await this.insertText(markers, type, selectionText);
|
||||
|
||||
editor.edit(builder => {
|
||||
builder.replace(selection, txt);
|
||||
});
|
||||
} else {
|
||||
const txt = await this.insertText(markers, type);
|
||||
|
||||
// Insert the markers where cursor is located.
|
||||
const markerLength = this.isMarkupWrapping(type) ? txt.length + 1 : markers.length;
|
||||
let newPosition = crntSelection.with(crntSelection.line, crntSelection.character + markerLength);
|
||||
|
||||
await editor.edit(builder => {
|
||||
builder.insert(newPosition, txt);
|
||||
});
|
||||
|
||||
if (type === MarkupType.codeblock) {
|
||||
newPosition = crntSelection.with(crntSelection.line + 1, 0);
|
||||
}
|
||||
|
||||
editor.selection = new Selection(newPosition, newPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the text will be wrapped
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static isMarkupWrapping(type: MarkupType) {
|
||||
return (
|
||||
type === MarkupType.blockquote ||
|
||||
type === MarkupType.heading ||
|
||||
type === MarkupType.unorderedList ||
|
||||
type === MarkupType.orderedList ||
|
||||
type === MarkupType.taskList
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert text at the current cursor position
|
||||
*/
|
||||
private static async insertText(marker: string | undefined, type: MarkupType, text: string | null = null) {
|
||||
const crntText = text || this.lineBreak(type);
|
||||
|
||||
if (this.isMarkupWrapping(type)) {
|
||||
if (type === MarkupType.heading) {
|
||||
const headingLvl = await window.showQuickPick([
|
||||
"Heading 1",
|
||||
"Heading 2",
|
||||
"Heading 3",
|
||||
"Heading 4",
|
||||
"Heading 5",
|
||||
"Heading 6"
|
||||
], {
|
||||
canPickMany: false,
|
||||
placeHolder: "Which heading level do you want to insert?",
|
||||
ignoreFocusOut: false
|
||||
});
|
||||
|
||||
if (headingLvl) {
|
||||
const headingNr = parseInt(headingLvl.replace("Heading ", ""));
|
||||
return `${Array(headingNr + 1).join(marker)} ${crntText}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === MarkupType.unorderedList || type === MarkupType.taskList) {
|
||||
const lines = crntText.split("\n").map(line => `${marker} ${line}`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
if (type === MarkupType.orderedList) {
|
||||
const lines = crntText.split("\n").map((line, idx) => `${idx+1}. ${line}`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
return `${marker} ${crntText}`;
|
||||
} else {
|
||||
return `${marker}${crntText}${marker}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if linebreak needs to be added
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static lineBreak(type: MarkupType) {
|
||||
if (type === MarkupType.codeblock) {
|
||||
return `\n\n`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of markers
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static getMarkers(type: MarkupType) {
|
||||
switch(type) {
|
||||
case MarkupType.bold:
|
||||
return `**`;
|
||||
case MarkupType.italic:
|
||||
return `*`;
|
||||
case MarkupType.strikethrough:
|
||||
return `~~`;
|
||||
case MarkupType.code:
|
||||
return "`";
|
||||
case MarkupType.codeblock:
|
||||
return "```";
|
||||
case MarkupType.blockquote:
|
||||
return ">";
|
||||
case MarkupType.heading:
|
||||
return "#";
|
||||
case MarkupType.unorderedList:
|
||||
return "-";
|
||||
case MarkupType.orderedList:
|
||||
return "1.";
|
||||
case MarkupType.taskList:
|
||||
return "- [ ]";
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/components/icons/CompressIcon.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ICompressIconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const CompressIcon: React.FunctionComponent<ICompressIconProps> = ({className}: React.PropsWithChildren<ICompressIconProps>) => {
|
||||
return (
|
||||
<svg className={className || ""} aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path fill="currentColor" d="M436 192H312c-13.3 0-24-10.7-24-24V44c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v84h84c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm-276-24V44c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v84H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24zm0 300V344c0-13.3-10.7-24-24-24H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-84h84c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12H312c-13.3 0-24 10.7-24 24v124c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12z"></path>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,8 @@ export const DEFAULT_CONTENT_TYPE_NAME = 'default';
|
||||
|
||||
export const DEFAULT_CONTENT_TYPE: ContentType = {
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"previewPath": null,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
@@ -21,14 +23,14 @@ export const DEFAULT_CONTENT_TYPE: ContentType = {
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"title": "Article preview",
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
"type": "draft"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
export const DefaultFields = {
|
||||
PublishingDate: `date`,
|
||||
LastModified: `lastmod`,
|
||||
Description: `description`
|
||||
Description: `description`,
|
||||
Slug: `slug`
|
||||
};
|
||||
|
||||
@@ -3,9 +3,6 @@ const extensionName = "frontMatter";
|
||||
export const EXTENSION_ID = 'eliostruyf.vscode-front-matter';
|
||||
export const EXTENSION_BETA_ID = 'eliostruyf.vscode-front-matter-beta';
|
||||
|
||||
export const EXTENSION_STATE_VERSION = 'frontMatter:Version';
|
||||
export const EXTENSION_STATE_PAGES_VIEW = 'frontMatter:Pages:ViewType';
|
||||
|
||||
export const getCommandName = (command: string) => {
|
||||
return `${extensionName}.${command}`;
|
||||
};
|
||||
@@ -25,10 +22,29 @@ export const COMMAND_NAME = {
|
||||
registerFolder: getCommandName("registerFolder"),
|
||||
unregisterFolder: getCommandName("unregisterFolder"),
|
||||
createContent: getCommandName("createContent"),
|
||||
createByContentType: getCommandName("createByContentType"),
|
||||
createByTemplate: getCommandName("createByTemplate"),
|
||||
createTemplate: getCommandName("createTemplate"),
|
||||
collapseSections: getCommandName("collapseSections"),
|
||||
preview: getCommandName("preview"),
|
||||
dashboard: getCommandName("dashboard"),
|
||||
dashboardMedia: getCommandName("dashboard.media"),
|
||||
dashboardClose: getCommandName("dashboard.close"),
|
||||
promote: getCommandName("promoteSettings"),
|
||||
insertImage: getCommandName("insertImage"),
|
||||
createFolder: getCommandName("createFolder"),
|
||||
diagnostics: getCommandName("diagnostics"),
|
||||
|
||||
// WYSIWYG
|
||||
bold: getCommandName("markup.bold"),
|
||||
italic: getCommandName("markup.italic"),
|
||||
strikethrough: getCommandName("markup.strikethrough"),
|
||||
code: getCommandName("markup.code"),
|
||||
codeblock: getCommandName("markup.codeblock"),
|
||||
heading: getCommandName("markup.heading"),
|
||||
blockquote: getCommandName("markup.blockquote"),
|
||||
unorderedlist: getCommandName("markup.unorderedlist"),
|
||||
orderedlist: getCommandName("markup.orderedlist"),
|
||||
taskList: getCommandName("markup.tasklist"),
|
||||
options: getCommandName("markup.options"),
|
||||
};
|
||||
17
src/constants/ExtensionState.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
export const ExtensionState = {
|
||||
PagesView: `frontMatter:Pages:ViewType`,
|
||||
SelectedFolder: `frontMatter:SelectedFolder`,
|
||||
Version: `frontMatter:Version`,
|
||||
SettingPromoted: `frontMatter:Settings:Promoted`,
|
||||
MoveTemplatesFolder: `frontMatter:Templates:Move`,
|
||||
|
||||
Dashboard: {
|
||||
Contents: {
|
||||
Sorting: `frontMatter:Dashboard:Contents:Sorting`,
|
||||
},
|
||||
Media: {
|
||||
Sorting: `frontMatter:Dashboard:Media:Sorting`,
|
||||
}
|
||||
}
|
||||
};
|
||||
21
src/constants/FrameworkDetectors.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export const FrameworkDetectors = [
|
||||
{
|
||||
"framework": {"name": "gatsby", "dist": "public", "static": "static", "build": "gatsby build"},
|
||||
"requiredFiles": ["gatsby-config.js"],
|
||||
"requiredDependencies": ["gatsby"]
|
||||
},
|
||||
{
|
||||
"framework": {"name": "hugo", "dist": "public", "static": "static", "build": "hugo"},
|
||||
"requiredFiles": ["config.toml", "config.yaml", "config.yml"]
|
||||
},
|
||||
{
|
||||
"framework": {"name": "next", "dist": ".next", "static": "public", "build": "next build"},
|
||||
"requiredFiles": ["next.config.js"],
|
||||
"requiredDependencies": ["next"]
|
||||
},
|
||||
{
|
||||
"framework": {"name": "nuxt", "dist": "dist", "static": "static", "build": "nuxt"},
|
||||
"requiredFiles": ["nuxt.config.js"],
|
||||
"requiredDependencies": ["nuxt"]
|
||||
}
|
||||
];
|
||||
@@ -1,4 +1,6 @@
|
||||
export const GITHUB_LINK = "https://github.com/estruyf/vscode-front-matter";
|
||||
export const ISSUE_LINK = "https://github.com/estruyf/vscode-front-matter/issues";
|
||||
export const SPONSOR_LINK = "https://github.com/sponsors/estruyf";
|
||||
export const REVIEW_LINK = "https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter&ssr=false#review-details";
|
||||
export const REVIEW_LINK = "https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter&ssr=false#review-details";
|
||||
|
||||
export const SENTRY_LINK = "https://1ac45704bbe74264a7b4674bdc2abf48@o1022172.ingest.sentry.io/5988293";
|
||||
8
src/constants/LocalStore.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
export const LocalStore = {
|
||||
rootFolder: ".frontmatter",
|
||||
contentFolder: "content",
|
||||
templatesFolder: "templates",
|
||||
mediaDatabaseFile: "mediaDb.json"
|
||||
}
|
||||
1
src/constants/Navigation.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const HOME_PAGE_NAVIGATION_ID = "FrontMatter:RootFolder";
|
||||
@@ -1,5 +1,8 @@
|
||||
export const CONTEXT = {
|
||||
canInit: "frontMatterCanInit",
|
||||
canOpenPreview: "frontMatterCanOpenPreview",
|
||||
canOpenDashboard: "frontMatterCanOpenDashboard"
|
||||
canOpenDashboard: "frontMatterCanOpenDashboard",
|
||||
isEnabled: "frontMatter:enabled",
|
||||
isDashboardOpen: "frontMatter:dashboard:open",
|
||||
wysiwyg: "frontMatter:markdown:wysiwyg",
|
||||
};
|
||||
@@ -1,6 +1,10 @@
|
||||
export * from './ContentType';
|
||||
export * from './DefaultFields';
|
||||
export * from './Extension';
|
||||
export * from './ExtensionState';
|
||||
export * from './Links';
|
||||
export * from './LocalStore';
|
||||
export * from './Navigation';
|
||||
export * from './charMap';
|
||||
export * from './context';
|
||||
export * from './settings';
|
||||
|
||||
@@ -2,8 +2,12 @@ export const EXTENSION_NAME = "Front Matter";
|
||||
|
||||
export const CONFIG_KEY = "frontMatter";
|
||||
|
||||
export const SETTING_GLOBAL_NOTIFICATIONS = "global.notifications";
|
||||
|
||||
export const SETTING_TAXONOMY_TAGS = "taxonomy.tags";
|
||||
export const SETTING_TAXONOMY_CATEGORIES = "taxonomy.categories";
|
||||
export const SETTING_TAXONOMY_CUSTOM = "taxonomy.customTaxonomy";
|
||||
|
||||
export const SETTING_DATE_FORMAT = "taxonomy.dateFormat";
|
||||
export const SETTING_COMMA_SEPARATED_FIELDS = "taxonomy.commaSeparatedFields";
|
||||
export const SETTING_TAXONOMY_CONTENT_TYPES = "taxonomy.contentTypes";
|
||||
@@ -20,6 +24,7 @@ export const SETTING_REMOVE_QUOTES = "taxonomy.noPropertyValueQuotes";
|
||||
export const SETTING_FRONTMATTER_TYPE = "taxonomy.frontMatterType";
|
||||
|
||||
export const SETTING_SEO_TITLE_LENGTH = "taxonomy.seoTitleLength";
|
||||
export const SETTING_SEO_SLUG_LENGTH = "taxonomy.seoSlugLength";
|
||||
export const SETTING_SEO_DESCRIPTION_LENGTH = "taxonomy.seoDescriptionLength";
|
||||
export const SETTING_SEO_CONTENT_MIN_LENGTH = "taxonomy.seoContentLengh";
|
||||
export const SETTING_SEO_DESCRIPTION_FIELD = "taxonomy.seoDescriptionField";
|
||||
@@ -36,12 +41,22 @@ export const SETTING_CUSTOM_SCRIPTS = "custom.scripts";
|
||||
|
||||
export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate";
|
||||
export const SETTINGS_CONTENT_PAGE_FOLDERS = "content.pageFolders";
|
||||
export const SETTINGS_CONTENT_STATIC_FOLDERS = "content.publicFolder";
|
||||
export const SETTINGS_CONTENT_STATIC_FOLDER = "content.publicFolder";
|
||||
export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
|
||||
export const SETTINGS_CONTENT_DRAFT_FIELD = "content.draftField";
|
||||
export const SETTINGS_CONTENT_SORTING = "content.sorting";
|
||||
export const SETTINGS_CONTENT_WYSIWYG = "content.wysiwyg";
|
||||
|
||||
export const SETTINGS_CONTENT_SORTING_DEFAULT = "content.defaultSorting";
|
||||
export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting";
|
||||
|
||||
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
|
||||
export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
|
||||
|
||||
export const SETTINGS_FRAMEWORK_ID = "framework.id";
|
||||
|
||||
export const SETTING_SITE_BASEURL = "site.baseURL";
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@@ -4,4 +4,5 @@ export enum DashboardCommand {
|
||||
settings = "settings",
|
||||
media = "media",
|
||||
viewData = "viewData",
|
||||
mediaUpdate = "mediaUpdate"
|
||||
}
|
||||
@@ -4,6 +4,8 @@ export enum DashboardMessage {
|
||||
openFile = 'openFile',
|
||||
getTheme = 'getTheme',
|
||||
createContent = 'createContent',
|
||||
createByContentType = 'createByContentType',
|
||||
createByTemplate = 'createByTemplate',
|
||||
updateSetting = 'updateSetting',
|
||||
initializeProject = 'initializeProject',
|
||||
reload = 'reload',
|
||||
@@ -14,4 +16,9 @@ export enum DashboardMessage {
|
||||
uploadMedia = 'uploadMedia',
|
||||
deleteMedia = 'deleteMedia',
|
||||
insertPreviewImage = 'insertPreviewImage',
|
||||
updateMediaMetadata = 'updateMediaMetadata',
|
||||
createMediaFolder = 'createMediaFolder',
|
||||
setFramework = 'setFramework',
|
||||
setState = 'setState',
|
||||
runCustomScript = 'runCustomScript',
|
||||
}
|
||||
52
src/dashboardWebView/components/ChoiceButton.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import {ChevronDownIcon} from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { MenuItem, MenuItems } from './Menu';
|
||||
|
||||
export interface IChoiceButtonProps {
|
||||
title: string;
|
||||
choices: {
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
}[];
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const ChoiceButton: React.FunctionComponent<IChoiceButtonProps> = ({onClick, disabled, choices, title}: React.PropsWithChildren<IChoiceButtonProps>) => {
|
||||
return (
|
||||
<span className="relative z-50 inline-flex shadow-sm rounded-md">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-600 hover:bg-teal-700 focus:outline-none disabled:bg-gray-500"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
|
||||
<Menu as="span" className="-ml-px relative block">
|
||||
<Menu.Button
|
||||
className="h-full inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-700 hover:bg-teal-800 focus:outline-none disabled:bg-gray-500"
|
||||
disabled={disabled}>
|
||||
<span className="sr-only">Open options</span>
|
||||
<ChevronDownIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</Menu.Button>
|
||||
|
||||
<MenuItems widthClass={`w-56`}>
|
||||
<div className="py-1">
|
||||
{choices.map((choice) => (
|
||||
<MenuItem
|
||||
key={choice.title}
|
||||
title={choice.title}
|
||||
value={null}
|
||||
onClick={choice.onClick}
|
||||
disabled={choice.disabled} />
|
||||
))}
|
||||
</div>
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { Header } from '../Header';
|
||||
import { Overview } from './Overview';
|
||||
import { Spinner } from '../Spinner';
|
||||
import { SponsorMsg } from '../SponsorMsg';
|
||||
import usePages from '../../hooks/usePages';
|
||||
|
||||
export interface IContentsProps {
|
||||
pages: Page[];
|
||||
@@ -14,23 +15,22 @@ export interface IContentsProps {
|
||||
|
||||
export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loading}: React.PropsWithChildren<IContentsProps>) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const { pageItems } = usePages(pages);
|
||||
|
||||
const pageFolders = [...new Set(pages.map(page => page.fmFolder))];
|
||||
const pageFolders = [...new Set(pageItems.map(page => page.fmFolder))];
|
||||
|
||||
return (
|
||||
<main className={`h-full w-full`}>
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<Header
|
||||
folders={pageFolders}
|
||||
totalPages={pages.length}
|
||||
settings={settings} />
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<Header
|
||||
folders={pageFolders}
|
||||
totalPages={pageItems.length}
|
||||
settings={settings} />
|
||||
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
|
||||
{ loading ? <Spinner /> : <Overview pages={pages} settings={settings} /> }
|
||||
</div>
|
||||
|
||||
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} />
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
|
||||
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,29 +3,35 @@ import { useRecoilValue } from 'recoil';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Page } from '../../models/Page';
|
||||
import { ViewSelector, ViewType } from '../../state';
|
||||
import { SettingsSelector, ViewSelector } from '../../state';
|
||||
import { DateField } from '../DateField';
|
||||
import { Status } from '../Status';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import useContentType from '../../../hooks/useContentType';
|
||||
import { DashboardViewType } from '../../models';
|
||||
|
||||
export interface IItemProps extends Page {}
|
||||
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, draft, description, preview }: React.PropsWithChildren<IItemProps>) => {
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, draft, description, type, ...pageData }: React.PropsWithChildren<IItemProps>) => {
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const contentType = useContentType(settings, { type });
|
||||
|
||||
const previewField = contentType.fields.find(field => field.isPreviewImage && field.type === "image")?.name || "preview";
|
||||
|
||||
const openFile = () => {
|
||||
Messenger.send(DashboardMessage.openFile, fmFilePath);
|
||||
};
|
||||
|
||||
if (view === ViewType.Grid) {
|
||||
if (view === DashboardViewType.Grid) {
|
||||
return (
|
||||
<li className="relative">
|
||||
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left overflow-hidden shadow-md hover:shadow-xl dark:hover:bg-vulcan-100`}
|
||||
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left overflow-hidden shadow-md hover:shadow-xl dark:hover:bg-vulcan-100 border border-gray-100 dark:border-vulcan-50`}
|
||||
onClick={openFile}>
|
||||
<div className="relative h-36 w-full overflow-hidden border-b border-gray-100 dark:border-vulcan-100 dark:group-hover:border-vulcan-200">
|
||||
{
|
||||
preview ? (
|
||||
<img src={`${preview}`} alt={title} className="absolute inset-0 h-full w-full object-cover" loading="lazy" />
|
||||
previewField && pageData[previewField] ? (
|
||||
<img src={`${pageData[previewField]}`} alt={title} className="absolute inset-0 h-full w-full object-cover" loading="lazy" />
|
||||
) : (
|
||||
<div className={`flex items-center justify-center bg-whisper-500 dark:bg-vulcan-200 dark:group-hover:bg-vulcan-100`}>
|
||||
<MarkdownIcon className={`h-32 text-vulcan-100 dark:text-whisper-100`} />
|
||||
@@ -36,7 +42,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
|
||||
<div className="p-4 w-full">
|
||||
<div className={`flex justify-between items-center`}>
|
||||
<Status draft={!!draft} />
|
||||
<Status draft={draft} />
|
||||
|
||||
<DateField value={date} />
|
||||
</div>
|
||||
@@ -48,7 +54,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
} else if (view === ViewType.List) {
|
||||
} else if (view === DashboardViewType.List) {
|
||||
return (
|
||||
<li className="relative">
|
||||
<button className={`px-5 cursor-pointer w-full text-left grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8 py-2 border-b border-gray-300 hover:bg-gray-200 dark:border-vulcan-50 dark:hover:bg-vulcan-50 hover:bg-opacity-70`} onClick={openFile}>
|
||||
@@ -59,7 +65,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
<DateField value={date} />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Status draft={!!draft} />
|
||||
<Status draft={draft} />
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { ViewSelector, ViewType } from '../../state';
|
||||
import { DashboardViewType } from '../../models';
|
||||
import { ViewSelector } from '../../state';
|
||||
|
||||
export interface IListProps {}
|
||||
|
||||
@@ -8,15 +9,15 @@ export const List: React.FunctionComponent<IListProps> = ({children}: React.Prop
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
|
||||
let className = '';
|
||||
if (view === ViewType.Grid) {
|
||||
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`;
|
||||
} else if (view === ViewType.List) {
|
||||
} else if (view === DashboardViewType.List) {
|
||||
className = `-mx-4`;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul role="list" className={className}>
|
||||
{view === ViewType.List && (
|
||||
{view === DashboardViewType.List && (
|
||||
<li className="px-5 relative uppercase text-vulcan-100 dark:text-whisper-900 py-2 border-b border-vulcan-50">
|
||||
<div className={`grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8`}>
|
||||
<div className="col-span-8">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import { ChevronRightIcon } from '@heroicons/react/solid';
|
||||
import {ChevronRightIcon} from '@heroicons/react/solid';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { groupBy } from '../../../helpers/GroupBy';
|
||||
|
||||
@@ -2,12 +2,12 @@ import * as React from 'react';
|
||||
import { Spinner } from './Spinner';
|
||||
import useMessages from '../hooks/useMessages';
|
||||
import useDarkMode from '../../hooks/useDarkMode';
|
||||
import usePages from '../hooks/usePages';
|
||||
import { WelcomeScreen } from './WelcomeScreen';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DashboardViewSelector, ViewDataAtom } from '../state';
|
||||
import { DashboardViewSelector } from '../state';
|
||||
import { Contents } from './Contents/Contents';
|
||||
import { Media } from './Media/Media';
|
||||
import { NavigationType } from '../models';
|
||||
|
||||
export interface IDashboardProps {
|
||||
showWelcome: boolean;
|
||||
@@ -15,7 +15,6 @@ export interface IDashboardProps {
|
||||
|
||||
export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome}: React.PropsWithChildren<IDashboardProps>) => {
|
||||
const { loading, pages, settings } = useMessages();
|
||||
const { pageItems } = usePages(pages);
|
||||
const view = useRecoilValue(DashboardViewSelector);
|
||||
useDarkMode();
|
||||
|
||||
@@ -31,9 +30,15 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
|
||||
return <WelcomeScreen settings={settings} />;
|
||||
}
|
||||
|
||||
if (view === 'media') {
|
||||
return <Media />;
|
||||
}
|
||||
|
||||
return <Contents pages={pageItems} loading={loading} />;
|
||||
return (
|
||||
<main className={`h-full w-full`}>
|
||||
{
|
||||
view === NavigationType.Media ? (
|
||||
<Media />
|
||||
) : (
|
||||
<Contents pages={pages} loading={loading} />
|
||||
)
|
||||
}
|
||||
</main>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { format, parseJSON } from 'date-fns';
|
||||
import { format } from 'date-fns';
|
||||
import * as React from 'react';
|
||||
import { DateHelper } from '../../helpers/DateHelper';
|
||||
|
||||
export interface IDateFieldProps {
|
||||
value: Date | string;
|
||||
@@ -10,9 +11,9 @@ export const DateField: React.FunctionComponent<IDateFieldProps> = ({value}: Rea
|
||||
|
||||
React.useEffect(() => {
|
||||
try {
|
||||
const parsedValue = typeof value === 'string' ? parseJSON(value) : value;
|
||||
const dateString = format(parsedValue, 'yyyy-MM-dd');
|
||||
setDateValue(dateString);
|
||||
const parsedValue = typeof value === 'string' ? DateHelper.tryParse(value) : value;
|
||||
const dateString = parsedValue ? format(parsedValue, 'yyyy-MM-dd') : parsedValue;
|
||||
setDateValue(dateString || "");
|
||||
} catch (e) {
|
||||
// Date is invalid
|
||||
}
|
||||
|
||||
105
src/dashboardWebView/components/Header/Breadcrumb.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { CollectionIcon } from '@heroicons/react/outline';
|
||||
import { basename, join } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { HOME_PAGE_NAVIGATION_ID } from '../../../constants';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { SearchAtom, SelectedMediaFolderAtom, SettingsAtom } from '../../state';
|
||||
|
||||
export interface IBreadcrumbProps {}
|
||||
|
||||
export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (props: React.PropsWithChildren<IBreadcrumbProps>) => {
|
||||
const [ selectedFolder, setSelectedFolder ] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const [ , setSearchValue ] = useRecoilState(SearchAtom);
|
||||
const [ folders, setFolders ] = React.useState<string[]>([]);
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
|
||||
const updateFolder = (folder: string) => {
|
||||
setSearchValue('');
|
||||
setSelectedFolder(folder);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { wsFolder, staticFolder, contentFolders } = settings;
|
||||
|
||||
const isValid = (folderPath: string) => {
|
||||
if (staticFolder) {
|
||||
const staticPath = parseWinPath(join(wsFolder, staticFolder)) as string;
|
||||
const relPath = folderPath.replace(staticPath, '') as string;
|
||||
|
||||
if (relPath.length > 1 && folderPath.startsWith(staticPath)) {
|
||||
return true;
|
||||
} else if (relPath.length === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < contentFolders.length; i++) {
|
||||
const folder = contentFolders[i];
|
||||
const contentFolder = parseWinPath(folder.path) as string;
|
||||
const relContentPath = folderPath.replace(contentFolder, '');
|
||||
return relContentPath.length > 1 && folderPath.startsWith(contentFolder);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!selectedFolder) {
|
||||
setFolders([]);
|
||||
} else {
|
||||
const relPath = parseWinPath(selectedFolder.replace(parseWinPath(settings.wsFolder) as string, '')) as string;
|
||||
const folderParts = relPath.split('/').filter(f => f);
|
||||
const allFolders: string[] = [];
|
||||
let previousFolder = parseWinPath(settings.wsFolder) as string;
|
||||
|
||||
for (const part of folderParts) {
|
||||
const folder = join(previousFolder, part);
|
||||
if (isValid(folder)) {
|
||||
allFolders.push(folder);
|
||||
}
|
||||
previousFolder = folder;
|
||||
}
|
||||
|
||||
setFolders(allFolders);
|
||||
}
|
||||
}, [selectedFolder, settings]);
|
||||
|
||||
return (
|
||||
<ol role="list" className="flex space-x-4 px-5 flex-1">
|
||||
<li className="flex">
|
||||
<div className="flex items-center">
|
||||
<button onClick={() => setSelectedFolder(HOME_PAGE_NAVIGATION_ID)} className="text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500">
|
||||
<CollectionIcon className="flex-shrink-0 h-5 w-5" aria-hidden="true" />
|
||||
<span className="sr-only">Home</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{folders.map((folder) => (
|
||||
<li key={folder} className="flex">
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="flex-shrink-0 h-5 w-5 text-gray-300 dark:text-whisper-900"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M5.555 17.776l8-16 .894.448-8 16-.894-.448z" />
|
||||
</svg>
|
||||
<button
|
||||
onClick={() => updateFolder(folder)}
|
||||
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500"
|
||||
>
|
||||
{basename(folder)}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { XCircleIcon } from '@heroicons/react/solid';
|
||||
import {XCircleIcon} from '@heroicons/react/solid';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
import { SortingSelector, FolderSelector, TagSelector, CategorySelector, SortingAtom, DEFAULT_SORTING_OPTION, FolderAtom, DEFAULT_FOLDER_STATE, TagAtom, CategoryAtom, DEFAULT_TAG_STATE, DEFAULT_CATEGORY_STATE } from '../../state';
|
||||
import { FolderSelector, TagSelector, CategorySelector, SortingAtom, FolderAtom, DEFAULT_FOLDER_STATE, TagAtom, CategoryAtom, DEFAULT_TAG_STATE, DEFAULT_CATEGORY_STATE } from '../../state';
|
||||
|
||||
import { DefaultValue } from 'recoil';
|
||||
|
||||
@@ -17,7 +17,6 @@ export interface IClearFiltersProps {}
|
||||
export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (props: React.PropsWithChildren<IClearFiltersProps>) => {
|
||||
const [ show, setShow ] = React.useState(false);
|
||||
|
||||
const sorting = useRecoilValue(SortingSelector);
|
||||
const folder = useRecoilValue(FolderSelector);
|
||||
const tag = useRecoilValue(TagSelector);
|
||||
const category = useRecoilValue(CategorySelector);
|
||||
@@ -36,19 +35,19 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (props:
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (sorting !== DEFAULT_SORTING_OPTION || folder !== DEFAULT_FOLDER_STATE || tag !== DEFAULT_TAG_STATE || category !== DEFAULT_CATEGORY_STATE) {
|
||||
if (folder !== DEFAULT_FOLDER_STATE || tag !== DEFAULT_TAG_STATE || category !== DEFAULT_CATEGORY_STATE) {
|
||||
setShow(true);
|
||||
} else {
|
||||
setShow(false);
|
||||
}
|
||||
}, [sorting, folder, tag, category]);
|
||||
}, [folder, tag, category]);
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<button className="flex items-center hover:text-teal-600" onClick={reset} title={`Clear filters, grouping, and sorting`}>
|
||||
<XCircleIcon className={`inline-block w-5 h-5 mr-1`} /><span>Clear</span>
|
||||
<span className={`sr-only`}> filters, grouping, and sorting</span>
|
||||
<span className={`sr-only`}> filters and grouping</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { FilterIcon } from '@heroicons/react/solid';
|
||||
import {FilterIcon} from '@heroicons/react/solid';
|
||||
import * as React from 'react';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Menu, Transition } from '@headlessui/react';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { FolderAtom } from '../../state';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { FolderAtom, SettingsSelector } from '../../state';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
|
||||
export interface IFoldersProps {
|
||||
folders: string[];
|
||||
}
|
||||
export interface IFoldersProps {}
|
||||
|
||||
const DEFAULT_TYPE = "All types";
|
||||
|
||||
export const Folders: React.FunctionComponent<IFoldersProps> = ({folders}: React.PropsWithChildren<IFoldersProps>) => {
|
||||
export const Folders: React.FunctionComponent<IFoldersProps> = ({}: React.PropsWithChildren<IFoldersProps>) => {
|
||||
const [ crntFolder, setCrntFolder ] = useRecoilState(FolderAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const contentFolders = settings?.contentFolders || [];
|
||||
|
||||
if (folders.length <= 1) {
|
||||
if (contentFolders.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@ export const Folders: React.FunctionComponent<IFoldersProps> = ({folders}: React
|
||||
isCurrent={!crntFolder}
|
||||
onClick={(value) => setCrntFolder(value)} />
|
||||
|
||||
{folders.map((option) => (
|
||||
{contentFolders.map((option) => (
|
||||
<MenuItem
|
||||
key={option}
|
||||
title={option}
|
||||
value={option}
|
||||
isCurrent={option === crntFolder}
|
||||
key={option.title}
|
||||
title={option.title}
|
||||
value={option.title}
|
||||
isCurrent={option.title === crntFolder}
|
||||
onClick={(value) => setCrntFolder(value)} />
|
||||
))}
|
||||
</MenuItems>
|
||||
|
||||
@@ -3,20 +3,21 @@ import { Sorting } from './Sorting';
|
||||
import { Searchbox } from './Searchbox';
|
||||
import { Filter } from './Filter';
|
||||
import { Folders } from './Folders';
|
||||
import { Settings } from '../../models';
|
||||
import { Settings, NavigationType } from '../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Startup } from '../Startup';
|
||||
import { Button } from '../Button';
|
||||
import { Navigation } from '../Navigation';
|
||||
import { Grouping } from '.';
|
||||
import { ViewSwitch } from './ViewSwitch';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { CategoryAtom, DashboardViewAtom, TagAtom } from '../../state';
|
||||
import { useRecoilState, useResetRecoilState } from 'recoil';
|
||||
import { CategoryAtom, DashboardViewAtom, SortingAtom, TagAtom } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ClearFilters } from './ClearFilters';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { PhotographIcon } from '@heroicons/react/outline';
|
||||
import { Pagination } from '../Media/Pagination';
|
||||
import {PhotographIcon} from '@heroicons/react/outline';
|
||||
import { MediaHeaderTop } from '../Media/MediaHeaderTop';
|
||||
import { ChoiceButton } from '../ChoiceButton';
|
||||
import { MediaHeaderBottom } from '../Media/MediaHeaderBottom';
|
||||
|
||||
export interface IHeaderProps {
|
||||
settings: Settings | null;
|
||||
@@ -32,27 +33,45 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
|
||||
const [ crntTag, setCrntTag ] = useRecoilState(TagAtom);
|
||||
const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom);
|
||||
const [ view, setView ] = useRecoilState(DashboardViewAtom);
|
||||
const resetSorting = useResetRecoilState(SortingAtom)
|
||||
|
||||
const createContent = () => {
|
||||
Messenger.send(DashboardMessage.createContent);
|
||||
};
|
||||
|
||||
const createByContentType = () => {
|
||||
Messenger.send(DashboardMessage.createByContentType);
|
||||
};
|
||||
|
||||
const createByTemplate = () => {
|
||||
Messenger.send(DashboardMessage.createByTemplate);
|
||||
};
|
||||
|
||||
const updateView = (view: NavigationType) => {
|
||||
setView(view);
|
||||
resetSorting();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`w-full sticky top-0 z-40 bg-gray-100 dark:bg-vulcan-500`}>
|
||||
|
||||
<div className={`px-4 bg-gray-50 dark:bg-vulcan-50 border-b-2 border-gray-200 dark:border-vulcan-200`}>
|
||||
<div className={`flex items-center justify-start`}>
|
||||
<button className={`p-2 flex items-center ${view === "contents" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => setView("contents")}>
|
||||
<MarkdownIcon className={`h-6 w-auto mr-2`} /><span>Contents</span>
|
||||
</button>
|
||||
<button className={`p-2 flex items-center ${view === "media" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => setView("media")}>
|
||||
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mb-0 border-b bg-gray-100 dark:bg-vulcan-500 border-gray-200 dark:border-vulcan-300 h-12">
|
||||
<ul className="flex items-center justify-start h-full -mb-px" data-tabs-toggle="#myTabContent" role="tablist">
|
||||
<li className="mr-2" role="presentation">
|
||||
<button className={`flex items-center py-2 px-4 text-sm font-medium text-center border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 ${view === NavigationType.Contents ? "border-vulcan-500 text-vulcan-500 dark:border-whisper-500 dark:text-whisper-500" : "text-gray-500 dark:text-gray-400"}`} type="button" role="tab" aria-controls="profile" aria-selected="false" onClick={() => updateView(NavigationType.Contents)}>
|
||||
<MarkdownIcon className={`h-6 w-auto mr-2`} /><span>Contents</span>
|
||||
</button>
|
||||
</li>
|
||||
<li className="mr-2" role="presentation">
|
||||
<button className={`flex items-center py-2 px-4 text-sm font-medium text-center text-gray-500 border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 ${view === NavigationType.Media ? "border-vulcan-500 text-vulcan-500 dark:border-whisper-500 dark:text-whisper-500" : "text-gray-500 dark:text-gray-400"}`} type="button" role="tab" aria-controls="dashboard" aria-selected="true" onClick={() => updateView(NavigationType.Media)}>
|
||||
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{
|
||||
view === "contents" && (
|
||||
view === NavigationType.Contents && (
|
||||
<>
|
||||
<div className={`px-4 mt-3 mb-2 flex items-center justify-between`}>
|
||||
<Searchbox />
|
||||
@@ -60,7 +79,19 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
|
||||
<div className={`flex items-center space-x-4`}>
|
||||
<Startup settings={settings} />
|
||||
|
||||
<Button onClick={createContent} disabled={!settings?.initialized}>Create content</Button>
|
||||
<ChoiceButton
|
||||
title={`Create content`}
|
||||
choices={[{
|
||||
title: `Create by content type`,
|
||||
onClick: createByContentType,
|
||||
disabled: !settings?.initialized
|
||||
}, {
|
||||
title: `Create by template`,
|
||||
onClick: createByTemplate,
|
||||
disabled: !settings?.initialized
|
||||
}]}
|
||||
onClick={createContent}
|
||||
disabled={!settings?.initialized} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -74,10 +105,10 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end space-x-4 lg:space-x-6 xl:space-x-8 bg-gray-200 border-b border-gray-300 dark:bg-vulcan-400 dark:border-vulcan-100`}>
|
||||
<div className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end bg-gray-200 border-b border-gray-300 dark:bg-vulcan-400 dark:border-vulcan-100 space-x-4 lg:space-x-6 xl:space-x-8`}>
|
||||
<ClearFilters />
|
||||
|
||||
<Folders folders={folders || []} />
|
||||
<Folders />
|
||||
|
||||
<Filter label={`Tag`} activeItem={crntTag} items={settings?.tags || []} onClick={(value) => setCrntTag(value)} />
|
||||
|
||||
@@ -85,15 +116,19 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
|
||||
|
||||
<Grouping />
|
||||
|
||||
<Sorting />
|
||||
<Sorting view={NavigationType.Contents} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
view === "media" && (
|
||||
<Pagination />
|
||||
view === NavigationType.Media && (
|
||||
<>
|
||||
<MediaHeaderTop />
|
||||
|
||||
<MediaHeaderBottom />
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
72
src/dashboardWebView/components/Header/Pagination.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { LIMIT } from '../../hooks/useMedia';
|
||||
import { MediaTotalSelector, PageAtom } from '../../state';
|
||||
import { PaginationButton } from './PaginationButton';
|
||||
|
||||
export interface IPaginationProps {}
|
||||
|
||||
export const Pagination: React.FunctionComponent<IPaginationProps> = (props: React.PropsWithChildren<IPaginationProps>) => {
|
||||
const [ page, setPage ] = useRecoilState(PageAtom);
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
|
||||
const totalPages = Math.ceil(totalMedia / LIMIT) - 1;
|
||||
|
||||
const getButtons = (): number[] => {
|
||||
const maxButtons = 5;
|
||||
const buttons: number[] = [];
|
||||
const start = page - maxButtons;
|
||||
const end = page + maxButtons;
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
if (i >= 0 && i <= totalPages) {
|
||||
buttons.push(i);
|
||||
}
|
||||
}
|
||||
return buttons;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center sm:justify-end space-x-2 text-sm">
|
||||
<PaginationButton
|
||||
title="First"
|
||||
disabled={page === 0}
|
||||
onClick={() => {
|
||||
if (page > 0) {
|
||||
setPage(0)
|
||||
}
|
||||
}} />
|
||||
|
||||
<PaginationButton
|
||||
title="Previous"
|
||||
disabled={page === 0}
|
||||
onClick={() => {
|
||||
if (page > 0) {
|
||||
setPage(page - 1)
|
||||
}
|
||||
}} />
|
||||
|
||||
{getButtons().map((button) => (
|
||||
<button
|
||||
key={button}
|
||||
disabled={button === page}
|
||||
onClick={() => {
|
||||
setPage(button)
|
||||
}
|
||||
}
|
||||
className={`${page === button ? 'bg-gray-200 px-2 text-vulcan-500' : 'text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500'} max-h-8`}
|
||||
>{button + 1}</button>
|
||||
))}
|
||||
|
||||
<PaginationButton
|
||||
title="Next"
|
||||
disabled={page >= totalPages}
|
||||
onClick={() => setPage(page + 1)} />
|
||||
|
||||
<PaginationButton
|
||||
title="Last"
|
||||
disabled={page >= totalPages}
|
||||
onClick={() => setPage(totalPages)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
46
src/dashboardWebView/components/Header/PaginationStatus.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaTotalSelector, PageAtom, SearchAtom, SelectedMediaFolderSelector } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { RefreshIcon } from '@heroicons/react/outline';
|
||||
import { LIMIT } from '../../hooks/useMedia';
|
||||
|
||||
export interface IPaginationStatusProps {}
|
||||
|
||||
export const PaginationStatus: React.FunctionComponent<IPaginationStatusProps> = (props: React.PropsWithChildren<IPaginationStatusProps>) => {
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const [ page, setPage ] = useRecoilState(PageAtom);
|
||||
const [ , setSearch ] = useRecoilState(SearchAtom);
|
||||
|
||||
const getTotalPage = () => {
|
||||
const mediaItems = ((page + 1) * LIMIT);
|
||||
if (totalMedia < mediaItems) {
|
||||
return totalMedia;
|
||||
}
|
||||
return mediaItems;
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
setPage(0);
|
||||
setSearch('');
|
||||
Messenger.send(DashboardMessage.refreshMedia, { folder: selectedFolder });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="hidden sm:flex">
|
||||
<button className={`mr-2 text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500`}
|
||||
title="Refresh media"
|
||||
onClick={refresh}>
|
||||
<RefreshIcon className={`h-5 w-5`} />
|
||||
<span className="sr-only">Refresh media</span>
|
||||
</button>
|
||||
|
||||
<p className="text-sm text-gray-500 dark:text-whisper-900">
|
||||
Showing <span className="font-medium">{(page * LIMIT) + 1}</span> to <span className="font-medium">{getTotalPage()}</span> of{' '}
|
||||
<span className="font-medium">{totalMedia}</span> results
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,37 +1,46 @@
|
||||
import { FilterIcon, SearchIcon } from '@heroicons/react/solid';
|
||||
import {SearchIcon} from '@heroicons/react/solid';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useDebounce } from '../../../hooks/useDebounce';
|
||||
import { SearchAtom } from '../../state';
|
||||
|
||||
export interface ISearchboxProps {}
|
||||
export interface ISearchboxProps {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({}: React.PropsWithChildren<ISearchboxProps>) => {
|
||||
export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({placeholder}: React.PropsWithChildren<ISearchboxProps>) => {
|
||||
const [ value, setValue ] = React.useState('');
|
||||
const [ , setDebounceValue ] = useRecoilState(SearchAtom);
|
||||
const [ debounceSearchValue, setDebounceValue ] = useRecoilState(SearchAtom);
|
||||
const debounceSearch = useDebounce<string>(value, 500);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(event.target.value);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!debounceSearchValue && value) {
|
||||
setValue('');
|
||||
}
|
||||
} , [debounceSearchValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setDebounceValue(debounceSearch);
|
||||
}, [debounceSearch]);
|
||||
|
||||
return (
|
||||
<div className="flex space-x-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex space-x-4 flex-1">
|
||||
<div className="min-w-0">
|
||||
<label htmlFor="search" className="sr-only">Search</label>
|
||||
<div className="relative flex justify-center">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="search"
|
||||
name="search"
|
||||
className={`block w-full py-2 pl-10 pr-3 sm:text-sm 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`}
|
||||
placeholder="Search"
|
||||
placeholder={placeholder || "Search"}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
@@ -1,37 +1,85 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { ExtensionState } from '../../../constants';
|
||||
import { SortOrder, SortType } from '../../../models';
|
||||
import { SortOption } from '../../constants/SortOption';
|
||||
import { SearchSelector, SortingAtom } from '../../state';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { NavigationType } from '../../models';
|
||||
import { SortingOption } from '../../models/SortingOption';
|
||||
import { SearchSelector, SettingsSelector, SortingAtom } from '../../state';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
|
||||
export interface ISortingProps {}
|
||||
export interface ISortingProps {
|
||||
disableCustomSorting?: boolean;
|
||||
view: NavigationType;
|
||||
}
|
||||
|
||||
export const sortOptions = [
|
||||
{ name: "Last modified", id: SortOption.LastModified },
|
||||
{ name: "By filename (asc)", id: SortOption.FileNameAsc },
|
||||
{ name: "By filename (desc)", id: SortOption.FileNameDesc },
|
||||
export const sortOptions: SortingOption[] = [
|
||||
{ name: "Last modified (asc)", id: SortOption.LastModifiedAsc, order: SortOrder.asc, type: SortType.date },
|
||||
{ name: "Last modified (desc)", id: SortOption.LastModifiedDesc, order: SortOrder.desc, type: SortType.date },
|
||||
{ name: "By filename (asc)", id: SortOption.FileNameAsc, order: SortOrder.asc, type: SortType.string },
|
||||
{ name: "By filename (desc)", id: SortOption.FileNameDesc, order: SortOrder.desc, type: SortType.string },
|
||||
];
|
||||
|
||||
export const Sorting: React.FunctionComponent<ISortingProps> = ({}: React.PropsWithChildren<ISortingProps>) => {
|
||||
export const Sorting: React.FunctionComponent<ISortingProps> = ({disableCustomSorting, view}: React.PropsWithChildren<ISortingProps>) => {
|
||||
const [ crntSorting, setCrntSorting ] = useRecoilState(SortingAtom);
|
||||
const searchValue = useRecoilValue(SearchSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const crntSort = sortOptions.find(x => x.id === crntSorting);
|
||||
const updateSorting = (value: SortingOption) => {
|
||||
Messenger.send(DashboardMessage.setState, {
|
||||
key: `${view === NavigationType.Contents ? ExtensionState.Dashboard.Contents.Sorting : ExtensionState.Dashboard.Media.Sorting}`,
|
||||
value: value
|
||||
});
|
||||
|
||||
setCrntSorting(value)
|
||||
};
|
||||
|
||||
let allOptions = [...sortOptions];
|
||||
if (settings?.customSorting && !disableCustomSorting) {
|
||||
allOptions = [...allOptions, ...settings.customSorting.map((s) => ({
|
||||
title: s.title || s.name,
|
||||
name: s.name,
|
||||
id: s.id || `${s.name}-${s.order}`,
|
||||
order: s.order,
|
||||
type: s.type
|
||||
}))];
|
||||
}
|
||||
|
||||
let crntSortingOption = crntSorting;
|
||||
if (!crntSortingOption) {
|
||||
if (view === NavigationType.Contents) {
|
||||
crntSortingOption = settings?.dashboardState?.contents?.sorting || null;
|
||||
} else if (view === NavigationType.Media) {
|
||||
crntSortingOption = settings?.dashboardState?.media?.sorting || null;
|
||||
}
|
||||
|
||||
if (crntSortingOption === null) {
|
||||
if (view === NavigationType.Contents && settings?.dashboardState.contents.defaultSorting) {
|
||||
crntSortingOption = allOptions.find(f => f.id === settings?.dashboardState.contents.defaultSorting) || null;
|
||||
} else if (view === NavigationType.Media && settings?.dashboardState.contents.defaultSorting) {
|
||||
crntSortingOption = allOptions.find(f => f.id === settings?.dashboardState.contents.defaultSorting) || null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let crntSort = allOptions.find(x => x.id === crntSortingOption?.id) || sortOptions[0];
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Menu as="div" className="relative z-10 inline-block text-left">
|
||||
<MenuButton label={`Sort by`} title={crntSort?.name || ""} disabled={!!searchValue} />
|
||||
<MenuButton label={`Sort by`} title={crntSort?.title || crntSort?.name || ""} disabled={!!searchValue} />
|
||||
|
||||
<MenuItems>
|
||||
{sortOptions.map((option) => (
|
||||
{allOptions.map((option) => (
|
||||
<MenuItem
|
||||
key={option.id}
|
||||
title={option.name}
|
||||
value={option.id}
|
||||
isCurrent={option.id === crntSorting}
|
||||
onClick={(value) => setCrntSorting(value)} />
|
||||
title={option.title || option.name}
|
||||
value={option}
|
||||
isCurrent={option.id === crntSort.id}
|
||||
onClick={(value) => updateSorting(value)} />
|
||||
))}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { ViewAtom, ViewType, SettingsSelector } from '../../state';
|
||||
import { ViewGridIcon, ViewListIcon } from '@heroicons/react/solid';
|
||||
import { ViewAtom, SettingsSelector } from '../../state';
|
||||
import {ViewListIcon, ViewGridIcon} from '@heroicons/react/solid';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { DashboardViewType } from '../../models';
|
||||
|
||||
export interface IViewSwitchProps {}
|
||||
|
||||
@@ -12,7 +13,7 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (props: Rea
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const toggleView = () => {
|
||||
const newView = view === ViewType.Grid ? ViewType.List : ViewType.Grid;
|
||||
const newView = view === DashboardViewType.Grid ? DashboardViewType.List : DashboardViewType.Grid;
|
||||
setView(newView);
|
||||
Messenger.send(DashboardMessage.setPageViewType, newView);
|
||||
};
|
||||
@@ -25,11 +26,11 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (props: Rea
|
||||
|
||||
return (
|
||||
<div className={`flex rounded-sm bg-vulcan-50 lg:mb-1`}>
|
||||
<button className={`flex items-center px-2 py-1 rounded-l-sm ${view === ViewType.Grid ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
|
||||
<button className={`flex items-center px-2 py-1 rounded-l-sm ${view === DashboardViewType.Grid ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
|
||||
<ViewGridIcon className={`w-4 h-4`} />
|
||||
<span className={`sr-only`}>Change to grid</span>
|
||||
</button>
|
||||
<button className={`flex items-center px-2 py-1 rounded-r-sm ${view === ViewType.List ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
|
||||
<button className={`flex items-center px-2 py-1 rounded-r-sm ${view === DashboardViewType.List ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
|
||||
<ViewListIcon className={`w-4 h-4`} />
|
||||
<span className={`sr-only`}>Change to list</span>
|
||||
</button>
|
||||
|
||||
53
src/dashboardWebView/components/Media/FolderCreation.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import {FolderAddIcon} from '@heroicons/react/outline';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SelectedMediaFolderAtom, SettingsSelector } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ChoiceButton } from '../ChoiceButton';
|
||||
import { CustomScript, ScriptType } from '../../../models';
|
||||
|
||||
export interface IFolderCreationProps {}
|
||||
|
||||
export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (props: React.PropsWithChildren<IFolderCreationProps>) => {
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const onFolderCreation = () => {
|
||||
Messenger.send(DashboardMessage.createMediaFolder, {
|
||||
selectedFolder
|
||||
});
|
||||
};
|
||||
|
||||
const runCustomScript = (script: CustomScript) => {
|
||||
Messenger.send(DashboardMessage.runCustomScript, {script, path: selectedFolder});
|
||||
};
|
||||
|
||||
const scripts = (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFolder);
|
||||
if (scripts.length > 0) {
|
||||
return (
|
||||
<div className="flex flex-1 justify-end">
|
||||
<ChoiceButton
|
||||
title={`Create new folder`}
|
||||
choices={scripts.map(s => ({
|
||||
title: s.title,
|
||||
onClick: () => runCustomScript(s)
|
||||
}))}
|
||||
onClick={onFolderCreation}
|
||||
disabled={!settings?.initialized} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 justify-end">
|
||||
<button
|
||||
className={`inline-flex items-center px-3 py-1 border border-transparent text-xs leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-600 hover:bg-teal-700 focus:outline-none disabled:bg-gray-500`}
|
||||
title={`Create new folder`}
|
||||
onClick={onFolderCreation}>
|
||||
<FolderAddIcon className={`mr-2 h-6 w-6`} />
|
||||
<span className={``}>Create new folder</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
31
src/dashboardWebView/components/Media/FolderItem.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import {FolderIcon} from '@heroicons/react/solid';
|
||||
import { basename } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedMediaFolderAtom } from '../../state';
|
||||
|
||||
export interface IFolderItemProps {
|
||||
folder: string;
|
||||
wsFolder?: string;
|
||||
staticFolder?: string;
|
||||
}
|
||||
|
||||
export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({ folder, wsFolder, staticFolder }: React.PropsWithChildren<IFolderItemProps>) => {
|
||||
const [ , setSelectedFolder ] = useRecoilState(SelectedMediaFolderAtom);
|
||||
|
||||
const relFolderPath = wsFolder ? folder.replace(wsFolder, '') : folder;
|
||||
|
||||
return (
|
||||
<li className={`group relative hover:shadow-xl dark:hover:bg-vulcan-100 text-gray-600 hover:text-gray-700 dark:text-whisper-900 dark:hover:text-whisper-800 p-4`}>
|
||||
<button className={`w-full flex flex-row items-center h-full`} onClick={() => setSelectedFolder(folder)}>
|
||||
<div>
|
||||
<FolderIcon className={`h-12 w-12 mr-4`} />
|
||||
</div>
|
||||
|
||||
<p className="text-sm font-bold pointer-events-none flex items-center text-left overflow-hidden break-words">
|
||||
{basename(relFolderPath)}
|
||||
</p>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { XIcon } from '@heroicons/react/outline';
|
||||
import Downshift from 'downshift';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaFoldersSelector, SelectedMediaFolderAtom } from '../../state';
|
||||
|
||||
export interface IFolderSelectionProps {}
|
||||
|
||||
export const FolderSelection: React.FunctionComponent<IFolderSelectionProps> = (props: React.PropsWithChildren<IFolderSelectionProps>) => {
|
||||
const folders = useRecoilValue(MediaFoldersSelector);
|
||||
const [ selectedFolder, setSelectedFolder ] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const [ focus, setFocus ] = React.useState(false);
|
||||
|
||||
let allFolders: string[] = Object.assign([], folders);
|
||||
allFolders = allFolders.sort((a: string, b: string) => {
|
||||
if (a.toLowerCase() < b.toLowerCase()) return -1;
|
||||
if (a.toLowerCase() > b.toLowerCase()) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Downshift
|
||||
isOpen={focus}
|
||||
selectedItem={selectedFolder}
|
||||
onOuterClick={() => setFocus(false)}
|
||||
onSelect={(selFolder) => {
|
||||
setSelectedFolder(selFolder);
|
||||
setFocus(false);
|
||||
}}>
|
||||
{
|
||||
({
|
||||
getInputProps,
|
||||
getItemProps,
|
||||
getMenuProps,
|
||||
isOpen,
|
||||
inputValue,
|
||||
getRootProps
|
||||
}) => (
|
||||
<div className={`relative flex items-center`}>
|
||||
<label className={`text-sm text-gray-500 dark:text-whisper-900`}>Filter by: </label>
|
||||
|
||||
<div
|
||||
className={`inline-flex items-center`}
|
||||
{...getRootProps({} as any, {suppressRefError: true})}
|
||||
>
|
||||
<input disabled={!!selectedFolder} onFocus={() => setFocus(true)} className={`ml-2 py-1 px-2 sm:text-sm 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`} {...getInputProps()} />
|
||||
|
||||
{
|
||||
selectedFolder && (
|
||||
<button title={`Clear`} onClick={() => setSelectedFolder(null)}><XIcon className={`ml-2 h-6 w-6 text-red-500 hover:text-red-800`} /></button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={`${focus ? `block` : `hidden`} top-8 absolute right-0 z-10 mt-2 w-min rounded-md shadow-2xl bg-white dark:bg-vulcan-500 ring-1 ring-vulcan-400 dark:ring-white ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto`} {...getMenuProps()}>
|
||||
{isOpen
|
||||
? allFolders
|
||||
.filter((item: string) => !inputValue || item.includes(inputValue))
|
||||
.map((item, index) => (
|
||||
<div
|
||||
className="cursor-pointer text-gray-500 dark:text-whisper-900 block px-4 py-2 text-sm font-medium w-full text-left hover:bg-gray-100 hover:text-gray-700 dark:hover:text-whisper-600 dark:hover:bg-vulcan-100"
|
||||
{...getItemProps({ key: item, index, item })}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Downshift>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +1,37 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { CheckCircleIcon, ClipboardCopyIcon, CodeIcon, PhotographIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { ClipboardIcon, CodeIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { CustomScript } from '../../../helpers/CustomScript';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { ScriptType } from '../../../models';
|
||||
import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { LightboxAtom, PageSelector, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { MenuItem, MenuItems } from '../Menu';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
|
||||
import { Metadata } from '../Modals/Metadata';
|
||||
import { MenuButton } from './MenuButton'
|
||||
import { QuickAction } from './QuickAction';
|
||||
|
||||
export interface IItemProps {
|
||||
media: MediaInfo;
|
||||
}
|
||||
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWithChildren<IItemProps>) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const [ , setLightbox ] = useRecoilState(LightboxAtom);
|
||||
const [ showAlert, setShowAlert ] = React.useState(false);
|
||||
const [ showForm, setShowForm ] = React.useState(false);
|
||||
const [ caption, setCaption ] = React.useState(media.caption);
|
||||
const [ alt, setAlt ] = React.useState(media.alt);
|
||||
const [ filename, setFilename ] = React.useState<string | null>(null);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
|
||||
const parseWinPath = (path: string | undefined) => {
|
||||
return path?.split(`\\`).join(`/`);
|
||||
}
|
||||
const page = useRecoilValue(PageSelector);
|
||||
|
||||
const getFolder = () => {
|
||||
if (settings?.wsFolder && media.fsPath) {
|
||||
@@ -48,29 +58,48 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
return relPath;
|
||||
};
|
||||
|
||||
const getFileName = () => {
|
||||
return basename(parseWinPath(media.fsPath) || "");
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
const relPath = getRelPath();
|
||||
Messenger.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || "");
|
||||
};
|
||||
|
||||
const runCustomScript = (script: CustomScript) => {
|
||||
Messenger.send(DashboardMessage.runCustomScript, {script, path: media.fsPath});
|
||||
};
|
||||
|
||||
const insertToArticle = () => {
|
||||
const relPath = getRelPath();
|
||||
Messenger.send(DashboardMessage.insertPreviewImage, {
|
||||
image: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath,
|
||||
fieldName: viewData?.data?.fieldName,
|
||||
position: viewData?.data?.position || null
|
||||
multiple: viewData?.data?.multiple,
|
||||
value: viewData?.data?.value,
|
||||
position: viewData?.data?.position || null,
|
||||
alt: alt || "",
|
||||
caption: caption || ""
|
||||
});
|
||||
};
|
||||
|
||||
const insertSnippet = () => {
|
||||
const relPath = getRelPath();
|
||||
let snippet = settings?.mediaSnippet.join("\n");
|
||||
|
||||
snippet = snippet?.replace("{mediaUrl}", parseWinPath(relPath) || "");
|
||||
snippet = snippet?.replace("{alt}", alt || "");
|
||||
snippet = snippet?.replace("{caption}", caption || "");
|
||||
snippet = snippet?.replace("{filename}", basename(relPath || ""));
|
||||
|
||||
Messenger.send(DashboardMessage.insertPreviewImage, {
|
||||
image: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath,
|
||||
fieldName: viewData?.data?.fieldName,
|
||||
position: viewData?.data?.position || null,
|
||||
snippet: settings?.mediaSnippet.join("\n").replace("{mediaUrl}", parseWinPath(relPath) || "")
|
||||
snippet
|
||||
});
|
||||
};
|
||||
|
||||
@@ -86,23 +115,87 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
};
|
||||
|
||||
const calculateSize = () => {
|
||||
if (media?.stats?.size) {
|
||||
const size = media.stats.size / (1024*1024);
|
||||
if (size > 1) {
|
||||
return `${size.toFixed(2)} MB`;
|
||||
} else {
|
||||
return `${(size * 1024).toFixed(2)} KB`;
|
||||
let sizeDetails = [];
|
||||
|
||||
if (media?.dimensions) {
|
||||
if (media.dimensions.width && media.dimensions.height) {
|
||||
sizeDetails.push(`${media.dimensions.width}x${media.dimensions.height}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (media?.size) {
|
||||
const size = media.size / (1024*1024);
|
||||
if (size > 1) {
|
||||
sizeDetails.push(`${size.toFixed(2)} MB`);
|
||||
} else {
|
||||
sizeDetails.push(`${(size * 1024).toFixed(2)} KB`);
|
||||
}
|
||||
}
|
||||
|
||||
return sizeDetails.join(" — ");
|
||||
};
|
||||
|
||||
const openLightbox = () => {
|
||||
setLightbox(media.vsPath || "");
|
||||
};
|
||||
|
||||
const updateMetadata = () => {
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
const submitMetadata = () => {
|
||||
Messenger.send(DashboardMessage.updateMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
filename,
|
||||
caption,
|
||||
alt,
|
||||
folder: selectedFolder,
|
||||
page
|
||||
});
|
||||
|
||||
setShowForm(false);
|
||||
|
||||
// Reset the values
|
||||
setAlt(media.alt);
|
||||
setCaption(media.caption);
|
||||
setFilename(getFileName());
|
||||
};
|
||||
|
||||
const customScriptActions = () => {
|
||||
return (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFile).map(script => (
|
||||
<MenuItem
|
||||
key={script.title}
|
||||
title={script.title}
|
||||
onClick={() => runCustomScript(script)} />
|
||||
))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (media.alt !== alt) {
|
||||
setAlt(media.alt);
|
||||
}
|
||||
}, [media.alt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (media.caption !== caption) {
|
||||
setCaption(media.caption);
|
||||
}
|
||||
}, [media.caption]);
|
||||
|
||||
useEffect(() => {
|
||||
const name = basename(parseWinPath(media.fsPath) || "");
|
||||
if (name !== filename) {
|
||||
setFilename(getFileName());
|
||||
}
|
||||
}, [media.fsPath]);
|
||||
|
||||
const fileInfo = filename ? basename(filename).split('.') : null;
|
||||
const extension = fileInfo?.pop();
|
||||
const name = fileInfo?.join('.');
|
||||
|
||||
return (
|
||||
<>
|
||||
<li className="group relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100">
|
||||
<li className="group relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100 border border-gray-100 dark:border-vulcan-50">
|
||||
<button className="relative bg-gray-200 dark:bg-vulcan-300 block w-full aspect-w-10 aspect-h-7 overflow-hidden cursor-pointer h-48" onClick={openLightbox}>
|
||||
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
|
||||
<PhotographIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />
|
||||
@@ -111,64 +204,192 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
<img src={media.vsPath} alt={basename(media.fsPath)} className="mx-auto object-cover" />
|
||||
</div>
|
||||
</button>
|
||||
<div className={`relative py-4 pl-4 pr-10`}>
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-2`}>
|
||||
{
|
||||
viewData?.data?.filePath ? (
|
||||
<>
|
||||
<button
|
||||
title={`Insert into your article`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={insertToArticle}>
|
||||
<CheckCircleIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Insert into your article</span>
|
||||
</button>
|
||||
<div className={`relative py-4 pl-4 pr-12`}>
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
|
||||
<div className="flex items-center border border-transparent group-hover:bg-gray-50 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
|
||||
|
||||
<div className='hidden group-hover:inline-block h-5'>
|
||||
<QuickAction
|
||||
title='Edit metadata'
|
||||
onClick={updateMetadata}>
|
||||
<PencilIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{
|
||||
viewData?.data?.filePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title='Insert image with markdown markup'
|
||||
onClick={insertToArticle}>
|
||||
<PlusIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{
|
||||
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
|
||||
<QuickAction
|
||||
title='Insert snippet'
|
||||
onClick={insertSnippet}>
|
||||
<CodeIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<QuickAction
|
||||
title='Copy media path'
|
||||
onClick={copyToClipboard}>
|
||||
<ClipboardIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction
|
||||
title='Delete media file'
|
||||
onClick={deleteMedia}>
|
||||
<TrashIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<Menu as="div" className="relative z-10 inline-block text-left h-5">
|
||||
<MenuButton title={`Menu`} />
|
||||
|
||||
<MenuItems widthClass='w-40'>
|
||||
<MenuItem
|
||||
title={`Edit metadata`}
|
||||
onClick={updateMetadata}
|
||||
/>
|
||||
|
||||
{
|
||||
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
|
||||
<button
|
||||
title={`Insert your media snippet`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={insertSnippet}>
|
||||
<CodeIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Insert your media snippet</span>
|
||||
</button>
|
||||
viewData?.data?.filePath ? (
|
||||
<>
|
||||
<MenuItem
|
||||
title={`Insert image markdown`}
|
||||
onClick={insertToArticle} />
|
||||
|
||||
{
|
||||
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
|
||||
<MenuItem
|
||||
title={`Insert snippet`}
|
||||
onClick={insertSnippet} />
|
||||
)
|
||||
}
|
||||
|
||||
{ customScriptActions() }
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MenuItem
|
||||
title={`Copy media path`}
|
||||
onClick={copyToClipboard} />
|
||||
|
||||
{ customScriptActions() }
|
||||
|
||||
<MenuItem
|
||||
title={`Delete`}
|
||||
onClick={deleteMedia} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button title={`Copy media path`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={copyToClipboard}>
|
||||
<ClipboardCopyIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Copy media path</span>
|
||||
</button>
|
||||
<button title={`Delete media`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={deleteMedia}>
|
||||
<TrashIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Delete media</span>
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center">
|
||||
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center break-all">
|
||||
{basename(parseWinPath(media.fsPath) || "")}
|
||||
</p>
|
||||
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
|
||||
<b className={`mr-2`}>Folder:</b> {getFolder()}
|
||||
{
|
||||
media.caption && (
|
||||
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
|
||||
<b className={`mr-2`}>Caption:</b>
|
||||
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{media.caption}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
{
|
||||
media.alt && (
|
||||
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
|
||||
<b className={`mr-2`}>Alt:</b>
|
||||
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{media.alt}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
|
||||
<b className={`mr-2`}>Folder:</b>
|
||||
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{getFolder()}</span>
|
||||
</p>
|
||||
{
|
||||
media?.stats?.size && (
|
||||
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
|
||||
<b className={`mr-1`}>Size:</b> {calculateSize()}
|
||||
(media?.size || media?.dimensions) && (
|
||||
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
|
||||
<b className={`mr-1`}>Size:</b>
|
||||
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{calculateSize()}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{
|
||||
showForm && (
|
||||
<Metadata
|
||||
title={`Set metadata for: ${basename(parseWinPath(media.fsPath) || "")}`}
|
||||
description={`Please specify the metadata you want to set for the file.`}
|
||||
okBtnText={`Save`}
|
||||
cancelBtnText={`Cancel`}
|
||||
dismiss={() => setShowForm(false)}
|
||||
trigger={submitMetadata}
|
||||
isSaveDisabled={!filename}>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div>
|
||||
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
|
||||
Filename
|
||||
</label>
|
||||
<div className="relative mt-1">
|
||||
<input
|
||||
className="py-1 px-2 sm:text-sm 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 w-full"
|
||||
value={name}
|
||||
onChange={(e) => setFilename(`${e.target.value}.${extension}`)} />
|
||||
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||
<span className="text-gray-500 sm:text-sm">
|
||||
.{extension}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
|
||||
Caption
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
rows={3}
|
||||
className="py-1 px-2 sm:text-sm 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 w-full"
|
||||
value={caption || ''}
|
||||
onChange={(e) => setCaption(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
|
||||
Alt tag value
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
className="py-1 px-2 sm:text-sm 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 w-full"
|
||||
value={alt || ''}
|
||||
onChange={(e) => setAlt(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Metadata>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
showAlert && (
|
||||
<Alert
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IListProps {}
|
||||
export interface IListProps {
|
||||
gap?: number;
|
||||
}
|
||||
|
||||
export const List: React.FunctionComponent<IListProps> = ({gap, children}: React.PropsWithChildren<IListProps>) => {
|
||||
const gapClass = gap !== undefined ? `gap-y-${gap}` : `gap-y-8`;
|
||||
|
||||
export const List: React.FunctionComponent<IListProps> = ({children}: React.PropsWithChildren<IListProps>) => {
|
||||
return (
|
||||
<ul role="list" 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">
|
||||
<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`}>
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
|
||||