Compare commits

...

86 Commits

Author SHA1 Message Date
Elio Struyf
01b1b3d0ea Merge pull request #100 from estruyf/dev
Merge for 3.1.0
2021-09-10 17:43:27 +02:00
Elio Struyf
9f2c68fd66 Updated showcase 2021-09-10 16:56:20 +02:00
Elio Struyf
9f21042e58 Updated documentations with delete and drag&drop 2021-09-10 14:07:33 +02:00
Elio Struyf
840e6fc6fd Delete media 2021-09-10 14:01:46 +02:00
Elio Struyf
3f237386b2 #98 - Drag and drop functionality added 2021-09-10 13:04:49 +02:00
Elio Struyf
eca6e00bf2 New images 2021-09-10 09:42:04 +02:00
Elio Struyf
afcccd780b New media images 2021-09-10 09:37:06 +02:00
Elio Struyf
239c611d3e Add bg to media card 2021-09-10 09:31:46 +02:00
Elio Struyf
4a2f362ed7 Version number update 2021-09-10 09:10:42 +02:00
Elio
b040669bfb Added Windows path media parsing 2021-09-09 19:44:10 +02:00
Elio
2c5a35d9b8 Windows fixes 2021-09-09 19:27:54 +02:00
Elio Struyf
eade3f4799 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2021-09-09 18:36:51 +02:00
Elio Struyf
98d8daaa7c Run cleanup 2021-09-09 18:36:43 +02:00
Elio
1c227c547f Always show unregister folder action, due to new pageFolders setting 2021-09-09 18:34:18 +02:00
Elio Struyf
167c1fafa1 Small header fix + webpack dev mode 2021-09-09 17:54:37 +02:00
Elio Struyf
edf5fc8401 Added beta readme 2021-09-09 14:22:34 +02:00
Elio Struyf
3de0006651 Added .all-contributorsrc 2021-09-09 12:14:17 +02:00
Elio Struyf
f79d382da5 Lightbox + documentation updates 2021-09-09 10:02:51 +02:00
Elio Struyf
11bb0fabcd Update URL 2021-09-09 08:07:59 +02:00
Elio Struyf
4c43eb5eeb Updated beta baseImagesUrl 2021-09-09 07:17:39 +02:00
Elio Struyf
364d6ec3f9 Updated dependency 2021-09-08 16:34:28 +02:00
Elio Struyf
40646fd1b3 Added media refresh + spinner 2021-09-08 16:18:26 +02:00
Elio Struyf
2769eb9b71 Fix beta startup + main release script to reset the id 2021-09-08 10:29:34 +02:00
Elio Struyf
aa97051437 Updated beta script 2021-09-08 10:14:36 +02:00
Elio Struyf
496391b048 Updated changelog + beta release script 2021-09-08 10:12:15 +02:00
Elio Struyf
2e743c896f Stable version check 2021-09-08 10:05:05 +02:00
Elio Struyf
3941c33b1d Updated isProduction function 2021-09-08 10:01:36 +02:00
Elio Struyf
cdc49c09f1 Link to beta docs 2021-09-08 09:52:39 +02:00
Elio Struyf
fc7300ba87 Beta doc updates 2021-09-08 09:43:11 +02:00
Elio Struyf
d3aa51c9ed Updated docs 2021-09-08 09:09:24 +02:00
Elio Struyf
387c906af2 Update docs 2021-09-08 09:06:07 +02:00
Elio Struyf
d04c2a96b7 Updated beta release 2021-09-08 08:51:00 +02:00
Elio Struyf
797c4cd2b1 Updated beta number 2021-09-08 08:46:35 +02:00
Elio Struyf
0ebc874796 3.1.0 2021-09-08 08:43:33 +02:00
Elio Struyf
0b970a73b6 Updated beta workflow 2021-09-08 08:43:30 +02:00
Elio Struyf
065b2fef1b Update beta script 2021-09-08 08:41:15 +02:00
Elio Struyf
b5033f6b45 Beta release 2021-09-08 08:39:24 +02:00
Elio Struyf
274b341648 Merge pull request #86 from estruyf/dependabot/npm_and_yarn/docs/next-11.1.1
Bump next from 11.1.0 to 11.1.1 in /docs
2021-09-08 08:11:05 +02:00
Elio Struyf
c51f2a80c3 Merge pull request #92 from estruyf/dependabot/npm_and_yarn/docs/remark-html-14.0.1
Bump remark-html from 14.0.0 to 14.0.1 in /docs
2021-09-08 08:10:55 +02:00
dependabot[bot]
e6415a3ee9 Bump remark-html from 14.0.0 to 14.0.1 in /docs
Bumps [remark-html](https://github.com/remarkjs/remark-html) from 14.0.0 to 14.0.1.
- [Release notes](https://github.com/remarkjs/remark-html/releases)
- [Commits](https://github.com/remarkjs/remark-html/compare/14.0.0...14.0.1)

---
updated-dependencies:
- dependency-name: remark-html
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-07 23:30:28 +00:00
Elio Struyf
134a5aaa0d #72 #91 - Further implementation of media view + content folder support 2021-09-07 17:52:34 +02:00
Elio Struyf
86c609f49f media dashboard implementation 2021-09-06 16:26:13 +02:00
Elio Struyf
bf3dec46bc Updated changelog 2021-09-06 10:38:47 +02:00
Elio Struyf
2d7c9cfc05 vscode helper integration 2021-09-06 10:33:40 +02:00
Elio Struyf
7e705ccfd8 #89 - Clear filters, sorting, and grouping button 2021-09-06 10:26:15 +02:00
Elio Struyf
1680637235 Create codeql-analysis.yml 2021-09-06 08:57:01 +02:00
Elio Struyf
3de56b94c3 Fix sitemap 2021-09-06 08:32:16 +02:00
dependabot[bot]
1b5840b2bf Bump next from 11.1.0 to 11.1.1 in /docs
Bumps [next](https://github.com/vercel/next.js) from 11.1.0 to 11.1.1.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v11.1.0...v11.1.1)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-06 06:30:20 +00:00
Elio Struyf
b14c4c50cf Added sitemap building 2021-09-06 08:29:14 +02:00
Elio Struyf
1f8c2f5b1e Updated references 2021-09-04 15:01:37 +02:00
Elio Struyf
db3a584ab9 #73 - Color enhancements 2021-09-04 12:57:00 +02:00
Elio Struyf
918f914c91 #73 - persist view type setting 2021-09-04 12:40:28 +02:00
Elio Struyf
9f5d180447 Update changelog 2021-09-03 17:47:39 +02:00
Elio Struyf
77e2c68810 Update changelog 2021-09-03 17:46:59 +02:00
Elio Struyf
60caf743c6 #90 #89 - Recoil implementation + Search sorting fix 2021-09-03 17:46:18 +02:00
Elio Struyf
b8dee7a5c9 #73 - List view 2021-09-03 16:45:22 +02:00
Elio Struyf
3b6a7e9b71 Add CMS to the images 2021-09-03 15:37:50 +02:00
Elio Struyf
648ee34171 Style improvements for smaller screens 2021-09-03 11:36:53 +02:00
Elio Struyf
4a88e471f6 Updated changelog + added links to settings in config settings 2021-09-03 10:56:50 +02:00
Elio Struyf
14a2b715e7 Merge branch 'issue/81' 2021-09-03 10:42:12 +02:00
Elio Struyf
0c368dbe65 #85 - Updated showcases 2021-09-03 10:34:11 +02:00
Elio Struyf
f0b7542d94 Removed unused references 2021-09-03 10:29:23 +02:00
Elio Struyf
80cd7d3eac Updated changelog 2021-09-02 17:27:37 +02:00
Elio Struyf
ef08e0a34b #87 - Fix issue with autofocus and command palette 2021-09-02 17:26:40 +02:00
Elio Struyf
62a2c22ba1 Typo fix 2021-09-02 14:12:53 +02:00
Elio Struyf
1417575b98 Added process variables 2021-09-02 14:09:52 +02:00
Elio Struyf
4862810035 Search implementation 2021-09-02 13:47:27 +02:00
Elio Struyf
e1b9bad05b Mobile navigation 2021-09-02 10:58:04 +02:00
Elio Struyf
47f7521e93 Remove reference 2021-09-02 10:29:15 +02:00
Elio Struyf
771f86710f Website content improvements 2021-09-02 10:26:04 +02:00
Elio Struyf
dc55cedb0d Update Navigation.tsx 2021-09-01 21:21:27 +02:00
Elio Struyf
e93c6832e4 Update CTA.tsx 2021-09-01 21:17:16 +02:00
Elio Struyf
0f0c7f9f3d Update en.ts 2021-09-01 21:14:06 +02:00
Elio Struyf
d9984ce292 #81 - Optimize the content folder settings + migration script for deprecated setting 2021-09-01 18:55:01 +02:00
Elio Struyf
c9e813effe Added Vercel as sponsor 2021-09-01 11:53:59 +02:00
Elio Struyf
3fea99ef24 Updated height 2021-09-01 11:43:27 +02:00
Elio Struyf
06a3841dd3 Added Vercel as sponsor 2021-09-01 11:42:59 +02:00
Elio Struyf
32fb742de5 Updated changelog + added visitors on docs 2021-09-01 09:56:39 +02:00
Elio Struyf
c41b7adcfd updated changelog 2021-09-01 09:49:58 +02:00
Elio Struyf
be55a2237d #77 - Grouping implementation 2021-09-01 09:44:15 +02:00
Elio Struyf
265f788f98 Restructuring 2021-09-01 08:46:33 +02:00
Elio Struyf
c7d3a7dcf6 Updated showcase 2021-08-31 17:44:30 +02:00
Elio Struyf
e02a766070 Updated readme with contributors 2021-08-31 14:31:31 +02:00
Elio Struyf
c9dd684451 Added review 2021-08-31 13:34:51 +02:00
Elio Struyf
5329033b46 Update typos 2021-08-31 12:25:44 +02:00
Elio Struyf
4bd914fdba Styling updates 2021-08-31 12:18:06 +02:00
164 changed files with 3665 additions and 678 deletions

11
.all-contributorsrc Normal file
View File

@@ -0,0 +1,11 @@
{
"files": [],
"imageSize": 100,
"contributorsPerLine": 7,
"contributorsSortAlphabetically": false,
"badgeTemplate": "",
"contributorTemplate": "",
"types": {},
"skipCi": "true",
"contributors": []
}

View File

@@ -1,7 +1,7 @@
---
name: Bug report
about: Create a report to help us improve
title: Issue
title: 'Issue: '
labels: ''
assignees: ''

View File

@@ -1,7 +1,7 @@
---
name: Feature request
about: Suggest an idea for this project
title: Enhancement
title: 'Enhancement: '
labels: ''
assignees: ''

20
.github/ISSUE_TEMPLATE/showcase.md vendored Normal file
View File

@@ -0,0 +1,20 @@
---
name: Showcase
about: Let us know that you are using Front Matter and we'll add you on our showcase page
title: 'Showcase: '
labels: 'showcase'
assignees: ''
---
**Title you want to give your site**
Define a clear title that will be used on the showcase page. Example: `Front Matter`.
**Link to the site**
A URL to the site to add.
**A nice and clean description**
Keep it simple. Just let us know which static-site generator you used, and other frameworks.

71
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '24 14 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

27
.github/workflows/release-beta.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: BETA Release
on:
push:
branches:
- dev
jobs:
build:
name: "Build and release"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14
registry-url: https://registry.npmjs.org/
- name: Install the dependencies
run: npm i
- name: Prepare BETA
run: node scripts/beta-release.js $GITHUB_RUN_ID
- name: Publish
run: npx vsce publish -p ${{ secrets.VSCE_PAT }} --baseImagesUrl https://raw.githubusercontent.com/estruyf/vscode-front-matter/dev

View File

@@ -19,10 +19,10 @@ jobs:
- name: Install the dependencies
run: npm i
- name: Install vsce
run: npm i -g vsce
- name: Prepare MAIN release
run: node scripts/main-release.js
- name: Publish
run: vsce publish -p ${{ secrets.VSCE_PAT }}
run: npx vsce publish -p ${{ secrets.VSCE_PAT }}

66
.vscode/recoil.code-snippets vendored Normal file
View File

@@ -0,0 +1,66 @@
{
"Recoil Atom": {
"prefix": "sq-atom",
"body": [
"import { atom } from 'recoil';",
"",
"export const ${1:CollectionId}Atom = atom({",
" key: '${1:CollectionId}Atom',",
" default: 1",
"});"
],
"description": "Creates a new atom",
"scope": "typescript"
},
"Recoil Selector (sync)": {
"prefix": "sq-selector-sync",
"body": [
"import { selector } from 'recoil';",
"",
"export const ${1:CollectionData}Selector = selector({",
" key: '${1:CollectionData}Selector',",
" get: ({get}) => {",
" return get(${1:CollectionData}Atom);",
" }",
"});"
],
"description": "Creates a new synchronous selector",
"scope": "typescript"
},
"Recoil Selector (async)": {
"prefix": "sq-selector-async",
"body": [
"import { selector } from 'recoil';",
"",
"export const ${1:CollectionData}Selector = selector({",
" key: '${1:CollectionData}Selector',",
" get: async ({get}) => {",
" return await dataFetch(get(${2:CollectionIdState}));",
" }",
"});"
],
"description": "Creates a new asynchronous selector",
"scope": "typescript"
},
"Recoil selectorFamily": {
"prefix": "sq-selector-fam",
"body": [
"import { selectorFamily } from 'recoil';",
"",
"export const ${1:CollectionData}Selector = selectorFamily({",
" key: '${1:CollectionData}Selector',",
" get: id => async () => {",
" return await dataFetch({id});",
" }",
"});"
],
"description": "Creates a selectorFamily (same as selector, but used to provide parameters)",
"scope": "typescript"
},
"useTranslation": {
"prefix": ["sq-translation", "useTranslation"],
"body": "const { t: strings } = useTranslation();",
"description": "Include the translations",
"scope": "typescriptreact"
}
}

12
.vscode/settings.json vendored
View File

@@ -19,5 +19,15 @@
}
],
"eliostruyf.writingstyleguide.terms.isDisabled": true,
"eliostruyf.writingstyleguide.biasFree.isDisabled": true
"eliostruyf.writingstyleguide.biasFree.isDisabled": true,
"exportall.config.folderListener": [
"/src/pagesView/state/atom",
"/src/pagesView/state/selectors"
],
"frontMatter.content.pageFolders": [
{
"title": "documentation",
"path": "[[workspace]]/docs/content/docs"
}
]
}

View File

@@ -15,4 +15,6 @@ tailwind.config.js
sample
postcss.config.js
.templates
.github
.github
scripts
.all-contributorsrc

View File

@@ -1,5 +1,19 @@
# Change Log
## [3.1.0] - (Upcoming release)
- BETA version available at: [beta.frontmatter.codes](https://beta.frontmatter.codes)
- [#72](https://github.com/estruyf/vscode-front-matter/issues/72): Media view on the dashboard
- [#73](https://github.com/estruyf/vscode-front-matter/issues/73): List view option for the dashboard
- [#77](https://github.com/estruyf/vscode-front-matter/issues/77): Dashboard grouping pages functionality integrated
- [#81](https://github.com/estruyf/vscode-front-matter/issues/81): Optimizing the content folders to use a new setting to simplify configuration
- [#87](https://github.com/estruyf/vscode-front-matter/issues/87): Fix issue with autofocus and command palette
- [#88](https://github.com/estruyf/vscode-front-matter/issues/88): Fix issue with search sorting
- [#89](https://github.com/estruyf/vscode-front-matter/issues/89): Clear filter, sorting, and grouping button added
- [#90](https://github.com/estruyf/vscode-front-matter/issues/90): Refactoring to use Recoil state management
- [#91](https://github.com/estruyf/vscode-front-matter/issues/91): Support image previews from content folders
- [#98](https://github.com/estruyf/vscode-front-matter/issues/98): Add drag and drop support for media upload in a folder
## [3.0.2] - 2021-08-31
- [#82](https://github.com/estruyf/vscode-front-matter/issues/82): Hide the register and unregister commands from the command palette

93
README.beta.md Normal file
View File

@@ -0,0 +1,93 @@
<h1 align="center">
<a href="https://beta.frontmatter.codes">
<img alt="Front Matter BETA" src="./assets/frontmatter-beta.png">
</a>
</h1>
<h2 align="center">Front Matter BETA is an essential Visual Studio Code extension when you want to manage the markdown pages of your static sites.</h2>
<h2 align="center">This is the BETA version of Front Matter. If you were looking for the main version, check it out at <a href="https://frontmatter.codes">frontmatter.codes</a></h2>
<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter" title="Check it out on the Visual Studio Marketplace">
<img src="https://vsmarketplacebadge.apphb.com/version/eliostruyf.vscode-front-matter.svg" alt="Visual Studio Marketplace" style="display: inline-block" />
</a>
<img src="https://vsmarketplacebadge.apphb.com/installs/eliostruyf.vscode-front-matter.svg" alt="Number of installs" style="display: inline-block;margin-left:10px" />
<img src="https://vsmarketplacebadge.apphb.com/rating/eliostruyf.vscode-front-matter.svg" alt="Ratings" style="display: inline-block;margin-left:10px" />
<a href="https://www.buymeacoffee.com/zMeFRy9" title="Buy me a coffee" style="margin-left:10px">
<img src="https://img.shields.io/badge/Buy%20me%20a%20coffee-€%203-blue?logo=buy-me-a-coffee&style=flat" alt="Buy me a coffee" style="display: inline-block" />
</a>
</p>
<h2 align="center">
<a href="https://beta.frontmatter.codes" title="Documentation @ beta.frontmatter.codes">
Check out the extension documentation at beta.frontmatter.codes
</a>
</h2>
Front Matter BETA is an essential Visual Studio Code extension that simplifies working and managing your markdown articles. We created the extension to support many static-site generators like Hugo, Jekyll, Hexo, NextJs, Gatsby, and more.
The extension brings Content Management System (CMS) capabilities straight within Visual Studio Code. For example, you can keep a list of the used tags, categories, create content, and so much more.
Our main extension features are:
- Page dashboard where you can get an overview of all your markdown pages. You can use it to search, filter, sort your contents.
- Site preview within Visual Studio Code
- SEO checks for title, description, and keywords
- Support for custom actions/scripts
- and many more
<p align="center">
<img src="./assets/v2.5.0/site-preview.png" alt="Site preview" style="display: inline-block" />
</p>
> If you see something missing in your article creation flow, please feel free to reach out.
**Version 3**
In version v3 we introduced the welcome and dashboard webview. The welcome view allows to get you started using the extension, and the dashboard allows you to manage all your markdown pages in one place. This makes it easy to search, filter, sort, and more.
**Version 2**
In version v2 we released the re-designed sidebar panel with improved SEO support. This extension makes it the only extension to manage your Markdown pages for your static sites in Visual Studio Code.
<p align="center" style="margin-top: 2rem;">
<a href="https://www.producthunt.com/posts/front-matter?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-front-matter" target="_blank">
<img src="https://api.producthunt.com/widgets/embed-image/v1/featured.png?post_id=309033&theme=dark" alt="Front Matter BETA - Managing your static sites straight from within VS Code | Product Hunt" style="width: 250px; height: 40px;" />
</a>
</p>
<h2 align="center">
<a href="https://beta.frontmatter.codes" title="Documentation @ beta.frontmatter.codes">
Check out the extension documentation at beta.frontmatter.codes
</a>
</h2>
## 👉 Contributors 🤘
<a href="https://github.com/estruyf/vscode-front-matter/graphs/contributors">
<img src="https://contrib.rocks/image?repo=estruyf/vscode-front-matter" />
</a>
<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" />
</a>
</p>
<br />
<br />
<p align="center">
<a href="https://visitorbadge.io">
<img src="https://estruyf-github.azurewebsites.net/api/VisitorHit?user=estruyf&repo=vscode-front-matter&countColor=%23F05450&labelColor=%230E131F" height="25px" />
</a>
</p>

View File

@@ -21,8 +21,8 @@
</p>
<h2 align="center">
<a href="https://frontmatter.codes" title="Documenation @ frontmatter.codes">
Check out the extension documenation at frontmatter.codes
<a href="https://frontmatter.codes" title="Documentation @ frontmatter.codes">
Check out the extension documentation at frontmatter.codes
</a>
</h2>
@@ -52,18 +52,38 @@ In version v3 we introduced the welcome and dashboard webview. The welcome view
In version v2 we released the re-designed sidebar panel with improved SEO support. This extension makes it the only extension to manage your Markdown pages for your static sites in Visual Studio Code.
<h2 align="center">
<a href="https://frontmatter.codes" title="Documenation @ frontmatter.codes">
Check out the extension documenation at frontmatter.codes
</a>
</h2>
<p align="center" style="margin-top: 2rem;">
<a href="https://www.producthunt.com/posts/front-matter?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-front-matter" target="_blank">
<img src="https://api.producthunt.com/widgets/embed-image/v1/featured.png?post_id=309033&theme=dark" alt="Front Matter - Managing your static sites straight from within VS Code | Product Hunt" style="width: 250px; height: 40px;" />
</a>
</p>
<h2 align="center">
<a href="https://frontmatter.codes" title="Documentation @ frontmatter.codes">
Check out the extension documentation at frontmatter.codes
</a>
</h2>
## 👉 Contributors 🤘
<a href="https://github.com/estruyf/vscode-front-matter/graphs/contributors">
<img src="https://contrib.rocks/image?repo=estruyf/vscode-front-matter" />
</a>
<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" />
</a>
</p>
<br />
<br />
<p align="center">
<a href="https://visitorbadge.io">
<img src="https://estruyf-github.azurewebsites.net/api/VisitorHit?user=estruyf&repo=vscode-front-matter&countColor=%23F05450&labelColor=%230E131F" height="25px" />

BIN
assets/frontmatter-beta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,45 @@
<?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{fill:#FFE45E;}
.st1{fill:none;stroke:#FFE45E;stroke-width:2;stroke-miterlimit:10;}
.st2{font-family:'MyriadPro-Bold';}
.st3{font-size:8px;}
</style>
<g>
<g>
<path class="st0" d="M4.1,10.2H2.4V2.1h3.1V4H4.1v1.2h1.2V7H4.1V10.2z"/>
<path class="st0" d="M10.7,10.2H8.9L8,7.3c0-0.1,0-0.1,0-0.2C7.9,7.1,7.9,7,7.8,6.8v0.6v2.9H6.1V2.1h1.8c0.8,0,1.3,0.2,1.8,0.6
c0.5,0.5,0.8,1.2,0.8,2.1c0,1-0.4,1.6-1.1,2L10.7,10.2z M7.9,5.8L7.9,5.8c0.3,0,0.5-0.1,0.6-0.3S8.7,5,8.7,4.8c0-0.6-0.3-1-0.8-1
l0,0V5.8z"/>
<path class="st0" d="M16.1,6.2c0,1.2-0.2,2.3-0.7,3.1s-1.1,1.2-1.7,1.2s-1.2-0.3-1.6-0.9c-0.6-0.8-0.9-1.9-0.9-3.4
s0.3-2.6,0.9-3.4C12.6,2.3,13,2,13.7,2c0.8,0,1.3,0.4,1.8,1.2C15.8,3.8,16.1,4.8,16.1,6.2z M14.3,6.2c0-1.4-0.2-2.2-0.7-2.2
c-0.2,0-0.4,0.2-0.5,0.6c-0.1,0.4-0.2,0.9-0.2,1.6c0,0.7,0.1,1.2,0.2,1.6c0.1,0.4,0.3,0.6,0.5,0.6c0.2,0,0.4-0.2,0.5-0.6
C14.2,7.3,14.3,6.9,14.3,6.2z"/>
<path class="st0" d="M16.8,10.2V2.1h1.7l0.9,2.9c0.1,0.1,0.1,0.3,0.2,0.6c0.1,0.2,0.1,0.5,0.2,0.8L20,7c-0.1-0.7-0.1-1.3-0.2-1.8
s-0.1-1-0.1-1.2V2.1h1.7v8.2h-1.6l-0.9-3c-0.1-0.3-0.2-0.6-0.3-0.9c-0.1-0.3-0.1-0.6-0.2-0.8c0,0.6,0.1,1.1,0.1,1.5
c0,0.4,0,0.8,0,1.2v2.1h-1.7V10.2z"/>
<path class="st0" d="M24.6,10.2h-1.7V4h-1V2.1h3.7V4h-1.1V10.2z"/>
</g>
</g>
<rect class="st1" width="28" height="28"/>
<g>
<g>
<path class="st0" d="M3.1,11.6H4l0.6,3c0.1,0.4,0.2,0.8,0.2,1.2C4.9,16.2,4.9,16.6,5,17c0-0.1,0-0.1,0-0.1v-0.1l0.2-0.9l0.1-0.8
l0.1-0.5l0.6-3h0.9l0.7,7.5h-1l-0.2-2.6c0-0.1,0-0.2,0-0.3c0-0.1,0-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
c-0.1,0.2,0,0.2-0.1,0.3L6,15.7V16l-0.6,3.3H4.7l-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-1
L3.1,11.6z"/>
<path class="st0" d="M9.4,11.6h0.8l1.6,7.5h-1l-0.3-1.5H9l-0.3,1.5h-1L9.4,11.6z M10.4,16.8l-0.3-1.2C10,14.8,9.8,13.9,9.7,13
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.4,16.8L10.4,16.8z"/>
<path class="st0" d="M11.6,11.6h3.3v0.9h-1.1v6.7h-1v-6.7h-1.2V11.6z"/>
<path class="st0" d="M14.9,11.6h3.3v0.9h-1.1v6.7h-1v-6.7h-1.2V11.6z"/>
<path class="st0" d="M18.8,11.6h2.7v0.9h-1.7v2.4h1.5v0.9h-1.5v2.6h1.7v0.9h-2.7V11.6z"/>
<path class="st0" d="M22.3,11.6h1.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,11.6L22.3,11.6z M23.3,14.9c0.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
c-0.1-0.2-0.1-0.3-0.2-0.4c-0.1-0.1-0.2-0.2-0.3-0.2s-0.3-0.1-0.4-0.1h-0.2v2.5H23.3z"/>
</g>
</g>
<text transform="matrix(1 0 0 1 5.4457 25.9479)" class="st0 st2 st3">BETA</text>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 265 KiB

BIN
assets/v3.1.0/media.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

View File

@@ -41,9 +41,12 @@ export const Markdown: React.FunctionComponent<IMarkdownProps> = ({content}: Rea
components={{
a: ({node, ...props}) => {
const url = props?.href || "";
const vscodeUrl = props && (props as any)["data-vscode"] ? (props as any)["data-vscode"] : "";
const title = getTitle(props);
const elm = <Link key={url as string} href={url as string}><a title={title}>{title}</a></Link>;
return elm;
if (vscodeUrl) {
return <Link key={vscodeUrl as string} href={vscodeUrl as string}><a title={title}>{title}</a></Link>;
}
return <Link key={url as string} href={url as string}><a title={title}>{title}</a></Link>;
},
h1: ({node, ...props}) => (<h1 id={generateId(props)}>{getTitle(props)}</h1>),
h2: ({node, ...props}) => (<h2 id={generateId(props)}>{getTitle(props)}</h2>),

View File

@@ -4,6 +4,8 @@ import { Logo } from '../Images';
import Link from 'next/link';
import { Extension } from '../../constants/extension';
import { useRouter } from 'next/router';
import { Searchbox } from '../Page/Searchbox';
import { isProduction } from '../../helpers/isProduction';
export interface INavigationProps {}
@@ -11,39 +13,61 @@ export const Navigation: React.FunctionComponent<INavigationProps> = (props: Rea
const router = useRouter();
return (
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" aria-label="Top">
<div className="w-full py-6 flex items-center justify-center lg:justify-between border-b border-teal-500 lg:border-none">
<div className="flex items-center">
<Link href="/">
<a title={Extension.name}>
<span className="sr-only">{Extension.name}</span>
<Logo className={`text-whisper-500 h-12 w-auto`} />
<>
{
!isProduction() ? (
<div className={`bg-yellow-500 text-center py-2 px-4`}>
<a href={`https://frontmatter.codes`} title={`Go to main release documentation`} className={`text-base font-medium text-vulcan-500 hover:text-vulcan-900`}>
You are currently viewing the BETA version of Front Matter documentation. Click on the banner to go to the main release documentation.
</a>
</Link>
</div>
<div className="ml-10 space-x-4">
<div className="hidden ml-10 space-x-8 lg:block">
{navigation.main.map((link) => (
<a key={link.name} href={link.href} title={link.title} className={`text-base font-medium text-whisper-500 hover:text-whisper-900 ${link.href === router.asPath ? `text-teal-800` : ``}`}>
{link.name}
</div>
) : null
}
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" aria-label="Top">
<div className="w-full py-6 flex items-center justify-center lg:justify-between border-b border-teal-500 lg:border-none">
<div className="flex items-center">
<Link href="/">
<a title={Extension.name}>
<span className="sr-only">{Extension.name}</span>
<Logo className={`text-whisper-500 h-12 w-auto`} />
</a>
))}
{navigation.social.map((link) => (
<a key={link.name} href={link.href} title={link.title} className={`text-base font-medium text-whisper-500 hover:text-whisper-900`} rel={`noopener noreferrer`}>
<span className="sr-only">{link.name}</span>
<link.icon className="inline-block h-6 w-6" aria-hidden="true" />
</a>
))}
</Link>
</div>
<div className="space-x-4">
<div className="hidden ml-10 space-x-8 lg:flex justify-center items-center">
{navigation.main.map((link) => (
<a key={link.name} href={link.href} title={link.title} className={`text-base font-medium text-whisper-500 hover:text-whisper-900 ${link.href === router.asPath ? `text-teal-800` : ``}`}>
{link.name}
</a>
))}
{navigation.social.map((link) => (
<a key={link.name} href={link.href} title={link.title} className={`text-base font-medium text-whisper-500 hover:text-whisper-900`} rel={`noopener noreferrer`}>
<span className="sr-only">{link.name}</span>
<link.icon className="inline-block h-6 w-6" aria-hidden="true" />
</a>
))}
<Searchbox />
</div>
</div>
</div>
</div>
<div className="py-4 flex flex-wrap justify-center space-x-6 lg:hidden">
{navigation.main.map((link) => (
<a key={link.name} href={link.href} title={link.title} className="text-base font-medium text-whisper-500 hover:text-whisper-900">
{link.name}
</a>
))}
</div>
</nav>
<div className="py-4 flex flex-wrap justify-center space-x-6 lg:hidden">
{navigation.main.map((link) => (
<a key={link.name} href={link.href} title={link.title} className="text-base font-medium text-whisper-500 hover:text-whisper-900">
{link.name}
</a>
))}
</div>
<div className="py-4 flex flex-wrap justify-center space-x-6 lg:hidden">
{navigation.social.map((link) => (
<a key={link.name} href={link.href} title={link.title} className={`text-base font-medium text-whisper-500 hover:text-whisper-900`} rel={`noopener noreferrer`}>
<span className="sr-only">{link.name}</span>
<link.icon className="inline-block h-6 w-6" aria-hidden="true" />
</a>
))}
</div>
</nav>
</>
);
};
};

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Extension } from '../../constants/extension';
import { isProduction } from '../../helpers/isProduction';
export interface ICTAProps {}
@@ -8,22 +9,23 @@ export const CTA: React.FunctionComponent<ICTAProps> = (props: React.PropsWithCh
const { t: strings } = useTranslation();
return (
<div className="px-4 sm:px-0 pt-8 overflow-hidden sm:pt-12 lg:relative lg:py-48">
<div className="px-4 sm:px-0 py-8 overflow-hidden lg:relative lg:py-48">
<div className="mx-auto sm:max-w-3xl sm:px-6 lg:px-8 lg:max-w-7xl lg:grid lg:grid-cols-2 lg:gap-24">
<div className={`my-4 sm:my-5 lg:my-6`}>
<h1 className="text-5xl tracking-tight font-extrabold sm:leading-none lg:text-5xl xl:text-6xl">
<span className="md:block">{strings(`cta_title`)}</span>{' '}
<span className="text-teal-500 md:block">{Extension.name}</span>
<h1 className="text-5xl lg:text-5xl xl:text-6xl tracking-tight font-extrabold sm:leading-none">
<span className="text-teal-500 md:block">{Extension.name}</span>{' '}
<span className="block">{strings(`cta_title`)}</span>
<span className={`sr-only`}>{strings(`cta_title_sr`)}</span>
</h1>
<p className="mt-3 text-base text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
<h2 className="mt-3 text-base text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
{strings(`cta_description`)}
</p>
</h2>
<div className="mt-10 max-w-sm mx-auto sm:max-w-none">
<div className="space-y-4 sm:space-y-0 sm:mx-auto sm:inline-grid sm:grid-cols-2 sm:gap-5">
<a href={Extension.installLink} className="flex items-center justify-center px-4 py-3 border border-transparent text-base font-medium shadow-sm text-white bg-teal-500 hover:bg-opacity-70 sm:px-8" rel={`noopener noreferrer`}>
{strings(`cta_button_primary`)}
<a href={isProduction() ? Extension.installLink : Extension.installBetaLink} className="flex items-center justify-center px-4 py-3 border border-transparent text-base font-medium shadow-sm text-white bg-teal-500 hover:bg-opacity-70 sm:px-8" rel={`noopener noreferrer`}>
{isProduction() ? strings(`cta_button_primary`) : strings(`cta_button_beta_primary`)}
</a>
<a href={`/docs`} title={`Read our documentation`} className="flex items-center justify-center px-4 py-3 border border-transparent text-base font-medium shadow-sm text-vulcan-500 bg-whisper-500 hover:bg-opacity-70 sm:px-8">
{strings(`cta_button_secondary`)}
@@ -36,10 +38,10 @@ export const CTA: React.FunctionComponent<ICTAProps> = (props: React.PropsWithCh
<div className="sm:mx-auto sm:max-w-3xl sm:px-6">
<div className={`py-12 sm:relative sm:py-16 lg:absolute lg:inset-y-0 lg:right-0 lg:w-1/2`}>
<div className={`relative sm:mx-auto sm:max-w-3xl sm:px-0 lg:-mr-40 lg:max-w-none lg:h-full lg:pl-12`}>
<img className={`w-full lg:h-full lg:w-auto lg:max-w-none`} src={`/assets/site-preview.png`} alt={`Site preview`} />
<img className={`w-full lg:h-full lg:w-auto lg:max-w-none`} src={`/assets/site-preview.png`} alt={`Front Matter - Headless CMS - Live page preview`} />
</div>
</div>
</div>
</div>
);
};
};

View File

@@ -2,6 +2,8 @@ import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { features } from '../../constants/features';
import { CheckIcon } from '@heroicons/react/outline';
import Link from 'next/link';
import { Extension } from '../../constants/extension';
export interface IFeaturesProps {}
@@ -9,11 +11,11 @@ export const Features: React.FunctionComponent<IFeaturesProps> = (props: React.P
const { t: strings } = useTranslation();
return (
<div className={``}>
<div className="max-w-7xl mx-auto py-16 px-4 sm:px-6 lg:py-24 lg:px-8">
<div className={`bg-whisper-500 text-vulcan-500`}>
<div className="max-w-7xl mx-auto py-12 sm:py-16 px-4 sm:px-6 lg:px-8">
<div className="max-w-3xl mx-auto text-center">
<h2 className="text-3xl font-extrabold">{strings(`features_title`)}</h2>
<p className="mt-4 text-lg text-whisper-700">
<p className="mt-4 text-lg text-vulcan-300">
{strings(`features_description`)}
</p>
</div>
@@ -21,13 +23,28 @@ export const Features: React.FunctionComponent<IFeaturesProps> = (props: React.P
{features.map((feature) => (
<div key={feature.name} className="relative">
<dt>
<CheckIcon className="absolute h-6 w-6 text-teal-500" aria-hidden="true" />
<p className="ml-9 text-lg leading-6 font-medium text-whisper-500">{strings(feature.name)}</p>
<CheckIcon className="absolute h-6 w-6 text-teal-800" aria-hidden="true" />
<p className="ml-9 text-lg leading-6 font-medium text-vulcan-320">{strings(feature.name)}</p>
</dt>
<dd className="mt-2 ml-9 text-base text-whisper-800">{strings(feature.description)}</dd>
<dd className="mt-2 ml-9 text-base text-vulcan-100">{strings(feature.description)}</dd>
</div>
))}
</dl>
<div className="mt-6 flex flex-col justify-center items-center">
<p className={`text-xl tracking-tight font-bold sm:leading-none text-vulcan-50`}>
{strings(`features_cta_title`)}
</p>
<p className="mt-4">
<Link href={Extension.featureLink} >
<a className={`inline-block px-4 py-3 border border-transparent text-base font-medium shadow-sm text-white bg-vulcan-50 hover:bg-opacity-70 sm:px-8`}
target={`_blank`}
rel={`noopener noreferrer`}>
{strings(`features_cta_button`)}
</a>
</Link>
</p>
</div>
</div>
</div>
);

View File

@@ -18,8 +18,12 @@ export const Footer: React.FunctionComponent<IFooterProps> = (props: React.Props
))}
</nav>
<div className="mt-8 flex justify-center space-x-6">
<a href="https://visitorbadge.io/status?path=https%3A%2F%2Ffrontmatter.codes" title={`Daily Front Matter visitors`}>
<img src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes&countColor=%23060A15&labelColor=%23060A15" />
<a href="https://visitorbadge.io/status?path=https%3A%2F%2Ffrontmatter.codes" title={`Daily Front Matter visitors`} rel={`noopener noreferrer`}>
<img src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes&countColor=%23060A15&labelColor=%23060A15" alt={`Visitors`} />
</a>
<a href={Extension.extensionLink} title={`Extension installs`} rel={`noopener noreferrer`}>
<img src={`https://vsmarketplacebadge.apphb.com/installs-short/eliostruyf.vscode-front-matter.svg?style=for-the-badge&color=060A15&labelColor=060A15`} alt={`Installations of the extension`} />
</a>
{navigation.social.map((item) => (

View File

@@ -8,10 +8,10 @@ export const Generators: React.FunctionComponent<IGeneratorsProps> = (props: Rea
return (
<div className="bg-whisper-100">
<div className="max-w-7xl mx-auto py-16 px-4 sm:px-6 lg:px-8">
<p className="text-center text-sm font-semibold uppercase text-vulcan-500 tracking-wide">
<div className="max-w-7xl mx-auto py-12 sm:py-16 px-4 sm:px-6 lg:px-8">
<h2 className="text-center text-sm font-semibold uppercase text-vulcan-500 tracking-wide">
{strings(`generators_title`)}
</p>
</h2>
<div className="mt-6 grid grid-cols-2 gap-8 md:grid-cols-6">
<div className="col-span-1 flex justify-center">
@@ -35,7 +35,9 @@ export const Generators: React.FunctionComponent<IGeneratorsProps> = (props: Rea
</div>
<div className="mt-6 flex justify-center">
<p className={`text-2xl tracking-tight font-bold sm:leading-none text-vulcan-500`}>and many more...</p>
<p className={`text-2xl tracking-tight font-bold sm:leading-none text-vulcan-500`}>
{strings(`generators_more`)}
</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,57 @@
import Link from 'next/link';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
export interface IHeroProps {
view: "left" | "right";
title: string;
description: string | JSX.Element;
imgSrc: string;
imgAlt: string;
link?: string;
linkText?: string;
className?: string;
}
export const Hero: React.FunctionComponent<IHeroProps> = ({view, title, description, imgSrc, imgAlt, link, linkText, className}: React.PropsWithChildren<IHeroProps>) => {
return (
<div className={`overflow-hidden lg:relative`}>
<div className={`${className || ""} px-4 sm:px-6 xl:px-0 py-12 sm:py-16 lg:relative lg:mx-auto lg:max-w-7xl lg:grid lg:grid-cols-2 lg:grid-flow-col-dense lg:gap-24`}>
<div className={`max-w-3xl mx-auto lg:py-48 lg:max-w-none lg:mx-0 lg:px-0 ${view === "left" ? `lg:col-start-2` : `lg:col-start-1`}`}>
<div>
<h2 className="text-3xl lg:text-3xl xl:text-4xl tracking-tight font-extrabold sm:leading-none">
{title}
</h2>
{
typeof description === 'string' ? (
<p className="my-6 text-base text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
{description}
</p>
) : (
{...description}
)
}
{
link && linkText && (
<Link href={link} >
<a className={`inline-block px-4 py-3 border border-transparent text-base font-medium shadow-sm text-white bg-teal-500 hover:bg-opacity-70 sm:px-8`}>
{linkText}
</a>
</Link>
)
}
</div>
</div>
<div className={`sm:mx-auto sm:max-w-3xl sm:px-6 lg:px-0 lg:mx-0 lg:max-w-none mt-12 sm:mt-16 lg:mt-0 ${view === "left" ? `lg:col-start-1` : `lg:col-start-2`}`}>
<div className={`${view === "left" ? `lg:pr-6 lg:-ml-16` : `lg:pl-6 lg:-mr-16`} lg:px-0 lg:m-0 lg:relative lg:h-full`}>
<img className={`w-full rounded-xl lg:absolute lg:h-full lg:w-auto lg:max-w-none ${view === "left" ? `lg:right-0` : `lg:left-0`}`}
src={imgSrc}
alt={imgAlt} />
</div>
</div>
</div>
</div>
);
};

View File

@@ -1,13 +1,14 @@
import * as React from 'react';
import { Navigation } from '../Navigation';
import { Footer } from './Footer';
import { Sponsors } from './Sponsors';
export interface ILayoutProps {}
export const Layout: React.FunctionComponent<ILayoutProps> = (props: React.PropsWithChildren<ILayoutProps>) => {
return (
<div className={`flex flex-col h-screen`}>
<header>
<header className={`lg:sticky lg:top-0 z-50 bg-vulcan-500 bg-opacity-80 backdrop-blur-lg`}>
<Navigation />
</header>
@@ -15,6 +16,8 @@ export const Layout: React.FunctionComponent<ILayoutProps> = (props: React.Props
{props.children}
</main>
<Sponsors />
<Footer />
</div>
);

View File

@@ -0,0 +1,20 @@
import * as React from 'react';
// const docsearch = require('@docsearch/js');
// import { useEffect } from 'react';
import { DocSearch } from '@docsearch/react';
export interface ISearchboxProps {}
export const Searchbox: React.FunctionComponent<ISearchboxProps> = (props: React.PropsWithChildren<ISearchboxProps>) => {
return (
<>
<DocSearch
apiKey={process.env.NEXT_PUBLIC_AGOLIA_APIKEY || ""}
indexName={process.env.NEXT_PUBLIC_AGOLIA_INDEX || ""}
appId={process.env.NEXT_PUBLIC_AGOLIA_APPID || ""}
disableUserPersonalization={true}
/>
</>
);
};

View File

@@ -0,0 +1,24 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
export interface ISponsorsProps {}
export const Sponsors: React.FunctionComponent<ISponsorsProps> = (props: React.PropsWithChildren<ISponsorsProps>) => {
const { t: strings } = useTranslation();
return (
<div className="bg-vulcan-600">
<div className="max-w-7xl mx-auto pt-12 px-4 sm:px-6 lg:px-8">
<p className="text-center text-sm font-semibold uppercase text-whisper-900 tracking-wide">
{strings(`sponsors_title`)}
</p>
<div className="mt-6">
<a target={`_blank`} rel={`noopener noreferrer`} href={`https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss`} title={`Powered by Vercel`} className="col-span-1 flex justify-center">
<img className="h-12" src="/assets/sponsors/powered-by-vercel.svg" alt="Vercel" />
</a>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
export interface ITestimonialProps {}
export const Testimonial: React.FunctionComponent<ITestimonialProps> = (props: React.PropsWithChildren<ITestimonialProps>) => {
return (
null
);
};

View File

@@ -0,0 +1,8 @@
export * from './CTA';
export * from './Features';
export * from './Footer';
export * from './Generators';
export * from './Hero';
export * from './Layout';
export * from './Sponsors';
export * from './Testimonial';

View File

@@ -1,13 +1,16 @@
export const Extension = {
name: `Front Matter`,
home: `The CMS running in VS Code`,
description: `Front Matter is an essential Visual Studio Code extension that simplifies working and managing your markdown articles. We created the extension to support many static-site generators like Hugo, Jekyll, Hexo, NextJs, Gatsby, and more.`,
home: `The CMS running in VS Code for your static sites`,
description: `Headless CMS running in Visual Studio Code that helps managing your static sites. Supports Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and more.`,
githubLink: "https://github.com/estruyf/vscode-front-matter",
issueLink: "https://github.com/estruyf/vscode-front-matter/issues",
sponsorLink: "https://github.com/sponsors/estruyf",
extensionLink: "https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter",
extensionBetaLink: "https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta",
reviewLink: "https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter&ssr=false#review-details",
installLink: "vscode:extension/eliostruyf.vscode-front-matter"
installLink: "vscode:extension/eliostruyf.vscode-front-matter",
installBetaLink: "vscode:extension/eliostruyf.vscode-front-matter-beta",
showcaseLink: "https://github.com/estruyf/vscode-front-matter/issues/new?assignees=&labels=&template=showcase.md&title=Showcase%3A+",
featureLink: "https://github.com/estruyf/vscode-front-matter/issues/new?assignees=&labels=&template=feature_request.md&title=Enhancement%3A+"
}

View File

@@ -1,4 +1,4 @@
import { HeartIcon } from "@heroicons/react/outline";
import { HeartIcon, StarIcon } from "@heroicons/react/outline";
import React from "react";
import { Extension } from "./extension";
@@ -24,12 +24,20 @@ export const navigation = {
)
},
{
name: 'Sponsor us',
name: 'Become a sponsor',
title: 'Become a sponsor, and get mentioned',
href: Extension.sponsorLink,
icon: (props: any) => (
<HeartIcon {...props} />
)
},
{
name: 'Review',
title: 'Write a review on the marketplace',
href: Extension.reviewLink,
icon: (props: any) => (
<StarIcon {...props} />
)
}
]
};

View File

@@ -1,5 +1,19 @@
# Change Log
## [3.1.0] - (Upcoming release)
- BETA version available at: [beta.frontmatter.codes](https://beta.frontmatter.codes)
- [#72](https://github.com/estruyf/vscode-front-matter/issues/72): Media view on the dashboard
- [#73](https://github.com/estruyf/vscode-front-matter/issues/73): List view option for the dashboard
- [#77](https://github.com/estruyf/vscode-front-matter/issues/77): Dashboard grouping pages functionality integrated
- [#81](https://github.com/estruyf/vscode-front-matter/issues/81): Optimizing the content folders to use a new setting to simplify configuration
- [#87](https://github.com/estruyf/vscode-front-matter/issues/87): Fix issue with autofocus and command palette
- [#88](https://github.com/estruyf/vscode-front-matter/issues/88): Fix issue with search sorting
- [#89](https://github.com/estruyf/vscode-front-matter/issues/89): Clear filter, sorting, and grouping button added
- [#90](https://github.com/estruyf/vscode-front-matter/issues/90): Refactoring to use Recoil state management
- [#91](https://github.com/estruyf/vscode-front-matter/issues/91): Support image previews from content folders
- [#98](https://github.com/estruyf/vscode-front-matter/issues/98): Add drag and drop support for media upload in a folder
## [3.0.2] - 2021-08-31
- [#82](https://github.com/estruyf/vscode-front-matter/issues/82): Hide the register and unregister commands from the command palette

View File

@@ -9,7 +9,7 @@ weight: 6
# Commands
Front Matter actions are also available as commands. In this section of the documenation all commands will be explained.
Front Matter actions are also available as commands. In this section of the documentation all commands will be explained.
![Commands](/assets/commands.png)

View File

@@ -9,25 +9,51 @@ weight: 3
# Dashboard
Managing your Markdown pages has never been easier in VS Code. With the Front Matter dashboard, you will be able to view all your pages and **search** through them, **filter**, **sort**, and much more.
Managing your Markdown pages/media has never been easier in VS Code. With the Front Matter dashboard, you will be able to view all your pages and media.
![Dashboard](/assets/dashboard.png)
On the contents view, you can **search**, **filter**, **sort** your pages and much more.
![Dashboard - Contents view](/assets/dashboard.png)
On the media view, you can quickly glance all the available media files in your project and perform quick actions like copying the relative path.
![Dashboard - Media view](/assets/media.png)
In order to start using the dashboard, you will have to let the extension know in which folder(s) it can find your pages. Be sure to follow our [getting started](/docs/getting-started) guide.
> **Important**: If your preview images are not loading, it might be that you need to configure the `publicFolder` where the extension can find them. For instance, in Hugo, this is the static folder. You can configure this by updating the `frontMatter.content.publicFolder` setting.
## Supported filters
## Contents view
### Supported filters
- Tag filter
- Category filter
- Content folder (when you have multiple registered)
## Supported sorting
### Supported sorting
- Last modified
- Filename (asc/desc)
## Media view
The media view has been created to make it easier to look at all media files available for your articles. When you click on an image, it will show a lightbox, so that it is easier to glance at small images.
![Dashboard - Media view - Lightbox](/assets/lightbox.png)
### Media actions
On the image card, there are actions like copying the relative path or deleting the media file.
![Dashboard - Delete media file](/assets/delete-media.png)
### Drag and Drop
On the media view, we enabled drag and drop for your media files. You can easily drop any image from your explorer/finder window into one of your folders.
![Dashboard - Upload media file](/assets/upload-media.png)
## Show on startup
If you want, you can check on the `Open on startup?` checkbox. This setting will allow the dashboard to automatically open when you launch the project in VS Code. It will only apply to the current project, not for all of them.

View File

@@ -11,10 +11,23 @@ weight: 2
To get you started, you first need to install the extension in Visual Studio Code.
## Installation
You can get the extension via:
- The VS Code marketplace: [VS Code Marketplace - Front Matter](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter).
- The extension CLI: `ext install eliostruyf.vscode-front-matter`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter">open extension in VS Code</a>
### Beta version
If you have the courage to test out the beta features, we made available a beta version as well. You can install this via:
- The VS Code marketplace: [VS Code Marketplace - Front Matter BETA](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta).
- The extension CLI: `ext install eliostruyf.vscode-front-matter-beta`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter-beta">open extension in VS Code</a>
> **Info**: The BETA docs can be found on [beta.frontmatter.codes](https://beta.frontmatter.codes).
## Welcome screen

View File

@@ -1,9 +1,9 @@
---
title: Settings
slug: settings
description:
description: null
date: '2021-08-30T16:13:00.546Z'
lastmod: '2021-08-30T16:13:01.763Z'
lastmod: '2021-09-08T07:08:17.747Z'
weight: 7
---
@@ -31,9 +31,9 @@ Specify if you want to highlight the Front Matter in the Markdown file.
- Type: `boolean`
- Default: `true`
### frontMatter.content.folders
### frontMatter.content.pageFolders
This array of folders defines where the extension can easily create new content by running the create article command.
This array of folders defines where the extension can find your content and create new content by running the create article command.
- Type: `object[]`
- Default: `[]`
@@ -42,14 +42,17 @@ Sample:
```json
{
"frontMatter.content.folders": [{
"title": "Articles",
"fsPath": "<the path to the folder>",
"paths": ["<wsl-folder-path>"]
}]
"frontMatter.content.pageFolders": [
{
"title": "Blog posts",
"path": "[[workspace]]/content/posts"
}
]
}
```
> **Important**: `[[workspace]]` is a placeholder that the extension uses to replace the workspace path. The reason why we choose to use this, is because some do not keep the original folder name.
### frontMatter.content.publicFolder
Specify the folder name where all your assets are located. For instance in Hugo this is the `static` folder.
@@ -230,4 +233,11 @@ Specify the folder to use for your article templates.
Specify the prefix you want to add for your new article filenames.
- Type: `string`
- Default: `yyyy-MM-dd`
- Default: `yyyy-MM-dd`
## Deprecated settings
### frontMatter.content.folders
This setting has been deprecated since version `3.1.0` in favor of the newly introduced `frontMatter.content.pageFolders` setting.

View File

@@ -0,0 +1,3 @@
export const isProduction = () => process.env.NEXT_PUBLIC_VERCEL_ENV === 'production';

View File

@@ -3,26 +3,46 @@ import { Extension } from "../constants/extension";
export const strings = {
// CTA
cta_title: "Manage your static site with",
cta_description: "Create, edit, and preview your pages within Visual Studio Code. Front Matter allows you to keep control of your static site without any external tools.",
cta_title: "Headless CMS running in VS Code",
cta_title_sr: " that helps managing your static sites and Markdown based sites. Supports Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and more.",
cta_description: "Why leave your editor if it can give you all power to manage your static sites? Front Matter is a headless CMS that lets you create, edit, and preview your Markdown based content straight within Visual Studio Code.",
cta_button_primary: "Get the extension",
cta_button_beta_primary: "Get the BETA extension",
cta_button_secondary: "Read our docs",
// Generators
generators_title: "Built for any static-site generator you might like",
generators_more: "and many more...",
// Hero
hero_title: "Bringing the CMS to your editor",
hero_description: "Why would you leave your editor when you can perform all tasks straight from within it?",
hero_description_second: "We at Front Matter believe that you should keep using what you like. For us, this is Visual Studio Code. Use the same editor you use to code, but with unique features to make it suitable for writing and managing your Markdown articles.",
hero_button_primary: "Get started",
// Hero media
hero_media_title: "Checking your media was never easier",
hero_media_description: "Quickly glance all your media files straight from within VS Code. You will be able to filter by your content folders, and perform quick media actions.",
hero_media_button_primary: "See what it can do",
// Testimonials
testimonials_title: "What others are saying",
testimonials_description: "We love Front Matter and we're excited to share it with the world.",
// Features
features_title: "Features",
features_description: "Check out our main features which help you manage your static-site",
features_cta_title: "Missing a feature?",
features_cta_button: "Tell us what you need",
// Feature
feature_title_1: "Manage your site within VS Code",
feature_description_1: "A Content Management System built to run within Visual Studio Code. No dependencies on any website or API.",
feature_title_1: "Offline management",
feature_description_1: "A Content Management System built to run within Visual Studio Code. No dependencies on any website or API. Write wherever you are, commit when you are online.",
feature_title_2: "Preview",
feature_description_2: "Allow showing your page previews within Visual Studio Code.",
feature_title_2: "Full site/page preview",
feature_description_2: "Allow showing your site and page previews within Visual Studio Code without the need of opening a browser.",
feature_title_3: "Page dashboard",
feature_title_3: "Content dashboard",
feature_description_3: "Our page dashboard allows you to search, filter, sort, and group all your static site pages.",
feature_title_4: "SEO Checks",
@@ -34,17 +54,20 @@ export const strings = {
feature_title_6: "Extensibility",
feature_description_6: "Add your actions with our custom scripting capability. For instance, you can use a script and hook it up to the extension if you want to generate preview images. If we do not support it, you can build it and share it with us.",
// Sponsors
sponsors_title: "Special thanks to our sponsor",
// Documentation
documentation_title: "Documentation",
documentation_description: `Get to know more about how you can use ${Extension.name} with our documentation.`,
// Showcase
showcase_title: "Showcase",
showcase_description: "Check out our showcase of static-sites using Front Matter.",
showcase_description: "Check out our showcase of sites using Front Matter.",
// Changelog
changelog_title: "Changelog",
changelog_description: "Check out our changelog for Front Matter.",
changelog_page_title: `Latest updates`,
changelog_page_description: `An overview of all updates from the ${Extension.name} extension`,
};
};

319
docs/package-lock.json generated
View File

@@ -4,6 +4,142 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@algolia/autocomplete-core": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.2.2.tgz",
"integrity": "sha512-JOQaURze45qVa8OOFDh+ozj2a/ObSRsVyz6Zd0aiBeej+RSTqrr1hDVpGNbbXYLW26G5ujuc9QIdH+rBHn95nw==",
"requires": {
"@algolia/autocomplete-shared": "1.2.2"
}
},
"@algolia/autocomplete-preset-algolia": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.2.2.tgz",
"integrity": "sha512-AZkh+bAMaJDzMZTelFOXJTJqkp5VPGH8W3n0B+Ggce7DdozlMRsDLguKTCQAkZ0dJ1EbBPyFL5ztL/JImB137Q==",
"requires": {
"@algolia/autocomplete-shared": "1.2.2"
}
},
"@algolia/autocomplete-shared": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.2.2.tgz",
"integrity": "sha512-mLTl7d2C1xVVazHt/bqh9EE/u2lbp5YOxLDdcjILXmUqOs5HH1D4SuySblXaQG1uf28FhTqMGp35qE5wJQnqAw=="
},
"@algolia/cache-browser-local-storage": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.10.5.tgz",
"integrity": "sha512-cfX2rEKOtuuljcGI5DMDHClwZHdDqd2nT2Ohsc8aHtBiz6bUxKVyIqxr2gaC6tU8AgPtrTVBzcxCA+UavXpKww==",
"requires": {
"@algolia/cache-common": "4.10.5"
}
},
"@algolia/cache-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.10.5.tgz",
"integrity": "sha512-1mClwdmTHll+OnHkG+yeRoFM17kSxDs4qXkjf6rNZhoZGXDvfYLy3YcZ1FX4Kyz0DJv8aroq5RYGBDsWkHj6Tw=="
},
"@algolia/cache-in-memory": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.10.5.tgz",
"integrity": "sha512-+ciQnfIGi5wjMk02XhEY8fmy2pzy+oY1nIIfu8LBOglaSipCRAtjk6WhHc7/KIbXPiYzIwuDbM2K1+YOwSGjwA==",
"requires": {
"@algolia/cache-common": "4.10.5"
}
},
"@algolia/client-account": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.10.5.tgz",
"integrity": "sha512-I9UkSS2glXm7RBZYZIALjBMmXSQbw/fI/djPcBHxiwXIheNIlqIFl2SNPkvihpPF979BSkzjqdJNRPhE1vku3Q==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/client-search": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-analytics": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.10.5.tgz",
"integrity": "sha512-h2owwJSkovPxzc+xIsjY1pMl0gj+jdVwP9rcnGjlaTY2fqHbSLrR9yvGyyr6305LvTppxsQnfAbRdE/5Z3eFxw==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/client-search": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.10.5.tgz",
"integrity": "sha512-21FAvIai5qm8DVmZHm2Gp4LssQ/a0nWwMchAx+1hIRj1TX7OcdW6oZDPyZ8asQdvTtK7rStQrRnD8a95SCUnzA==",
"requires": {
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-personalization": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.10.5.tgz",
"integrity": "sha512-nH+IyFKBi8tCyzGOanJTbXC5t4dspSovX3+ABfmwKWUYllYzmiQNFUadpb3qo+MLA3jFx5IwBesjneN6dD5o3w==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-search": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.10.5.tgz",
"integrity": "sha512-1eQFMz9uodrc5OM+9HeT+hHcfR1E1AsgFWXwyJ9Q3xejA2c1c4eObGgOgC9ZoshuHHdptaTN1m3rexqAxXRDBg==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/logger-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.10.5.tgz",
"integrity": "sha512-gRJo9zt1UYP4k3woEmZm4iuEBIQd/FrArIsjzsL/b+ihNoOqIxZKTSuGFU4UUZOEhvmxDReiA4gzvQXG+TMTmA=="
},
"@algolia/logger-console": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.10.5.tgz",
"integrity": "sha512-4WfIbn4253EDU12u9UiYvz+QTvAXDv39mKNg9xSoMCjKE5szcQxfcSczw2byc6pYhahOJ9PmxPBfs1doqsdTKQ==",
"requires": {
"@algolia/logger-common": "4.10.5"
}
},
"@algolia/requester-browser-xhr": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.10.5.tgz",
"integrity": "sha512-53/MURQEqtK+bGdfq4ITSPwTh5hnADU99qzvpAINGQveUFNSFGERipJxHjTJjIrjFz3vxj5kKwjtxDnU6ygO9g==",
"requires": {
"@algolia/requester-common": "4.10.5"
}
},
"@algolia/requester-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.10.5.tgz",
"integrity": "sha512-UkVa1Oyuj6NPiAEt5ZvrbVopEv1m/mKqjs40KLB+dvfZnNcj+9Fry4Oxnt15HMy/HLORXsx4UwcthAvBuOXE9Q=="
},
"@algolia/requester-node-http": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.10.5.tgz",
"integrity": "sha512-aNEKVKXL4fiiC+bS7yJwAHdxln81ieBwY3tsMCtM4zF9f5KwCzY2OtN4WKEZa5AAADVcghSAUdyjs4AcGUlO5w==",
"requires": {
"@algolia/requester-common": "4.10.5"
}
},
"@algolia/transporter": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.10.5.tgz",
"integrity": "sha512-F8DLkmIlvCoMwSCZA3FKHtmdjH3o5clbt0pi2ktFStVNpC6ZDmY307HcK619bKP5xW6h8sVJhcvrLB775D2cyA==",
"requires": {
"@algolia/cache-common": "4.10.5",
"@algolia/logger-common": "4.10.5",
"@algolia/requester-common": "4.10.5"
}
},
"@babel/code-frame": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
@@ -67,6 +203,31 @@
"to-fast-properties": "^2.0.0"
}
},
"@docsearch/css": {
"version": "3.0.0-alpha.40",
"resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.0.0-alpha.40.tgz",
"integrity": "sha512-PrOTPgJMl+Iji1zOH0+J0PEDMriJ1teGxbgll7o4h8JrvJW6sJGqQw7/bLW7enWiFaxbJMK76w1yyPNLFHV7Qg=="
},
"@docsearch/js": {
"version": "3.0.0-alpha.40",
"resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.0.0-alpha.40.tgz",
"integrity": "sha512-0ysRM0jk1KAbw/QsHPHIKoa7OeCm2Mwz0JgcPnhWRvvA28dzP+f6OIsL6eGu3VJR043tH9OrvVf/FnvLtTtZtw==",
"requires": {
"@docsearch/react": "3.0.0-alpha.40",
"preact": "^10.0.0"
}
},
"@docsearch/react": {
"version": "3.0.0-alpha.40",
"resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.0.0-alpha.40.tgz",
"integrity": "sha512-aKxnu7sgpP1R7jtgOV/pZdJEHXx6Ts+jnS9U/ejSUS2BMUpwQI5SA3oLs1BA5TA9kIViJ5E+rrjh0VsbcsJ6sQ==",
"requires": {
"@algolia/autocomplete-core": "1.2.2",
"@algolia/autocomplete-preset-algolia": "1.2.2",
"@docsearch/css": "3.0.0-alpha.40",
"algoliasearch": "^4.0.0"
}
},
"@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -173,9 +334,9 @@
"integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA=="
},
"@next/env": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.0.tgz",
"integrity": "sha512-zPJkMFRenSf7BLlVee8987G0qQXAhxy7k+Lb/5hLAGkPVHAHm+oFFeL+2ipbI2KTEFlazdmGY0M+AlLQn7pWaw=="
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.1.tgz",
"integrity": "sha512-UEAzlfKofotLmj9LIgNixAfXpRck9rt/1CU9Q4ZtNDueGBJQP3HUzPHlrLChltWY2TA5MOzDQGL82H0a3+i5Ag=="
},
"@next/eslint-plugin-next": {
"version": "11.1.0",
@@ -187,14 +348,14 @@
}
},
"@next/polyfill-module": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.0.tgz",
"integrity": "sha512-64EgW8SzJRQls2yJ5DkuljRxgE24o2kYtX/ghTkPUJYsfidHMWzQGwg26IgRbb/uHqTd1G0W5UkKag+Nt8TWaQ=="
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.1.tgz",
"integrity": "sha512-9FyVSnz00WGdlLsgc2w1xL1Lm/Q25y6FYIyA+1WlJvT6LA2lbR78GKiHgedzUvrAatVGAcg/Og+d0d7B4tsJOg=="
},
"@next/react-dev-overlay": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz",
"integrity": "sha512-h+ry0sTk1W3mJw+TwEf91aqLbBJ5oqAsxfx+QryqEItNtfW6zLSSjxkyTYTqX8DkgSssQQutQfATkzBVgOR+qQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz",
"integrity": "sha512-CXc/A0DbSk5VXYu4+zr0fHm52Zh/LhPlLyVPEctJOZL64ccxkls5xGoXvgolJCku9L0pLjJzvdfAmhNLOp5dyw==",
"requires": {
"@babel/code-frame": "7.12.11",
"anser": "1.4.9",
@@ -255,9 +416,33 @@
}
},
"@next/react-refresh-utils": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz",
"integrity": "sha512-g5DtFTpLTGa36iy9DuZawtJeitI11gysFGKPQQqy+mNbSFazguArcJ10gAYFlbqpIi4boUamWNI5mAoSPx3kog=="
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz",
"integrity": "sha512-j186y+lWc8BHAuysAWvlOqO9Bp7E3BLK/d/Ju3W2sP5BCH5ZLyLG/p308zSy/O0MGTag0B038ZA1dCy/msouRQ=="
},
"@next/swc-darwin-arm64": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz",
"integrity": "sha512-KyB0aLpfQ+B2dsyGYpkM0ZwK3PV0t4C4b9yjgQc1VoTVnIjzXdDPnNOuVvmD849ZNOHfj3x8e2rlbxkj0lPm3A==",
"optional": true
},
"@next/swc-darwin-x64": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz",
"integrity": "sha512-B3ZXgrGx0bQplbrk2oggPjKPPsmyg8Fl0PJLMTVQ+erQ8g1m5QzyS9P6tB3SiIZa180JgENuguTHlVK5qEj4UA==",
"optional": true
},
"@next/swc-linux-x64-gnu": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz",
"integrity": "sha512-qvZL7gSKF+E+GZ3L1XiTnE3cOh9rk0wkqimT/q+wwcZA4E720Lu4lrT79I3HPuj6i/JPgGvmNskcnYrDeaoFaw==",
"optional": true
},
"@next/swc-win32-x64-msvc": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz",
"integrity": "sha512-jhnCiA1De1L+kA0gmHG1AJijHoxOcrETWziDWy8fcqSrM1NlC4aJ5Mnu6k0QMcM9MnmXTA4TQZOEv3kF7vhJUQ==",
"optional": true
},
"@node-rs/helper": {
"version": "1.2.1",
@@ -340,9 +525,9 @@
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
"@types/node": {
"version": "16.7.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.6.tgz",
"integrity": "sha512-VESVNFoa/ahYA62xnLBjo5ur6gPsgEE5cNRy8SrdnkZ2nwJSW0kJ4ufbFr2zuU9ALtHM8juY53VcRoTA7htXSg=="
"version": "16.7.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz",
"integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA=="
},
"@types/parse-json": {
"version": "4.0.0",
@@ -520,6 +705,27 @@
"uri-js": "^4.2.2"
}
},
"algoliasearch": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.10.5.tgz",
"integrity": "sha512-KmH2XkiN+8FxhND4nWFbQDkIoU6g2OjfeU9kIv4Lb+EiOOs3Gpp7jvd+JnatsCisAZsnWQdjd7zVlW7I/85QvQ==",
"requires": {
"@algolia/cache-browser-local-storage": "4.10.5",
"@algolia/cache-common": "4.10.5",
"@algolia/cache-in-memory": "4.10.5",
"@algolia/client-account": "4.10.5",
"@algolia/client-analytics": "4.10.5",
"@algolia/client-common": "4.10.5",
"@algolia/client-personalization": "4.10.5",
"@algolia/client-search": "4.10.5",
"@algolia/logger-common": "4.10.5",
"@algolia/logger-console": "4.10.5",
"@algolia/requester-browser-xhr": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/requester-node-http": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"anser": {
"version": "1.4.9",
"resolved": "https://registry.npmjs.org/anser/-/anser-1.4.9.tgz",
@@ -695,9 +901,9 @@
}
},
"available-typed-arrays": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz",
"integrity": "sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA=="
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
},
"axe-core": {
"version": "4.3.3",
@@ -1069,9 +1275,9 @@
"dev": true
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"cosmiconfig": {
"version": "7.0.1",
@@ -2391,9 +2597,9 @@
}
},
"hast-util-to-html": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.1.tgz",
"integrity": "sha512-S1mTqXvWVGIxrWw0xOHHvmevwCBFTRGNvXWsjE32IyEAlMhbMkK+ZuP6CAqkQ6Vb7swrehaHpfXHEI6voGDh0w==",
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.2.tgz",
"integrity": "sha512-ipLhUTMyyJi9F/LXaNDG9BrRdshP6obCfmUZYbE/+T639IdzqAOkKN4DyrEyID0gbb+rsC3PKf0XlviZwzomhw==",
"dev": true,
"requires": {
"@types/hast": "^2.0.0",
@@ -2802,11 +3008,11 @@
}
},
"is-typed-array": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.7.tgz",
"integrity": "sha512-VxlpTBGknhQ3o7YiVjIhdLU6+oD8dPz/79vvvH4F+S/c8608UCVa9fgDpa1kZgFoUST2DCgacc70UszKgzKuvA==",
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz",
"integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==",
"requires": {
"available-typed-arrays": "^1.0.4",
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
"es-abstract": "^1.18.5",
"foreach": "^2.0.5",
@@ -3458,16 +3664,20 @@
"dev": true
},
"next": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/next/-/next-11.1.0.tgz",
"integrity": "sha512-GHBk/c7Wyr6YbFRFZF37I0X7HKzkHHI8pur/loyXo5AIE8wdkbGPGO0ds3vNAO6f8AxZAKGCRYtAzoGlVLoifA==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/next/-/next-11.1.1.tgz",
"integrity": "sha512-vfLJDkwAHsZUho5R1K4w49nfYhftUMWNmeNSjCtulOvnRBuEFb7ROyRZOQk7f29rMz02eLQrPZ9yiAmPsexL2g==",
"requires": {
"@babel/runtime": "7.12.5",
"@babel/runtime": "7.15.3",
"@hapi/accept": "5.0.2",
"@next/env": "11.1.0",
"@next/polyfill-module": "11.1.0",
"@next/react-dev-overlay": "11.1.0",
"@next/react-refresh-utils": "11.1.0",
"@next/env": "11.1.1",
"@next/polyfill-module": "11.1.1",
"@next/react-dev-overlay": "11.1.1",
"@next/react-refresh-utils": "11.1.1",
"@next/swc-darwin-arm64": "11.1.1",
"@next/swc-darwin-x64": "11.1.1",
"@next/swc-linux-x64-gnu": "11.1.1",
"@next/swc-win32-x64-msvc": "11.1.1",
"@node-rs/helper": "1.2.1",
"assert": "2.0.0",
"ast-types": "0.13.2",
@@ -3509,11 +3719,19 @@
"timers-browserify": "2.0.12",
"tty-browserify": "0.0.1",
"use-subscription": "1.5.1",
"util": "0.12.3",
"util": "0.12.4",
"vm-browserify": "1.1.2",
"watchpack": "2.1.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.15.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz",
"integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"postcss": {
"version": "8.2.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz",
@@ -4128,6 +4346,11 @@
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
"dev": true
},
"preact": {
"version": "10.5.14",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz",
"integrity": "sha512-KojoltCrshZ099ksUZ2OQKfbH66uquFoxHSbnwKbTJHeQNvx42EmC7wQVWNuDt6vC5s3nudRHFtKbpY4ijKlaQ=="
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -4557,9 +4780,9 @@
}
},
"remark-html": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/remark-html/-/remark-html-14.0.0.tgz",
"integrity": "sha512-ISQjSlOI3Hb99REjDz0cAhPJVJZDednsj4GNj4Ve7DEZdEXhVPOzBvym0Di+1K3p/RmKXqSw0r02JDmtATh6Dw==",
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/remark-html/-/remark-html-14.0.1.tgz",
"integrity": "sha512-a/x5bTlFrkwYkz43zuJIk0m0IuS5Rx8zLztGwdzmAdUj0Hsi4C4nkJ8gTQRNXY/ET/gMrqQORMMI0arRItq/aQ==",
"dev": true,
"requires": {
"@types/mdast": "^3.0.0",
@@ -5457,9 +5680,9 @@
}
},
"util": {
"version": "0.12.3",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz",
"integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==",
"version": "0.12.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
"integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==",
"requires": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
@@ -5580,16 +5803,16 @@
}
},
"which-typed-array": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.6.tgz",
"integrity": "sha512-DdY984dGD5sQ7Tf+x1CkXzdg85b9uEel6nr4UkFg1LoE9OXv3uRuZhe5CoWdawhGACeFpEZXH8fFLQnDhbpm/Q==",
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz",
"integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==",
"requires": {
"available-typed-arrays": "^1.0.4",
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
"es-abstract": "^1.18.5",
"foreach": "^2.0.5",
"has-tostringtag": "^1.0.0",
"is-typed-array": "^1.1.6"
"is-typed-array": "^1.1.7"
}
},
"word-wrap": {

View File

@@ -3,20 +3,22 @@
"version": "0.1.0",
"private": true,
"scripts": {
"changelog": "node scripts/copy-changelog.js",
"dev": "npm run changelog && next dev",
"build": "npm run changelog && next build",
"build": "npm run changelog && npm run sitemap && next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"changelog": "node scripts/copy-changelog.js",
"sitemap": "node scripts/generate-sitemap.js"
},
"dependencies": {
"@docsearch/js": "^3.0.0-alpha.40",
"@headlessui/react": "^1.4.0",
"@heroicons/react": "^1.0.4",
"date-fns": "^2.23.0",
"gray-matter": "^4.0.3",
"i18next": "^20.4.0",
"i18next-browser-languagedetector": "^6.1.2",
"next": "11.1.0",
"next": "11.1.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-i18next": "^11.11.4",
@@ -33,7 +35,7 @@
"postcss": "^8.3.6",
"postcss-nested": "^5.0.6",
"remark": "14.0.1",
"remark-html": "14.0.0",
"remark-html": "14.0.1",
"tailwindcss": "^2.2.8",
"typescript": "4.4.2"
}

View File

@@ -9,7 +9,7 @@ class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Html lang="en" data-theme="dark">
<Head>
<meta charSet="UTF-8" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />

View File

@@ -16,8 +16,8 @@ export default function Home({ content }: any) {
<OtherMeta image={`/assets/frontmatter-preview.png`} />
<Layout>
<div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:py-24 lg:px-8 divide-y-2 divide-vulcan-200">
<div className="py-8 space-y-2 md:space-y-5 ">
<div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8 divide-y-2 divide-vulcan-200">
<div className="pb-8 space-y-2 md:space-y-5 ">
<h1 className="text-5xl tracking-tight font-extrabold sm:leading-none lg:text-5xl xl:text-6xl">{strings(`changelog_page_title`)}</h1>
<p className="mt-3 text-base text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">{strings(`changelog_page_description`)}</p>

View File

@@ -1,13 +1,13 @@
import type { NextPage } from 'next';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Description, OtherMeta, Title } from '../components/Meta';
import { CTA } from '../components/Page/CTA';
import { Features } from '../components/Page/Features';
import { Generators } from '../components/Page/Generators';
import { Layout } from '../components/Page/Layout';
import { CTA, Features, Generators, Hero, Layout } from '../components/Page';
import { Extension } from '../constants/extension';
const Home: NextPage = () => {
const { t: strings } = useTranslation();
return (
<>
<Title value={Extension.home} />
@@ -19,6 +19,35 @@ const Home: NextPage = () => {
<Generators />
<Hero
view={"left"}
title={strings(`hero_title`)}
description={(
<>
<p className="my-6 text-base text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
{strings(`hero_description`)}
</p>
<p className="my-6 text-base text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
{strings(`hero_description_second`)}
</p>
</>
)}
imgSrc={"/assets/dashboard.png"}
imgAlt={"Front Matter CMS editor dashboard of your static site content"}
link={`/docs/getting-started`}
linkText={strings(`hero_button_primary`)} />
<Hero
view={"right"}
title={strings(`hero_media_title`)}
description={strings(`hero_media_description`)}
imgSrc={"/assets/media.png"}
imgAlt={"Front Matter CMS - media management was never easier in VS Code"}
link={`/docs/dashboard`}
linkText={strings(`hero_media_button_primary`)}
className={`-mt-12 sm:-mt-16`} />
<Features />
</Layout>
</>

View File

@@ -2,7 +2,34 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Description, OtherMeta, Title } from '../../components/Meta';
import { Layout } from '../../components/Page/Layout';
import { Extension } from '../../constants/extension';
import showcases from '../../showcases.json';
import Image from 'next/image';
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#0e131f" offset="20%" />
<stop stop-color="#222733" offset="50%" />
<stop stop-color="#0e131f" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#333" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) =>
typeof window === 'undefined'
? Buffer.from(str).toString('base64')
: window.btoa(str);
const sortTitle = (a: { title: string }, b: { title: string }) => {
if (a.title < b.title) return -1;
if (a.title > b.title) return 1;
return 0;
};
export default function Home({ showcases }: any) {
const { t: strings } = useTranslation();
@@ -14,18 +41,27 @@ export default function Home({ showcases }: any) {
<OtherMeta image={`/assets/frontmatter-preview.png`} />
<Layout>
<div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:py-24 lg:px-8 divide-y-2 divide-vulcan-200">
<div className="py-8 space-y-2 md:space-y-5 ">
<div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8 divide-y-2 divide-vulcan-200">
<div className="pb-8 space-y-2 md:space-y-5 ">
<h1 className="text-5xl tracking-tight font-extrabold sm:leading-none lg:text-5xl xl:text-6xl">{strings(`showcase_title`)}</h1>
<p className="mt-3 text-base text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">{strings(`showcase_description`)}</p>
</div>
<div className={`py-8 grid grid-cols-1 lg:grid-cols-2 gap-8`}>
{showcases.filter((showcase: any) => showcase.image).map((showcase: any) => (
{showcases.filter((showcase: any) => showcase.image).sort(sortTitle).map((showcase: any) => (
<a key={showcase.title} className="group space-y-2 md:space-y-5 relative" href={showcase.link} title={showcase.title} rel={`noopener noreferrer`}>
<figure className={`relative h-64 lg:h-[25rem] overflow-hidden grayscale group-hover:grayscale-0`}>
<img className={`w-full object-cover`} src={`/showcases/${showcase.image}`} alt={showcase.title} loading={`lazy`} />
<Image
className={`w-full object-cover object-left-top`}
src={`/showcases/${showcase.image}`}
alt={showcase.title}
loading={`lazy`}
placeholder="blur"
blurDataURL={`data:image/svg+xml;base64,${toBase64(shimmer(592, 400))}`}
width={592}
height={400}
/>
</figure>
<h2 className="text-3xl tracking-tight font-extrabold sm:leading-none lg:text-3xl xl:text-4xl">{showcase.title}</h2>
@@ -34,6 +70,12 @@ export default function Home({ showcases }: any) {
</a>
))}
</div>
<div className="">
<div className="mt-8 text-sm">
<p>Want to add your site to our showcase? Great, open a showcase on <a className="text-teal-500 hover:text-teal-900" href={Extension.showcaseLink} target="_blank" rel="noopener noreferrer">Github</a>!</p>
</div>
</div>
</div>
</Layout>
</>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

View File

@@ -0,0 +1,65 @@
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');
(async () => {
const baseUrl = `https://frontmatter.codes`;
// Ignore Next.js specific files (e.g., _app.js) and API routes.
let pages = fs
.readdirSync(path.join(__dirname, '../pages'))
.filter((staticPage) => {
return ![
"_app.tsx",
"_document.tsx",
"_error.tsx",
"sitemap.xml.tsx",
"index.tsx",
"404",
"api"
].includes(staticPage);
})
.map((staticPagePath) => ({
lastModified: new Date().toISOString(),
slug: `${baseUrl}/${staticPagePath}`
}));
const mdDir = path.join(process.cwd(), 'content');
const mdFiles = fs.readdirSync(path.join(mdDir, 'docs')).filter(f => f.endsWith(`.md`));
const mdPages = mdFiles.map((fileName) => {
const fullPath = path.join(mdDir, 'docs', `${fileName}`)
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
return {
lastModified: data["lastmod"] || new Date().toISOString(),
slug: `${baseUrl}/docs/${data['slug'] || fileName.split('.').slice(0, -1).join('.')}`
};
});
pages = [...pages, ...mdPages];
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>${baseUrl}</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
${pages
.map((page) => {
return `
<url>
<loc>${`${page.slug}`}</loc>
<lastmod>${page.lastModified}</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
`;
})
.join('')}
</urlset>
`;
fs.writeFileSync('public/sitemap.xml', sitemap);
})();

View File

@@ -0,0 +1,16 @@
{
"index_name": "documentation",
"start_urls": [
"https://frontmatter.codes/docs/"
],
"stop_urls": [],
"selectors": {
"lvl0": ".markdown h1",
"lvl1": ".markdown h2",
"lvl2": ".markdown h3",
"lvl3": ".markdown h4",
"lvl4": ".markdown h5",
"lvl5": ".markdown h6",
"text": ".markdown p, .markdown li, .markdown pre"
}
}

View File

@@ -19,5 +19,19 @@
"description": "Squarl is created with Next.js and Tailwind. Managed by Front Matter.",
"image": "squarl.png",
"generator": "Next.js"
},
{
"title": "M365Princess.com",
"link": "https://m365princess.com",
"description": "Personal website/blog of Luise Freese created with a Hugo template. Managed with Front Matter.",
"image": "m365princess.png",
"generator": "Hugo"
},
{
"title": "MichaelFasani.com",
"link": "https://www.michaelfasani.com",
"description": "Personal blog of Michael Fasani, content stored as markdown in GitHub, built with Gatsby, managed with Front Matter.",
"image": "www.michaelfasani.com.png",
"generator": "Gatsby"
}
]

View File

@@ -2,6 +2,8 @@
@tailwind components;
@tailwind utilities;
@import "@docsearch/css";
html, body {height: 100%;}
.changelog {
@@ -82,4 +84,56 @@ html, body {height: 100%;}
pre {
@apply p-4 my-4 bg-vulcan-200;
}
}
/* DocSearch */
html[data-theme=dark] {
--docsearch-text-color: #f3eff5;
--docsearch-logo-color: #f3eff5;
/* Search button */
--docsearch-searchbox-background: #2c313d;
--docsearch-searchbox-focus-background: #222733;
--docsearch-muted-color: #cbc7cd;
/* Overlap background */
--docsearch-container-background: rgba(54,59,71, 0.8);
/* Modal */
--docsearch-modal-background: #0e131f;
--docsearch-modal-shadow: none;
--docsearch-footer-background: #0e131f;
/* Colors */
--docsearch-primary-color: #15c2cb;
--docsearch-hit-color: #bec3c9;
--docsearch-hit-background: #090a11;
--docsearch-hit-shadow: none;
/* --docsearch-key-gradient: none; */
--docsearch-key-shadow: none;
--docsearch-footer-shadow: none;
}
.DocSearch-Screen-Icon svg {
@apply mx-auto;
}
.DocSearch-Button {
@apply border border-transparent !important;
border-radius: 0;
box-shadow: none !important;
&:hover {
@apply border border-whisper-50 !important;
}
}
.DocSearch-Button-Keys, .DocSearch-Button-Key {
@apply top-auto;
}
.DocSearch--active {
overflow: auto !important;
}

315
package-lock.json generated
View File

@@ -1,9 +1,145 @@
{
"name": "vscode-front-matter",
"version": "3.0.2",
"name": "vscode-front-matter-beta",
"version": "3.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@algolia/autocomplete-core": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.2.2.tgz",
"integrity": "sha512-JOQaURze45qVa8OOFDh+ozj2a/ObSRsVyz6Zd0aiBeej+RSTqrr1hDVpGNbbXYLW26G5ujuc9QIdH+rBHn95nw==",
"requires": {
"@algolia/autocomplete-shared": "1.2.2"
}
},
"@algolia/autocomplete-preset-algolia": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.2.2.tgz",
"integrity": "sha512-AZkh+bAMaJDzMZTelFOXJTJqkp5VPGH8W3n0B+Ggce7DdozlMRsDLguKTCQAkZ0dJ1EbBPyFL5ztL/JImB137Q==",
"requires": {
"@algolia/autocomplete-shared": "1.2.2"
}
},
"@algolia/autocomplete-shared": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.2.2.tgz",
"integrity": "sha512-mLTl7d2C1xVVazHt/bqh9EE/u2lbp5YOxLDdcjILXmUqOs5HH1D4SuySblXaQG1uf28FhTqMGp35qE5wJQnqAw=="
},
"@algolia/cache-browser-local-storage": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.10.5.tgz",
"integrity": "sha512-cfX2rEKOtuuljcGI5DMDHClwZHdDqd2nT2Ohsc8aHtBiz6bUxKVyIqxr2gaC6tU8AgPtrTVBzcxCA+UavXpKww==",
"requires": {
"@algolia/cache-common": "4.10.5"
}
},
"@algolia/cache-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.10.5.tgz",
"integrity": "sha512-1mClwdmTHll+OnHkG+yeRoFM17kSxDs4qXkjf6rNZhoZGXDvfYLy3YcZ1FX4Kyz0DJv8aroq5RYGBDsWkHj6Tw=="
},
"@algolia/cache-in-memory": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.10.5.tgz",
"integrity": "sha512-+ciQnfIGi5wjMk02XhEY8fmy2pzy+oY1nIIfu8LBOglaSipCRAtjk6WhHc7/KIbXPiYzIwuDbM2K1+YOwSGjwA==",
"requires": {
"@algolia/cache-common": "4.10.5"
}
},
"@algolia/client-account": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.10.5.tgz",
"integrity": "sha512-I9UkSS2glXm7RBZYZIALjBMmXSQbw/fI/djPcBHxiwXIheNIlqIFl2SNPkvihpPF979BSkzjqdJNRPhE1vku3Q==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/client-search": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-analytics": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.10.5.tgz",
"integrity": "sha512-h2owwJSkovPxzc+xIsjY1pMl0gj+jdVwP9rcnGjlaTY2fqHbSLrR9yvGyyr6305LvTppxsQnfAbRdE/5Z3eFxw==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/client-search": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.10.5.tgz",
"integrity": "sha512-21FAvIai5qm8DVmZHm2Gp4LssQ/a0nWwMchAx+1hIRj1TX7OcdW6oZDPyZ8asQdvTtK7rStQrRnD8a95SCUnzA==",
"requires": {
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-personalization": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.10.5.tgz",
"integrity": "sha512-nH+IyFKBi8tCyzGOanJTbXC5t4dspSovX3+ABfmwKWUYllYzmiQNFUadpb3qo+MLA3jFx5IwBesjneN6dD5o3w==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-search": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.10.5.tgz",
"integrity": "sha512-1eQFMz9uodrc5OM+9HeT+hHcfR1E1AsgFWXwyJ9Q3xejA2c1c4eObGgOgC9ZoshuHHdptaTN1m3rexqAxXRDBg==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/logger-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.10.5.tgz",
"integrity": "sha512-gRJo9zt1UYP4k3woEmZm4iuEBIQd/FrArIsjzsL/b+ihNoOqIxZKTSuGFU4UUZOEhvmxDReiA4gzvQXG+TMTmA=="
},
"@algolia/logger-console": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.10.5.tgz",
"integrity": "sha512-4WfIbn4253EDU12u9UiYvz+QTvAXDv39mKNg9xSoMCjKE5szcQxfcSczw2byc6pYhahOJ9PmxPBfs1doqsdTKQ==",
"requires": {
"@algolia/logger-common": "4.10.5"
}
},
"@algolia/requester-browser-xhr": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.10.5.tgz",
"integrity": "sha512-53/MURQEqtK+bGdfq4ITSPwTh5hnADU99qzvpAINGQveUFNSFGERipJxHjTJjIrjFz3vxj5kKwjtxDnU6ygO9g==",
"requires": {
"@algolia/requester-common": "4.10.5"
}
},
"@algolia/requester-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.10.5.tgz",
"integrity": "sha512-UkVa1Oyuj6NPiAEt5ZvrbVopEv1m/mKqjs40KLB+dvfZnNcj+9Fry4Oxnt15HMy/HLORXsx4UwcthAvBuOXE9Q=="
},
"@algolia/requester-node-http": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.10.5.tgz",
"integrity": "sha512-aNEKVKXL4fiiC+bS7yJwAHdxln81ieBwY3tsMCtM4zF9f5KwCzY2OtN4WKEZa5AAADVcghSAUdyjs4AcGUlO5w==",
"requires": {
"@algolia/requester-common": "4.10.5"
}
},
"@algolia/transporter": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.10.5.tgz",
"integrity": "sha512-F8DLkmIlvCoMwSCZA3FKHtmdjH3o5clbt0pi2ktFStVNpC6ZDmY307HcK619bKP5xW6h8sVJhcvrLB775D2cyA==",
"requires": {
"@algolia/cache-common": "4.10.5",
"@algolia/logger-common": "4.10.5",
"@algolia/requester-common": "4.10.5"
}
},
"@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
@@ -48,10 +184,44 @@
"lit-element": "^2.5.1"
}
},
"@docsearch/css": {
"version": "3.0.0-alpha.40",
"resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.0.0-alpha.40.tgz",
"integrity": "sha512-PrOTPgJMl+Iji1zOH0+J0PEDMriJ1teGxbgll7o4h8JrvJW6sJGqQw7/bLW7enWiFaxbJMK76w1yyPNLFHV7Qg=="
},
"@docsearch/js": {
"version": "3.0.0-alpha.40",
"resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.0.0-alpha.40.tgz",
"integrity": "sha512-0ysRM0jk1KAbw/QsHPHIKoa7OeCm2Mwz0JgcPnhWRvvA28dzP+f6OIsL6eGu3VJR043tH9OrvVf/FnvLtTtZtw==",
"requires": {
"@docsearch/react": "3.0.0-alpha.40",
"preact": "^10.0.0"
}
},
"@docsearch/react": {
"version": "3.0.0-alpha.40",
"resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.0.0-alpha.40.tgz",
"integrity": "sha512-aKxnu7sgpP1R7jtgOV/pZdJEHXx6Ts+jnS9U/ejSUS2BMUpwQI5SA3oLs1BA5TA9kIViJ5E+rrjh0VsbcsJ6sQ==",
"requires": {
"@algolia/autocomplete-core": "1.2.2",
"@algolia/autocomplete-preset-algolia": "1.2.2",
"@docsearch/css": "3.0.0-alpha.40",
"algoliasearch": "^4.0.0"
}
},
"@estruyf/vscode": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@estruyf/vscode/-/vscode-0.0.2.tgz",
"integrity": "sha512-Qb2UGiARR0Pxeknjzwq925kxyxWMsASIcCOfwOJGZed7EPhDLc6Zd1v6AReYpkQFyxSUjBVg38dT7dn5bh19fQ==",
"dev": true,
"requires": {
"@types/vscode-webview": "1.57.0"
}
},
"@headlessui/react": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.0.tgz",
"integrity": "sha512-C+FmBVF6YGvqcEI5fa2dfVbEaXr2RGR6Kw1E5HXIISIZEfsrH/yuCgsjWw5nlRF9vbCxmQ/EKs64GAdKeb8gCw==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.1.tgz",
"integrity": "sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg==",
"dev": true
},
"@heroicons/react": {
@@ -92,6 +262,15 @@
"fastq": "^1.6.0"
}
},
"@tailwindcss/forms": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.3.3.tgz",
"integrity": "sha512-U8Fi/gq4mSuaLyLtFISwuDYzPB73YzgozjxOIHsK6NXgg/IWD1FLaHbFlWmurAMyy98O+ao74ksdQefsquBV1Q==",
"dev": true,
"requires": {
"mini-svg-data-uri": "^1.2.3"
}
},
"@types/anymatch": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@@ -246,6 +425,12 @@
"integrity": "sha512-C/jZ35OT5k/rsJyAK8mS1kM++vMcm89oSWegkzxRCvHllIq0cToZAkIDs6eCY4SKrvik3nrhELizyLcM0onbQA==",
"dev": true
},
"@types/vscode-webview": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.0.tgz",
"integrity": "sha512-x3Cb/SMa1IwRHfSvKaZDZOTh4cNoG505c3NjTqGlMC082m++x/ETUmtYniDsw6SSmYzZXO8KBNhYxR0+VqymqA==",
"dev": true
},
"@types/webpack": {
"version": "4.41.25",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.25.tgz",
@@ -535,6 +720,27 @@
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
},
"algoliasearch": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.10.5.tgz",
"integrity": "sha512-KmH2XkiN+8FxhND4nWFbQDkIoU6g2OjfeU9kIv4Lb+EiOOs3Gpp7jvd+JnatsCisAZsnWQdjd7zVlW7I/85QvQ==",
"requires": {
"@algolia/cache-browser-local-storage": "4.10.5",
"@algolia/cache-common": "4.10.5",
"@algolia/cache-in-memory": "4.10.5",
"@algolia/client-account": "4.10.5",
"@algolia/client-analytics": "4.10.5",
"@algolia/client-common": "4.10.5",
"@algolia/client-personalization": "4.10.5",
"@algolia/client-search": "4.10.5",
"@algolia/logger-common": "4.10.5",
"@algolia/logger-console": "4.10.5",
"@algolia/requester-browser-xhr": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/requester-node-http": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@@ -666,6 +872,12 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"dev": true
},
"attr-accept": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
"dev": true
},
"autoprefixer": {
"version": "10.3.2",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.3.2.tgz",
@@ -979,6 +1191,17 @@
"ssri": "^6.0.1",
"unique-filename": "^1.1.1",
"y18n": "^4.0.0"
},
"dependencies": {
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
}
}
},
"cache-base": {
@@ -1271,6 +1494,17 @@
"mkdirp": "^0.5.1",
"rimraf": "^2.5.4",
"run-queue": "^1.0.0"
},
"dependencies": {
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
}
}
},
"copy-descriptor": {
@@ -2098,6 +2332,23 @@
"integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
"dev": true
},
"file-selector": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz",
"integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==",
"dev": true,
"requires": {
"tslib": "^2.0.3"
},
"dependencies": {
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
}
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -2456,6 +2707,12 @@
"strip-bom-string": "^1.0.0"
}
},
"hamt_plus": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
"integrity": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE=",
"dev": true
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -3615,6 +3872,12 @@
}
}
},
"mini-svg-data-uri": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.3.3.tgz",
"integrity": "sha512-+fA2oRcR1dJI/7ITmeQJDrYWks0wodlOz0pAEhKYJ2IVc1z0AnwJUsKY2fzFmPAM3Jo9J0rBx8JAA9QQSJ5PuA==",
"dev": true
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -3716,6 +3979,17 @@
"mkdirp": "^0.5.1",
"rimraf": "^2.5.4",
"run-queue": "^1.0.3"
},
"dependencies": {
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
}
}
},
"ms": {
@@ -4344,6 +4618,11 @@
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
"dev": true
},
"preact": {
"version": "10.5.14",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz",
"integrity": "sha512-KojoltCrshZ099ksUZ2OQKfbH66uquFoxHSbnwKbTJHeQNvx42EmC7wQVWNuDt6vC5s3nudRHFtKbpY4ijKlaQ=="
},
"pretty-error": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz",
@@ -4540,6 +4819,17 @@
"scheduler": "^0.20.1"
}
},
"react-dropzone": {
"version": "11.3.4",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.3.4.tgz",
"integrity": "sha512-B1nzNRZ4F1cnrfEC0T6KXeBN1mCPinu4JCoTrp7NjB+442KSPxqfDrw41QIA2kAwlYs1+wj/0BTedeM5hc2+xw==",
"dev": true,
"requires": {
"attr-accept": "^2.2.1",
"file-selector": "^0.2.2",
"prop-types": "^15.7.2"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -4571,6 +4861,15 @@
"picomatch": "^2.2.1"
}
},
"recoil": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.4.1.tgz",
"integrity": "sha512-vp6KPwlHOjJ4bJofmdDchmgI9ilMTCoUisK8/WYLl8dThH7e7KmtZttiLgvDb2Em99dUfTEsk8vT8L1nUMgqXQ==",
"dev": true,
"requires": {
"hamt_plus": "1.0.2"
}
},
"reduce-css-calc": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
@@ -4760,9 +5059,9 @@
"dev": true
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"

View File

@@ -1,9 +1,9 @@
{
"name": "vscode-front-matter",
"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...",
"icon": "assets/frontmatter-teal-128x128.png",
"version": "3.0.2",
"version": "3.1.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -35,6 +35,11 @@
"Taxonomy"
],
"license": "MIT",
"author": "Elio Struyf <elio@struyfconsulting.be> (https://www.eliostruyf.com)",
"homepage": "https://frontmatter.codes",
"bugs": {
"url": "https://github.com/estruyf/vscode-front-matter/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/estruyf/vscode-front-matter"
@@ -88,27 +93,27 @@
"frontMatter.content.autoUpdateDate": {
"type": "boolean",
"default": false,
"description": "Specify if you want to automatically update the modified date of your article/page."
"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)"
},
"frontMatter.content.fmHighlight": {
"type": "boolean",
"default": true,
"description": "Specify if you want to highlight the Front Matter in the Markdown file."
"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)"
},
"frontMatter.content.folders": {
"frontMatter.content.pageFolders": {
"type": "array",
"default": [],
"markdownDescription": "This array of folders defines where the extension can easily create new content by running the create article command."
"markdownDescription": "This array of folders defines where the extension can retrieve or create new pages. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.pagefolders)"
},
"frontMatter.content.publicFolder": {
"type": "string",
"default": "",
"markdownDescription": "Specify the folder name where all your assets are located. For instance in Hugo this is the `static` folder."
"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)"
},
"frontMatter.custom.scripts": {
"type": "array",
"default": [],
"markdownDescription": "Specify the path to a Node.js script to execute. The current file path will be provided as an argument."
"markdownDescription": "Specify the path to a Node.js script to execute. The current file path will be provided as an argument. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.custom.scripts)"
},
"frontMatter.dashboard.openOnStart": {
"type": [
@@ -116,67 +121,67 @@
"null"
],
"default": null,
"description": "Specify if you want to open the dashboard when you start VS Code."
"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)"
},
"frontMatter.panel.freeform": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true."
"markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.panel.freeform)"
},
"frontMatter.preview.host": {
"type": "string",
"default": "",
"markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview."
"markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.preview.host)"
},
"frontMatter.preview.pathName": {
"type": "string",
"default": "",
"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."
"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)"
},
"frontMatter.taxonomy.dateField": {
"type": "string",
"default": "date",
"description": "Specifies the date field name to use in your Front Matter"
"markdownDescription": "Specifies the date field name to use in your Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.datefield)"
},
"frontMatter.taxonomy.modifiedField": {
"type": "string",
"default": "lastmod",
"description": "Specifies the modified date field name to use in your Front Matter"
"markdownDescription": "Specifies the modified date field name to use in your Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.modifiedfield)"
},
"frontMatter.taxonomy.tags": {
"type": "array",
"description": "Specifies the tags which can be used in the Front Matter"
"markdownDescription": "Specifies the tags which can be used in the Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.tags)"
},
"frontMatter.taxonomy.categories": {
"type": "array",
"description": "Specifies the categories which can be used in the Front Matter"
"markdownDescription": "Specifies the categories which can be used in the Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.categories)"
},
"frontMatter.taxonomy.dateFormat": {
"type": "string",
"markdownDescription": "Specify the date format for your articles. Check [date-fns formating](https://date-fns.org/v2.0.1/docs/format) for more information."
"markdownDescription": "Specify the date format for your articles. Check [date-fns formating](https://date-fns.org/v2.0.1/docs/format) for more information. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.dateformat)"
},
"frontMatter.taxonomy.slugPrefix": {
"type": "string",
"markdownDescription": "Specify a prefix for the slug"
"markdownDescription": "Specify a prefix for the slug. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.slugprefix)"
},
"frontMatter.taxonomy.slugSuffix": {
"type": "string",
"markdownDescription": "Specify a suffix for the slug"
"markdownDescription": "Specify a suffix for the slug. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.slugsuffix)"
},
"frontMatter.taxonomy.alignFilename": {
"type": "boolean",
"default": false,
"markdownDescription": "Align the filename with the new slug when it gets generated."
"markdownDescription": "Align the filename with the new slug when it gets generated. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.alignfilename)"
},
"frontMatter.taxonomy.indentArrays": {
"type": "boolean",
"default": true,
"markdownDescription": "Specify if arrays in front matter are indented. Default: true."
"markdownDescription": "Specify if arrays in front matter are indented. Default: true. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.indentarrays)"
},
"frontMatter.taxonomy.noPropertyValueQuotes": {
"type": "array",
"default": [],
"markdownDescription": "Specify the properties from which quotes need to be removed."
"markdownDescription": "Specify the properties from which quotes need to be removed. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.nopropertyvaluequotes)"
},
"frontMatter.taxonomy.frontMatterType": {
"type": "string",
@@ -185,6 +190,7 @@
"YAML",
"TOML"
],
"markdownDescription": "Specify the type of Front Matter to use. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.frontmattertype)",
"enumDescriptions": [
"Specifies you want to use YAML markup for the front matter (default)",
"Specifies you want to use TOML markup for the front matter"
@@ -193,32 +199,32 @@
"frontMatter.taxonomy.seoTitleLength": {
"type": "number",
"default": 60,
"description": "Specifies the optimal title length for SEO (set to `-1` to turn it off)."
"markdownDescription": "Specifies the optimal title length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seotitlelength)"
},
"frontMatter.taxonomy.seoDescriptionLength": {
"type": "number",
"default": 160,
"description": "Specifies the optimal description length for SEO (set to `-1` to turn it off)."
"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)"
},
"frontMatter.taxonomy.seoContentLengh": {
"type": "number",
"default": 1760,
"description": "Specifies the optimal minimum length for your articles. Between 1,760 words 2,400 is the absolute ideal article length for SEO in 2021. (set to `-1` to turn it off)."
"markdownDescription": "Specifies the optimal minimum length for your articles. Between 1,760 words 2,400 is the absolute ideal article length for SEO in 2021. (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seocontentlengh)"
},
"frontMatter.taxonomy.seoDescriptionField": {
"type": "string",
"default": "description",
"description": "Specifies the name of the SEO description field for your page. Default is 'description'."
"markdownDescription": "Specifies the name of the SEO description field for your page. Default is 'description'. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seodescriptionfield)"
},
"frontMatter.templates.folder": {
"type": "string",
"default": ".templates",
"description": "Specify the folder to use for your article templates."
"markdownDescription": "Specify the folder to use for your article templates. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.templates.folder)"
},
"frontMatter.templates.prefix": {
"type": "string",
"default": "yyyy-MM-dd",
"description": "Specify the prefix you want to add for your new article filenames."
"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)"
}
}
},
@@ -329,7 +335,7 @@
},
{
"command": "frontMatter.unregisterFolder",
"when": "explorerResourceIsFolder && resourcePath in frontMatter.registeredFolders",
"when": "explorerResourceIsFolder",
"group": "Front Matter@3"
}
],
@@ -382,14 +388,19 @@
},
"scripts": {
"vscode:prepublish": "npm run clean && webpack --mode production",
"build:ext": "webpack --mode development",
"dev:ext": "webpack --mode development --watch",
"build:ext": "npm run clean && webpack --mode development",
"dev:ext": "npm run clean && webpack --mode development --watch",
"test-compile": "tsc -p ./",
"clean": "rm -rf dist"
"clean": "rimraf dist",
"start:site": "cd ./docs && npm run dev"
},
"devDependencies": {
"@bendera/vscode-webview-elements": "0.6.2",
"@estruyf/vscode": "0.0.2",
"@headlessui/react": "^1.4.1",
"@heroicons/react": "1.0.4",
"@iarna/toml": "2.2.3",
"@tailwindcss/forms": "^0.3.3",
"@types/glob": "7.1.3",
"@types/js-yaml": "3.12.1",
"@types/lodash.uniqby": "4.7.6",
@@ -408,11 +419,15 @@
"gray-matter": "4.0.2",
"html-loader": "1.3.2",
"html-webpack-plugin": "4.5.0",
"lodash.uniqby": "4.7.0",
"mdast-util-from-markdown": "1.0.0",
"postcss": "^8.3.6",
"postcss-loader": "4.3.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-dropzone": "^11.3.4",
"recoil": "^0.4.1",
"rimraf": "^3.0.2",
"style-loader": "2.0.0",
"tailwindcss": "^2.2.7",
"ts-loader": "8.0.3",
@@ -420,10 +435,9 @@
"typescript": "4.0.2",
"wc-react": "github:estruyf/wc-react",
"webpack": "4.44.2",
"webpack-cli": "3.3.12",
"@headlessui/react": "1.4.0",
"@heroicons/react": "1.0.4",
"lodash.uniqby": "4.7.0"
"webpack-cli": "3.3.12"
},
"dependencies": {}
"dependencies": {
"@docsearch/js": "^3.0.0-alpha.40"
}
}

20
scripts/beta-release.js Normal file
View File

@@ -0,0 +1,20 @@
const fs = require('fs');
const path = require('path');
const packageJson = require('../package.json');
const version = packageJson.version.split('.');
packageJson.version = `${version[0]}.${version[1]}.${process.argv[process.argv.length-1].substr(0, 7)}`;
packageJson.preview = true;
packageJson.name = "vscode-front-matter-beta";
packageJson.displayName = `${packageJson.displayName} BETA`;
packageJson.description = `BETA Version of Front Matter. ${packageJson.description}`;
packageJson.icon = "assets/frontmatter-beta.png";
packageJson.homepage = "https://beta.frontmatter.codes";
console.log(packageJson.version);
fs.writeFileSync(path.join(path.resolve('.'), 'package.json'), JSON.stringify(packageJson, null, 2));
let readme = fs.readFileSync(path.join(__dirname, '../README.beta.md'), 'utf8');
fs.writeFileSync(path.join(__dirname, '../README.md'), readme);

7
scripts/main-release.js Normal file
View File

@@ -0,0 +1,7 @@
const fs = require('fs');
const path = require('path');
const packageJson = require('../package.json');
packageJson.name = "vscode-front-matter";
fs.writeFileSync(path.join(path.resolve('.'), 'package.json'), JSON.stringify(packageJson, null, 2));

View File

@@ -1,25 +1,32 @@
import { SETTINGS_CONTENT_STATIC_FOLDERS, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART } from './../constants/settings';
import { ArticleHelper } from './../helpers/ArticleHelper';
import { join } from "path";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace } from "vscode";
import { basename, dirname, extname, join } from "path";
import { existsSync, statSync, unlinkSync, writeFileSync } from "fs";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env } from "vscode";
import { SettingsHelper } from '../helpers';
import { TaxonomyType } from '../models';
import { Folders } from './Folders';
import { getNonce } from '../helpers/getNonce';
import { DashboardCommand } from '../pagesView/DashboardCommand';
import { DashboardMessage } from '../pagesView/DashboardMessage';
import { Page } from '../pagesView/models/Page';
import { openFileInEditor } from '../helpers/openFileInEditor';
import { COMMAND_NAME } from '../constants/Extension';
import { COMMAND_NAME, EXTENSION_STATE_PAGES_VIEW } from '../constants/Extension';
import { Template } from './Template';
import { Notifications } from '../helpers/Notifications';
import { Settings } from '../pagesView/models/Settings';
import { Extension } from '../helpers/Extension';
import { parseJSON } from 'date-fns';
import { ViewType } from '../pagesView/state';
import { WebviewHelper } from '@estruyf/vscode';
import { MediaInfo, MediaPaths } from './../models/MediaPaths';
import { decodeBase64Image } from '../helpers/decodeBase64Image';
export class Dashboard {
private static webview: WebviewPanel | null = null;
private static isDisposed: boolean = true;
private static media: MediaInfo[] = [];
private static timers: { [folder: string]: any } = {};
/** 
* Init the dashboard
@@ -113,14 +120,35 @@ export class Dashboard {
case DashboardMessage.updateSetting:
Dashboard.updateSetting(msg.data);
break;
case DashboardMessage.InitializeProject:
case DashboardMessage.initializeProject:
await commands.executeCommand(COMMAND_NAME.init, Dashboard.getSettings);
break;
case DashboardMessage.Reload:
Dashboard.webview?.dispose();
setTimeout(() => {
Dashboard.open();
}, 100);
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;
}
});
@@ -130,15 +158,23 @@ export class Dashboard {
* Retrieve the settings for the dashboard
*/
private static async getSettings() {
const ext = Extension.getInstance();
const config = SettingsHelper.getConfig();
const wsFolder = Folders.getWorkspaceFolder();
Dashboard.postWebviewMessage({
command: DashboardCommand.settings,
data: {
beta: ext.isBetaVersion(),
wsFolder: wsFolder ? wsFolder.fsPath : '',
staticFolder: config.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.getConfig().get(SETTINGS_DASHBOARD_OPENONSTART),
versionInfo: Extension.getInstance().getVersion()
openOnStart: config.get(SETTINGS_DASHBOARD_OPENONSTART),
versionInfo: ext.getVersion(),
pageViewType: await ext.getState<ViewType | undefined>(EXTENSION_STATE_PAGES_VIEW)
} as Settings
});
}
@@ -151,13 +187,98 @@ export class Dashboard {
Dashboard.getSettings();
}
/**
* Retrieve all media files
*/
private static async getMedia(page: number = 0, folder: string = '') {
const wsFolder = Folders.getWorkspaceFolder();
const config = SettingsHelper.getConfig();
const staticFolder = config.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 config = SettingsHelper.getConfig();
const wsFolders = workspace.workspaceFolders;
const crntWsFolder = wsFolders && wsFolders.length > 0 ? wsFolders[0] : null;
const wsFolder = Folders.getWorkspaceFolder();
const descriptionField = config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description";
const dateField = config.get(SETTING_DATE_FIELD) as string || "date";
@@ -177,10 +298,12 @@ export class Dashboard {
const page: Page = {
...article.data,
// FrontMatter properties
fmGroup: folder.title,
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,
@@ -189,16 +312,28 @@ export class Dashboard {
description: article?.data[descriptionField] || "",
};
if (article?.data.preview && crntWsFolder) {
const previewPath = join(crntWsFolder.uri.fsPath, staticFolder || "", article?.data.preview);
const previewUri = Uri.file(previewPath);
const preview = Dashboard.webview?.webview.asWebviewUri(previewUri);
page.preview = preview?.toString() || "";
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) {
} catch (error: any) {
Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`);
}
}
@@ -212,6 +347,84 @@ export class Dashboard {
});
}
/**
* 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 config = SettingsHelper.getConfig();
const staticFolder = config.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)}`);
}
}
/**
* Post data to the dashboard
* @param msg
@@ -227,7 +440,7 @@ export class Dashboard {
private static getWebviewContent(webView: Webview, extensionPath: Uri): string {
const scriptUri = webView.asWebviewUri(Uri.joinPath(extensionPath, 'dist', 'pages.js'));
const nonce = getNonce();
const nonce = WebviewHelper.getNonce();
const version = Extension.getInstance().getVersion();
@@ -243,7 +456,7 @@ export class Dashboard {
<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>
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759" alt="Daily usage" />
<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>
</body>

View File

@@ -1,5 +1,5 @@
import { SETTINGS_CONTENT_PAGE_FOLDERS } from './../constants/settings';
import { commands, Uri, workspace, window } from "vscode";
import { SETTINGS_CONTENT_FOLDERS } from "../constants";
import { basename, join } from "path";
import { ContentFolder, FileInfo, FolderInfo } from "../models";
import uniqBy = require("lodash.uniqby");
@@ -8,6 +8,8 @@ import { Notifications } from "../helpers/Notifications";
import { CONTEXT } from "../constants/context";
import { SettingsHelper } from "../helpers";
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
export class Folders {
/**
@@ -38,7 +40,7 @@ export class Folders {
const location = folders.find(f => f.title === selectedFolder);
if (location) {
const folderPath = Folders.getFolderPath(Uri.file(location.fsPath));
const folderPath = Folders.getFolderPath(Uri.file(location.path));
if (folderPath) {
Template.create(folderPath);
}
@@ -55,7 +57,7 @@ export class Folders {
let folders = Folders.get();
const exists = folders.find(f => f.paths.includes(folder.fsPath) || f.paths.includes(wslPath));
const exists = folders.find(f => f.path.includes(folder.fsPath) || f.path.includes(wslPath));
if (exists) {
Notifications.warning(`Folder is already registered`);
@@ -70,17 +72,14 @@ export class Folders {
folders.push({
title: folderName,
fsPath: folder.fsPath,
paths: folder.fsPath === wslPath ? [folder.fsPath] : [folder.fsPath, wslPath]
path: folder.fsPath
} as ContentFolder);
folders = uniqBy(folders, f => f.fsPath);
folders = uniqBy(folders, f => f.path);
await Folders.update(folders);
Notifications.info(`Folder registered`);
}
Folders.updateVsCodeCtx();
}
/**
@@ -90,23 +89,9 @@ export class Folders {
public static async unregister(folder: Uri) {
if (folder && folder.path) {
let folders = Folders.get();
folders = folders.filter(f => f.fsPath !== folder.fsPath);
folders = folders.filter(f => f.path !== folder.fsPath);
await Folders.update(folders);
}
Folders.updateVsCodeCtx();
}
/**
* Update the registered folders context
*/
public static updateVsCodeCtx() {
const folders = Folders.get();
let allFolders: string[] = [];
for (const folder of folders) {
allFolders = [...allFolders, ...folder.paths]
}
commands.executeCommand('setContext', CONTEXT.registeredFolders, allFolders);
}
/**
@@ -159,7 +144,7 @@ export class Folders {
for (const folder of folders) {
try {
const projectName = Folders.getProjectFolderName();
let projectStart = folder.fsPath.split(projectName).pop();
let projectStart = folder.path.split(projectName).pop();
if (projectStart) {
projectStart = projectStart.replace(/\\/g, '/');
projectStart = projectStart.startsWith('/') ? projectStart.substr(1) : projectStart;
@@ -211,10 +196,15 @@ export class Folders {
* Get the folder settings
* @returns
*/
public static get() {
public static get(): ContentFolder[] {
const config = SettingsHelper.getConfig();
const folders: ContentFolder[] = config.get(SETTINGS_CONTENT_FOLDERS) as ContentFolder[];
return folders;
const wsFolder = Folders.getWorkspaceFolder();
const folders: ContentFolder[] = config.get(SETTINGS_CONTENT_PAGE_FOLDERS) as ContentFolder[];
return folders.map(folder => ({
title: folder.title,
path: Folders.absWsFolder(folder, wsFolder)
}));
}
/**
@@ -223,6 +213,33 @@ export class Folders {
*/
private static async update(folders: ContentFolder[]) {
const config = SettingsHelper.getConfig();
await config.update(SETTINGS_CONTENT_FOLDERS, folders);
const wsFolder = Folders.getWorkspaceFolder();
await config.update(SETTINGS_CONTENT_PAGE_FOLDERS, folders.map(folder => ({ title: folder.title, path: Folders.relWsFolder(folder, wsFolder) })));
}
/**
* Generate the absolute URL for the workspace
* @param folder
* @param wsFolder
* @returns
*/
private static absWsFolder(folder: ContentFolder, wsFolder?: Uri) {
const isWindows = process.platform === 'win32';
let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, wsFolder?.fsPath || "");
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
return absPath;
}
/**
* Generate relative folder path
* @param folder
* @param wsFolder
* @returns
*/
private static relWsFolder(folder: ContentFolder, wsFolder?: Uri) {
const isWindows = process.platform === 'win32';
let absPath = folder.path.replace(wsFolder?.fsPath || "", WORKSPACE_PLACEHOLDER);
absPath = isWindows ? absPath.split('\\').join('/') : absPath;
return absPath;
}
}

View File

@@ -4,6 +4,7 @@ import { join } from "path";
import * as fs from "fs";
import { Notifications } from "../helpers/Notifications";
import { Template } from "./Template";
import { Folders } from "./Folders";
export class Project {
@@ -52,14 +53,13 @@ categories: []
*/
public static templatePath() {
const folder = Template.getSettings();
const workspaceFolders = workspace.workspaceFolders;
const wsFolder = Folders.getWorkspaceFolder();
if (!folder || !workspaceFolders || workspaceFolders.length === 0) {
if (!folder || !wsFolder) {
return null;
}
const workspaceFolder = workspaceFolders[0];
const templatePath = Uri.file(join(workspaceFolder.uri.fsPath, folder));
const templatePath = Uri.file(join(wsFolder.fsPath, folder));
return templatePath;
}
}

View File

@@ -9,6 +9,7 @@ import { Article } from '.';
import { Notifications } from '../helpers/Notifications';
import { CONTEXT } from '../constants/context';
import { Project } from './Project';
import { Folders } from './Folders';
export class Template {
@@ -24,15 +25,14 @@ export class Template {
* Check if the project is already initialized
*/
public static async isInitialized() {
const workspaceFolders = vscode.workspace.workspaceFolders;
const wsFolder = Folders.getWorkspaceFolder();
const folder = Template.getSettings();
if (!folder || !workspaceFolders || workspaceFolders.length === 0) {
if (!folder || !wsFolder) {
return false;
}
const workspaceFolder = workspaceFolders[0];
const templatePath = vscode.Uri.file(path.join(workspaceFolder.uri.fsPath, folder));
const templatePath = vscode.Uri.file(path.join(wsFolder.fsPath, folder));
try {
await vscode.workspace.fs.stat(templatePath);

View File

@@ -1,7 +1,10 @@
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}`;

View File

@@ -1,6 +1,5 @@
export const CONTEXT = {
canInit: "frontMatterCanInit",
canOpenPreview: "frontMatterCanOpenPreview",
canOpenDashboard: "frontMatterCanOpenDashboard",
registeredFolders: 'frontMatter.registeredFolders'
canOpenDashboard: "frontMatterCanOpenDashboard"
};

View File

@@ -33,8 +33,13 @@ export const SETTING_PREVIEW_PATHNAME = "preview.pathName";
export const SETTING_CUSTOM_SCRIPTS = "custom.scripts";
export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate";
export const SETTINGS_CONTENT_FOLDERS = "content.folders";
export const SETTINGS_CONTENT_PAGE_FOLDERS = "content.pageFolders";
export const SETTINGS_CONTENT_STATIC_FOLDERS = "content.publicFolder";
export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
/**
* @deprecated
*/
export const SETTINGS_CONTENT_FOLDERS = "content.folders";

View File

@@ -5,12 +5,14 @@ import { Folders } from './commands/Folders';
import { Preview } from './commands/Preview';
import { Project } from './commands/Project';
import { Template } from './commands/Template';
import { COMMAND_NAME } from './constants/Extension';
import { COMMAND_NAME, EXTENSION_BETA_ID, EXTENSION_ID } from './constants/Extension';
import { TaxonomyType } from './models';
import { MarkdownFoldingProvider } from './providers/MarkdownFoldingProvider';
import { TagType } from './viewpanel/TagType';
import { ExplorerView } from './webview/ExplorerView';
import { Extension } from './helpers/Extension';
import { basename } from 'path';
import { Notifications } from './helpers/Notifications';
let frontMatterStatusBar: vscode.StatusBarItem;
let statusDebouncer: { (fnc: any, time: number): void; };
@@ -19,8 +21,16 @@ let collection: vscode.DiagnosticCollection;
const mdSelector: vscode.DocumentSelector = { language: 'markdown', scheme: 'file' };
export async function activate({ subscriptions, extensionUri, extensionPath, globalState }: vscode.ExtensionContext) {
const extension = Extension.getInstance(globalState, extensionUri);
export async function activate(context: vscode.ExtensionContext) {
const { subscriptions, extensionUri, extensionPath } = context;
const extension = Extension.getInstance(context);
if (!extension.checkIfExtensionCanRun()) {
return undefined;
}
extension.migrateSettings()
collection = vscode.languages.createDiagnosticCollection('frontMatter');
@@ -97,8 +107,6 @@ export async function activate({ subscriptions, extensionUri, extensionPath, glo
const createContent = vscode.commands.registerCommand(COMMAND_NAME.createContent, Folders.create);
Folders.updateVsCodeCtx();
// Initialize command
Template.init();
const projectInit = vscode.commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
@@ -118,7 +126,6 @@ export async function activate({ subscriptions, extensionUri, extensionPath, glo
vscode.workspace.onDidChangeConfiguration(() => {
Template.init();
Preview.init();
Folders.updateVsCodeCtx();
const exView = ExplorerView.getInstance();
exView.getSettings();

View File

@@ -1,19 +1,24 @@
import { Memento, extensions, Uri } from "vscode";
import { EXTENSION_ID, EXTENSION_STATE_VERSION } from "../constants/Extension";
import { basename } from "path";
import { extensions, Uri, ExtensionContext } from "vscode";
import { Folders, WORKSPACE_PLACEHOLDER } from "../commands/Folders";
import { SETTINGS_CONTENT_FOLDERS, SETTINGS_CONTENT_PAGE_FOLDERS } from "../constants";
import { EXTENSION_BETA_ID, EXTENSION_ID, EXTENSION_STATE_VERSION } from "../constants/Extension";
import { Notifications } from "./Notifications";
import { SettingsHelper } from "./SettingsHelper";
export class Extension {
private static instance: Extension;
private constructor(private globalState: Memento, private extPath: Uri) {}
private constructor(private ctx: ExtensionContext) {}
/**
* Creates the singleton instance for the panel
* @param extPath
*/
public static getInstance(globalState?: Memento, extPath?: Uri): Extension {
if (!Extension.instance && globalState && extPath) {
Extension.instance = new Extension(globalState, extPath);
public static getInstance(ctx?: ExtensionContext): Extension {
if (!Extension.instance && ctx) {
Extension.instance = new Extension(ctx);
}
return Extension.instance;
@@ -23,9 +28,13 @@ export class Extension {
* Get the current version information for the extension
*/
public getVersion(): { usedVersion: string | undefined, installedVersion: string } {
const frontMatter = extensions.getExtension(EXTENSION_ID)!;
const installedVersion = frontMatter.packageJSON.version;
const usedVersion = this.globalState.get<string>(EXTENSION_STATE_VERSION);
const frontMatter = extensions.getExtension(this.isBetaVersion() ? EXTENSION_BETA_ID : EXTENSION_ID)!;
let installedVersion = frontMatter.packageJSON.version;
const usedVersion = this.ctx.globalState.get<string>(EXTENSION_STATE_VERSION);
if (this.isBetaVersion()) {
installedVersion = `${installedVersion}-beta`;
}
if (!usedVersion) {
this.setVersion(installedVersion);
@@ -41,13 +50,57 @@ export class Extension {
* Set the current version information for the extension
*/
public setVersion(installedVersion: string): void {
this.globalState.update(EXTENSION_STATE_VERSION, installedVersion);
this.ctx.globalState.update(EXTENSION_STATE_VERSION, installedVersion);
}
/**
* Get the path to the extension
*/
public get extensionPath(): Uri {
return this.extPath;
return this.ctx.extensionUri;
}
/**
* Migrate old settings to new settings
*/
public async migrateSettings(): Promise<void> {
const config = SettingsHelper.getConfig();
const folders = config.get<any>(SETTINGS_CONTENT_FOLDERS);
if (folders && folders.length > 0) {
const workspace = Folders.getWorkspaceFolder();
const projectFolder = basename(workspace?.fsPath || "");
const paths = folders.map((folder: any) => ({
title: folder.title,
path: `${WORKSPACE_PLACEHOLDER}${folder.fsPath.split(projectFolder).slice(1).join('')}`.split('\\').join('/')
}));
await config.update(`${SETTINGS_CONTENT_PAGE_FOLDERS}`, paths);
}
}
public async setState(propKey: string, propValue: string): Promise<void> {
await this.ctx.globalState.update(propKey, propValue);
}
public async getState<T>(propKey: string): Promise<T | undefined> {
return await this.ctx.globalState.get(propKey);
}
public isBetaVersion() {
return basename(this.ctx.globalStorageUri.fsPath) === EXTENSION_BETA_ID;
}
public checkIfExtensionCanRun() {
if (this.isBetaVersion()) {
const mainVersionInstalled = extensions.getExtension(EXTENSION_ID);
if (mainVersionInstalled) {
Notifications.error(`Front Matter BETA cannot be used while the main version is installed. Please ensure that you have only over version installed.`);
return false;
}
}
return true;
}
}

6
src/helpers/GroupBy.ts Normal file
View File

@@ -0,0 +1,6 @@
export const groupBy = (array: any[], key: string) => {
return array.reduce((result, currentValue) => {
(result[currentValue[key]] = result[currentValue[key]] || []).push(currentValue);
return result;
}, {});
};

View File

@@ -0,0 +1,13 @@
export const decodeBase64Image = (dataString: string) => {
const matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
let response: any = {};
if (matches?.length !== 3) {
return null;
}
response.type = matches[1];
response.data = Buffer.from(matches[2], 'base64');
return response;
}

View File

@@ -1,5 +1,4 @@
export interface ContentFolder {
title: string;
fsPath: string;
paths: string[];
path: string;
}

13
src/models/MediaPaths.ts Normal file
View File

@@ -0,0 +1,13 @@
import { Stats } from "fs";
export interface MediaPaths {
media: MediaInfo[];
total: number;
folders: string[];
}
export interface MediaInfo {
fsPath: string;
vsPath: string | undefined;
stats: Stats | undefined;
}

View File

@@ -1,3 +1,4 @@
export * from './ContentFolder';
export * from './PanelSettings';
export * from './TaxonomyType';
export * from './VersionInfo';

View File

@@ -1,5 +1,6 @@
export enum DashboardCommand {
loading = "loading",
pages = "pages",
settings = "settings"
settings = "settings",
media = "media",
}

View File

@@ -4,6 +4,12 @@ export enum DashboardMessage {
getTheme = 'getTheme',
createContent = 'createContent',
updateSetting = 'updateSetting',
InitializeProject = 'InitializeProject',
Reload = 'Reload',
initializeProject = 'initializeProject',
reload = 'reload',
setPageViewType = 'setPageViewType',
getMedia = 'getMedia',
copyToClipboard = 'copyToClipboard',
refreshMedia = 'refreshMedia',
uploadMedia = 'uploadMedia',
deleteMedia = 'deleteMedia',
}

View File

@@ -0,0 +1,36 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { Page } from '../../models';
import { SettingsSelector } from '../../state';
import { Header } from '../Header';
import { Overview } from './Overview';
import { Spinner } from '../Spinner';
import { SponsorMsg } from '../SponsorMsg';
export interface IContentsProps {
pages: Page[];
loading: boolean;
}
export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loading}: React.PropsWithChildren<IContentsProps>) => {
const settings = useRecoilValue(SettingsSelector);
const pageFolders = [...new Set(pages.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="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>
</main>
);
};

View File

@@ -0,0 +1,70 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { MarkdownIcon } from '../../../viewpanel/components/Icons/MarkdownIcon';
import { DashboardMessage } from '../../DashboardMessage';
import { Page } from '../../models/Page';
import { ViewSelector, ViewType } from '../../state';
import { DateField } from '../DateField';
import { Status } from '../Status';
import { Messenger } from '@estruyf/vscode/dist/client';
export interface IItemProps extends Page {}
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, draft, description, preview }: React.PropsWithChildren<IItemProps>) => {
const view = useRecoilValue(ViewSelector);
const openFile = () => {
Messenger.send(DashboardMessage.openFile, fmFilePath);
};
if (view === ViewType.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`}
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" />
) : (
<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`} />
</div>
)
}
</div>
<div className="p-4 w-full">
<div className={`flex justify-between items-center`}>
<Status draft={!!draft} />
<DateField value={date} />
</div>
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{description}</p>
</div>
</button>
</li>
);
} else if (view === ViewType.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}>
<div className="col-span-8 font-bold truncate">
{title}
</div>
<div className="col-span-2">
<DateField value={date} />
</div>
<div className="col-span-2">
<Status draft={!!draft} />
</div>
</button>
</li>
);
}
return null;
};

View File

@@ -0,0 +1,37 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { ViewSelector, ViewType } from '../../state';
export interface IListProps {}
export const List: React.FunctionComponent<IListProps> = ({children}: React.PropsWithChildren<IListProps>) => {
const view = useRecoilValue(ViewSelector);
let className = '';
if (view === ViewType.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) {
className = `-mx-4`;
}
return (
<ul role="list" className={className}>
{view === ViewType.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">
Title
</div>
<div className="col-span-2">
Date
</div>
<div className="col-span-2">
Status
</div>
</div>
</li>
)}
{children}
</ul>
);
};

View File

@@ -0,0 +1,88 @@
import { Disclosure } from '@headlessui/react';
import { ChevronRightIcon } from '@heroicons/react/solid';
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { groupBy } from '../../../helpers/GroupBy';
import { FrontMatterIcon } from '../../../viewpanel/components/Icons/FrontMatterIcon';
import { GroupOption } from '../../constants/GroupOption';
import { Page } from '../../models/Page';
import { Settings } from '../../models/Settings';
import { GroupingSelector } from '../../state';
import { Item } from './Item';
import { List } from './List';
export interface IOverviewProps {
pages: Page[];
settings: Settings | null;
}
export const Overview: React.FunctionComponent<IOverviewProps> = ({pages, settings}: React.PropsWithChildren<IOverviewProps>) => {
const grouping = useRecoilValue(GroupingSelector);
if (!pages || !pages.length) {
return (
<div className={`flex items-center justify-center h-full`}>
<div className={`max-w-xl text-center`}>
<FrontMatterIcon className={`text-vulcan-300 dark:text-whisper-800 h-32 mx-auto opacity-90 mb-8`} />
{
settings && settings?.folders?.length > 0 ? (
<p className={`text-xl font-medium`}>No Markdown to show</p>
) : (
<>
<p className={`text-lg font-medium`}>Make sure you registered a content folder in your project to let Front Matter find the contents.</p>
</>
)
}
</div>
</div>
);
}
if (grouping !== GroupOption.none) {
const groupedPages = groupBy(pages, grouping === GroupOption.Year ? 'fmYear' : 'fmDraft');
let groupKeys = Object.keys(groupedPages);
if (grouping === GroupOption.Year) {
groupKeys = groupKeys.sort((a, b) => { return parseInt(b) - parseInt(a) });
}
return (
<>
{
groupKeys.map((groupId, idx) => (
<Disclosure key={groupId} as={`div`} className={`w-full`} defaultOpen>
{({ open }) => (
<>
<Disclosure.Button className={`mb-4 ${idx !== 0 ? "mt-8" : ""}`}>
<h2 className={`text-2xl font-bold flex items-center`}>
<ChevronRightIcon
className={`w-8 h-8 mr-1 ${open ? "transform rotate-90" : ""}`}
/>
{GroupOption[grouping]}: {groupId} ({groupedPages[groupId].length})
</h2>
</Disclosure.Button>
<Disclosure.Panel>
<List>
{groupedPages[groupId].map((page: Page) => (
<Item key={`${page.slug}-${idx}`} {...page} />
))}
</List>
</Disclosure.Panel>
</>
)}
</Disclosure>
))
}
</>
);
}
return (
<List>
{pages.map((page, idx) => (
<Item key={`${page.slug}-${idx}`} {...page} />
))}
</List>
);
};

View File

@@ -1,14 +1,13 @@
import * as React from 'react';
import { Spinner } from './Spinner';
import useMessages from '../hooks/useMessages';
import { Overview } from './Overview';
import { Header } from './Header';
import { Tab } from '../constants/Tab';
import { SortOption } from '../constants/SortOption';
import useDarkMode from '../../hooks/useDarkMode';
import usePages from '../hooks/usePages';
import { SponsorMsg } from './SponsorMsg';
import { WelcomeScreen } from './WelcomeScreen';
import { useRecoilValue } from 'recoil';
import { DashboardViewSelector } from '../state';
import { Contents } from './Contents/Contents';
import { Media } from './Media/Media';
export interface IDashboardProps {
showWelcome: boolean;
@@ -16,17 +15,10 @@ export interface IDashboardProps {
export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome}: React.PropsWithChildren<IDashboardProps>) => {
const { loading, pages, settings } = useMessages();
const [ tab, setTab ] = React.useState(Tab.All);
const [ sorting, setSorting ] = React.useState(SortOption.LastModified);
const [ group, setGroup ] = React.useState<string | null>(null);
const [ search, setSearch ] = React.useState<string | null>(null);
const [ tag, setTag ] = React.useState<string | null>(null);
const [ category, setCategory ] = React.useState<string | null>(null);
const { pageItems } = usePages(pages, tab, sorting, group, search, tag, category);
const { pageItems } = usePages(pages);
const view = useRecoilValue(DashboardViewSelector);
useDarkMode();
const pageGroups = [...new Set(pages.map(page => page.fmGroup))];
if (!settings) {
return <Spinner />;
}
@@ -38,32 +30,14 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
if (!settings.initialized || settings.folders?.length === 0) {
return <WelcomeScreen settings={settings} />;
}
return (
<main className={`h-full w-full`}>
<div className="flex flex-col h-full overflow-auto">
<Header currentTab={tab}
currentSorting={sorting}
groups={pageGroups}
crntGroup={group}
totalPages={pageItems.length}
crntTag={tag}
crntCategory={category}
switchTab={(tabId: Tab) => setTab(tabId)}
switchSorting={(sortId: SortOption) => setSorting(sortId)}
switchGroup={(groupId: string | null) => setGroup(groupId)}
switchTag={(tagId: string | null) => setTag(tagId)}
switchCategory={(categoryId: string | null) => setCategory(categoryId)}
onSearch={(value: string | null) => setSearch(value)}
settings={settings}
/>
<div className="flex-grow max-w-7xl mx-auto py-6 px-4">
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
</div>
<SponsorMsg />
</div>
</main>
);
if (view === "contents") {
return (
<Contents pages={pageItems} loading={loading} />
);
} else {
return (
<Media />
);
}
};

View File

@@ -1,46 +0,0 @@
import { Menu, Transition } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/solid';
import * as React from 'react';
import { Fragment } from 'react';
import { MenuButton } from './MenuButton';
import { MenuItem } from './MenuItem';
import { MenuItems } from './MenuItems';
export interface IGroupingProps {
groups: string[];
crntGroup: string | null;
switchGroup: (group: string | null) => void;
}
const DEFAULT_TYPE = "All types";
export const Grouping: React.FunctionComponent<IGroupingProps> = ({groups, crntGroup, switchGroup}: React.PropsWithChildren<IGroupingProps>) => {
if (groups.length <= 1) {
return null;
}
return (
<div className="flex items-center ml-6">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton label={`Showing`} title={crntGroup || DEFAULT_TYPE} />
<MenuItems>
<MenuItem
title={DEFAULT_TYPE}
value={null}
isCurrent={!crntGroup}
onClick={switchGroup} />
{groups.map((option) => (
<MenuItem
key={option}
title={option}
value={option}
isCurrent={option === crntGroup}
onClick={switchGroup} />
))}
</MenuItems>
</Menu>
</div>
);
};

View File

@@ -1,75 +0,0 @@
import * as React from 'react';
import { Tab } from '../constants/Tab';
import { SortOption } from '../constants/SortOption';
import { Navigation } from './Navigation';
import { Sorting } from './Sorting';
import { Grouping } from './Grouping';
import { MessageHelper } from '../../helpers/MessageHelper';
import { DashboardMessage } from '../DashboardMessage';
import { Searchbox } from './Searchbox';
import { Settings } from '../models/Settings';
import { Startup } from './Startup';
import { Button } from './Button';
import { Filter } from './Filter';
export interface IHeaderProps {
settings: Settings;
// Navigation
currentTab: Tab;
totalPages: number;
switchTab: (tabId: Tab) => void;
// Sorting
currentSorting: SortOption;
switchSorting: (sortId: SortOption) => void;
// Grouping
groups: string[];
crntGroup: string | null;
switchGroup: (group: string | null) => void;
// Searching
onSearch: (value: string | null) => void;
// Tags
crntTag: string | null;
switchTag: (tag: string | null) => void;
// Categories
crntCategory: string | null;
switchCategory: (category: string | null) => void;
}
export const Header: React.FunctionComponent<IHeaderProps> = ({currentTab, currentSorting, switchSorting, switchTab, totalPages, crntGroup, groups, switchGroup, onSearch, settings, switchTag, crntTag, switchCategory, crntCategory}: React.PropsWithChildren<IHeaderProps>) => {
const createContent = () => {
MessageHelper.sendMessage(DashboardMessage.createContent);
};
return (
<div className={`w-full max-w-7xl mx-auto sticky top-0 z-40 bg-gray-100 dark:bg-vulcan-500`}>
<div className={`px-4 my-2 flex items-center justify-between`}>
<Searchbox onSearch={onSearch} />
<div className={`flex items-center space-x-4`}>
<Startup settings={settings} />
<Button onClick={createContent} disabled={!settings.initialized}>Create content</Button>
</div>
</div>
<div className="px-4 flex items-center border-b border-gray-200 dark:border-whisper-600">
<Navigation currentTab={currentTab} totalPages={totalPages} switchTab={switchTab} />
<Grouping crntGroup={crntGroup} groups={groups} switchGroup={switchGroup} />
<Filter label={`Tag filter`} activeItem={crntTag} items={settings.tags} onClick={switchTag} />
<Filter label={`Category filter`} activeItem={crntCategory} items={settings.categories} onClick={switchCategory} />
<Sorting currentSorting={currentSorting} switchSorting={switchSorting} />
</div>
</div>
);
};

View File

@@ -0,0 +1,54 @@
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 { DefaultValue } from 'recoil';
export const guardRecoilDefaultValue = (
candidate: any
): candidate is DefaultValue => {
if (candidate instanceof DefaultValue) return true;
return false;
};
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);
const resetSorting = useResetRecoilState(SortingAtom);
const resetFolder = useResetRecoilState(FolderAtom);
const resetTag = useResetRecoilState(TagAtom);
const resetCategory = useResetRecoilState(CategoryAtom);
const reset = () => {
setShow(false);
resetSorting();
resetFolder();
resetTag();
resetCategory();
};
React.useEffect(() => {
if (sorting !== DEFAULT_SORTING_OPTION || folder !== DEFAULT_FOLDER_STATE || tag !== DEFAULT_TAG_STATE || category !== DEFAULT_CATEGORY_STATE) {
setShow(true);
} else {
setShow(false);
}
}, [sorting, 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>
</button>
);
};

View File

@@ -1,8 +1,7 @@
import { Menu } from '@headlessui/react';
import { FilterIcon } from '@heroicons/react/solid';
import * as React from 'react';
import { MenuButton } from './MenuButton';
import { MenuItem } from './MenuItem';
import { MenuItems } from './MenuItems';
import { MenuButton, MenuItem, MenuItems } from '../Menu';
export interface IFilterProps {
label: string;
@@ -20,9 +19,15 @@ export const Filter: React.FunctionComponent<IFilterProps> = ({label, activeItem
}
return (
<div className="flex items-center ml-6">
<div className="flex items-center">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton label={label} title={activeItem || DEFAULT_VALUE} />
<MenuButton
label={(
<>
<FilterIcon className={`inline-block w-5 h-5 mr-1`} /><span>{label}</span>
</>
)}
title={activeItem || DEFAULT_VALUE} />
<MenuItems>
<MenuItem

View File

@@ -0,0 +1,44 @@
import { Menu, Transition } from '@headlessui/react';
import * as React from 'react';
import { useRecoilState } from 'recoil';
import { FolderAtom } from '../../state';
import { MenuButton, MenuItem, MenuItems } from '../Menu';
export interface IFoldersProps {
folders: string[];
}
const DEFAULT_TYPE = "All types";
export const Folders: React.FunctionComponent<IFoldersProps> = ({folders}: React.PropsWithChildren<IFoldersProps>) => {
const [ crntFolder, setCrntFolder ] = useRecoilState(FolderAtom);
if (folders.length <= 1) {
return null;
}
return (
<div className="flex items-center">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton label={`Showing`} title={crntFolder || DEFAULT_TYPE} />
<MenuItems>
<MenuItem
title={DEFAULT_TYPE}
value={null}
isCurrent={!crntFolder}
onClick={(value) => setCrntFolder(value)} />
{folders.map((option) => (
<MenuItem
key={option}
title={option}
value={option}
isCurrent={option === crntFolder}
onClick={(value) => setCrntFolder(value)} />
))}
</MenuItems>
</Menu>
</div>
);
};

View File

@@ -0,0 +1,39 @@
import { Menu } from '@headlessui/react';
import * as React from 'react';
import { useRecoilState } from 'recoil';
import { GroupOption } from '../../constants/GroupOption';
import { GroupingAtom } from '../../state';
import { MenuButton, MenuItem, MenuItems } from '../Menu';
export interface IGroupingProps {}
export const groupOptions = [
{ name: "None", id: GroupOption.none },
{ name: "Year", id: GroupOption.Year },
{ name: "Draft/Published", id: GroupOption.Draft },
];
export const Grouping: React.FunctionComponent<IGroupingProps> = ({}: React.PropsWithChildren<IGroupingProps>) => {
const [ group, setGroup ] = useRecoilState(GroupingAtom);
const crntGroup = groupOptions.find(x => x.id === group);
return (
<div className="flex items-center">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton label={`Group by`} title={crntGroup?.name || ""} />
<MenuItems>
{groupOptions.map((option) => (
<MenuItem
key={option.id}
title={option.name}
value={option.id}
isCurrent={option.id === crntGroup?.id}
onClick={(value) => setGroup(value)} />
))}
</MenuItems>
</Menu>
</div>
);
};

View File

@@ -0,0 +1,101 @@
import * as React from 'react';
import { Sorting } from './Sorting';
import { Searchbox } from './Searchbox';
import { Filter } from './Filter';
import { Folders } from './Folders';
import { Settings } 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 { Messenger } from '@estruyf/vscode/dist/client';
import { ClearFilters } from './ClearFilters';
import { MarkdownIcon } from '../../../viewpanel/components/Icons/MarkdownIcon';
import { PhotographIcon } from '@heroicons/react/outline';
import { Pagination } from '../Media/Pagination';
export interface IHeaderProps {
settings: Settings | null;
// Navigation
totalPages?: number;
// Page folders
folders?: string[];
}
export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folders, settings }: React.PropsWithChildren<IHeaderProps>) => {
const [ crntTag, setCrntTag ] = useRecoilState(TagAtom);
const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom);
const [ view, setView ] = useRecoilState(DashboardViewAtom);
const createContent = () => {
Messenger.send(DashboardMessage.createContent);
};
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>
{
view === "contents" && (
<>
<div className={`px-4 mt-3 mb-2 flex items-center justify-between`}>
<Searchbox />
<div className={`flex items-center space-x-4`}>
<Startup settings={settings} />
<Button onClick={createContent} disabled={!settings?.initialized}>Create content</Button>
</div>
</div>
<div className="px-4 flex flex-row items-center border-b border-gray-200 dark:border-vulcan-100 justify-between">
<div>
<Navigation totalPages={totalPages || 0} />
</div>
<div>
<ViewSwitch />
</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`}>
<ClearFilters />
<Folders folders={folders || []} />
<Filter label={`Tag`} activeItem={crntTag} items={settings?.tags || []} onClick={(value) => setCrntTag(value)} />
<Filter label={`Category`} activeItem={crntCategory} items={settings?.categories || []} onClick={(value) => setCrntCategory(value)} />
<Grouping />
<Sorting />
</div>
</>
)
}
{
view === "media" && (
<Pagination />
)
}
</div>
);
};

View File

@@ -1,13 +1,14 @@
import { FilterIcon, SearchIcon } from '@heroicons/react/solid';
import * as React from 'react';
import { useDebounce } from '../../hooks/useDebounce';
import { useRecoilState } from 'recoil';
import { useDebounce } from '../../../hooks/useDebounce';
import { SearchAtom } from '../../state';
export interface ISearchboxProps {
onSearch: (searchText: string) => void;
}
export interface ISearchboxProps {}
export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({onSearch}: React.PropsWithChildren<ISearchboxProps>) => {
export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({}: React.PropsWithChildren<ISearchboxProps>) => {
const [ value, setValue ] = React.useState('');
const [ , setDebounceValue ] = useRecoilState(SearchAtom);
const debounceSearch = useDebounce<string>(value, 500);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -15,7 +16,7 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({onSearch}:
};
React.useEffect(() => {
onSearch(debounceSearch);
setDebounceValue(debounceSearch);
}, [debounceSearch]);
return (
@@ -32,7 +33,6 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({onSearch}:
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"
value={value}
autoFocus={true}
onChange={handleChange}
/>
</div>

View File

@@ -0,0 +1,40 @@
import { Menu } from '@headlessui/react';
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { SortOption } from '../../constants/SortOption';
import { SearchSelector, SortingAtom } from '../../state';
import { MenuButton, MenuItem, MenuItems } from '../Menu';
export interface ISortingProps {}
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 Sorting: React.FunctionComponent<ISortingProps> = ({}: React.PropsWithChildren<ISortingProps>) => {
const [ crntSorting, setCrntSorting ] = useRecoilState(SortingAtom);
const searchValue = useRecoilValue(SearchSelector);
const crntSort = sortOptions.find(x => x.id === crntSorting);
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} />
<MenuItems>
{sortOptions.map((option) => (
<MenuItem
key={option.id}
title={option.name}
value={option.id}
isCurrent={option.id === crntSorting}
onClick={(value) => setCrntSorting(value)} />
))}
</MenuItems>
</Menu>
</div>
);
};

View File

@@ -0,0 +1,38 @@
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { ViewAtom, ViewType, SettingsSelector } from '../../state';
import { ViewGridIcon, ViewListIcon } from '@heroicons/react/solid';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
export interface IViewSwitchProps {}
export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (props: React.PropsWithChildren<IViewSwitchProps>) => {
const [ view, setView ] = useRecoilState(ViewAtom);
const settings = useRecoilValue(SettingsSelector);
const toggleView = () => {
const newView = view === ViewType.Grid ? ViewType.List : ViewType.Grid;
setView(newView);
Messenger.send(DashboardMessage.setPageViewType, newView);
};
React.useEffect(() => {
if (settings?.pageViewType) {
setView(settings?.pageViewType);
}
}, [settings?.pageViewType]);
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}>
<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}>
<ViewListIcon className={`w-4 h-4`} />
<span className={`sr-only`}>Change to list</span>
</button>
</div>
);
};

View File

@@ -0,0 +1,6 @@
export * from './Filter';
export * from './Folders';
export * from './Grouping';
export * from './Header';
export * from './Searchbox';
export * from './Sorting';

View File

@@ -1,47 +0,0 @@
import * as React from 'react';
import { MessageHelper } from '../../helpers/MessageHelper';
import { MarkdownIcon } from '../../viewpanel/components/Icons/MarkdownIcon';
import { DashboardMessage } from '../DashboardMessage';
import { Page } from '../models/Page';
import { DateField } from './DateField';
import { Status } from './Status';
export interface IItemProps extends Page {}
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, draft, description, preview }: React.PropsWithChildren<IItemProps>) => {
const openFile = () => {
MessageHelper.sendMessage(DashboardMessage.openFile, fmFilePath);
};
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`}
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" />
) : (
<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`} />
</div>
)
}
</div>
<div className="p-4 w-full">
<div className={`flex justify-between items-center`}>
<Status draft={!!draft} />
<DateField value={date} />
</div>
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{description}</p>
</div>
</button>
</li>
);
};

View File

@@ -0,0 +1,77 @@
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>
);
};

View File

@@ -0,0 +1,132 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { ClipboardCopyIcon, PhotographIcon, TrashIcon } from '@heroicons/react/outline';
import { basename, dirname } from 'path';
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { MediaInfo } from '../../../models/MediaPaths';
import { DashboardMessage } from '../../DashboardMessage';
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state';
import { Alert } from '../Modals/Alert';
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 parseWinPath = (path: string | undefined) => {
return path?.split(`\\`).join(`/`);
}
const getFolder = () => {
if (settings?.wsFolder && media.fsPath) {
let relPath = media.fsPath.split(settings.wsFolder).pop();
if (settings.staticFolder && relPath) {
relPath = relPath.split(settings.staticFolder).pop();
}
return dirname(parseWinPath(relPath) || "");
}
return "";
};
const copyToClipboard = () => {
let relPath: string | undefined = "";
if (settings?.wsFolder && media.fsPath) {
relPath = media.fsPath.split(settings.wsFolder).pop();
if (settings.staticFolder && relPath) {
relPath = relPath.split(settings.staticFolder).pop();
}
}
Messenger.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || "");
};
const deleteMedia = () => {
setShowAlert(true);
};
const confirmDeletion = () => {
Messenger.send(DashboardMessage.deleteMedia, {
file: media.fsPath,
folder: selectedFolder
});
};
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`;
}
}
};
const openLightbox = () => {
setLightbox(media.vsPath || "");
};
return (
<>
<li className="group relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100">
<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`} />
</div>
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
<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`}>
<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>
</div>
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center">
{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()}
</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()}
</p>
)
}
</div>
</li>
{
showAlert && (
<Alert
title={`Delete: ${basename(parseWinPath(media.fsPath) || "")}`}
description={`Are you sure you want to delete the file from the ${getFolder()} folder?`}
okBtnText={`Delete`}
cancelBtnText={`Cancel`}
dismiss={() => setShowAlert(false)}
trigger={confirmDeletion} />
)
}
</>
);
};

Some files were not shown because too many files have changed in this diff Show More