forked from iarv/vscode-front-matter
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e695bad1c6 | ||
|
|
fe31081907 | ||
|
|
248ccb3718 | ||
|
|
2260174ec2 | ||
|
|
cd3a867422 | ||
|
|
05a63dd110 | ||
|
|
cfc0c3d5a1 | ||
|
|
d6dbca25ce | ||
|
|
d21ad14e89 | ||
|
|
8d00726322 | ||
|
|
af11c304d3 | ||
|
|
223276f6af | ||
|
|
a6fdfe0dfa | ||
|
|
6154164b1c | ||
|
|
1cdb6c56a5 | ||
|
|
c63310a2db | ||
|
|
9f681a7459 | ||
|
|
2610032a38 | ||
|
|
d11e8112e0 | ||
|
|
df5e346cf1 | ||
|
|
9892d14a62 | ||
|
|
8c61f79885 | ||
|
|
ffa6638d3d | ||
|
|
f9ef12bd3a | ||
|
|
b79216f2b5 | ||
|
|
2597a63718 | ||
|
|
126a21a6b5 | ||
|
|
3abfbd5302 | ||
|
|
efdbce2d08 | ||
|
|
09eea16d60 | ||
|
|
71697a09b6 | ||
|
|
3583a2b962 | ||
|
|
2825d5ddd8 | ||
|
|
2e66174c4a | ||
|
|
e78069ad17 | ||
|
|
4c97993c5f | ||
|
|
a452173d9a | ||
|
|
60a38be923 | ||
|
|
6c3d286282 | ||
|
|
32c7bbd3f9 | ||
|
|
426dbc2e46 | ||
|
|
9882dea960 | ||
|
|
eb9a05e90c | ||
|
|
4e59e736ed | ||
|
|
9f91ebf289 | ||
|
|
b80de402bd |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: 'Issue: '
|
||||
labels: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: 'Enhancement: '
|
||||
labels: ''
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
33
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
33
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# PR Details
|
||||
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Related Issue
|
||||
|
||||
<!--- This project only accepts pull requests related to open issues -->
|
||||
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
|
||||
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
|
||||
<!--- Please link to the issue here: -->
|
||||
|
||||
## Motivation and Context
|
||||
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
|
||||
## How Has This Been Tested
|
||||
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] Docs change / refactoring / dependency upgrade
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
27
.github/workflows/project-labelling.yml
vendored
Normal file
27
.github/workflows/project-labelling.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Project labelling
|
||||
|
||||
on:
|
||||
project_card:
|
||||
types: [created, moved, deleted]
|
||||
|
||||
jobs:
|
||||
automate-issues-labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch project data
|
||||
run: |
|
||||
echo 'PROJECT_DATA<<EOF' >> $GITHUB_ENV
|
||||
curl --request GET --url '${{ github.event.project_card.project_url }}' --header 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' >> $GITHUB_ENV
|
||||
echo 'EOF' >> $GITHUB_ENV
|
||||
|
||||
- name: Add the project label
|
||||
uses: andymckay/labeler@master
|
||||
if: ${{ contains(github.event.action, 'created') || contains(github.event.action, 'moved') }}
|
||||
with:
|
||||
add-labels: "Project: ${{ fromJSON(env.PROJECT_DATA).name }}"
|
||||
|
||||
- name: Remove the project label
|
||||
uses: andymckay/labeler@master
|
||||
if: ${{ contains(github.event.action, 'deleted') }}
|
||||
with:
|
||||
remove-labels: "Project: ${{ fromJSON(env.PROJECT_DATA).name }}"
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -2,6 +2,7 @@
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"ms-vscode.vscode-typescript-tslint-plugin"
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"eliostruyf.vscode-typescript-exportallmodules"
|
||||
]
|
||||
}
|
||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,5 +1,39 @@
|
||||
# Change Log
|
||||
|
||||
## [7.3.2] - 2022-06-01
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#346](https://github.com/estruyf/vscode-front-matter/issues/346): Fix media dashboard refresh action
|
||||
|
||||
|
||||
## [7.3.1] - 2022-05-26
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#343](https://github.com/estruyf/vscode-front-matter/issues/343): Fix in the schema for the `frontMatter.taxonomy.fieldGroups` setting
|
||||
|
||||
## [7.3.0] - 2022-05-25 - [Release notes](https://beta.frontmatter.codes/updates/v7.3.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- JSON schema enhancements for working with data files
|
||||
- [#330](https://github.com/estruyf/vscode-front-matter/issues/330): Allow custom scripts to easily update front matter
|
||||
- [#331](https://github.com/estruyf/vscode-front-matter/issues/331): Added functionality to run other type of scripts
|
||||
- [#332](https://github.com/estruyf/vscode-front-matter/issues/332): New `dataFile` field which allows you to create data file references
|
||||
- [#333](https://github.com/estruyf/vscode-front-matter/issues/333): Automatically mark Jekyll posts in `_drafts` folder as draft
|
||||
- [#335](https://github.com/estruyf/vscode-front-matter/issues/335): Merge media snippets with content snippets to allow you to define multiple media snippets and use these in your content
|
||||
- [#336](https://github.com/estruyf/vscode-front-matter/issues/336): Support added for inverting the draft field so that SSGs/authors can use a published field instead
|
||||
- [#337](https://github.com/estruyf/vscode-front-matter/issues/337): Allow multiple front matter types to be used
|
||||
- [#338](https://github.com/estruyf/vscode-front-matter/issues/338): Ability to disable the templates functionality (default is disabled)
|
||||
- [#340](https://github.com/estruyf/vscode-front-matter/issues/340): Show an error message when there is a content folder registered that does not exist in the project
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#334](https://github.com/estruyf/vscode-front-matter/issues/334): Fix for locked content folders retrieval
|
||||
- [#339](https://github.com/estruyf/vscode-front-matter/issues/339): Fix for content folders without a title
|
||||
|
||||
|
||||
## [7.2.0] - 2022-05-02 - [Release notes](https://beta.frontmatter.codes/updates/v7.2.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
elio@struyfconsulting.be.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
59
CONTRIBUTING.md
Normal file
59
CONTRIBUTING.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Contributing to Front Matter
|
||||
|
||||
First of all, it is amazing you want to contribute to Front Matter 💚.
|
||||
|
||||
There are various ways in how you can contribute to the project, it can be as simple from opening a bug report to implementing fixes or features.
|
||||
|
||||
## How you can help us
|
||||
|
||||
- Testing out the extension and providing feedback
|
||||
- Reporting issues and bugs
|
||||
- Suggesting new features
|
||||
- Fixing an issue
|
||||
- Updating documentation
|
||||
- UI improvements
|
||||
- Tutorials
|
||||
- etc.
|
||||
|
||||
Eager to start contributing? Great 🤩, you can contribute to the following projects:
|
||||
|
||||
- [Extension](https://github.com/estruyf/vscode-front-matter)
|
||||
- [Documentation](https://github.com/FrontMatter/web-documentation-nextjs)
|
||||
- [Sample Projects](https://github.com/FrontMatter/project-samples)
|
||||
|
||||
## How to get started
|
||||
|
||||
- Start by forking this project;
|
||||
- Clone your fork to your local machine;
|
||||
- Run `npm i`;
|
||||
- Open the project in VS Code;
|
||||
- To start developing, run `npm run dev:ext` and press `f5` to start the debugging session.
|
||||
|
||||
### Tips
|
||||
|
||||
- Ensure that the main branch on your fork is in sync with the original **vscode-front-matter** repository
|
||||
|
||||
```bash
|
||||
# assuming you are in the folder of your locally cloned fork....
|
||||
git checkout main
|
||||
|
||||
# assuming you have a remote named `upstream` pointing to the official **vscode-front-matter** repo
|
||||
git fetch upstream
|
||||
|
||||
# update your local main to be a mirror of what's in the main repo
|
||||
git pull --rebase upstream main
|
||||
```
|
||||
|
||||
- Create a feature branch in your fork. In case you get stuck, or have issues with merging your PR, this will allow you to have a clean main branch that you can use for contributing other changes.
|
||||
|
||||
```bash
|
||||
git checkout -b issue/<id>
|
||||
```
|
||||
|
||||
## Pull request
|
||||
|
||||
Once you are done with implementing the fix or feature. Please create a PR to our `dev` branch.
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under its MIT License.
|
||||
@@ -50,6 +50,28 @@
|
||||
],
|
||||
"openingTags": "{{",
|
||||
"closingTags": "}}"
|
||||
},
|
||||
"Issue link": {
|
||||
"description": "Link to a GitHub issue",
|
||||
"body": "- [#{{id}}](https://github.com/estruyf/vscode-front-matter/issues/{{id}}): {{title}}",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"title": "Issue ID",
|
||||
"type": "string",
|
||||
"single": true,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"title": "Title",
|
||||
"type": "string",
|
||||
"single": true,
|
||||
"default": ""
|
||||
}
|
||||
],
|
||||
"openingTags": "{{",
|
||||
"closingTags": "}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
8610
package-lock.json
generated
8610
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
135
package.json
135
package.json
@@ -3,7 +3,7 @@
|
||||
"displayName": "Front Matter",
|
||||
"description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
|
||||
"icon": "assets/frontmatter-teal-128x128.png",
|
||||
"version": "7.2.0",
|
||||
"version": "7.3.2",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -137,6 +137,10 @@
|
||||
"type": "string",
|
||||
"description": "Name of the field to use"
|
||||
},
|
||||
"invert": {
|
||||
"type": "boolean",
|
||||
"description": "By default the draft field is set to true when the content is a draft. Set this to true to set it to false."
|
||||
},
|
||||
"choices": {
|
||||
"type": "array",
|
||||
"description": "List of choices for the field",
|
||||
@@ -225,8 +229,7 @@
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"body",
|
||||
"fields"
|
||||
"body"
|
||||
],
|
||||
"properties": {
|
||||
"body": {
|
||||
@@ -255,6 +258,11 @@
|
||||
"description": "The snippet closing tags.",
|
||||
"type": "string",
|
||||
"default": "]]"
|
||||
},
|
||||
"isMediaSnippet": {
|
||||
"description": "Specify if the snippet is to be used for media files.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -342,7 +350,7 @@
|
||||
},
|
||||
"nodeBin": {
|
||||
"type": "string",
|
||||
"description": "Path to the node executable. This is required when using NVM, so that there is no confusion of which node version to use."
|
||||
"description": "Path to the node executable. This is required when using NVM, so that there is no confusion of which node version to use. (deprecated: use the command property instead)"
|
||||
},
|
||||
"bulk": {
|
||||
"type": "boolean",
|
||||
@@ -369,6 +377,25 @@
|
||||
"mediaFile"
|
||||
],
|
||||
"description": "The type for which the script will be used."
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"node",
|
||||
"bash",
|
||||
"powershell",
|
||||
"python",
|
||||
"python3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "The type of script you want to execute.",
|
||||
"default": "node"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -389,6 +416,7 @@
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"markdownDescription": "Specify the a snippet for your custom media insert markup. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.mediasnippet)",
|
||||
"deprecationMessage": "This setting is deprecated and will be removed in the next major version. Please define your media snippet in the `frontMatter.content.snippet` setting.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "Use the `{mediaUrl}`, `{caption}`, `{alt}`, `{filename}`, `{mediaHeight}`, and `{mediaWidth}` placeholders in your snippet to automatically insert the media information."
|
||||
@@ -426,7 +454,8 @@
|
||||
},
|
||||
"file": {
|
||||
"type": "string",
|
||||
"description": "Path to the file to load. Only JSON or YAML files are supported."
|
||||
"description": "Path to the file to load. Only JSON or YAML files are supported.",
|
||||
"default": "[[workspace]]/"
|
||||
},
|
||||
"fileType": {
|
||||
"type": "string",
|
||||
@@ -438,10 +467,38 @@
|
||||
"description": "Defines how you want to parse the file. JSON is the default."
|
||||
},
|
||||
"schema": {
|
||||
"$id": "#dataFileSchema",
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"description": "The JSON schema for your data which will be used to render the data form.",
|
||||
"additionalProperties": true
|
||||
"additionalProperties": true,
|
||||
"required": [
|
||||
"type",
|
||||
"properties"
|
||||
],
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Title of the form."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Defines the type of the form. Default is 'object'.",
|
||||
"default": "object"
|
||||
},
|
||||
"required": {
|
||||
"type": "array",
|
||||
"description": "Defines the required fields for the form.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Defines the fields of the form.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
@@ -488,13 +545,11 @@
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the folder to load files."
|
||||
"description": "Path to the folder to load files.",
|
||||
"default": "[[workspace]]/"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"description": "The JSON schema for your data which will be used to render the data form.",
|
||||
"additionalProperties": true
|
||||
"$ref": "#dataFileSchema"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
@@ -535,10 +590,7 @@
|
||||
"description": "Your unique ID you want to use for your data type."
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"description": "The JSON schema for your data which will be used to render the data form.",
|
||||
"additionalProperties": true
|
||||
"$ref": "#dataFileSchema"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -753,7 +805,8 @@
|
||||
"draft",
|
||||
"fields",
|
||||
"json",
|
||||
"block"
|
||||
"block",
|
||||
"dataFile"
|
||||
],
|
||||
"description": "Define the type of field"
|
||||
},
|
||||
@@ -875,6 +928,21 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specify if the field is the modified date field"
|
||||
},
|
||||
"dataFileId": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Specify the ID of the data file to use for this field"
|
||||
},
|
||||
"dataFileKey": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Specify the key of the data file to use for this field"
|
||||
},
|
||||
"dataFileValue": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Specify the property name that will be used to show the value for the field"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -883,6 +951,21 @@
|
||||
"name"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "dataFile"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"dataFileId",
|
||||
"dataFileKey"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
@@ -1091,7 +1174,7 @@
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"id",
|
||||
"fields"
|
||||
]
|
||||
}
|
||||
@@ -1198,6 +1281,12 @@
|
||||
"default": "yyyy-MM-dd",
|
||||
"markdownDescription": "Specify the prefix you want to add for your new article filenames. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.templates.prefix)",
|
||||
"scope": "Templates"
|
||||
},
|
||||
"frontMatter.templates.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"markdownDescription": "Specify if you want to use templates. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.templates.enabled)",
|
||||
"scope": "Templates"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1276,9 +1365,14 @@
|
||||
"dark": "assets/icons/close-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.initTemplate",
|
||||
"title": "Initialize the template folder",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.createTemplate",
|
||||
"title": "Create a template from current file",
|
||||
"title": "Create template from current file",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
@@ -1799,9 +1893,10 @@
|
||||
"start:site": "cd ./docs && npm run dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.8.2",
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "0.0.3",
|
||||
"@headlessui/react": "^1.5.0",
|
||||
"@headlessui/react": "1.5.0",
|
||||
"@heroicons/react": "1.0.4",
|
||||
"@iarna/toml": "2.2.3",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
@@ -1835,7 +1930,7 @@
|
||||
"downshift": "6.0.6",
|
||||
"fuse.js": "6.5.3",
|
||||
"glob": "7.1.6",
|
||||
"gray-matter": "4.0.2",
|
||||
"gray-matter": "4.0.3",
|
||||
"html-loader": "1.3.2",
|
||||
"html-webpack-plugin": "4.5.0",
|
||||
"image-size": "^1.0.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const core = require('@actions/core');
|
||||
|
||||
const packageJson = require('../package.json');
|
||||
const version = packageJson.version.split('.');
|
||||
@@ -14,7 +15,24 @@ packageJson.homepage = "https://beta.frontmatter.codes";
|
||||
|
||||
console.log(packageJson.version);
|
||||
|
||||
core.summary.addHeading(`Version info`).addDetails(`${packageJson.version}`);
|
||||
|
||||
const scripts = packageJson.scripts;
|
||||
for (const key in scripts) {
|
||||
if (key.startsWith(`prod:`)) {
|
||||
scripts[key] = scripts[key].replace("production", "development");
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(packageJson.scripts, null, 2));
|
||||
|
||||
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);
|
||||
fs.writeFileSync(path.join(__dirname, '../README.md'), readme);
|
||||
|
||||
// Update the .vscodeignore file
|
||||
const ignoreFilePath = path.join(path.resolve('.'), '.vscodeignore');
|
||||
let vscodeignore = fs.readFileSync(ignoreFilePath, 'utf8');
|
||||
vscodeignore = vscodeignore.replace(`**/*.map`, '');
|
||||
fs.writeFileSync(ignoreFilePath, vscodeignore);
|
||||
@@ -69,7 +69,8 @@ export class Article {
|
||||
|
||||
const selectedOptions = await vscode.window.showQuickPick(options, {
|
||||
placeHolder: `Select your ${type === TaxonomyType.Tag ? "tags" : "categories"} to insert`,
|
||||
canPickMany: true
|
||||
canPickMany: true,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (selectedOptions) {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { commands, QuickPickItem, window } from 'vscode';
|
||||
import { COMMAND_NAME } from '../constants';
|
||||
import { COMMAND_NAME, SETTING_TEMPLATES_ENABLED } from '../constants';
|
||||
import { Settings } from '../helpers';
|
||||
|
||||
export class Content {
|
||||
|
||||
public static async create() {
|
||||
const templatesEnabled = await Settings.get(SETTING_TEMPLATES_ENABLED);
|
||||
if (!templatesEnabled) {
|
||||
commands.executeCommand(COMMAND_NAME.createByContentType);
|
||||
}
|
||||
|
||||
const options: QuickPickItem[] = [{
|
||||
label: "Create content by content type",
|
||||
@@ -15,7 +20,8 @@ export class Content {
|
||||
|
||||
const selectedOption = await window.showQuickPick(options, {
|
||||
placeHolder: `Select how you want to create your new content`,
|
||||
canPickMany: false
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (selectedOption) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ContentFolder, FileInfo, FolderInfo } from "../models";
|
||||
import uniqBy = require("lodash.uniqby");
|
||||
import { Template } from "./Template";
|
||||
import { Notifications } from "../helpers/Notifications";
|
||||
import { Settings } from "../helpers";
|
||||
import { Logger, Settings } from "../helpers";
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { format } from 'date-fns';
|
||||
import { Dashboard } from './Dashboard';
|
||||
@@ -98,7 +98,10 @@ export class Folders {
|
||||
* Register the new folder path
|
||||
* @param folder
|
||||
*/
|
||||
public static async register(folder: Uri) {
|
||||
public static async register(folderInfo: { title: string, path: Uri } | Uri) {
|
||||
let folderName = folderInfo instanceof Uri ? undefined : folderInfo.title;
|
||||
const folder = folderInfo instanceof Uri ? folderInfo : folderInfo.path;
|
||||
|
||||
if (folder && folder.fsPath) {
|
||||
const wslPath = folder.fsPath.replace(/\//g, '\\');
|
||||
|
||||
@@ -111,11 +114,14 @@ export class Folders {
|
||||
return;
|
||||
}
|
||||
|
||||
const folderName = await window.showInputBox({
|
||||
prompt: `Which name would you like to specify for this folder?`,
|
||||
placeHolder: `Folder name`,
|
||||
value: basename(folder.fsPath)
|
||||
});
|
||||
if (!folderName) {
|
||||
folderName = await window.showInputBox({
|
||||
prompt: `Which name would you like to specify for this folder?`,
|
||||
placeHolder: `Folder name`,
|
||||
value: basename(folder.fsPath),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
}
|
||||
|
||||
folders.push({
|
||||
title: folderName,
|
||||
@@ -287,11 +293,30 @@ export class Folders {
|
||||
public static get(): ContentFolder[] {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
|
||||
|
||||
const contentFolders = folders.map(folder => {
|
||||
if (!folder.title) {
|
||||
folder.title = basename(folder.path);
|
||||
}
|
||||
|
||||
let folderPath = Folders.absWsFolder(folder, wsFolder);
|
||||
if (!existsSync(folderPath)) {
|
||||
Notifications.errorShowOnce(`Folder "${folder.title} (${folder.path})" does not exist. Please remove it from the settings.`, "Remove folder").then(answer => {
|
||||
if (answer === "Remove folder") {
|
||||
let folders = Folders.get();
|
||||
Folders.update(folders.filter(f => f.path !== folder.path));
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...folder,
|
||||
path: folderPath
|
||||
}
|
||||
})
|
||||
|
||||
return folders.map(folder => ({
|
||||
...folder,
|
||||
path: Folders.absWsFolder(folder, wsFolder)
|
||||
}));
|
||||
return contentFolders.filter(folder => folder !== null) as ContentFolder[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,11 +383,15 @@ export class Folders {
|
||||
// Find folders that contain files
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES;
|
||||
const patterns = supportedFiles.map(fileType => `${join(wsFolder?.fsPath || "", "**", `*${fileType.startsWith('.') ? '' : '.'}${fileType}`)}`);
|
||||
const patterns = supportedFiles.map(fileType => `${join(parseWinPath(wsFolder?.fsPath || ""), "**", `*${fileType.startsWith('.') ? '' : '.'}${fileType}`)}`);
|
||||
let folders: string[] = [];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
folders = [...folders, ...(await this.findFolders(pattern))];
|
||||
try {
|
||||
folders = [...folders, ...(await this.findFolders(pattern))];
|
||||
} catch (e) {
|
||||
Logger.error(`Something went wrong while searching for folders with pattern "${pattern}": ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out the workspace folder
|
||||
|
||||
@@ -24,35 +24,25 @@ categories: []
|
||||
---
|
||||
`;
|
||||
|
||||
public static isInitialized() {
|
||||
return Settings.hasProjectFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new "Project" instance.
|
||||
*/
|
||||
public static async init(sampleTemplate: boolean = true) {
|
||||
public static async init(sampleTemplate?: boolean) {
|
||||
try {
|
||||
Settings.createTeamSettings();
|
||||
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
|
||||
|
||||
const folder = Template.getSettings();
|
||||
const templatePath = Project.templatePath();
|
||||
|
||||
if (!folder || !templatePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = Uri.file(join(templatePath.fsPath, `article.${fileType}`));
|
||||
|
||||
if (!fs.existsSync(templatePath.fsPath)) {
|
||||
await workspace.fs.createDirectory(templatePath);
|
||||
}
|
||||
|
||||
if (sampleTemplate) {
|
||||
fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" });
|
||||
if (sampleTemplate !== undefined) {
|
||||
await Project.createSampleTemplate();
|
||||
} else {
|
||||
Notifications.info("Project initialized successfully.");
|
||||
}
|
||||
|
||||
|
||||
Telemetry.send(TelemetryEvent.initialization);
|
||||
|
||||
|
||||
// Check if you can find the framework
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const framework = FrameworkDetector.get(wsFolder?.fsPath || "");
|
||||
@@ -68,6 +58,33 @@ categories: []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the templates folder + sample if needed
|
||||
* @param sampleTemplate
|
||||
* @returns
|
||||
*/
|
||||
public static async createSampleTemplate(sampleTemplate?: boolean) {
|
||||
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
|
||||
|
||||
const folder = Template.getSettings();
|
||||
const templatePath = Project.templatePath();
|
||||
|
||||
if (!folder || !templatePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = Uri.file(join(templatePath.fsPath, `article.${fileType}`));
|
||||
|
||||
if (!fs.existsSync(templatePath.fsPath)) {
|
||||
await workspace.fs.createDirectory(templatePath);
|
||||
}
|
||||
|
||||
if (sampleTemplate) {
|
||||
fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" });
|
||||
Notifications.info("Sample template created.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template path for the current project
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,8 @@ export class Settings {
|
||||
public static async create(type: TaxonomyType) {
|
||||
const newOption = await vscode.window.showInputBox({
|
||||
prompt: `Insert the value of the ${type === TaxonomyType.Tag ? "tag" : "category"} that you want to add to your configuration.`,
|
||||
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`
|
||||
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (newOption) {
|
||||
@@ -36,7 +37,11 @@ export class Settings {
|
||||
await SettingsHelper.updateTaxonomy(type, options);
|
||||
|
||||
// Ask if the new term needs to be added to the page
|
||||
const addToPage = await vscode.window.showQuickPick(["yes", "no"], { canPickMany: false, placeHolder: `Do you want to add the new ${type === TaxonomyType.Tag ? "tag" : "category"} to the page?` });
|
||||
const addToPage = await vscode.window.showQuickPick(["yes", "no"], {
|
||||
canPickMany: false,
|
||||
placeHolder: `Do you want to add the new ${type === TaxonomyType.Tag ? "tag" : "category"} to the page?`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (addToPage && addToPage === "yes") {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
@@ -149,7 +154,8 @@ export class Settings {
|
||||
"Category"
|
||||
], {
|
||||
placeHolder: `What do you want to remap?`,
|
||||
canPickMany: false
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (!taxType) {
|
||||
return;
|
||||
@@ -165,7 +171,8 @@ export class Settings {
|
||||
|
||||
const selectedOption = await vscode.window.showQuickPick(options, {
|
||||
placeHolder: `Select your ${type === TaxonomyType.Tag ? "tags" : "categories"} to insert`,
|
||||
canPickMany: false
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (!selectedOption) {
|
||||
@@ -174,11 +181,16 @@ export class Settings {
|
||||
|
||||
const newOptionValue = await vscode.window.showInputBox({
|
||||
prompt: `Specify the value of the ${type === TaxonomyType.Tag ? "tag" : "category"} with which you want to remap "${selectedOption}". Leave the input <blank> if you want to remove the ${type === TaxonomyType.Tag ? "tag" : "category"} from all articles.`,
|
||||
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`
|
||||
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (!newOptionValue) {
|
||||
const deleteAnswer = await vscode.window.showQuickPick(["yes", "no"], { canPickMany: false, placeHolder: `Delete ${selectedOption} ${type === TaxonomyType.Tag ? "tag" : "category"}?` });
|
||||
const deleteAnswer = await vscode.window.showQuickPick(["yes", "no"], {
|
||||
canPickMany: false,
|
||||
placeHolder: `Delete ${selectedOption} ${type === TaxonomyType.Tag ? "tag" : "category"}?`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (deleteAnswer === "no") {
|
||||
return;
|
||||
}
|
||||
@@ -226,7 +238,7 @@ export class Settings {
|
||||
data[matterProp] = [...new Set(taxonomies)].sort();
|
||||
const spaces = vscode.window.activeTextEditor?.options?.tabSize;
|
||||
// Update the file
|
||||
fs.writeFileSync(file.path, FrontMatterParser.toFile(article.content, article.data, {
|
||||
fs.writeFileSync(file.path, FrontMatterParser.toFile(article.content, article.data, mdFile, {
|
||||
indent: spaces || 2
|
||||
} as DumpOptions as any), { encoding: "utf8" });
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ export class Template {
|
||||
|
||||
const titleValue = await vscode.window.showInputBox({
|
||||
prompt: `What name would you like to give your template?`,
|
||||
placeHolder: `article`
|
||||
placeHolder: `article`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (!titleValue) {
|
||||
@@ -77,6 +78,7 @@ export class Template {
|
||||
{
|
||||
canPickMany: false,
|
||||
placeHolder: `Do you want to keep the contents for the template?`,
|
||||
ignoreFocusOut: true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -98,11 +100,24 @@ export class Template {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all templates
|
||||
*/
|
||||
public static async getTemplates() {
|
||||
const folder = Settings.get<string>(SETTING_TEMPLATES_FOLDER);
|
||||
|
||||
if (!folder) {
|
||||
Notifications.warning(`No templates found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
return await vscode.workspace.findFiles(`${folder}/**/*`, "**/node_modules/**,**/archetypes/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a template
|
||||
*/
|
||||
public static async create(folderPath: string) {
|
||||
const folder = Settings.get<string>(SETTING_TEMPLATES_FOLDER);
|
||||
const contentTypes = ContentType.getAll();
|
||||
|
||||
if (!folderPath) {
|
||||
@@ -110,19 +125,15 @@ export class Template {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!folder) {
|
||||
Notifications.warning(`No templates found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const templates = await vscode.workspace.findFiles(`${folder}/**/*`, "**/node_modules/**,**/archetypes/**");
|
||||
const templates = await Template.getTemplates();
|
||||
if (!templates || templates.length === 0) {
|
||||
Notifications.warning(`No templates found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedTemplate = await vscode.window.showQuickPick(templates.map(t => path.basename(t.fsPath)), {
|
||||
placeHolder: `Select the content template to use`
|
||||
placeHolder: `Select the content template to use`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (!selectedTemplate) {
|
||||
Notifications.warning(`No template selected.`);
|
||||
|
||||
@@ -59,8 +59,8 @@ export class Wysiwyg {
|
||||
|
||||
const option = await window.showQuickPick([ ...qpItems ], {
|
||||
placeHolder: "Which type of markup would you like to insert?",
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: false,
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (option) {
|
||||
@@ -161,8 +161,8 @@ export class Wysiwyg {
|
||||
"Heading 6"
|
||||
], {
|
||||
canPickMany: false,
|
||||
placeHolder: "Which heading level do you want to insert?",
|
||||
ignoreFocusOut: false
|
||||
placeHolder: "Which heading level do you want to insert?",
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (headingLvl) {
|
||||
|
||||
@@ -25,6 +25,7 @@ export const COMMAND_NAME = {
|
||||
createByContentType: getCommandName("createByContentType"),
|
||||
createByTemplate: getCommandName("createByTemplate"),
|
||||
createTemplate: getCommandName("createTemplate"),
|
||||
initTemplate: getCommandName("initTemplate"),
|
||||
collapseSections: getCommandName("collapseSections"),
|
||||
preview: getCommandName("preview"),
|
||||
dashboard: getCommandName("dashboard"),
|
||||
@@ -37,6 +38,8 @@ export const COMMAND_NAME = {
|
||||
diagnostics: getCommandName("diagnostics"),
|
||||
modeSwitch: getCommandName("mode.switch"),
|
||||
|
||||
showOutputChannel: getCommandName("showOutputChannel"),
|
||||
|
||||
// Insert dashboards
|
||||
insertMedia: getCommandName("insertMedia"),
|
||||
insertSnippet: getCommandName("insertSnippet"),
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
|
||||
|
||||
export enum GeneralCommands{
|
||||
setMode = "setMode"
|
||||
export const GeneralCommands = {
|
||||
toWebview: {
|
||||
setMode: "setMode",
|
||||
},
|
||||
toVSCode: {
|
||||
openLink: "openLink",
|
||||
}
|
||||
};
|
||||
@@ -32,6 +32,7 @@ export const SETTING_SEO_DESCRIPTION_FIELD = "taxonomy.seoDescriptionField";
|
||||
|
||||
export const SETTING_TEMPLATES_FOLDER = "templates.folder";
|
||||
export const SETTING_TEMPLATES_PREFIX = "templates.prefix";
|
||||
export const SETTING_TEMPLATES_ENABLED = "templates.enabled";
|
||||
|
||||
export const SETTING_TELEMETRY_DISABLE = "telemetry.disable";
|
||||
|
||||
@@ -61,7 +62,6 @@ export const SETTING_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes";
|
||||
export const SETTING_MEDIA_SUPPORTED_MIMETYPES = "media.supportedMimeTypes";
|
||||
|
||||
export const SETTING_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
|
||||
export const SETTING_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
|
||||
export const SETTING_DASHBOARD_CONTENT_TAGS = "dashboard.content.cardTags";
|
||||
|
||||
export const SETTING_DATA_FILES = "data.files";
|
||||
@@ -89,3 +89,8 @@ export const SETTING_DATE_FIELD = "taxonomy.dateField";
|
||||
* Use the `isModifiedDate` property on the content type datetime field instead
|
||||
*/
|
||||
export const SETTING_MODIFIED_FIELD = "taxonomy.modifiedField";
|
||||
/**
|
||||
* @deprecated
|
||||
* Use the `frontMatter.content.snippets` setting instead
|
||||
*/
|
||||
export const SETTING_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { EyeIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { EyeIcon, TerminalIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { CustomScript, ScriptType } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
@@ -43,7 +43,7 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
return (scripts || []).filter(script => (script.type === undefined || script.type === ScriptType.Content) && !script.bulk).map(script => (
|
||||
<MenuItem
|
||||
key={script.title}
|
||||
title={script.title}
|
||||
title={<div className='flex items-center'><TerminalIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>{script.title}</span></div>}
|
||||
onClick={(value, e) => runCustomScript(e, script)} />
|
||||
))
|
||||
}, [scripts]);
|
||||
@@ -70,15 +70,15 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
|
||||
<ActionMenuButton title={`Menu`} />
|
||||
|
||||
<MenuItems widthClass='w-40' marginTopClass='mt-6'>
|
||||
<MenuItems widthClass='w-44' marginTopClass='mt-6'>
|
||||
<MenuItem
|
||||
title={`View`}
|
||||
title={<div className='flex items-center'><EyeIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>View</span></div>}
|
||||
onClick={(value, e) => onView(e)} />
|
||||
|
||||
{ customScriptActions }
|
||||
|
||||
<MenuItem
|
||||
title={`Delete`}
|
||||
title={<div className='flex items-center'><TrashIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Delete</span></div>}
|
||||
onClick={(value, e) => onDelete(e)} />
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
|
||||
@@ -9,14 +9,16 @@ import { Status } from '../Status';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardViewType } from '../../models';
|
||||
import { ContentActions } from './ContentActions';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface IItemProps extends Page {}
|
||||
|
||||
const PREVIEW_IMAGE_FIELD = 'fmPreviewImage';
|
||||
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, draft, description, type, ...pageData }: React.PropsWithChildren<IItemProps>) => {
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, description, type, ...pageData }: React.PropsWithChildren<IItemProps>) => {
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const draftField = useMemo(() => settings?.draftField, [settings]);
|
||||
|
||||
const openFile = () => {
|
||||
Messenger.send(DashboardMessage.openFile, fmFilePath);
|
||||
@@ -51,7 +53,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
|
||||
<div className="relative p-4 w-full">
|
||||
<div className={`flex justify-between items-center`}>
|
||||
<Status draft={draft} />
|
||||
{ draftField && draftField.name && <Status draft={pageData[draftField.name]} /> }
|
||||
|
||||
<DateField className={`mr-4`} value={date} />
|
||||
|
||||
@@ -96,7 +98,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
<DateField value={date} />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Status draft={draft} />
|
||||
{ draftField && draftField.name && <Status draft={pageData[draftField.name]} /> }
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -149,9 +149,9 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (props: React.P
|
||||
<div className={`divide-y divide-gray-200 dark:divide-vulcan-300 border-t border-b border-gray-200 dark:border-vulcan-300`}>
|
||||
{
|
||||
(dataFiles && dataFiles.length > 0) && (
|
||||
dataFiles.map((dataFile) => (
|
||||
dataFiles.map((dataFile, idx) => (
|
||||
<button
|
||||
key={dataFile.id}
|
||||
key={`${dataFile.id}-${idx}`}
|
||||
type='button'
|
||||
className={`px-4 py-2 flex items-center text-sm font-medium w-full text-left hover:bg-gray-200 dark:hover:bg-vulcan-400 hover:text-vulcan-500 dark:hover:text-whisper-500 ${selectedData?.id === dataFile.id ? 'bg-gray-300 dark:bg-vulcan-300 text-vulcan-500 dark:text-whisper-500' : 'text-gray-500 dark:text-whisper-900'}`}
|
||||
onClick={() => setSchema(dataFile)}>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { ClipboardIcon, CodeIcon, DocumentIcon, EyeIcon, MusicNoteIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon, VideoCameraIcon } from '@heroicons/react/outline';
|
||||
import { ClipboardIcon, CodeIcon, DocumentIcon, EyeIcon, MusicNoteIcon, PencilIcon, PhotographIcon, PlusIcon, TerminalIcon, TrashIcon, VideoCameraIcon } from '@heroicons/react/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { CustomScript } from '../../../helpers/CustomScript';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { ScriptType } from '../../../models';
|
||||
import { SnippetParser } from '../../../helpers/SnippetParser';
|
||||
import { ScriptType, Snippet } from '../../../models';
|
||||
import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
@@ -15,6 +16,7 @@ import { MenuItem, MenuItems } from '../Menu';
|
||||
import { ActionMenuButton } from '../Menu/ActionMenuButton';
|
||||
import { QuickAction } from '../Menu/QuickAction';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { InfoDialog } from '../Modals/InfoDialog';
|
||||
import { DetailsSlideOver } from './DetailsSlideOver';
|
||||
|
||||
export interface IItemProps {
|
||||
@@ -25,6 +27,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
const [ , setLightbox ] = useRecoilState(LightboxAtom);
|
||||
const [ showAlert, setShowAlert ] = React.useState(false);
|
||||
const [ showForm, setShowForm ] = React.useState(false);
|
||||
const [ showSnippetSelection, setShowSnippetSelection ] = React.useState(false);
|
||||
const [ showDetails, setShowDetails ] = React.useState(false);
|
||||
const [ caption, setCaption ] = React.useState(media.caption);
|
||||
const [ alt, setAlt ] = React.useState(media.alt);
|
||||
@@ -33,6 +36,15 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
|
||||
const mediaSnippets = useMemo(() => {
|
||||
if (!settings?.snippets) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const keys = Object.keys(settings.snippets);
|
||||
return keys.filter(key => (settings.snippets || {})[key].isMediaSnippet).map(key => ({ title: key, ...(settings.snippets || {})[key]}));
|
||||
}, [settings]);
|
||||
|
||||
const getFolder = () => {
|
||||
if (settings?.wsFolder && media.fsPath) {
|
||||
let relPath = media.fsPath.split(settings.wsFolder).pop();
|
||||
@@ -104,25 +116,39 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
};
|
||||
|
||||
const insertSnippet = useCallback(() => {
|
||||
const relPath = getRelPath();
|
||||
let snippet = settings?.mediaSnippet.join("\n");
|
||||
if (mediaSnippets.length === 1) {
|
||||
processSnippet(mediaSnippets[0]);
|
||||
} else {
|
||||
// Show dialog to select
|
||||
setShowSnippetSelection(true);
|
||||
}
|
||||
}, [mediaSnippets]);
|
||||
|
||||
snippet = snippet?.replace("{mediaUrl}", parseWinPath(relPath) || "");
|
||||
snippet = snippet?.replace("{alt}", alt || "");
|
||||
snippet = snippet?.replace("{caption}", caption || "");
|
||||
snippet = snippet?.replace("{title}", media.title || "");
|
||||
snippet = snippet?.replace("{filename}", basename(relPath || ""));
|
||||
snippet = snippet?.replace("{mediaWidth}", media?.dimensions?.width?.toString() || "");
|
||||
snippet = snippet?.replace("{mediaHeight}", media?.dimensions?.height?.toString() || "");
|
||||
const processSnippet = useCallback((snippet: Snippet) => {
|
||||
setShowSnippetSelection(false);
|
||||
|
||||
const relPath = getRelPath();
|
||||
|
||||
const fieldData = {
|
||||
mediaUrl: parseWinPath(relPath) || "",
|
||||
alt: alt || "",
|
||||
caption: caption || "",
|
||||
title: media.title || "",
|
||||
filename: basename(relPath || ""),
|
||||
mediaWidth: media?.dimensions?.width?.toString() || "",
|
||||
mediaHeight: media?.dimensions?.height?.toString() || "",
|
||||
};
|
||||
|
||||
const output = SnippetParser.render(snippet.body, fieldData, snippet?.openingTags, snippet?.closingTags);
|
||||
|
||||
Messenger.send(DashboardMessage.insertMedia, {
|
||||
relPath: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath,
|
||||
fieldName: viewData?.data?.fieldName,
|
||||
position: viewData?.data?.position || null,
|
||||
snippet
|
||||
snippet: output
|
||||
});
|
||||
}, [alt, caption, media, settings, viewData]);
|
||||
}, [alt, caption, media, settings, viewData, mediaSnippets]);
|
||||
|
||||
const deleteMedia = () => {
|
||||
setShowAlert(true);
|
||||
@@ -198,7 +224,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
return (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFile).map(script => (
|
||||
<MenuItem
|
||||
key={script.title}
|
||||
title={script.title}
|
||||
title={<div className='flex items-center'><TerminalIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>{script.title}</span></div>}
|
||||
onClick={() => runCustomScript(script)} />
|
||||
))
|
||||
}
|
||||
@@ -323,7 +349,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
</QuickAction>
|
||||
|
||||
{
|
||||
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
|
||||
(viewData?.data?.position && mediaSnippets.length > 0) && (
|
||||
<QuickAction
|
||||
title='Insert snippet'
|
||||
onClick={insertSnippet}>
|
||||
@@ -354,7 +380,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
|
||||
<MenuItems widthClass='w-40'>
|
||||
<MenuItem
|
||||
title={`Edit metadata`}
|
||||
title={<div className='flex items-center'><PencilIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Edit metadata</span></div>}
|
||||
onClick={updateMetadata}
|
||||
/>
|
||||
|
||||
@@ -362,15 +388,16 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
viewData?.data?.filePath ? (
|
||||
<>
|
||||
<MenuItem
|
||||
title={`Insert image markdown`}
|
||||
title={<div className='flex items-center'><PlusIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Insert image markdown</span></div>}
|
||||
onClick={insertToArticle} />
|
||||
|
||||
{
|
||||
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
|
||||
(viewData?.data?.position && mediaSnippets.length > 0) && mediaSnippets.map((snippet, idx) => (
|
||||
<MenuItem
|
||||
title={`Insert snippet`}
|
||||
onClick={insertSnippet} />
|
||||
)
|
||||
key={idx}
|
||||
title={<div className='flex items-center'><CodeIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>{snippet.title}</span></div>}
|
||||
onClick={() => processSnippet(snippet)} />
|
||||
))
|
||||
}
|
||||
|
||||
{ customScriptActions() }
|
||||
@@ -387,11 +414,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
}
|
||||
|
||||
<MenuItem
|
||||
title={`Reveal media`}
|
||||
title={<div className='flex items-center'><EyeIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Reveal media</span></div>}
|
||||
onClick={revealMedia} />
|
||||
|
||||
<MenuItem
|
||||
title={`Delete`}
|
||||
title={<div className='flex items-center'><TrashIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Delete</span></div>}
|
||||
onClick={deleteMedia} />
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
@@ -436,6 +463,32 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{
|
||||
showSnippetSelection && (
|
||||
<InfoDialog
|
||||
icon={<CodeIcon className="h-6 w-6" aria-hidden="true" />}
|
||||
title='Insert snippet'
|
||||
description='Select the media snippet to use for the current media file.'
|
||||
dismiss={() => setShowSnippetSelection(false)}>
|
||||
|
||||
<ul className='flex justify-center'>
|
||||
{
|
||||
mediaSnippets.map((snippet, idx) => (
|
||||
<li key={idx} className="inline-flex items-center pb-2 mr-2">
|
||||
<button
|
||||
className="w-full inline-flex justify-center border border-transparent shadow-sm px-4 py-2 bg-teal-600 text-base font-medium text-white hover:bg-teal-700 dark:hover:bg-teal-900 focus:outline-none sm:w-auto sm:text-sm disabled:opacity-30"
|
||||
onClick={() => processSnippet(snippet)}>
|
||||
{snippet.title}
|
||||
</button>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
</InfoDialog>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
showDetails && (
|
||||
<DetailsSlideOver
|
||||
|
||||
@@ -18,7 +18,7 @@ export const MenuItems: React.FunctionComponent<IMenuItemsProps> = ({widthClass,
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className={`${widthClass || ""} ${marginTopClass || "mt-2"} origin-top-right absolute right-0 z-10 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`}>
|
||||
<Menu.Items className={`${widthClass || ""} ${marginTopClass || "mt-2"} origin-top-right absolute right-0 z-20 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`}>
|
||||
<div className="py-1">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
72
src/dashboardWebView/components/Modals/InfoDialog.tsx
Normal file
72
src/dashboardWebView/components/Modals/InfoDialog.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
export interface IInfoDialogProps {
|
||||
icon?: JSX.Element;
|
||||
title: string;
|
||||
description: string;
|
||||
dismiss: () => void;
|
||||
}
|
||||
|
||||
export const InfoDialog: React.FunctionComponent<IInfoDialogProps> = ({dismiss, icon, title, description, children}: React.PropsWithChildren<IInfoDialogProps>) => {
|
||||
return (
|
||||
<Transition.Root show={true} as={Fragment}>
|
||||
<Dialog className="fixed z-10 inset-0 overflow-y-auto" onClose={dismiss}>
|
||||
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-vulcan-500 bg-opacity-75 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div className="inline-block align-bottom bg-white dark:bg-vulcan-500 rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6 border-2 border-whisper-900">
|
||||
<div className="sm:flex sm:items-start">
|
||||
{
|
||||
icon && (
|
||||
<div className="mt-3 sm:mr-4 mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full sm:mx-0 sm:h-10 sm:w-10 bg-gray-50 dark:bg-vulcan-400">
|
||||
{icon}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="mt-3 text-center sm:mt-0 sm:text-left">
|
||||
<Dialog.Title as="h3" className="text-lg leading-6 font-medium text-vulcan-300 dark:text-whisper-900">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-vulcan-500 dark:text-whisper-500">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 sm:mt-4">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { CodeIcon, DotsHorizontalIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { CodeIcon, DocumentTextIcon, DotsHorizontalIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlag } from '../../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../../constants';
|
||||
import { SnippetParser } from '../../../helpers/SnippetParser';
|
||||
import { Snippet, Snippets } from '../../../models';
|
||||
import { FileIcon } from '../../../panelWebView/components/Icons/FileIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { QuickAction } from '../Menu';
|
||||
@@ -31,9 +32,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
const [ snippetTitle, setSnippetTitle ] = useState<string>('');
|
||||
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
|
||||
const [ snippetOriginalBody, setSnippetOriginalBody ] = useState<string>('');
|
||||
const [ mediaSnippet, setMediaSnippet ] = useState<boolean>(false);
|
||||
|
||||
const formRef = useRef<SnippetFormHandle>(null);
|
||||
|
||||
const insertToContent = useMemo(() => viewData?.data?.filePath, [ viewData ]);
|
||||
|
||||
const insertToArticle = () => {
|
||||
formRef.current?.onSave();
|
||||
setShowInsertDialog(false);
|
||||
@@ -44,6 +48,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
setSnippetTitle('');
|
||||
setSnippetDescription('');
|
||||
setSnippetOriginalBody('');
|
||||
setMediaSnippet(false);
|
||||
};
|
||||
|
||||
const onOpenEdit = useCallback(() => {
|
||||
@@ -51,6 +56,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
setSnippetDescription(snippet.description);
|
||||
setSnippetOriginalBody(typeof snippet.body === "string" ? snippet.body : snippet.body.join(`\n`));
|
||||
setShowEditDialog(true);
|
||||
setMediaSnippet(!!snippet.isMediaSnippet);
|
||||
}, [snippet]);
|
||||
|
||||
const onSnippetUpdate = useCallback(() => {
|
||||
@@ -68,11 +74,16 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
|
||||
const snippetContents: Snippet = {
|
||||
...crntSnippet,
|
||||
fields,
|
||||
description: snippetDescription || '',
|
||||
body: snippetLines.length === 1 ? snippetLines[0] : snippetLines
|
||||
};
|
||||
|
||||
if (!mediaSnippet) {
|
||||
snippetContents.fields = fields;
|
||||
} else {
|
||||
snippetContents.isMediaSnippet = true;
|
||||
}
|
||||
|
||||
// Check if new or update
|
||||
if (title === snippetTitle) {
|
||||
snippets[title] = snippetContents;
|
||||
@@ -84,7 +95,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
Messenger.send(DashboardMessage.updateSnippet, { snippets });
|
||||
|
||||
reset();
|
||||
}, [settings?.snippets, title, snippetTitle, snippetDescription, snippetOriginalBody]);
|
||||
}, [settings?.snippets, title, snippetTitle, snippetDescription, snippetOriginalBody, mediaSnippet]);
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
const snippets = Object.assign({}, settings?.snippets || {});
|
||||
@@ -102,13 +113,17 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
<CodeIcon className='w-64 h-64 opacity-5 text-vulcan-200 dark:text-gray-400' />
|
||||
</div>
|
||||
|
||||
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
|
||||
<h2 className="mt-2 mb-2 font-bold flex items-center" title={snippet.isMediaSnippet ? "Media snippet" : "Content snippet"}>
|
||||
{ snippet.isMediaSnippet ? <PhotographIcon className='w-5 h-5 mr-1' aria-hidden={true} /> : <DocumentTextIcon className='w-5 h-5 mr-1' aria-hidden={true} /> }
|
||||
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<FeatureFlag
|
||||
features={mode?.features || []}
|
||||
flag={FEATURE_FLAG.dashboard.snippets.manage}
|
||||
alternative={(
|
||||
viewData?.data?.filePath ? (
|
||||
insertToContent ? (
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className="flex items-center border border-transparent group-hover:bg-gray-200 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
|
||||
<div className='group-hover:hidden'>
|
||||
@@ -135,7 +150,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
|
||||
<div className='hidden group-hover:flex'>
|
||||
{
|
||||
viewData?.data?.filePath && (
|
||||
insertToContent && !snippet.isMediaSnippet && (
|
||||
<>
|
||||
<QuickAction
|
||||
title={`Insert snippet`}
|
||||
@@ -170,7 +185,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
<FormDialog
|
||||
title={`Insert snippet: ${title}`}
|
||||
description={`Insert the ${title.toLowerCase()} snippet into the current article`}
|
||||
isSaveDisabled={!viewData?.data?.filePath}
|
||||
isSaveDisabled={!insertToContent}
|
||||
trigger={insertToArticle}
|
||||
dismiss={() => setShowInsertDialog(false)}
|
||||
okBtnText='Insert'
|
||||
@@ -200,6 +215,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
title={snippetTitle}
|
||||
description={snippetDescription}
|
||||
body={snippetOriginalBody}
|
||||
isMediaSnippet={mediaSnippet}
|
||||
onMediaSnippetUpdate={(value: boolean) => setMediaSnippet(value)}
|
||||
onTitleUpdate={(value: string) => setSnippetTitle(value)}
|
||||
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
|
||||
onBodyUpdate={(value: string) => setSnippetOriginalBody(value)} />
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { GeneralCommands } from '../../../constants';
|
||||
|
||||
export interface INewFormProps {
|
||||
title: string;
|
||||
description: string;
|
||||
body: string;
|
||||
isMediaSnippet: boolean;
|
||||
|
||||
onMediaSnippetUpdate: (value: boolean) => void;
|
||||
onTitleUpdate: (value: string) => void;
|
||||
onDescriptionUpdate: (value: string) => void;
|
||||
onBodyUpdate: (value: string) => void;
|
||||
}
|
||||
|
||||
export const NewForm: React.FunctionComponent<INewFormProps> = ({ title, description, body, onTitleUpdate, onDescriptionUpdate, onBodyUpdate }: React.PropsWithChildren<INewFormProps>) => {
|
||||
export const NewForm: React.FunctionComponent<INewFormProps> = ({ title, description, body, isMediaSnippet, onMediaSnippetUpdate, onTitleUpdate, onDescriptionUpdate, onBodyUpdate }: React.PropsWithChildren<INewFormProps>) => {
|
||||
|
||||
const openLink = () => {
|
||||
Messenger.send(GeneralCommands.toVSCode.openLink, "https://frontmatter.codes/docs/markdown#placeholders");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
@@ -60,6 +68,36 @@ export const NewForm: React.FunctionComponent<INewFormProps> = ({ title, descrip
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor={`snippet`} className="block text-sm font-medium">
|
||||
Is a media snippet?
|
||||
</label>
|
||||
<div className="mt-1 relative flex items-start">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
id="isMediaSnippet"
|
||||
aria-describedby="isMediaSnippet-description"
|
||||
name="isMediaSnippet"
|
||||
type="checkbox"
|
||||
checked={isMediaSnippet}
|
||||
onChange={(e) => onMediaSnippetUpdate(e.currentTarget.checked)}
|
||||
className="focus:ring-teal-500 h-4 w-4 text-teal-600 border-gray-300 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
<label htmlFor="isMediaSnippet" className="font-medium text-vulcan-100 dark:text-whisper-900">
|
||||
Media snippet
|
||||
</label>
|
||||
<p id="isMediaSnippet-description" className="text-vulcan-300 dark:text-whisper-500">
|
||||
Use the current snippet for inserting media files into your content.
|
||||
</p>
|
||||
<p>
|
||||
Check our <button className='text-teal-700 hover:text-teal-500' onClick={openLink} title='media snippet placeholders'>media snippet placeholders</button> documentation to know which placeholders you can use.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -25,6 +25,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
|
||||
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
|
||||
const [ snippetBody, setSnippetBody ] = useState<string>('');
|
||||
const [ showCreateDialog, setShowCreateDialog ] = useState(false);
|
||||
const [ mediaSnippet, setMediaSnippet ] = useState(false);
|
||||
|
||||
const snippets = settings?.snippets || {};
|
||||
const snippetKeys = useMemo(() => Object.keys(snippets) || [], [settings?.snippets]);
|
||||
@@ -41,11 +42,12 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
|
||||
title: snippetTitle,
|
||||
description: snippetDescription || '',
|
||||
body: snippetBody,
|
||||
fields
|
||||
fields,
|
||||
isMediaSnippet: mediaSnippet
|
||||
});
|
||||
|
||||
reset();
|
||||
}, [snippetTitle, snippetDescription, snippetBody]);
|
||||
}, [snippetTitle, snippetDescription, snippetBody, mediaSnippet]);
|
||||
|
||||
const reset = () => {
|
||||
setShowCreateDialog(false);
|
||||
@@ -127,6 +129,8 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
|
||||
title={snippetTitle}
|
||||
description={snippetDescription}
|
||||
body={snippetBody}
|
||||
isMediaSnippet={mediaSnippet}
|
||||
onMediaSnippetUpdate={(value: boolean) => setMediaSnippet(value)}
|
||||
onTitleUpdate={(value: string) => setSnippetTitle(value)}
|
||||
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
|
||||
onBodyUpdate={(value: string) => setSnippetBody(value)} />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsAtom } from '../state';
|
||||
|
||||
@@ -9,15 +10,29 @@ export interface IStatusProps {
|
||||
export const Status: React.FunctionComponent<IStatusProps> = ({draft}: React.PropsWithChildren<IStatusProps>) => {
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
|
||||
const draftField = useMemo(() => settings?.draftField, [settings]);
|
||||
|
||||
const draftValue = useMemo(() => {
|
||||
if (draftField && draftField.type === 'choice') {
|
||||
return draft;
|
||||
} else if (draftField && typeof draftField.invert !== 'undefined' && draftField.invert) {
|
||||
return !draft;
|
||||
} else {
|
||||
return draft;
|
||||
}
|
||||
}, [draftField, draft]);
|
||||
|
||||
if (settings?.draftField && settings.draftField.type === "choice") {
|
||||
if (draft) {
|
||||
return <span className={`inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 bg-teal-500`}>{draft}</span>;
|
||||
if (draftValue) {
|
||||
return <span className={`inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 bg-teal-500`}>{draftValue}</span>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={`inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 ${draft ? "bg-red-500" : "bg-teal-500"}`}>{draft ? "Draft" : "Published"}</span>
|
||||
<span className={`inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 ${draftValue ? "bg-red-500" : "bg-teal-500"}`}>
|
||||
{draftValue ? "Draft" : "Published"}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -47,7 +47,7 @@ export default function useMessages() {
|
||||
case DashboardCommand.searchReady:
|
||||
setSearchReady(true);
|
||||
break;
|
||||
case GeneralCommands.setMode:
|
||||
case GeneralCommands.toWebview.setMode:
|
||||
setMode(message.data);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Sorting } from '../../helpers/Sorting';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../DashboardMessage';
|
||||
import { EventData } from '@estruyf/vscode/dist/models';
|
||||
import { parseWinPath } from '../../helpers/parseWinPath';
|
||||
|
||||
export default function usePages(pages: Page[]) {
|
||||
const [ pageItems, setPageItems ] = useState<Page[]>([]);
|
||||
@@ -24,10 +25,27 @@ export default function usePages(pages: Page[]) {
|
||||
|
||||
const processPages = useCallback((searchedPages: Page[]) => {
|
||||
const draftField = settings?.draftField;
|
||||
const framework = settings?.crntFramework;
|
||||
|
||||
// Filter the pages
|
||||
let pagesToShow: Page[] = Object.assign([], searchedPages);
|
||||
|
||||
// Framework specific actions
|
||||
if (framework?.toLowerCase() === "jekyll") {
|
||||
pagesToShow = pagesToShow.map(page => {
|
||||
// https://jekyllrb.com/docs/posts/#drafts
|
||||
const filePath = parseWinPath(page.fmFilePath);
|
||||
page.draft = filePath.indexOf(`/_drafts/`) > -1;
|
||||
|
||||
// Published field: https://jekyllrb.com/docs/front-matter/#predefined-global-variables
|
||||
if (typeof page.published !== "undefined") {
|
||||
page.draft = !page.published;
|
||||
}
|
||||
|
||||
return page;
|
||||
});
|
||||
}
|
||||
|
||||
const draftTypes = Object.assign({}, tabInfo);
|
||||
draftTypes[Tab.All] = pagesToShow.length;
|
||||
|
||||
@@ -46,15 +64,19 @@ export default function usePages(pages: Page[]) {
|
||||
pagesToShow = searchedPages;
|
||||
}
|
||||
} else {
|
||||
// Draft field is a boolean field
|
||||
const draftFieldName = draftField?.name || "draft";
|
||||
|
||||
const drafts = pagesToShow.filter(page => page[draftFieldName] == true || page[draftFieldName] === "true");
|
||||
const published = pagesToShow.filter(page => page[draftFieldName] == false || page[draftFieldName] === "false" || typeof page[draftFieldName] === "undefined");
|
||||
|
||||
draftTypes[Tab.Draft] = pagesToShow.filter(page => !!page[draftFieldName]).length;
|
||||
draftTypes[Tab.Published] = pagesToShow.filter(page => !page[draftFieldName]).length;
|
||||
draftTypes[Tab.Draft] = draftField?.invert ? published.length : drafts.length;
|
||||
draftTypes[Tab.Published] = draftField?.invert ? drafts.length : published.length;
|
||||
|
||||
if (tab === Tab.Published) {
|
||||
pagesToShow = searchedPages.filter(page => !page[draftFieldName]);
|
||||
pagesToShow = draftField?.invert ? drafts : published;
|
||||
} else if (tab === Tab.Draft) {
|
||||
pagesToShow = searchedPages.filter(page => !!page[draftFieldName]);
|
||||
pagesToShow = draftField?.invert ? published : drafts;
|
||||
} else {
|
||||
pagesToShow = searchedPages;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface Page {
|
||||
title: string;
|
||||
slug: string;
|
||||
date: string | Date;
|
||||
draft: string;
|
||||
draft: boolean | string;
|
||||
description: string;
|
||||
|
||||
preview?: string;
|
||||
|
||||
@@ -16,7 +16,6 @@ export interface Settings {
|
||||
openOnStart: boolean | null;
|
||||
versionInfo: VersionInfo;
|
||||
pageViewType: DashboardViewType | undefined;
|
||||
mediaSnippet: string[];
|
||||
contentTypes: ContentType[];
|
||||
contentFolders: ContentFolder[];
|
||||
crntFramework: string;
|
||||
|
||||
@@ -136,6 +136,10 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
});
|
||||
|
||||
let createTemplate = vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.initTemplate, () => Project.createSampleTemplate(true))
|
||||
);
|
||||
|
||||
const toggleDraftCommand = COMMAND_NAME.toggleDraft;
|
||||
const toggleDraft = vscode.commands.registerCommand(toggleDraftCommand, async () => {
|
||||
@@ -211,6 +215,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
subscriptions.push(vscode.workspace.onDidChangeTextDocument((TextDocumentChangeEvent) => {
|
||||
const filePath = TextDocumentChangeEvent.document.uri.fsPath;
|
||||
if (filePath && !filePath.toLowerCase().startsWith(`extension-output`)) {
|
||||
MarkdownFoldingProvider.triggerHighlighting();
|
||||
statusDebouncer(() => triggerShowDraftStatus(`onDidChangeTextEditorSelection`), 200);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Uri, workspace } from 'vscode';
|
||||
import { MarkdownFoldingProvider } from './../providers/MarkdownFoldingProvider';
|
||||
import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ContentType';
|
||||
import * as vscode from 'vscode';
|
||||
@@ -82,6 +83,23 @@ export class ArticleHelper {
|
||||
await editor.edit(builder => builder.replace(update.range, update.newText));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the new information for the article path
|
||||
*
|
||||
* @param path
|
||||
* @param article
|
||||
*/
|
||||
public static async updateByPath(path: string, article: ParsedFrontMatter) {
|
||||
const file = await workspace.openTextDocument(Uri.parse(path));
|
||||
const editor = await window.showTextDocument(file);
|
||||
|
||||
if (file && editor) {
|
||||
const update = this.generateUpdate(file, article);
|
||||
|
||||
await editor.edit(builder => builder.replace(update.range, update.newText));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the update to be applied to the article.
|
||||
* @param article
|
||||
@@ -97,7 +115,7 @@ export class ArticleHelper {
|
||||
const lastLine = lines.pop();
|
||||
const endsWithNewLine = lastLine !== undefined && lastLine.trim() === "";
|
||||
|
||||
let newMarkdown = this.stringifyFrontMatter(article.content, Object.assign({}, article.data));
|
||||
let newMarkdown = this.stringifyFrontMatter(article.content, Object.assign({}, article.data), document?.getText());
|
||||
|
||||
// Logic to not include a new line at the end of the file
|
||||
if (!endsWithNewLine) {
|
||||
@@ -132,8 +150,9 @@ export class ArticleHelper {
|
||||
*
|
||||
* @param content
|
||||
* @param data
|
||||
* @param originalContent
|
||||
*/
|
||||
public static stringifyFrontMatter(content: string, data: any) {
|
||||
public static stringifyFrontMatter(content: string, data: any, originalContent?: string) {
|
||||
const indentArray = Settings.get(SETTING_INDENT_ARRAY) as boolean;
|
||||
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
|
||||
|
||||
@@ -147,7 +166,7 @@ export class ArticleHelper {
|
||||
}
|
||||
}
|
||||
|
||||
return FrontMatterParser.toFile(content, data, ({
|
||||
return FrontMatterParser.toFile(content, data, originalContent, ({
|
||||
noArrayIndent: !indentArray,
|
||||
skipInvalid: true,
|
||||
noCompatMode: true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ModeListener } from './../listeners/general/ModeListener';
|
||||
import { PagesListener } from './../listeners/dashboard';
|
||||
import { ArticleHelper, Settings } from ".";
|
||||
import { FEATURE_FLAG, SETTING_CONTENT_DRAFT_FIELD, SETTING_DATE_FORMAT, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
|
||||
import { FEATURE_FLAG, SETTING_CONTENT_DRAFT_FIELD, SETTING_DATE_FORMAT, SETTING_FRAMEWORK_ID, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
|
||||
import { ContentType as IContentType, DraftField, Field } from '../models';
|
||||
import { Uri, commands, window } from 'vscode';
|
||||
import { Folders } from "../commands/Folders";
|
||||
@@ -367,6 +367,16 @@ export class ContentType {
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentType.name === "default") {
|
||||
const crntFramework = Settings.get<string>(SETTING_FRAMEWORK_ID);
|
||||
if (crntFramework?.toLowerCase() === "jekyll") {
|
||||
const idx = contentType.fields.findIndex(f => f.name === "draft");
|
||||
if (idx > -1) {
|
||||
contentType.fields.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data: any = this.processFields(contentType, titleValue, {});
|
||||
|
||||
data = ArticleHelper.updateDates(Object.assign({}, data));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommandType } from './../models/PanelSettings';
|
||||
import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettings';
|
||||
import { window, env as vscodeEnv, ProgressLocation } from 'vscode';
|
||||
import { ArticleHelper, Telemetry } from '.';
|
||||
import { ArticleHelper, Logger, Telemetry } from '.';
|
||||
import { Folders } from '../commands/Folders';
|
||||
import { exec } from 'child_process';
|
||||
import * as os from 'os';
|
||||
@@ -41,6 +42,13 @@ export class CustomScript {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the script on the current file
|
||||
* @param wsPath
|
||||
* @param script
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
private static async singleRun(wsPath: string, script: ICustomScript, path: string | null = null): Promise<void> {
|
||||
let articlePath: string | null = path;
|
||||
let article: ParsedFrontMatter | null = null;
|
||||
@@ -62,13 +70,19 @@ export class CustomScript {
|
||||
cancellable: false
|
||||
}, async () => {
|
||||
const output = await CustomScript.runScript(wsPath, article, articlePath as string, script);
|
||||
CustomScript.showOutput(output, script);
|
||||
CustomScript.showOutput(output, script, articlePath);
|
||||
});
|
||||
} else {
|
||||
Notifications.warning(`${script.title}: Article couldn't be retrieved.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the script on multiple files
|
||||
* @param wsPath
|
||||
* @param script
|
||||
* @returns
|
||||
*/
|
||||
private static async bulkRun(wsPath: string, script: ICustomScript): Promise<void> {
|
||||
const folders = await Folders.getInfo();
|
||||
|
||||
@@ -106,6 +120,13 @@ export class CustomScript {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a script for a media file
|
||||
* @param wsPath
|
||||
* @param path
|
||||
* @param script
|
||||
* @returns
|
||||
*/
|
||||
private static async runMediaScript(wsPath: string, path: string | null, script: ICustomScript): Promise<void> {
|
||||
if (!path) {
|
||||
Notifications.error(`${script.title}: There was no folder or media path specified.`);
|
||||
@@ -118,28 +139,34 @@ export class CustomScript {
|
||||
title: `Executing: ${script.title}`,
|
||||
cancellable: false
|
||||
}, async () => {
|
||||
exec(`${script.nodeBin || "node"} ${join(wsPath, script.script)} "${wsPath}" "${path}"`, (error, stdout) => {
|
||||
if (error) {
|
||||
Notifications.error(`${script.title}: ${error.message}`);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
CustomScript.showOutput(stdout, script);
|
||||
try {
|
||||
const output = await CustomScript.executeScript(script, wsPath, `"${wsPath}" "${path}"`);
|
||||
|
||||
CustomScript.showOutput(output, script);
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.mediaUpdate
|
||||
});
|
||||
|
||||
resolve();
|
||||
|
||||
return;
|
||||
});
|
||||
} catch (e) {
|
||||
Notifications.error(`${script.title}: ${(e as Error).message}`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Script runner
|
||||
* @param wsPath
|
||||
* @param article
|
||||
* @param contentPath
|
||||
* @param script
|
||||
* @returns
|
||||
*/
|
||||
private static async runScript(wsPath: string, article: ParsedFrontMatter | null, contentPath: string, script: ICustomScript): Promise<string | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let articleData = "";
|
||||
if (os.type() === "Windows_NT") {
|
||||
articleData = `"${JSON.stringify(article?.data).replace(/"/g, `""`)}"`;
|
||||
@@ -148,31 +175,97 @@ export class CustomScript {
|
||||
articleData = `'${articleData}'`;
|
||||
}
|
||||
|
||||
exec(`${script.nodeBin || "node"} ${join(wsPath, script.script)} "${wsPath}" "${contentPath}" ${articleData}`, (error, stdout) => {
|
||||
const output = await CustomScript.executeScript(script, wsPath, `"${wsPath}" "${contentPath}" ${articleData}`);
|
||||
return output;
|
||||
} catch (e) {
|
||||
Notifications.error(`${script.title}: ${(e as Error).message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show/process the output of the script
|
||||
* @param output
|
||||
* @param script
|
||||
*/
|
||||
private static showOutput(output: string | null, script: ICustomScript, articlePath?: string | null): void {
|
||||
if (output) {
|
||||
try {
|
||||
const data = JSON.parse(output);
|
||||
|
||||
if (data.frontmatter) {
|
||||
let article = null;
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
if (!articlePath) {
|
||||
if (!editor) return;
|
||||
|
||||
articlePath = editor.document.uri.fsPath;
|
||||
article = ArticleHelper.getFrontMatter(editor);
|
||||
} else {
|
||||
article = ArticleHelper.getFrontMatterByPath(articlePath);
|
||||
}
|
||||
|
||||
if (article && article.data) {
|
||||
for (const key in data.frontmatter) {
|
||||
article.data[key] = data.frontmatter[key];
|
||||
}
|
||||
|
||||
if (articlePath) {
|
||||
ArticleHelper.updateByPath(articlePath, article);
|
||||
} else if (editor) {
|
||||
ArticleHelper.update(editor, article);
|
||||
} else {
|
||||
throw new Error(`Couldn't update article.`);
|
||||
}
|
||||
Notifications.info(`${script.title}: front matter updated.`);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`No frontmatter found.`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (script.output === "editor") {
|
||||
ContentProvider.show(output, script.title, script.outputType || "text");
|
||||
} else {
|
||||
window.showInformationMessage(`${script.title}: ${output}`, 'Copy output').then(value => {
|
||||
if (value === 'Copy output') {
|
||||
vscodeEnv.clipboard.writeText(output);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Notifications.info(`${script.title}: Executed your custom script.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute script
|
||||
* @param script
|
||||
* @param wsPath
|
||||
* @param args
|
||||
* @returns
|
||||
*/
|
||||
private static async executeScript(script: ICustomScript, wsPath: string, args: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// Check the command to use
|
||||
let command = script.nodeBin || "node";
|
||||
if (script.command && script.command !== CommandType.Node) {
|
||||
command = script.command;
|
||||
}
|
||||
|
||||
const scriptPath = join(wsPath, script.script);
|
||||
const fullScript = `${command} ${scriptPath} ${args}`;
|
||||
Logger.info(`Executing: ${fullScript}`);
|
||||
|
||||
exec(fullScript, (error, stdout) => {
|
||||
if (error) {
|
||||
Notifications.error(`${script.title}: ${error.message}`);
|
||||
resolve(null);
|
||||
return;
|
||||
reject(error.message);
|
||||
}
|
||||
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static showOutput(output: string | null, script: ICustomScript): void {
|
||||
if (output) {
|
||||
if (script.output === "editor") {
|
||||
ContentProvider.show(output, script.title, script.outputType || "text");
|
||||
} else {
|
||||
window.showInformationMessage(`${script.title}: ${output}`, 'Copy output').then(value => {
|
||||
if (value === 'Copy output') {
|
||||
vscodeEnv.clipboard.writeText(output);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Notifications.info(`${script.title}: Executed your custom script.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { basename, join } from "path";
|
||||
import { workspace } from "vscode";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { Project } from "../commands/Project";
|
||||
import { Template } from "../commands/Template";
|
||||
import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_CONTENT_STATIC_FOLDER, SETTING_DASHBOARD_MEDIA_SNIPPET, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES } from "../constants";
|
||||
import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_CONTENT_STATIC_FOLDER, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES } from "../constants";
|
||||
import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models";
|
||||
import { CustomScript, DraftField, ScriptType, Snippets, SortingSetting, TaxonomyType } from "../models";
|
||||
import { CustomScript, DraftField, Snippets, SortingSetting, TaxonomyType } from "../models";
|
||||
import { DataFile } from "../models/DataFile";
|
||||
import { DataFolder } from "../models/DataFolder";
|
||||
import { DataType } from "../models/DataType";
|
||||
@@ -18,9 +19,7 @@ export class DashboardSettings {
|
||||
public static async get() {
|
||||
const ext = Extension.getInstance();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isInitialized = await Template.isInitialized();
|
||||
|
||||
const contentFolders = await Folders.getContentFolders();
|
||||
const isInitialized = Project.isInitialized();
|
||||
|
||||
return {
|
||||
beta: ext.isBetaVersion(),
|
||||
@@ -32,7 +31,6 @@ export class DashboardSettings {
|
||||
openOnStart: Settings.get(SETTING_DASHBOARD_OPENONSTART),
|
||||
versionInfo: ext.getVersion(),
|
||||
pageViewType: await ext.getState<DashboardViewType | undefined>(ExtensionState.PagesView, "workspace"),
|
||||
mediaSnippet: Settings.get<string[]>(SETTING_DASHBOARD_MEDIA_SNIPPET) || [],
|
||||
contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
|
||||
draftField: Settings.get<DraftField>(SETTING_CONTENT_DRAFT_FIELD),
|
||||
customSorting: Settings.get<SortingSetting[]>(SETTING_CONTENT_SORTING),
|
||||
@@ -56,7 +54,7 @@ export class DashboardSettings {
|
||||
mimeTypes: Settings.get<string[]>(SETTING_MEDIA_SUPPORTED_MIMETYPES)
|
||||
},
|
||||
welcome: {
|
||||
contentFolders
|
||||
contentFolders: await Folders.getContentFolders()
|
||||
}
|
||||
},
|
||||
dataFiles: await this.getDataFiles(),
|
||||
|
||||
74
src/helpers/DataFileHelper.ts
Normal file
74
src/helpers/DataFileHelper.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { DataFile } from "../models";
|
||||
import * as yaml from 'js-yaml';
|
||||
import { Logger } from "./Logger";
|
||||
import { Notifications } from "./Notifications";
|
||||
import { commands } from "vscode";
|
||||
import { COMMAND_NAME, SETTING_DATA_FILES } from "../constants";
|
||||
import { Settings } from "./SettingsHelper";
|
||||
|
||||
|
||||
export class DataFileHelper {
|
||||
|
||||
/**
|
||||
* Retrieve the file data
|
||||
* @param filePath
|
||||
* @returns
|
||||
*/
|
||||
public static get(filePath: string) {
|
||||
const absPath = Folders.getAbsFilePath(filePath);
|
||||
if (existsSync(absPath)) {
|
||||
return readFileSync(absPath, 'utf8');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get by the id of the data file
|
||||
* @param id
|
||||
*/
|
||||
public static getById(id: string) {
|
||||
const files = Settings.get<DataFile[]>(SETTING_DATA_FILES);
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = files.find(f => f.id === id);
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
return DataFileHelper.process(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the data file
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
public static async process(data: DataFile) {
|
||||
try {
|
||||
const { file, fileType } = data;
|
||||
const dataFile = DataFileHelper.get(file);
|
||||
|
||||
if (fileType === "yaml") {
|
||||
return yaml.safeLoad(dataFile || "");
|
||||
} else {
|
||||
return dataFile ? JSON.parse(dataFile) : undefined;
|
||||
}
|
||||
} catch (ex) {
|
||||
Logger.error(`DataFileHelper::process: ${(ex as Error).message}`);
|
||||
const btnClick = await Notifications.error(`Something went wrong while processing the data file. Check your file and output log for more information.`, 'Open output');
|
||||
|
||||
if (btnClick && btnClick === 'Open output') {
|
||||
commands.executeCommand(COMMAND_NAME.showOutputChannel);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { basename } from "path";
|
||||
import { extensions, Uri, ExtensionContext, window, workspace, commands, ExtensionMode, DiagnosticCollection, languages } from "vscode";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { EXTENSION_NAME, GITHUB_LINK, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, EXTENSION_BETA_ID, EXTENSION_ID, ExtensionState, CONFIG_KEY, SETTING_CONTENT_PAGE_FOLDERS } from "../constants";
|
||||
import { ContentFolder } from "../models";
|
||||
import { Template } from "../commands/Template";
|
||||
import { EXTENSION_NAME, GITHUB_LINK, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, EXTENSION_BETA_ID, EXTENSION_ID, ExtensionState, CONFIG_KEY, SETTING_CONTENT_PAGE_FOLDERS, SETTING_DASHBOARD_MEDIA_SNIPPET, SETTING_CONTENT_SNIPPETS, SETTING_TEMPLATES_ENABLED } from "../constants";
|
||||
import { ContentFolder, Snippet } from "../models";
|
||||
import { Notifications } from "./Notifications";
|
||||
import { Settings } from "./SettingsHelper";
|
||||
|
||||
@@ -189,6 +190,41 @@ export class Extension {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (major <= 7 && minor < 3) {
|
||||
const mediaSnippet = Settings.get<string[]>(SETTING_DASHBOARD_MEDIA_SNIPPET);
|
||||
if (mediaSnippet && mediaSnippet.length > 0) {
|
||||
let snippet = mediaSnippet.join(`\n`);
|
||||
|
||||
snippet = snippet.replace(`{mediaUrl}`, `[[&mediaUrl]]`);
|
||||
snippet = snippet.replace(`{mediaHeight}`, `[[mediaHeight]]`);
|
||||
snippet = snippet.replace(`{mediaWidth}`, `[[mediaWidth]]`);
|
||||
snippet = snippet.replace(`{caption}`, `[[&caption]]`);
|
||||
snippet = snippet.replace(`{alt}`, `[[alt]]`);
|
||||
snippet = snippet.replace(`{filename}`, `[[filename]]`);
|
||||
snippet = snippet.replace(`{title}`, `[[title]]`);
|
||||
|
||||
const snippets = Settings.get<Snippet[]>(SETTING_CONTENT_SNIPPETS) || {} as any;
|
||||
snippets[`Media snippet (migrated)`] = {
|
||||
body: snippet.split(`\n`),
|
||||
isMediaSnippet: true,
|
||||
description: `Migrated media snippet from frontMatter.dashboard.mediaSnippet setting`
|
||||
}
|
||||
|
||||
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
|
||||
}
|
||||
|
||||
const templates = await Template.getTemplates();
|
||||
if (templates && templates.length > 0) {
|
||||
const answer = await window.showQuickPick(["Yes", "No"], {
|
||||
title: "Front Matter - Templates",
|
||||
placeHolder: "Do you want to keep on using the template functionality?",
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
Settings.update(SETTING_TEMPLATES_ENABLED, answer?.toLocaleLowerCase() === "yes", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async setState<T>(propKey: string, propValue: T, type: "workspace" | "global" = "global"): Promise<void> {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import jsyaml = require("js-yaml");
|
||||
import { join, resolve } from "path";
|
||||
import { commands, Uri } from "vscode";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { COMMAND_NAME } from "../constants";
|
||||
import { FrameworkDetectors } from "../constants/FrameworkDetectors";
|
||||
import { Framework } from "../models";
|
||||
import { Logger } from "./Logger";
|
||||
|
||||
export class FrameworkDetector {
|
||||
|
||||
@@ -73,4 +79,50 @@ export class FrameworkDetector {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static checkDefaultSettings(framework: Framework) {
|
||||
if (framework.name.toLowerCase() === "jekyll") {
|
||||
FrameworkDetector.jekyll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static jekyll() {
|
||||
try {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const jekyllConfig = join(wsFolder?.fsPath || "", '_config.yml');
|
||||
let collectionDir = "";
|
||||
|
||||
if (existsSync(jekyllConfig)) {
|
||||
const content = readFileSync(jekyllConfig, "utf8");
|
||||
// Convert YAML to JSON
|
||||
const config = jsyaml.safeLoad(content);
|
||||
|
||||
if (config.collections_dir) {
|
||||
collectionDir = config.collections_dir;
|
||||
}
|
||||
}
|
||||
|
||||
const draftsPath = join(wsFolder?.fsPath || "", collectionDir, "_drafts");
|
||||
const postsPath = join(wsFolder?.fsPath || "", collectionDir, "_posts");
|
||||
|
||||
if (existsSync(draftsPath)) {
|
||||
const folderUri = Uri.file(draftsPath);
|
||||
commands.executeCommand(COMMAND_NAME.registerFolder, {
|
||||
title: "drafts",
|
||||
path: folderUri
|
||||
});
|
||||
}
|
||||
|
||||
if (existsSync(postsPath)) {
|
||||
const folderUri = Uri.file(postsPath);
|
||||
commands.executeCommand(COMMAND_NAME.registerFolder, {
|
||||
title: "posts",
|
||||
path: folderUri
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error(`Something failed while processing your Jekyll configuration. ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { Extension } from './Extension';
|
||||
import { OutputChannel, window } from 'vscode';
|
||||
import { commands, OutputChannel, window } from 'vscode';
|
||||
import { format } from 'date-fns';
|
||||
import { COMMAND_NAME } from '../constants';
|
||||
|
||||
export class Logger {
|
||||
private static instance: Logger;
|
||||
private static channel: OutputChannel | null = null;
|
||||
public static channel: OutputChannel | null = null;
|
||||
|
||||
private constructor() {
|
||||
const displayName = Extension.getInstance().displayName;
|
||||
Logger.channel = window.createOutputChannel(displayName);
|
||||
commands.registerCommand(COMMAND_NAME.showOutputChannel, () => { Logger.channel?.show(); });
|
||||
}
|
||||
|
||||
public static getInstance(): Logger {
|
||||
|
||||
@@ -65,7 +65,7 @@ export class MediaHelpers {
|
||||
relSelectedFolderPath = selectedFolder.replace(parsedPath, '');
|
||||
}
|
||||
|
||||
if (relSelectedFolderPath.startsWith('/')) {
|
||||
if (relSelectedFolderPath && relSelectedFolderPath.startsWith('/')) {
|
||||
relSelectedFolderPath = relSelectedFolderPath.substring(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
|
||||
import { CommandToCode } from "../panelWebView/CommandToCode";
|
||||
|
||||
|
||||
interface ClientVsCode<T> {
|
||||
getState: () => T;
|
||||
setState: (data: T) => void;
|
||||
postMessage: (msg: unknown) => void;
|
||||
}
|
||||
|
||||
declare const acquireVsCodeApi: <T = unknown>() => ClientVsCode<T>;
|
||||
|
||||
export class MessageHelper {
|
||||
private static vscode: ClientVsCode<any>;
|
||||
|
||||
public static getVsCodeAPI() {
|
||||
if (!MessageHelper.vscode) {
|
||||
MessageHelper.vscode = acquireVsCodeApi();
|
||||
}
|
||||
return MessageHelper.vscode;
|
||||
}
|
||||
|
||||
public static sendMessage = (command: CommandToCode | DashboardMessage, data?: any) => {
|
||||
const vscode = MessageHelper.getVsCodeAPI();
|
||||
if (data) {
|
||||
vscode.postMessage({ command, data });
|
||||
} else {
|
||||
vscode.postMessage({ command });
|
||||
}
|
||||
}
|
||||
|
||||
public static getState = () => {
|
||||
const vscode = MessageHelper.getVsCodeAPI();
|
||||
return vscode.getState();
|
||||
}
|
||||
|
||||
public static setState = (data: any) => {
|
||||
const vscode = MessageHelper.getVsCodeAPI();
|
||||
vscode.setState({
|
||||
...data
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Settings } from "./SettingsHelper";
|
||||
|
||||
|
||||
export class Notifications {
|
||||
private static notifications: string[] = [];
|
||||
|
||||
public static info(message: string, ...items: any): Thenable<string | undefined> {
|
||||
Logger.info(`${EXTENSION_NAME}: ${message}`, "INFO");
|
||||
@@ -36,6 +37,16 @@ export class Notifications {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
public static async errorShowOnce(message: string, ...items: any): Promise<string | undefined> {
|
||||
if (this.notifications.includes(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notifications.push(message);
|
||||
|
||||
return this.error(message, ...items);
|
||||
}
|
||||
|
||||
private static shouldShow(level: "INFO" | "WARNING" | "ERROR"): boolean {
|
||||
let levels = Settings.get<string[]>(SETTING_GLOBAL_NOTIFICATIONS);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export class Questions {
|
||||
* @returns
|
||||
*/
|
||||
public static async yesOrNo(placeholder: string) {
|
||||
const answer = await window.showQuickPick(["yes", "no"], { canPickMany: false, placeHolder: placeholder, ignoreFocusOut: false });
|
||||
const answer = await window.showQuickPick(["yes", "no"], { canPickMany: false, placeHolder: placeholder, ignoreFocusOut: true });
|
||||
return answer === "yes";
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ export class Questions {
|
||||
public static async ContentTitle(showWarning: boolean = true): Promise<string | undefined> {
|
||||
const title = await window.showInputBox({
|
||||
prompt: `What would you like to use as a title for the content to create?`,
|
||||
placeHolder: `Content title`
|
||||
placeHolder: `Content title`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (!title && showWarning) {
|
||||
@@ -82,7 +83,8 @@ export class Questions {
|
||||
|
||||
const selectedOption = await window.showQuickPick(options, {
|
||||
placeHolder: `Select the content type to create your new content`,
|
||||
canPickMany: false
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (!selectedOption && showWarning) {
|
||||
|
||||
@@ -170,6 +170,15 @@ export class Settings {
|
||||
await Settings.config.update(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the project contains the frontmatter.json file
|
||||
*/
|
||||
public static hasProjectFile() {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const configPath = join(wsFolder?.fsPath || "", Settings.globalFile);
|
||||
return existsSync(configPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create team settings
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from './ArticleHelper';
|
||||
export * from './ContentType';
|
||||
export * from './CustomScript';
|
||||
export * from './DashboardSettings';
|
||||
export * from './DataFileHelper';
|
||||
export * from './DateHelper';
|
||||
export * from './Extension';
|
||||
export * from './FilesHelper';
|
||||
@@ -11,13 +12,15 @@ export * from './ImageHelper';
|
||||
export * from './Logger';
|
||||
export * from './MediaHelpers';
|
||||
export * from './MediaLibrary';
|
||||
export * from './MessageHelper';
|
||||
export * from './Notifications';
|
||||
export * from './PanelSettings';
|
||||
export * from './PlaceholderHelper';
|
||||
export * from './Questions';
|
||||
export * from './Sanitize';
|
||||
export * from './SeoHelper';
|
||||
export * from './SettingsHelper';
|
||||
export * from './SlugHelper';
|
||||
export * from './SnippetParser';
|
||||
export * from './Sorting';
|
||||
export * from './StringHelpers';
|
||||
export * from './Telemetry';
|
||||
|
||||
@@ -3,11 +3,10 @@ import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { Folders } from '../../commands/Folders';
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { dirname } from 'path';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { Logger, Notifications } from '../../helpers';
|
||||
import { commands } from 'vscode';
|
||||
import { DataFileHelper } from '../../helpers';
|
||||
|
||||
|
||||
export class DataListener extends BaseListener {
|
||||
@@ -57,38 +56,7 @@ export class DataListener extends BaseListener {
|
||||
* @param msgData
|
||||
*/
|
||||
private static async processDataFile(msgData: DataFile) {
|
||||
try {
|
||||
const { file } = msgData;
|
||||
const dataFile = this.getDataFile(file);
|
||||
|
||||
if (msgData.fileType === "yaml") {
|
||||
const entries = yaml.safeLoad(dataFile || "");
|
||||
this.sendMsg(DashboardCommand.dataFileEntries, entries);
|
||||
} else {
|
||||
const jsonData = dataFile ? JSON.parse(dataFile) : [];
|
||||
this.sendMsg(DashboardCommand.dataFileEntries, jsonData);
|
||||
}
|
||||
} catch (ex) {
|
||||
Logger.error(`DataListener::processDataFile: ${(ex as Error).message}`);
|
||||
const btnClick = await Notifications.error(`Something went wrong while processing the data file. Check your file and output log for more information.`, 'Open output');
|
||||
|
||||
if (btnClick && btnClick === 'Open output') {
|
||||
commands.executeCommand(`workbench.panel.output.focus`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the file data
|
||||
* @param file
|
||||
* @returns
|
||||
*/
|
||||
private static getDataFile(file: string) {
|
||||
const absPath = Folders.getAbsFilePath(file);
|
||||
if (existsSync(absPath)) {
|
||||
return readFileSync(absPath, 'utf8');
|
||||
}
|
||||
|
||||
return null;
|
||||
const entries = await DataFileHelper.process(msgData);
|
||||
this.sendMsg(DashboardCommand.dataFileEntries, entries);
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,8 @@ export class SettingsListener extends BaseListener {
|
||||
const framework = allFrameworks.find((f: Framework) => f.name === frameworkId);
|
||||
if (framework) {
|
||||
Settings.update(SETTING_CONTENT_STATIC_FOLDER, framework.static, true);
|
||||
|
||||
FrameworkDetector.checkDefaultSettings(framework);
|
||||
} else {
|
||||
Settings.update(SETTING_CONTENT_STATIC_FOLDER, "", true);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Dashboard } from "../../commands/Dashboard";
|
||||
import { SETTING_CONTENT_SNIPPETS } from "../../constants";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Notifications, Settings } from "../../helpers";
|
||||
import { Snippet } from "../../models";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { SettingsListener } from "./SettingsListener";
|
||||
|
||||
@@ -27,7 +28,7 @@ export class SnippetListener extends BaseListener {
|
||||
}
|
||||
|
||||
private static async addSnippet(data: any) {
|
||||
const { title, description, body, fields } = data;
|
||||
const { title, description, body, fields, isMediaSnippet } = data;
|
||||
|
||||
if (!title || !body) {
|
||||
Notifications.warning("Snippet missing title or body");
|
||||
@@ -42,11 +43,18 @@ export class SnippetListener extends BaseListener {
|
||||
|
||||
const snippetLines = body.split("\n");
|
||||
|
||||
snippets[title] = {
|
||||
const snippetContent: any = {
|
||||
description,
|
||||
body: snippetLines.length === 1 ? snippetLines[0] : snippetLines,
|
||||
fields: fields || []
|
||||
body: snippetLines.length === 1 ? snippetLines[0] : snippetLines
|
||||
};
|
||||
|
||||
if (isMediaSnippet) {
|
||||
snippetContent.isMediaSnippet = true;
|
||||
} else {
|
||||
snippetContent.fields = fields || []
|
||||
}
|
||||
|
||||
snippets[title] = snippetContent;
|
||||
|
||||
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
|
||||
SettingsListener.getSettings();
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
import { GeneralCommands } from './../../constants';
|
||||
import { GeneralCommands } from './../../constants/GeneralCommands';
|
||||
import { Dashboard } from "../../commands/Dashboard";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { ExplorerView } from "../../explorerView/ExplorerView";
|
||||
import { Extension } from "../../helpers";
|
||||
import { Logger } from "../../helpers/Logger";
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { commands, Uri } from 'vscode';
|
||||
|
||||
|
||||
export abstract class BaseListener {
|
||||
|
||||
public static process(msg: { command: DashboardMessage, data: any }) {}
|
||||
public static process(msg: { command: DashboardMessage | CommandToCode | string , data: any }) {
|
||||
switch(msg.command) {
|
||||
case GeneralCommands.toVSCode.openLink:
|
||||
if (msg.data) {
|
||||
commands.executeCommand('vscode.open', Uri.parse(msg.data));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the webview
|
||||
* @param command
|
||||
* @param data
|
||||
*/
|
||||
public static sendMsg(command: GeneralCommands, data: any) {
|
||||
public static sendMsg(command: string, data: any) {
|
||||
Logger.info(`Sending message to webview (panel&dashboard): ${command}`);
|
||||
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
|
||||
@@ -35,7 +35,7 @@ export class ModeListener extends BaseListener {
|
||||
const activeMode = ModeSwitch.getMode();
|
||||
if (activeMode) {
|
||||
const mode = modes.find(m => m.id === activeMode);
|
||||
this.sendMsg(GeneralCommands.setMode as any, mode);
|
||||
this.sendMsg(GeneralCommands.toWebview.setMode as any, mode);
|
||||
|
||||
// Check the commands that need to be enabled/disabled
|
||||
const snippetsView = mode?.features.find(f => f === FEATURE_FLAG.dashboard.snippets.view);
|
||||
@@ -44,7 +44,7 @@ export class ModeListener extends BaseListener {
|
||||
await commands.executeCommand('setContext', CONTEXT.isSnippetsDashboardEnabled, !!snippetsView);
|
||||
await commands.executeCommand('setContext', CONTEXT.isDataDashboardEnabled, !!dataView);
|
||||
} else {
|
||||
this.sendMsg(GeneralCommands.setMode as any, undefined);
|
||||
this.sendMsg(GeneralCommands.toWebview.setMode as any, undefined);
|
||||
|
||||
// Enable dashboards
|
||||
await this.resetEnablement();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DataFileHelper } from './../../helpers/DataFileHelper';
|
||||
import { BlockFieldData } from './../../models/BlockFieldData';
|
||||
import { ImageHelper } from './../../helpers/ImageHelper';
|
||||
import { Folders } from "../../commands/Folders";
|
||||
@@ -45,10 +46,16 @@ export class DataListener extends BaseListener {
|
||||
break;
|
||||
case CommandToCode.generateContentType:
|
||||
commands.executeCommand(COMMAND_NAME.generateContentType);
|
||||
break;
|
||||
case CommandToCode.addMissingFields:
|
||||
commands.executeCommand(COMMAND_NAME.addMissingFields);
|
||||
break;
|
||||
case CommandToCode.setContentType:
|
||||
commands.executeCommand(COMMAND_NAME.setContentType);
|
||||
break;
|
||||
case CommandToCode.getDataEntries:
|
||||
this.getDataFileEntries(msg.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +108,7 @@ export class DataListener extends BaseListener {
|
||||
// Get the current content type
|
||||
const contentType = ArticleHelper.getContentType(updatedMetadata);
|
||||
if (contentType) {
|
||||
ImageHelper.processImageFields(updatedMetadata, contentType.fields)
|
||||
ImageHelper.processImageFields(updatedMetadata, contentType.fields);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,6 +285,17 @@ export class DataListener extends BaseListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data entries from local data files
|
||||
* @param data
|
||||
*/
|
||||
private static async getDataFileEntries(data: any) {
|
||||
const entries = await DataFileHelper.getById(data);
|
||||
if (entries) {
|
||||
this.sendMsg(Command.dataFileEntries, entries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a terminal and run the passed command
|
||||
* @param command
|
||||
|
||||
@@ -2,4 +2,5 @@ export interface DraftField {
|
||||
name: string;
|
||||
type: "boolean" | "choice";
|
||||
choices?: string[];
|
||||
invert?: boolean;
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export interface ContentType {
|
||||
pageBundle?: boolean;
|
||||
}
|
||||
|
||||
export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file";
|
||||
export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file" | "dataFile";
|
||||
|
||||
export interface Field {
|
||||
title?: string;
|
||||
@@ -71,6 +71,11 @@ export interface Field {
|
||||
// Date fields
|
||||
isPublishDate?: boolean;
|
||||
isModifiedDate?: boolean;
|
||||
|
||||
// Data file
|
||||
dataFileId?: string;
|
||||
dataFileKey?: string;
|
||||
dataFileValue?: string;
|
||||
}
|
||||
|
||||
export interface DateInfo {
|
||||
@@ -111,6 +116,7 @@ export interface CustomScript {
|
||||
output?: "notification" | "editor";
|
||||
outputType?: string;
|
||||
type?: ScriptType;
|
||||
command?: CommandType;
|
||||
}
|
||||
|
||||
export interface PreviewSettings {
|
||||
@@ -127,4 +133,12 @@ export enum ScriptType {
|
||||
Content = "content",
|
||||
MediaFolder = "mediaFolder",
|
||||
MediaFile = "mediaFile"
|
||||
}
|
||||
|
||||
export enum CommandType {
|
||||
Node = "node",
|
||||
Shell = "shell",
|
||||
PowerShell = "powershell",
|
||||
Python = "python",
|
||||
Python3 = "python3"
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export interface Snippet {
|
||||
fields: SnippetField[];
|
||||
openingTags?: string;
|
||||
closingTags?: string;
|
||||
isMediaSnippet?: boolean;
|
||||
}
|
||||
|
||||
export type SnippetSpecialPlaceholders = "FM_SELECTED_TEXT" | string;
|
||||
|
||||
@@ -9,4 +9,5 @@ export enum Command {
|
||||
mediaSelectionData = "mediaSelectionData",
|
||||
sendMediaUrl = "sendMediaUrl",
|
||||
updatePlaceholder = "updatePlaceholder",
|
||||
dataFileEntries = "dataFileEntries",
|
||||
}
|
||||
@@ -37,4 +37,5 @@ export enum CommandToCode {
|
||||
generateContentType = "generate-content-type",
|
||||
addMissingFields = "add-missing-fields",
|
||||
setContentType = "set-content-type",
|
||||
getDataEntries = "get-data-entries",
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { CustomScript, FolderInfo, Mode, PanelSettings } from '../../models';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import { GlobalSettings } from './GlobalSettings';
|
||||
import { OtherActions } from './OtherActions';
|
||||
@@ -10,6 +9,7 @@ import { SponsorMsg } from './SponsorMsg';
|
||||
import { StartServerButton } from './StartServerButton';
|
||||
import { FeatureFlag } from '../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../constants/Features';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export interface IBaseViewProps {
|
||||
settings: PanelSettings | undefined;
|
||||
@@ -20,23 +20,23 @@ export interface IBaseViewProps {
|
||||
const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings, folderAndFiles, mode}: React.PropsWithChildren<IBaseViewProps>) => {
|
||||
|
||||
const openDashboard = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.openDashboard);
|
||||
Messenger.send(CommandToCode.openDashboard);
|
||||
};
|
||||
|
||||
const initProject = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.initProject);
|
||||
Messenger.send(CommandToCode.initProject);
|
||||
};
|
||||
|
||||
const createContent = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.createContent);
|
||||
Messenger.send(CommandToCode.createContent);
|
||||
};
|
||||
|
||||
const openPreview = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.openPreview);
|
||||
Messenger.send(CommandToCode.openPreview);
|
||||
};
|
||||
|
||||
const runBulkScript = (script: CustomScript) => {
|
||||
MessageHelper.sendMessage(CommandToCode.runCustomScript, { title: script.title, script });
|
||||
Messenger.send(CommandToCode.runCustomScript, { title: script.title, script });
|
||||
};
|
||||
|
||||
const customActions: any[] = (settings?.scripts || []).filter(s => s.bulk && (s.type === "content" || !s.type));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { Command } from '../Command';
|
||||
import { VsCollapsible } from './VscodeComponents';
|
||||
|
||||
@@ -16,7 +16,7 @@ const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({id, children,
|
||||
const collapseKey = `collapse-${id}`;
|
||||
|
||||
useEffect(() => {
|
||||
const prevState = MessageHelper.getState();
|
||||
const prevState: any = Messenger.getState();
|
||||
if (!prevState || !prevState[collapseKey] || prevState[collapseKey] === null || prevState[collapseKey] === 'true') {
|
||||
setIsOpen(true);
|
||||
updateStorage(true);
|
||||
@@ -32,8 +32,8 @@ const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({id, children,
|
||||
}, ['']);
|
||||
|
||||
const updateStorage = (value: boolean) => {
|
||||
const prevState = MessageHelper.getState();
|
||||
MessageHelper.setState({
|
||||
const prevState: any = Messenger.getState();
|
||||
Messenger.setState({
|
||||
...prevState,
|
||||
[collapseKey]: value.toString()
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { VSCodeButton, VSCodeDivider } from '@vscode/webview-ui-toolkit/react';
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { MessageHelper } from '../../../helpers/MessageHelper';
|
||||
import { Field } from '../../../models';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { IMetadata } from '../Metadata';
|
||||
@@ -30,15 +30,15 @@ export const ContentTypeValidator: React.FunctionComponent<IContentTypeValidator
|
||||
|
||||
|
||||
const generateContentType = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.generateContentType);
|
||||
Messenger.send(CommandToCode.generateContentType);
|
||||
};
|
||||
|
||||
const addMissingFields = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.addMissingFields);
|
||||
Messenger.send(CommandToCode.addMissingFields);
|
||||
};
|
||||
|
||||
const setContentType = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.setContentType);
|
||||
Messenger.send(CommandToCode.setContentType);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { ActionButton } from './ActionButton';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export interface ICustomScriptProps {
|
||||
title: string;
|
||||
@@ -11,7 +11,7 @@ export interface ICustomScriptProps {
|
||||
const CustomScript: React.FunctionComponent<ICustomScriptProps> = ({title, script}: React.PropsWithChildren<ICustomScriptProps>) => {
|
||||
|
||||
const runCustomScript = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.runCustomScript, { title, script });
|
||||
Messenger.send(CommandToCode.runCustomScript, { title, script });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
181
src/panelWebView/components/Fields/DataFileField.tsx
Normal file
181
src/panelWebView/components/Fields/DataFileField.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { EventData } from '@estruyf/vscode/dist/models';
|
||||
import { ChevronDownIcon, DatabaseIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Command } from '../../Command';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import Downshift from 'downshift';
|
||||
import { ChoiceButton } from './ChoiceButton';
|
||||
|
||||
export interface IDataFileFieldProps {
|
||||
label: string;
|
||||
dataFileId?: string;
|
||||
dataFileKey?: string;
|
||||
dataFileValue?: string;
|
||||
selected: string | string[];
|
||||
multiSelect?: boolean;
|
||||
onChange: (value: string | string[]) => void;
|
||||
}
|
||||
|
||||
export const DataFileField: React.FunctionComponent<IDataFileFieldProps> = ({ label, dataFileId, dataFileKey, dataFileValue, selected, multiSelect, onChange }: React.PropsWithChildren<IDataFileFieldProps>) => {
|
||||
const [ dataEntries, setDataEntries ] = useState<string[] | null>(null);
|
||||
const [ crntSelected, setCrntSelected ] = React.useState<string | string[] | null>();
|
||||
const dsRef = React.useRef<Downshift<string> | null>(null);
|
||||
|
||||
const messageListener = (message: MessageEvent<EventData<any>>) => {
|
||||
const { command, data } = message.data;
|
||||
|
||||
if (command === Command.dataFileEntries) {
|
||||
setDataEntries(data || null);
|
||||
}
|
||||
};
|
||||
|
||||
const onValueChange = useCallback((txtValue: string) => {
|
||||
if (multiSelect) {
|
||||
const newValue = [...(crntSelected || []) as string[], txtValue];
|
||||
setCrntSelected(newValue);
|
||||
onChange(newValue);
|
||||
} else {
|
||||
setCrntSelected(txtValue);
|
||||
onChange(txtValue);
|
||||
}
|
||||
}, [crntSelected, multiSelect, onChange]);
|
||||
|
||||
const removeSelected = useCallback((txtValue: string) => {
|
||||
if (multiSelect) {
|
||||
const newValue = [...(crntSelected || [])].filter(v => v !== txtValue);
|
||||
setCrntSelected(newValue);
|
||||
onChange(newValue);
|
||||
} else {
|
||||
setCrntSelected("");
|
||||
onChange("");
|
||||
}
|
||||
}, [crntSelected, multiSelect, onChange]);
|
||||
|
||||
const allChoices = useMemo(() => {
|
||||
if (dataEntries && dataFileKey) {
|
||||
return dataEntries.map((r: any) => ({
|
||||
id: r[dataFileKey],
|
||||
title: r[dataFileValue || dataFileKey] || r[dataFileKey]
|
||||
})).filter(r => r.id);
|
||||
}
|
||||
return [];
|
||||
}, [crntSelected, dataEntries, dataFileKey, dataFileValue]);
|
||||
|
||||
const availableChoices = useMemo(() => {
|
||||
if (allChoices) {
|
||||
return allChoices.filter(choice => {
|
||||
if (choice) {
|
||||
if (typeof crntSelected === 'string') {
|
||||
return crntSelected !== choice.id;
|
||||
} else if (crntSelected instanceof Array) {
|
||||
return crntSelected.indexOf(choice.id) === -1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}, [allChoices]);
|
||||
|
||||
const getChoiceValue = useCallback((id: string) => {
|
||||
const choice = allChoices.find(r => r.id === id);
|
||||
if (choice) {
|
||||
return choice.title;
|
||||
}
|
||||
return "";
|
||||
}, [allChoices]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected) {
|
||||
if (multiSelect) {
|
||||
setCrntSelected(typeof selected === 'string' ? [selected] : selected);
|
||||
return;
|
||||
} else {
|
||||
setCrntSelected(selected instanceof Array ? selected[0] : selected);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setCrntSelected(multiSelect ? [] : "");
|
||||
}, [selected, multiSelect]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dataFileId) {
|
||||
Messenger.send(CommandToCode.getDataEntries, dataFileId);
|
||||
}
|
||||
}, [dataFileId]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.listen(messageListener);
|
||||
|
||||
return () => {
|
||||
Messenger.unlisten(messageListener);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`metadata_field`}>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<DatabaseIcon style={{ width: "16px", height: "16px" }} /> <span style={{ lineHeight: "16px"}}>{label}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
|
||||
<Downshift
|
||||
ref={dsRef}
|
||||
onSelect={(selected) => onValueChange(selected || "")}
|
||||
itemToString={item => (item ? item : '')}>
|
||||
{({ getToggleButtonProps, getItemProps, getMenuProps, isOpen, getRootProps }) => (
|
||||
<div {...getRootProps(undefined, {suppressRefError: true})} className={`metadata_field__choice`}>
|
||||
<button
|
||||
{...getToggleButtonProps({
|
||||
className: `metadata_field__choice__toggle`,
|
||||
disabled: availableChoices.length === 0
|
||||
})}>
|
||||
<span>{`Select ${label}`}</span>
|
||||
<ChevronDownIcon className="icon" />
|
||||
</button>
|
||||
|
||||
<ul className={`metadata_field__choice_list ${isOpen ? "open" : "closed" }`} {...getMenuProps()}>
|
||||
{
|
||||
isOpen ? availableChoices.map((choice, index) => (
|
||||
<li {...getItemProps({
|
||||
key: choice.id,
|
||||
index,
|
||||
item: choice.id,
|
||||
})}>
|
||||
{ choice.title || <span className={`metadata_field__choice_list__item`}>Clear value</span> }
|
||||
</li>
|
||||
)) : null
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</Downshift>
|
||||
|
||||
{
|
||||
crntSelected instanceof Array ? crntSelected.map((value: string) => (
|
||||
<ChoiceButton
|
||||
key={value}
|
||||
value={value}
|
||||
title={getChoiceValue(value)}
|
||||
onClick={removeSelected} />
|
||||
)) : (
|
||||
crntSelected && (
|
||||
<ChoiceButton
|
||||
key={crntSelected}
|
||||
value={crntSelected}
|
||||
title={getChoiceValue(crntSelected)}
|
||||
onClick={removeSelected} />
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DocumentIcon, PaperClipIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { basename } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { MessageHelper } from '../../../helpers/MessageHelper';
|
||||
import { BlockFieldData } from '../../../models';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
@@ -38,7 +38,7 @@ const File = ({ value, onRemove }: { value: string, onRemove: (value: string) =>
|
||||
export const FileField: React.FunctionComponent<IFileFieldProps> = ({ label, multiple, filePath, fileExtensions, fieldName, value, parents, blockData, onChange }: React.PropsWithChildren<IFileFieldProps>) => {
|
||||
|
||||
const selectFile = useCallback(() => {
|
||||
MessageHelper.sendMessage(CommandToCode.selectFile, {
|
||||
Messenger.send(CommandToCode.selectFile, {
|
||||
filePath,
|
||||
fieldName,
|
||||
value,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { MessageHelper } from '../../../helpers/MessageHelper';
|
||||
import { Command } from '../../Command';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { ImageFallback } from './ImageFallback';
|
||||
@@ -29,7 +29,7 @@ export const PreviewImage: React.FunctionComponent<IPreviewImageProps> = ({ valu
|
||||
if (value?.webviewUrl) {
|
||||
setImgUrl(value.webviewUrl);
|
||||
} else {
|
||||
MessageHelper.sendMessage(CommandToCode.getImageUrl, value)
|
||||
Messenger.send(CommandToCode.getImageUrl, value)
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import {PhotographIcon} from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { MessageHelper } from '../../../helpers/MessageHelper';
|
||||
import { BlockFieldData } from '../../../models';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
@@ -35,7 +35,7 @@ export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps>
|
||||
}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
|
||||
|
||||
const selectImage = useCallback(() => {
|
||||
MessageHelper.sendMessage(CommandToCode.selectImage, {
|
||||
Messenger.send(CommandToCode.selectImage, {
|
||||
filePath: filePath,
|
||||
fieldName,
|
||||
value,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { DateHelper } from '../../../helpers/DateHelper';
|
||||
import { MessageHelper } from '../../../helpers/MessageHelper';
|
||||
import { BlockFieldData, Field, PanelSettings } from '../../../models';
|
||||
import { Command } from '../../Command';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
@@ -17,6 +17,7 @@ import { IMetadata } from '../Metadata';
|
||||
import { TagPicker } from '../TagPicker';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import { ChoiceField } from './ChoiceField';
|
||||
import { DataFileField } from './DataFileField';
|
||||
import { DateTimeField } from './DateTimeField';
|
||||
import { DraftField } from './DraftField';
|
||||
import { FileField } from './FileField';
|
||||
@@ -98,7 +99,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
// Check if the field value contains a placeholder
|
||||
if (value && typeof value === "string" && value.includes(`{{`) && value.includes(`}}`)) {
|
||||
window.addEventListener('message', listener);
|
||||
MessageHelper.sendMessage(CommandToCode.updatePlaceholder, {
|
||||
Messenger.send(CommandToCode.updatePlaceholder, {
|
||||
field: field.name,
|
||||
title: metadata["title"],
|
||||
value
|
||||
@@ -346,6 +347,19 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
onSubmit={(value) => onSendUpdate(field.name, value, parentFields)} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'dataFile') {
|
||||
return (
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<DataFileField
|
||||
label={field.title || field.name}
|
||||
dataFileId={field.dataFileId}
|
||||
dataFileKey={field.dataFileKey}
|
||||
dataFileValue={field.dataFileValue}
|
||||
selected={fieldValue as string}
|
||||
multiSelect={field.multiple}
|
||||
onChange={(value => onSendUpdate(field.name, value, parentFields))} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else {
|
||||
console.warn(`Unknown field type: ${field.type}`);
|
||||
return null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { DEFAULT_FILE_TYPES } from '../../constants/DefaultFileTypes';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { FileIcon } from './Icons/FileIcon';
|
||||
import { MarkdownIcon } from './Icons/MarkdownIcon';
|
||||
@@ -15,7 +15,7 @@ export interface IFileItemProps {
|
||||
const FileItem: React.FunctionComponent<IFileItemProps> = ({ name, folderName, path }: React.PropsWithChildren<IFileItemProps>) => {
|
||||
|
||||
const openFile = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.openInEditor, path);
|
||||
Messenger.send(CommandToCode.openInEditor, path);
|
||||
};
|
||||
|
||||
const itemName = useMemo(() => {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { PanelSettings } from '../../models';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { useDebounce } from '../../hooks/useDebounce';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import { VsLabel } from './VscodeComponents';
|
||||
import useStartCommand from '../hooks/useStartCommand';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export interface IGlobalSettingsProps {
|
||||
settings: PanelSettings | undefined;
|
||||
@@ -24,11 +24,11 @@ const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({settings
|
||||
const debouncePreviewUrl = useDebounce(previewUrl, 1000);
|
||||
|
||||
const onDateCheck = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.updateModifiedUpdating, !modifiedDateUpdate);
|
||||
Messenger.send(CommandToCode.updateModifiedUpdating, !modifiedDateUpdate);
|
||||
};
|
||||
|
||||
const onHighlightCheck = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.updateFmHighlight, !fmHighlighting);
|
||||
Messenger.send(CommandToCode.updateFmHighlight, !fmHighlighting);
|
||||
};
|
||||
|
||||
const previewChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -54,14 +54,14 @@ const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({settings
|
||||
React.useEffect(() => {
|
||||
if (isDirty) {
|
||||
setIsDirty(false);
|
||||
MessageHelper.sendMessage(CommandToCode.updatePreviewUrl, debouncePreviewUrl);
|
||||
Messenger.send(CommandToCode.updatePreviewUrl, debouncePreviewUrl);
|
||||
}
|
||||
}, [debouncePreviewUrl]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isDirty) {
|
||||
setIsDirty(false);
|
||||
MessageHelper.sendMessage(CommandToCode.updateStartCommand, debounceStartCommand);
|
||||
Messenger.send(CommandToCode.updateStartCommand, debounceStartCommand);
|
||||
}
|
||||
}, [debounceStartCommand]);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { BlockFieldData, Field, PanelSettings } from '../../models';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { TagType } from '../TagType';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
@@ -10,6 +9,7 @@ import { WrapperField } from './Fields/WrapperField';
|
||||
import { ContentTypeValidator } from './ContentType/ContentTypeValidator';
|
||||
import { FeatureFlag } from '../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../constants';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export interface IMetadata {
|
||||
[prop: string]: string[] | string | null | IMetadata;
|
||||
@@ -30,7 +30,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, features,
|
||||
return;
|
||||
}
|
||||
|
||||
MessageHelper.sendMessage(CommandToCode.updateMetadata, {
|
||||
Messenger.send(CommandToCode.updateMetadata, {
|
||||
field,
|
||||
parents,
|
||||
value
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { PanelSettings } from '../../models';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import { BugIcon } from './Icons/BugIcon';
|
||||
import { CenterIcon } from './Icons/CenterIcon';
|
||||
@@ -12,6 +11,7 @@ import { TemplateIcon } from './Icons/TemplateIcon';
|
||||
import { WritingIcon } from './Icons/WritingIcon';
|
||||
import { OtherActionButton } from './OtherActionButton';
|
||||
import { ISSUE_LINK } from '../../constants/Links';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export interface IOtherActionsProps {
|
||||
isFile: boolean;
|
||||
@@ -22,23 +22,23 @@ export interface IOtherActionsProps {
|
||||
const OtherActions: React.FunctionComponent<IOtherActionsProps> = ({isFile, settings, isBase}: React.PropsWithChildren<IOtherActionsProps>) => {
|
||||
|
||||
const openSettings = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.openSettings);
|
||||
Messenger.send(CommandToCode.openSettings);
|
||||
};
|
||||
|
||||
const openFile = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.openFile);
|
||||
Messenger.send(CommandToCode.openFile);
|
||||
};
|
||||
|
||||
const openProject = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.openProject);
|
||||
Messenger.send(CommandToCode.openProject);
|
||||
};
|
||||
|
||||
const createAsTemplate = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.createTemplate);
|
||||
Messenger.send(CommandToCode.createTemplate);
|
||||
};
|
||||
|
||||
const toggleWritingSettings = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.toggleWritingSettings);
|
||||
Messenger.send(CommandToCode.toggleWritingSettings);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -46,7 +46,7 @@ const OtherActions: React.FunctionComponent<IOtherActionsProps> = ({isFile, sett
|
||||
<Collapsible id={`${isBase ? "base_" : ""}other_actions`} title="Other actions" className={`other_actions`}>
|
||||
<OtherActionButton className={settings?.writingSettingsEnabled ? "active" : ""} onClick={toggleWritingSettings} disabled={typeof settings?.writingSettingsEnabled === "undefined"}><WritingIcon /> <span>{settings?.writingSettingsEnabled ? "Writing settings enabled" : "Enable writing settings"}</span></OtherActionButton>
|
||||
|
||||
<OtherActionButton onClick={() => MessageHelper.sendMessage(CommandToCode.toggleCenterMode)}>
|
||||
<OtherActionButton onClick={() => Messenger.send(CommandToCode.toggleCenterMode)}>
|
||||
<CenterIcon /> <span>Toggle center mode</span>
|
||||
</OtherActionButton>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { ActionButton } from './ActionButton';
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface IPreviewProps {
|
||||
const Preview: React.FunctionComponent<IPreviewProps> = ({slug}: React.PropsWithChildren<IPreviewProps>) => {
|
||||
|
||||
const open = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.openPreview);
|
||||
Messenger.send(CommandToCode.openPreview);
|
||||
};
|
||||
|
||||
if (!slug) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { ActionButton } from './ActionButton';
|
||||
|
||||
@@ -13,7 +13,7 @@ const PublishAction: React.FunctionComponent<IPublishActionProps> = (props: Reac
|
||||
const { draft } = props;
|
||||
|
||||
const publish = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.publish);
|
||||
Messenger.send(CommandToCode.publish);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { ActionButton } from './ActionButton';
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface ISlugActionProps {}
|
||||
const SlugAction: React.FunctionComponent<ISlugActionProps> = ({}: React.PropsWithChildren<ISlugActionProps>) => {
|
||||
|
||||
const optimize = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.updateSlug);
|
||||
Messenger.send(CommandToCode.updateSlug);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { FrameworkDetectors } from '../../constants/FrameworkDetectors';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { PanelSettings } from '../../models';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import useStartCommand from '../hooks/useStartCommand';
|
||||
@@ -13,7 +12,7 @@ export const StartServerButton: React.FunctionComponent<IStartServerButtonProps>
|
||||
const { startCommand } = useStartCommand(settings);
|
||||
|
||||
const startLocalServer = (command: string) => {
|
||||
MessageHelper.sendMessage(CommandToCode.frameworkCommand, { command });
|
||||
Messenger.send(CommandToCode.frameworkCommand, { command });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,9 +6,9 @@ import { TagType } from '../TagType';
|
||||
import Downshift from 'downshift';
|
||||
import { AddIcon } from './Icons/AddIcon';
|
||||
import { VsLabel } from './VscodeComponents';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { BlockFieldData, CustomTaxonomyData } from '../../models';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export interface ITagPickerProps {
|
||||
type: TagType;
|
||||
@@ -52,11 +52,11 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsW
|
||||
*/
|
||||
const onCreate = (tag: string) => {
|
||||
if (type === TagType.tags) {
|
||||
MessageHelper.sendMessage(CommandToCode.addTagToSettings, tag);
|
||||
Messenger.send(CommandToCode.addTagToSettings, tag);
|
||||
} else if (type === TagType.categories) {
|
||||
MessageHelper.sendMessage(CommandToCode.addCategoryToSettings, tag);
|
||||
Messenger.send(CommandToCode.addCategoryToSettings, tag);
|
||||
} else if (type === TagType.custom) {
|
||||
MessageHelper.sendMessage(CommandToCode.addToCustomTaxonomy, {
|
||||
Messenger.send(CommandToCode.addToCustomTaxonomy, {
|
||||
id: taxonomyId,
|
||||
name: fieldName,
|
||||
option: tag
|
||||
@@ -70,26 +70,26 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsW
|
||||
*/
|
||||
const sendUpdate = (values: string[]) => {
|
||||
if (type === TagType.tags) {
|
||||
MessageHelper.sendMessage(CommandToCode.updateTags, {
|
||||
Messenger.send(CommandToCode.updateTags, {
|
||||
fieldName,
|
||||
values,
|
||||
parents,
|
||||
blockData
|
||||
});
|
||||
} else if (type === TagType.categories) {
|
||||
MessageHelper.sendMessage(CommandToCode.updateCategories, {
|
||||
Messenger.send(CommandToCode.updateCategories, {
|
||||
fieldName,
|
||||
values,
|
||||
parents,
|
||||
blockData
|
||||
});
|
||||
} else if (type === TagType.keywords) {
|
||||
MessageHelper.sendMessage(CommandToCode.updateKeywords, {
|
||||
Messenger.send(CommandToCode.updateKeywords, {
|
||||
values,
|
||||
parents
|
||||
});
|
||||
} else if (type === TagType.custom) {
|
||||
MessageHelper.sendMessage(CommandToCode.updateCustomTaxonomy, {
|
||||
Messenger.send(CommandToCode.updateCustomTaxonomy, {
|
||||
id: taxonomyId,
|
||||
name: fieldName,
|
||||
options: values,
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { GeneralCommands } from '../../constants';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { Mode } from '../../models/Mode';
|
||||
import { DashboardData } from '../../models/DashboardData';
|
||||
import { FolderInfo, PanelSettings } from '../../models/PanelSettings';
|
||||
import { Command } from '../Command';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { TagType } from '../TagType';
|
||||
|
||||
const vscode = MessageHelper.getVsCodeAPI();
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export default function useMessages() {
|
||||
const [metadata, setMetadata] = useState<any>({});
|
||||
@@ -46,7 +44,7 @@ export default function useMessages() {
|
||||
case Command.mediaSelectionData:
|
||||
setMediaSelecting(message.data);
|
||||
break;
|
||||
case GeneralCommands.setMode:
|
||||
case GeneralCommands.toWebview.setMode:
|
||||
setMode(message.data);
|
||||
break;
|
||||
}
|
||||
@@ -68,8 +66,8 @@ export default function useMessages() {
|
||||
setLoading(false);
|
||||
}, 5000);
|
||||
|
||||
vscode.postMessage({ command: CommandToCode.getData });
|
||||
vscode.postMessage({ command: CommandToCode.getMode });
|
||||
Messenger.send(CommandToCode.getData);
|
||||
Messenger.send(CommandToCode.getMode);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
|
||||
@@ -16,8 +16,13 @@ export interface ParsedFrontMatter {
|
||||
export class FrontMatterParser {
|
||||
public static currentContent: string | null = null;
|
||||
|
||||
/**
|
||||
* Convert the current content to a Front Matter object
|
||||
* @param content
|
||||
* @returns
|
||||
*/
|
||||
public static fromFile(content: string): ParsedFrontMatter {
|
||||
const format = getFormatOpts(this.getLanguage());
|
||||
const format = getFormatOpts(this.getLanguageFromContent(content));
|
||||
FrontMatterParser.currentContent = content;
|
||||
const result = matter(content, { ...Engines, ...format });
|
||||
// in the absent of a body when serializing an entry we use an empty one
|
||||
@@ -29,13 +34,21 @@ export class FrontMatterParser {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the Front Matter object to text
|
||||
* @param content
|
||||
* @param metadata
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
public static toFile(
|
||||
content: string,
|
||||
metadata: Object,
|
||||
originalContent?: string,
|
||||
options?: any
|
||||
) {
|
||||
// Stringify to YAML if the format was not set
|
||||
const format = getFormatOpts(this.getLanguage());
|
||||
const format = getFormatOpts(this.getLanguageFromContent(originalContent));
|
||||
|
||||
const trimLastLineBreak = content.slice(-1) !== '\n';
|
||||
const file = matter.stringify(content, metadata, {
|
||||
@@ -46,6 +59,30 @@ export class FrontMatterParser {
|
||||
return trimLastLineBreak && file.slice(-1) === '\n' ? file.substring(0, file.length - 1) : file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the type of front matter language that is used
|
||||
* @param contents
|
||||
*/
|
||||
public static getLanguageFromContent(contents: string | undefined) {
|
||||
if (!contents) {
|
||||
return this.getLanguage();
|
||||
}
|
||||
|
||||
if (contents.startsWith(`+++`)) {
|
||||
return "toml";
|
||||
} else if (contents.startsWith(`---`)) {
|
||||
return "yaml";
|
||||
} else if (contents.startsWith(`{`)) {
|
||||
return "json";
|
||||
} else {
|
||||
return "yaml";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the front matter language type
|
||||
* @returns
|
||||
*/
|
||||
private static getLanguage() {
|
||||
const language = Settings.get(SETTING_FRONTMATTER_TYPE) as string || "YAML";
|
||||
return language.toLowerCase();
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, Fold
|
||||
import { SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_CONTENT_SUPPORTED_FILETYPES, SETTING_FRONTMATTER_TYPE } from '../constants';
|
||||
import { Settings } from '../helpers';
|
||||
import { FrontMatterDecorationProvider } from './FrontMatterDecorationProvider';
|
||||
import { FrontMatterParser } from '../parsers';
|
||||
|
||||
export class MarkdownFoldingProvider implements FoldingRangeProvider {
|
||||
private static start: number | null = null;
|
||||
@@ -43,7 +44,7 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider {
|
||||
if (isSupported) {
|
||||
const fmHighlight = Settings.get<boolean>(SETTING_CONTENT_FRONTMATTER_HIGHLIGHT);
|
||||
|
||||
const range = this.getFrontMatterRange();
|
||||
const range = MarkdownFoldingProvider.getFrontMatterRange();
|
||||
|
||||
if (range) {
|
||||
if (MarkdownFoldingProvider.decType !== null) {
|
||||
@@ -64,17 +65,22 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider {
|
||||
* @returns
|
||||
*/
|
||||
public static getFrontMatterRange(document?: TextDocument) {
|
||||
const language = Settings.get(SETTING_FRONTMATTER_TYPE) as string || "YAML";
|
||||
const content = document?.getText();
|
||||
const language = FrontMatterParser.getLanguageFromContent(content);
|
||||
|
||||
let lineStart = "---";
|
||||
let lineEnd = "---";
|
||||
if (language === "TOML") {
|
||||
let lineEnd = lineStart;
|
||||
|
||||
if (language.toLowerCase() === "toml") {
|
||||
lineStart = "+++";
|
||||
lineEnd = lineStart;
|
||||
} else if (language.toLowerCase() === "json") {
|
||||
lineStart = "{";
|
||||
lineEnd = "}";
|
||||
}
|
||||
|
||||
if (document) {
|
||||
const lines = document.getText().split('\n');
|
||||
if (content) {
|
||||
const lines = content.split('\n');
|
||||
|
||||
let start = null;
|
||||
let end = null;
|
||||
|
||||
Reference in New Issue
Block a user