Compare commits

..

10 Commits

Author SHA1 Message Date
Elio Struyf
1c0ce6a6f2 Merge pull request #1007 from estruyf/revert-960-copilot/fix-958
Revert "Fix variable frontmatter error - handle both string and object formats for preview image fields"
2026-03-14 11:16:55 +01:00
Elio Struyf
bdb2179e3e Revert "Fix variable frontmatter error - handle both string and object formats for preview image fields" 2026-03-14 11:16:37 +01:00
Elio Struyf
bd8cd1f1d6 Merge pull request #960 from estruyf/copilot/fix-958
Fix variable frontmatter error - handle both string and object formats for preview image fields
2026-03-14 11:16:27 +01:00
Elio Struyf
3b65bb3cd7 Remove BEJS Community sponsorship section from README.md 2026-01-26 14:30:51 +01:00
Elio Struyf
829c5c6e64 Remove run.events sponsorship section from README.md 2026-01-26 14:30:33 +01:00
Elio Struyf
e6ef7555e3 Update README.md to clarify focus on sustainable revenue for open source projects 2026-01-26 14:23:09 +01:00
Elio Struyf
2af6c57a49 Update README.md with 2026 Open Source Priorities and content improvements 2026-01-26 14:20:48 +01:00
copilot-swe-agent[bot]
a387d5eb89 Fix ContentType validation to handle non-string/non-array field values safely
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
2025-06-20 12:48:08 +00:00
copilot-swe-agent[bot]
f1ae60f280 Fix variable frontmatter error - handle both string and object formats for preview image fields
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
2025-06-20 12:46:44 +00:00
copilot-swe-agent[bot]
0e2aea626f Initial plan for issue 2025-06-20 12:36:28 +00:00
46 changed files with 150 additions and 4854 deletions

View File

@@ -1,17 +1,5 @@
# Change Log
## [10.10.0] - 2025-xx-xx
### 🎨 Enhancements
- [#937](https://github.com/estruyf/vscode-front-matter/issues/937): Dashboard "Structure" view for documentation sites
- [#965](https://github.com/estruyf/vscode-front-matter/issues/965): Added SEO support for the keyword in the first paragraph
- [#973](https://github.com/estruyf/vscode-front-matter/issues/973): Support for number fields in the snippets
### 🐞 Fixes
- [#969](https://github.com/estruyf/vscode-front-matter/issues/969): Fix typo on welcome screen
## [10.9.0] - 2025-07-01 - [Release notes](https://beta.frontmatter.codes/updates/v10.9.0)
### 🎨 Enhancements

View File

@@ -117,7 +117,7 @@ In version v2 we released the re-designed sidebar panel with improved SEO suppor
You can get the extension via:
- The VS Code marketplace: [VS Code Marketplace - Front Matter](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter).
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter`
- The extension CLI: `ext install eliostruyf.vscode-front-matter`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter">open extension in VS Code</a>
> **Info**: The docs can be found on [frontmatter.codes](https://frontmatter.codes).
@@ -129,7 +129,7 @@ If you have the courage to test out the beta features, we made available a beta
- Uninstall the main Front Matter version
- Install the beta version
- VS Code marketplace: [VS Code Marketplace - Front Matter BETA](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta).
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter-beta`
- The extension CLI: `ext install eliostruyf.vscode-front-matter-beta`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter-beta">open extension in VS Code</a>
> **Info**: The BETA docs can be found on [beta.frontmatter.codes](https://beta.frontmatter.codes).

157
README.md
View File

@@ -4,7 +4,29 @@
</a>
</h1>
<h2 align="center">Front Matter a CMS running straight in Visual Studio Code</h2>
<h2 align="center">Front Matter - A Headless CMS for Visual Studio Code</h2>
> **📢 2026 Open Source Priorities Update**
>
> I love working with and creating open source products, but after careful
> evaluation and working with a coach, I've decided to focus my efforts on
> creating a better revenue stream. As open-source isn't providing me a
> sustainable income, I need to focus my time and effort more strategically on
> how to make my work sustainable.
>
> **Front Matter CMS will continue to be maintained** as I use it daily.
> However, major changes will only happen if there's a personal reason, a
> company commitment, or significant community support. Feature requests may
> take longer to be addressed.
>
> I'm shifting focus to open source projects that I can learn from or that have
> different outcomes, like **Demo Time**, which I use when presenting at
> conferences. If you or your company would like to sponsor my work on Front
> Matter CMS or other projects, I'd love to discuss how we can collaborate to
> make it even better!
>
> This is not about Front Matter CMS going away, but rather about managing
> expectations around feature development timelines.
<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter" title="Check it out on the Visual Studio Marketplace">
@@ -28,11 +50,17 @@
## ❓ What is Front Matter?
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. Jump right into editing and creating content with Front Matter and be able to preview it straight in VS Code.
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. Jump right into editing
and creating content with Front Matter and be able to preview it straight in VS
Code.
The extension supports various static-site generators and frameworks like Hugo, Jekyll, Hexo, NextJs, Gatsby, and more.
The extension supports various static-site generators and frameworks like Hugo,
Jekyll, Hexo, NextJs, Gatsby, and more.
A couple of our extension highlights that hopefully get you interested in giving Front Matter a try:
A couple of our extension highlights that hopefully get you interested in giving
Front Matter a try:
- Content, data, and media management
- Search, filter, sort, etc. all your content
@@ -41,30 +69,40 @@ A couple of our extension highlights that hopefully get you interested in giving
- Preview your site/content straight in Visual Studio Code
- SEO checks for title, description, and keywords
- Extensibility
- As we know, we cannot support all use cases. We provide a way to extend the functionality of the extension to your needs
- As we know, we cannot support all use cases. We provide a way to extend the
functionality of the extension to your needs
- and many more features ...
> Missing something? Let us know by opening an issue on the [GitHub repository](https://github.com/estruyf/vscode-front-matter/issues/new/choose)
> Missing something? Let us know by opening an issue on the
> [GitHub repository](https://github.com/estruyf/vscode-front-matter/issues/new/choose)
<p align="center">
<img src="https://frontmatter.codes/assets/marketplace/v6.0.0/content-preview.png" alt="Site preview" style="display: inline-block" />
</p>
> If you see something missing in your article creation flow, please feel free to reach out.
> If you see something missing in your article creation flow, please feel free
> to reach out.
**Version 10**
In version 10, we introduced the new i18n/multilingual support for your content. You can now manage your content in multiple languages, more information can be found in the [multilingual](https://frontmatter.codes/docs/content-creation/multilingual) section of our documentation.
In version 10, we introduced the new i18n/multilingual support for your content.
You can now manage your content in multiple languages, more information can be
found in the
[multilingual](https://frontmatter.codes/docs/content-creation/multilingual)
section of our documentation.
![Multilingual support](https://beta.frontmatter.codes/releases/v10.0.0/multilingual-content.png)
**Version 9**
The extension is now available in multiple languages: English, German, and Japanese. Want to add your language? Check out the [localization the extension](https://frontmatter.codes/docs/contributing#translating-the-extension).
The extension is now available in multiple languages: English, German, and
Japanese. Want to add your language? Check out the
[localization the extension](https://frontmatter.codes/docs/contributing#translating-the-extension).
**Version 8**
The taxonomy dashboard got introduced on which you can manage your tags, categories, and custom taxonomy.
The taxonomy dashboard got introduced on which you can manage your tags,
categories, and custom taxonomy.
![Taxonomy dashboard](https://frontmatter.codes/assets/marketplace/v8.1.0/taxonomy-dashboard.png)
@@ -76,17 +114,24 @@ Snippets support for Front Matter has been added!
**Version 6**
In this version, we introduced the new data files/folders dashboard. You can find more information about the release in our [v6.0.0 release notes](https://frontmatter.codes/updates/v6.0.0).
In this version, we introduced the new data files/folders dashboard. You can
find more information about the release in our
[v6.0.0 release notes](https://frontmatter.codes/updates/v6.0.0).
<p align="center">
<img src="https://frontmatter.codes/assets/marketplace/v6.0.0/data-dashboard.png" alt="Data dashboard" style="display: inline-block" />
</p>
> Data files/folders are pieces of content that do not belong to any markdown content, but live on their own. Most of the time, these data files are used to store additional information about your project/blog/website that will be used to render the content.
> Data files/folders are pieces of content that do not belong to any markdown
> content, but live on their own. Most of the time, these data files are used to
> store additional information about your project/blog/website that will be used
> to render the content.
**Version 5**
The new media dashboard redesign got introduced + support for setting metadata on media files [v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0).
The new media dashboard redesign got introduced + support for setting metadata
on media files
[v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0).
<p align="center">
<img src="https://frontmatter.codes/assets/marketplace/v5.9.0/media-dashboard.png" alt="Data dashboard" style="display: inline-block" />
@@ -94,15 +139,21 @@ The new media dashboard redesign got introduced + support for setting metadata o
**Version 4**
Support for Team level settings, content-types, and image support. Get to know more at: [v4.0.0 release notes](https://frontmatter.codes/updates/v4_0_0).
Support for Team level settings, content-types, and image support. Get to know
more at: [v4.0.0 release notes](https://frontmatter.codes/updates/v4_0_0).
**Version 3**
In version v3 we introduced the welcome and dashboard webview. The welcome view allows to get you started using the extension, and the dashboard allows you to manage all your markdown pages in one place. This makes it easy to search, filter, sort, and more.
In version v3 we introduced the welcome and dashboard webview. The welcome view
allows to get you started using the extension, and the dashboard allows you to
manage all your markdown pages in one place. This makes it easy to search,
filter, sort, and more.
**Version 2**
In version v2 we released the re-designed sidebar panel with improved SEO support. This extension makes it the only extension to manage your Markdown pages for your static sites in Visual Studio Code.
In version v2 we released the re-designed sidebar panel with improved SEO
support. This extension makes it the only extension to manage your Markdown
pages for your static sites in Visual Studio Code.
<p align="center" style="margin-top: 2rem;">
<a href="https://www.producthunt.com/posts/front-matter?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-front-matter" target="_blank">
@@ -114,33 +165,47 @@ In version v2 we released the re-designed sidebar panel with improved SEO suppor
You can get the extension via:
- The VS Code marketplace: [VS Code Marketplace - Front Matter](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter).
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter">open extension in VS Code</a>
- The VS Code marketplace:
[VS Code Marketplace - Front Matter](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter).
- The extension CLI: `ext install eliostruyf.vscode-front-matter`
- Or by clicking on the following link: <a href="" title="open extension in VS
Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter">open
extension in VS Code</a>
> **Info**: The docs can be found on [frontmatter.codes](https://frontmatter.codes).
> **Info**: The docs can be found on
> [frontmatter.codes](https://frontmatter.codes).
### 🧪 Beta version
If you have the courage to test out the beta features, we made available a beta version as well. You can install this via:
If you have the courage to test out the beta features, we made available a beta
version as well. You can install this via:
- Uninstall the main Front Matter version
- Install the beta version
- VS Code marketplace: [VS Code Marketplace - Front Matter BETA](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta).
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter-beta`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter-beta">open extension in VS Code</a>
- VS Code marketplace:
[VS Code Marketplace - Front Matter BETA](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta).
- The extension CLI: `ext install eliostruyf.vscode-front-matter-beta`
- Or by clicking on the following link: <a href="" title="open extension in VS
Code"
data-vscode="vscode:extension/eliostruyf.vscode-front-matter-beta">open
extension in VS Code</a>
> **Info**: The BETA docs can be found on [beta.frontmatter.codes](https://beta.frontmatter.codes).
> **Info**: The BETA docs can be found on
> [beta.frontmatter.codes](https://beta.frontmatter.codes).
## 📖 Documentation
All documentation can be found on [frontmatter.codes](https://frontmatter.codes).
All documentation can be found on
[frontmatter.codes](https://frontmatter.codes).
Documentation repository: [GitHub - Front Matter DOCs](https://github.com/FrontMatter/web-documentation-nextjs)
Documentation repository:
[GitHub - Front Matter DOCs](https://github.com/FrontMatter/web-documentation-nextjs)
## 💪 Contributing
Pull requests are welcome. Please open an issue first to discuss what you would like to change, or which problem you would like to fix. This makes it easier for us to follow-up and plan for future releases.
Pull requests are welcome. Please open an issue first to discuss what you would
like to change, or which problem you would like to fix. This makes it easier for
us to follow-up and plan for future releases.
You can always help us improve the extension in varous ways like:
@@ -153,7 +218,8 @@ You can always help us improve the extension in varous ways like:
- Tutorials
- etc.
Eager to start contributing? Great 🤩, you can contribute to the following projects:
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)
@@ -161,13 +227,16 @@ Eager to start contributing? Great 🤩, you can contribute to the following pro
## 👀 Show the work you are using Front Matter
Are you using Front Matter and are you interested in showing for which websites you use it? You can show your work by opening a [showcase issue](https://github.com/estruyf/vscode-front-matter/issues/new?assignees=&labels=&template=showcase.md&title=Showcase%3A+).
Are you using Front Matter and are you interested in showing for which websites
you use it? You can show your work by opening a
[showcase issue](https://github.com/estruyf/vscode-front-matter/issues/new?assignees=&labels=&template=showcase.md&title=Showcase%3A+).
You can open showcase issues for the following things:
- Show the website for which you use Front Matter;
- Share an article/video/webcast/... that explains how you use Front Matter;
- Got something else to share? Open an issue and we can see where it fits on our website.
- Got something else to share? Open an issue and we can see where it fits on our
website.
## 👉 Contributors 🤘
@@ -185,33 +254,23 @@ You can open showcase issues for the following things:
<br />
<p align="center" title="Support by run.events">
<a href="https://run.events/?utm_source=frontmatter&utm_campaign=oss">
<img src="https://frontmatter.codes/assets/sponsors/runevents-purple.webp" alt="run.events - Event Management Platform" height="50px" />
</a>
</p>
<br />
<p align="center" title="Powered by Netlify">
<a href="https://www.netlify.com?utm_source=vscode-frontmatter&utm_campaign=oss">
<img src="https://frontmatter.codes/assets/sponsors/netlify-dark.png" alt="Deploys by Netlify" height="51px" />
</a>
</p>
<br />
<p align="center">
<a href="http://bejs.io/" title="Supported by the BEJS Community">
<img src="https://frontmatter.codes/assets/sponsors/bejs-community.png" alt="Supported by the BEJS Community" height="50px"/>
</a>
</p>
## 📊 Telemetry
The Front Matter CMS extension only uses telemetry on application crashes. The extension respects the `telemetry.enableTelemetry` setting which you can learn more about in the [Visual Studio Code FAQ](https://aka.ms/vscode-remote/telemetry).
The Front Matter CMS extension only uses telemetry on application crashes. The
extension respects the `telemetry.enableTelemetry` setting which you can learn
more about in the
[Visual Studio Code FAQ](https://aka.ms/vscode-remote/telemetry).
For crash reports in the webviews, we make use of Sentry to help us understand what went wrong. This data is only used to fix issues and improve the extension. You can find more information about the Sentry implementation in the following files:
For crash reports in the webviews, we make use of Sentry to help us understand
what went wrong. This data is only used to fix issues and improve the extension.
You can find more information about the Sentry implementation in the following
files:
- [Sentry config](https://github.com/estruyf/vscode-front-matter/blob/63e296d62f11be73ac86d9e823084247952a7ddc/src/utils/sentryInit.ts)

View File

@@ -109,7 +109,6 @@
"dashboard.header.tabs.taxonomies": "Taxonomien",
"dashboard.header.viewSwitch.toGrid": "Zur Rasteransicht wechseln",
"dashboard.header.viewSwitch.toList": "Zur Listenansicht wechseln",
"dashboard.header.viewSwitch.toStructure": "Zur Strukturansicht wechseln",
"dashboard.layout.sponsor.support.msg": "Unterstützen Sie Front Matter",
"dashboard.layout.sponsor.review.label": "Bewerten",
"dashboard.layout.sponsor.review.msg": "Bewerten Sie Front Matter",

View File

@@ -109,7 +109,6 @@
"dashboard.header.tabs.taxonomies": "Taxonomies",
"dashboard.header.viewSwitch.toGrid": "Afficher en grille",
"dashboard.header.viewSwitch.toList": "Afficher en liste",
"dashboard.header.viewSwitch.toStructure": "Afficher en structure",
"dashboard.layout.sponsor.support.msg": "Soutenir Front Matter",
"dashboard.layout.sponsor.review.label": "Donnez votre avis",
"dashboard.layout.sponsor.review.msg": "Donnez votre avis sur Front Matter",

View File

@@ -214,7 +214,6 @@
"dashboard.header.viewSwitch.toGrid": "グリッド表示",
"dashboard.header.viewSwitch.toList": "リスト表示",
"dashboard.header.viewSwitch.toStructure": "構造表示",
"dashboard.layout.sponsor.support.msg": "Front Matterをサポートする",
"dashboard.layout.sponsor.review.label": "評価する",

View File

@@ -222,7 +222,6 @@
"dashboard.header.viewSwitch.toGrid": "Change to grid",
"dashboard.header.viewSwitch.toList": "Change to list",
"dashboard.header.viewSwitch.toStructure": "Change to structure",
"dashboard.layout.sponsor.support.msg": "Support Front Matter",
"dashboard.layout.sponsor.review.label": "Review",
@@ -333,7 +332,7 @@
"dashboard.steps.stepsToGetStarted.tags.name": "Import all tags and categories (optional)",
"dashboard.steps.stepsToGetStarted.tags.description": "Now that Front Matter knows all the content folders. Would you like to import all tags and categories from the available content?",
"dashboard.steps.stepsToGetStarted.git.name": "Do you want to enable Git synchronization?",
"dashboard.steps.stepsToGetStarted.git.description": "Enable Git synchronization to easily sync your changes with your repository.",
"dashboard.steps.stepsToGetStarted.git.description": "Enable Git synchronization to eaily sync your changes with your repository.",
"dashboard.steps.stepsToGetStarted.showDashboard.name": "Show the dashboard",
"dashboard.steps.stepsToGetStarted.showDashboard.description": "Once all actions are completed, the dashboard can be loaded.",
"dashboard.steps.stepsToGetStarted.template.name": "Use a configuration template",
@@ -505,7 +504,6 @@
"panel.seoKeywords.density": "Keyword density",
"panel.seoKeywordInfo.validInfo.label": "Heading(s)",
"panel.seoKeywordInfo.validInfo.content": "Content",
"panel.seoKeywordInfo.validInfo.firstParagraph": "First paragraph",
"panel.seoKeywordInfo.density.tooltip": "Recommended frequency: 0.75% - 1.5%",
"panel.seoKeywords.title": "Keywords",

View File

@@ -222,7 +222,6 @@
"dashboard.header.viewSwitch.toGrid": "切换到网格视图",
"dashboard.header.viewSwitch.toList": "切换到列表视图",
"dashboard.header.viewSwitch.toStructure": "切换到结构视图",
"dashboard.layout.sponsor.support.msg": "支持 Front Matter",
"dashboard.layout.sponsor.review.label": "评价",

5
lite/.gitignore vendored
View File

@@ -1,5 +0,0 @@
node_modules
dist
out
*.vsix
.vscode-test/

View File

@@ -1,9 +0,0 @@
.vscode/**
.vscode-test/**
src/**
.gitignore
tsconfig.json
webpack.config.js
node_modules/**
*.map
*.ts

View File

@@ -1,71 +0,0 @@
# Changelog - Front Matter Lite
All notable changes to the Front Matter Lite extension will be documented in this file.
## [Unreleased]
### Added
- **Metadata Panel** - Edit front matter fields directly in the sidebar panel
- View and edit all front matter fields for the current markdown file
- Support for text, textarea, date, and array fields (tags/categories)
- Auto-save changes to the file
- Refresh button to reload metadata
- Initial release of Front Matter Lite for virtual workspaces
- Dashboard webview with folder and file listing
- Register content folders via context menu
- Create content command
- Basic front matter template
- Virtual workspace detection
- Support for github.dev and vscode.dev
- File operations using VS Code FileSystem API
- Configuration persistence
- Content file browser in dashboard
### Features
- ✅ Register content folders
- ✅ Create new markdown files with front matter
-**Edit front matter metadata in panel**
- ✅ View registered folders
- ✅ List content files
- ✅ Open files from dashboard
- ✅ Manual refresh
### Limitations
- Dashboard is read-only (no inline editing)
- Limited to 100 files per folder
- No file system watch (manual refresh required)
- No media management
- No git integration
- No custom scripts
- No local server preview
## Architecture
Built as a web extension with:
- Target: `webworker` for browser compatibility
- No Node.js dependencies (fs, path, child_process)
- Uses VS Code FileSystem API (`vscode.workspace.fs`)
- Uses `vscode.Uri` for path operations
- Webview-based dashboard UI
## Roadmap
Future enhancements planned:
- [ ] Inline front matter editing in dashboard
- [ ] Better content filtering and search
- [ ] Tags and categories management
- [ ] Simple content preview
- [ ] Export content list
- [ ] Keyboard shortcuts
- [ ] Better error handling
- [ ] Content statistics
## Version 1.0.0 Goals
Before releasing v1.0.0:
- [ ] Complete testing in github.dev
- [ ] Complete testing in vscode.dev
- [ ] User feedback incorporated
- [ ] Documentation complete
- [ ] Bug fixes for all critical issues
- [ ] Performance optimization

View File

@@ -1,113 +0,0 @@
# Development Guide - Front Matter Lite
## Prerequisites
- Node.js (v18 or higher)
- npm or yarn
## Setup
```bash
cd lite
npm install
```
## Building
### Development Build
```bash
npm run dev
```
This will watch for changes and rebuild automatically.
### Production Build
```bash
npm run build
```
## Testing
### Local Testing
1. Build the extension:
```bash
npm run build
```
2. Press F5 in VS Code to open the Extension Development Host
3. Test in a virtual workspace:
- Open the Command Palette (F1)
- Run "Open Remote Repository"
- Enter a GitHub repository URL
- Test the lite version features
### Testing in github.dev
1. Package the extension:
```bash
npm install -g @vscode/vsce
vsce package
```
2. Navigate to github.dev in your browser
- Press `.` on any GitHub repository
- Install the extension manually
## Architecture
The lite version is designed to work without Node.js-specific APIs:
- **No Node.js fs module** - Uses `vscode.workspace.fs` instead
- **No Node.js path module** - Uses `vscode.Uri.joinPath` instead
- **No child_process** - No external script execution
- **Browser-compatible** - Built as a web extension (target: 'webworker')
## Key Differences from Full Extension
| Feature | Full Extension | Lite Version |
|---------|---------------|--------------|
| File Operations | Node.js `fs` | VS Code `workspace.fs` |
| Path Handling | Node.js `path` | `vscode.Uri` |
| Scripts | child_process | Not available |
| Workspace | File system only | Virtual workspaces |
| Dashboard | Full React app | Simplified (planned) |
## Contributing
When adding features to the lite version:
1. Ensure compatibility with virtual workspaces
2. Use only browser-compatible APIs
3. Test in both github.dev and vscode.dev
4. Document any limitations
## Debugging
Enable the Output Channel "Front Matter Lite" to see debug messages:
1. View > Output
2. Select "Front Matter Lite" from the dropdown
## Common Issues
### Extension not loading
- Check the Output channel for errors
- Ensure the extension is built correctly
- Verify the package.json has the correct `browser` entry point
### Features not working in virtual workspace
- Confirm the workspace scheme is not 'file'
- Check browser console for errors
- Verify you're using VS Code FileSystem API
## Resources
- [VS Code Web Extensions Guide](https://code.visualstudio.com/api/extension-guides/web-extensions)
- [Virtual Workspaces Documentation](https://code.visualstudio.com/api/extension-guides/virtual-workspaces)
- [Front Matter Documentation](https://frontmatter.codes)

View File

@@ -1,263 +0,0 @@
# Quick Start Guide for Maintainers
This guide helps you quickly test and publish Front Matter Lite.
## Prerequisites
- Node.js 18+
- VS Code installed
- (Optional) vsce installed globally: `npm install -g @vscode/vsce`
## Build and Test (5 minutes)
### 1. Build the Extension
```bash
cd lite
npm install
npm run build
```
Expected output: `dist/extension-web.js` created successfully (~12KB)
### 2. Test in VS Code Extension Development Host
```bash
# From the lite directory, press F5 in VS Code
# OR run:
code --extensionDevelopmentPath=/path/to/lite
```
This opens a new VS Code window with the extension loaded.
### 3. Test Virtual Workspace Features
In the Extension Development Host:
1. **Open Command Palette** (F1)
2. **Run**: "Open Remote Repository"
3. **Enter**: Any GitHub repo URL (e.g., `https://github.com/microsoft/vscode`)
4. **Verify**:
- "Front Matter Lite" appears in Activity Bar
- Dashboard loads
- Information message about virtual workspace mode appears
### 4. Test Core Features
**Register a Folder:**
1. In Explorer, right-click any folder
2. Select "Front Matter Lite > Register Content Folder (Lite)"
3. Enter a title
4. Check dashboard shows the folder
**Create Content:**
1. Click "Create Content" in dashboard
2. Select folder
3. Enter file name
4. Verify file is created with front matter
## Package for Distribution
### Create VSIX File
```bash
cd lite
vsce package
```
Output: `vscode-front-matter-lite-10.9.0.vsix`
### Test VSIX in github.dev
1. Navigate to any GitHub repo
2. Press `.` to open github.dev
3. Install extension:
- Extensions → "..." menu → "Install from VSIX..."
- Select the generated `.vsix` file
4. Test features
## Publish to Marketplace
### Prerequisites
- Azure DevOps account
- Personal Access Token (PAT) with Marketplace publish permissions
- Publisher ID set up
### Publish
```bash
# First time setup
vsce login <publisher-name>
# Publish
cd lite
vsce publish
```
### Update Version
```bash
# In package.json, update version
# Then publish with:
vsce publish minor # or major, patch
```
## Development Workflow
### Watch Mode
```bash
npm run dev
```
Keep this running while developing. Press F5 to test changes.
### Make Changes
1. Edit source files in `src/`
2. Save (watch mode rebuilds automatically)
3. Reload Extension Development Host (Ctrl+R in dev window)
4. Test changes
### Debug
1. Set breakpoints in source files
2. Press F5
3. Trigger the feature in dev host
4. Debugger stops at breakpoints
## Common Tasks
### Update Front Matter Template
Edit in `src/extension.ts` (~line 170):
```typescript
const content = `---
title: ${fileName}
description:
date: ${date}
tags: []
draft: false // Add new field
---`;
```
### Change Dashboard UI
Edit `src/DashboardProvider.ts` `_getHtmlForWebview()` method.
### Add New Command
1. Register in `package.json` `contributes.commands`
2. Implement in `src/extension.ts`
3. Add to menu if needed
### Modify Configuration Schema
Update `package.json` `contributes.configuration.properties`
## Troubleshooting
### Build Fails
```bash
# Clean and rebuild
rm -rf node_modules dist
npm install
npm run build
```
### Extension Not Loading
Check:
1. `dist/extension-web.js` exists
2. `package.json` has correct `browser` entry point
3. Output channel for errors
### Virtual Workspace Not Detected
Ensure workspace scheme is not 'file':
- github.dev uses 'vscode-vfs'
- Check Output channel for detection message
## Quality Checks
Before publishing:
```bash
# Build
npm run build
# Check size (should be ~12KB)
ls -lh dist/extension-web.js
# Lint (if you add linting)
npm run lint
# Package
vsce package
```
## Integration with Main Extension
The lite version is independent but uses compatible configuration:
```json
{
"frontMatter.content.pageFolders": [
{ "title": "Blog", "path": "content/blog" }
]
}
```
This works in both full and lite versions.
## Support
For issues:
1. Check Output channel "Front Matter Lite"
2. Check browser console (F12 in github.dev)
3. Review error messages
4. Check GitHub issues
## Next Steps
After testing:
1. ✅ Verify features work
2. ✅ Test in github.dev
3. ✅ Get user feedback
4. 📋 Iterate on improvements
5. 🚀 Publish to marketplace
6. 📢 Announce to users
## Quick Commands Reference
```bash
# Install dependencies
npm install
# Build for production
npm run build
# Build for development (watch mode)
npm run dev
# Package extension
vsce package
# Publish extension
vsce publish
# Test in VS Code
code --extensionDevelopmentPath=$(pwd)
```
## Files to Review
- `package.json` - Extension manifest
- `src/extension.ts` - Main logic
- `src/DashboardProvider.ts` - UI
- `README.md` - User documentation
That's it! You're ready to test and publish Front Matter Lite. 🚀

View File

@@ -1,108 +0,0 @@
# Front Matter CMS (Lite)
This is the lite version of Front Matter CMS designed specifically for **virtual workspaces** such as github.dev and vscode.dev.
## What is a Virtual Workspace?
Virtual workspaces allow you to work with code directly in your browser without cloning a repository locally. This includes:
- **github.dev** - Press `.` on any GitHub repository
- **vscode.dev** - Open VS Code in your browser
- **GitHub Codespaces** - Cloud-based development environments
## Features
The lite version provides core content management functionality:
### ✅ Supported Features
- **Metadata Panel** - View and edit front matter for the currently open markdown file
- **Register Content Folders** - Right-click on folders in the Explorer to register them as content folders
- **Create Content** - Create new markdown files with front matter
- **View Configuration** - Manage your content folder settings
### ❌ Limited/Unavailable Features
The following features from the full extension are not available in the lite version due to virtual workspace limitations:
- **Dashboard** - Full dashboard UI (basic version available)
- **Media Management** - File upload and media library
- **Local Server Preview** - Starting/stopping local dev servers
- **Git Integration** - Advanced git operations
- **Custom Scripts** - Running custom Node.js scripts
- **File System Watch** - Automatic content refresh
- **Complex Build Tools** - Framework-specific integrations
## Installation
1. Open a virtual workspace (github.dev or vscode.dev)
2. Install the "Front Matter CMS (Lite)" extension from the Extensions marketplace
3. Start managing your content!
## Usage
### Edit Front Matter Metadata
1. Open a markdown file in the editor
2. The **Metadata** panel in the Front Matter Lite sidebar shows all front matter fields
3. Edit fields directly in the panel:
- **Title** - Edit the page title
- **Description** - Edit the description (multiline)
- **Date** - Use the date picker to set publish date
- **Tags/Categories** - Add or remove tags by typing and pressing Enter
- **Other fields** - Edit any custom front matter fields
4. Changes are saved automatically to the file
### Register a Content Folder
1. In the Explorer, right-click on any folder
2. Select **Front Matter Lite > Register Content Folder (Lite)**
3. Enter a title for the folder
4. The folder is now registered and can be used for content creation
### Create New Content
1. Open the Command Palette (F1 or Ctrl/Cmd+Shift+P)
2. Run **Front Matter Lite: Create Content (Lite)**
3. Select a content folder
4. Enter a file name
5. Your new content file is created with basic front matter
## Configuration
The lite version uses the same configuration as the full extension. You can configure your content folders and content types in VS Code settings:
```json
{
"frontMatter.content.pageFolders": [
{
"title": "Blog Posts",
"path": "content/blog"
}
]
}
```
## Limitations
This lite version is designed to work within the constraints of virtual workspaces:
- Uses only the VS Code FileSystem API
- No Node.js file system operations
- No external process execution
- Limited to browser-compatible APIs
## Need More Features?
For the full Front Matter CMS experience with all features, install the regular extension in VS Code Desktop:
- [Front Matter CMS on the VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter)
- [Documentation](https://frontmatter.codes)
## Contributing
This is part of the Front Matter CMS project. Visit our [GitHub repository](https://github.com/estruyf/vscode-front-matter) to contribute or report issues.
## License
MIT

View File

@@ -1,322 +0,0 @@
# Front Matter Lite Setup Guide
This guide will help you set up and start using Front Matter Lite in virtual workspaces.
## Quick Start
### For Users (github.dev / vscode.dev)
1. **Open a repository in github.dev:**
- Navigate to any GitHub repository
- Press `.` (period key)
- OR change `github.com` to `github.dev` in URL
2. **Install Front Matter Lite:**
- Currently in development, will be available on the VS Code Marketplace
- For now, request the `.vsix` file from the project maintainers
3. **Get Started:**
- Look for "Front Matter Lite" in the Activity Bar (left sidebar)
- Click to open the dashboard
### For Developers
1. **Clone and Setup:**
```bash
git clone https://github.com/estruyf/vscode-front-matter.git
cd vscode-front-matter/lite
npm install
```
2. **Build:**
```bash
npm run build
```
3. **Test:**
- Press F5 in VS Code to open Extension Development Host
- OR package and install manually in github.dev
## First Time Setup
### 1. Register Your First Content Folder
After installing, you'll need to tell Front Matter Lite where your content is:
**Method A: Using Explorer Context Menu**
1. Open the Explorer view
2. Right-click on a folder containing your markdown files
3. Select **Front Matter Lite > Register Content Folder (Lite)**
4. Enter a descriptive title (e.g., "Blog Posts")
5. Click OK
**Method B: Manual Configuration**
1. Open Settings (Ctrl/Cmd + ,)
2. Search for "frontMatter.content.pageFolders"
3. Click "Edit in settings.json"
4. Add your folders:
```json
{
"frontMatter.content.pageFolders": [
{
"title": "Blog Posts",
"path": "content/blog"
},
{
"title": "Documentation",
"path": "docs"
}
]
}
```
### 2. Verify Setup
1. Open the Front Matter Lite dashboard
2. You should see your registered folders
3. Click "Refresh" to load existing content files
## Usage
### Creating New Content
1. **Via Dashboard:**
- Open Front Matter Lite dashboard
- Click "Create Content" button
- Select a content folder
- Enter a file name (without .md extension)
- File is created and opened
2. **Via Command Palette:**
- Press F1 or Ctrl/Cmd+Shift+P
- Type "Front Matter Lite: Create Content"
- Follow the prompts
### Viewing Content
1. Open the Front Matter Lite dashboard
2. Registered folders are listed at the top
3. Recent content files are shown below
4. Click any file to open it
### Editing Front Matter
Currently, front matter editing is done directly in the markdown file:
```markdown
---
title: My Post
description: A description of my post
date: 2024-01-07T10:00:00.000Z
tags: [blog, tutorial]
---
# My Post Content
Your content here...
```
## Configuration
### Basic Settings
Add to your workspace `.vscode/settings.json`:
```json
{
"frontMatter.content.pageFolders": [
{
"title": "Blog",
"path": "content/blog"
}
],
"frontMatter.taxonomy.contentTypes": [
{
"name": "default",
"fields": [
{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Date",
"name": "date",
"type": "datetime"
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
}
]
}
]
}
```
### Advanced Configuration
For more control over your content:
```json
{
"frontMatter.content.pageFolders": [
{
"title": "Blog Posts",
"path": "content/blog",
"excludeSubdir": false
},
{
"title": "Documentation",
"path": "docs",
"excludeSubdir": true
}
]
}
```
## Workflow Integration
### Hugo Example
```
my-hugo-site/
├── content/
│ ├── blog/ <- Register this folder
│ └── docs/ <- And this one
├── static/
└── config.toml
```
### Jekyll Example
```
my-jekyll-site/
├── _posts/ <- Register this folder
├── _pages/
└── _config.yml
```
### Next.js Example
```
my-nextjs-site/
├── content/ <- Register this folder
│ └── blog/
├── pages/
└── package.json
```
## Troubleshooting
### Extension Not Showing Up
1. **Check Extension is Installed:**
- Open Extensions view (Ctrl/Cmd+Shift+X)
- Search for "Front Matter Lite"
- Verify it's installed and enabled
2. **Check Output Channel:**
- View > Output
- Select "Front Matter Lite" from dropdown
- Look for activation message
### No Folders Showing in Dashboard
1. **Verify folders are registered:**
- Check settings: `frontMatter.content.pageFolders`
- Ensure paths are relative to workspace root
- Click "Refresh" button in dashboard
2. **Check workspace:**
- Ensure you have a workspace/folder open
- Verify the workspace contains the specified paths
### Can't Create Content
1. **Verify folder is registered:**
- At least one folder must be in `frontMatter.content.pageFolders`
2. **Check permissions:**
- Ensure you have write access to the repository
- In github.dev, you need to fork or have write access
### Files Not Appearing
1. **Click "Refresh":**
- Dashboard doesn't auto-update
- Click the "Refresh" button
2. **Check file extensions:**
- Only .md, .mdx, and .markdown files are shown
- Check your files have the correct extension
## Best Practices
### 1. Organize Content by Type
```json
{
"frontMatter.content.pageFolders": [
{ "title": "Blog Posts", "path": "content/blog" },
{ "title": "Tutorials", "path": "content/tutorials" },
{ "title": "Documentation", "path": "docs" }
]
}
```
### 2. Use Consistent Front Matter
Define a template for all your content:
```markdown
---
title: Post Title
description: Brief description
date: 2024-01-07T10:00:00.000Z
tags: []
categories: []
draft: false
---
```
### 3. Commit Configuration
Add `.vscode/settings.json` to your repository so all team members have the same setup.
### 4. Regular Backups
Even though you're in a virtual workspace:
- Commit changes regularly
- Push to GitHub frequently
- Use branches for experiments
## Getting Help
- **Documentation:** [https://frontmatter.codes](https://frontmatter.codes)
- **Issues:** [GitHub Issues](https://github.com/estruyf/vscode-front-matter/issues)
- **Discussions:** [GitHub Discussions](https://github.com/estruyf/vscode-front-matter/discussions)
## Next Steps
1. **Explore the Dashboard:**
- Familiarize yourself with the interface
- Try creating a few test posts
2. **Customize Front Matter:**
- Define content types that match your needs
- Add custom fields
3. **Share with Team:**
- Commit your configuration
- Share the setup guide with collaborators
4. **Upgrade to Full Version:**
- For advanced features, install the full Front Matter extension in VS Code Desktop
- Enjoy media management, custom scripts, and more

View File

@@ -1,204 +0,0 @@
# Front Matter Lite - Implementation Summary
## Overview
Front Matter Lite is a web extension that brings core Front Matter CMS functionality to virtual workspaces like github.dev, vscode.dev, and GitHub Codespaces. It addresses the original issue where users could not manage content in virtual workspaces.
## Problem Statement (Original Issue)
Users reported being unable to:
1. Select/register virtual workspace folders as content folders
2. Create content in virtual workspaces
3. Use the dashboard in github.dev or vscode.dev
4. Access basic Front Matter CMS functionality without cloning repositories locally
## Solution
Created a standalone lite web extension that:
- Works exclusively with VS Code FileSystem API (no Node.js dependencies)
- Provides folder registration via context menu
- Enables content creation with front matter templates
- Displays dashboard with content listing
- Detects and adapts to virtual workspace environments
## Architecture
### Technology Stack
- **TypeScript**: Type-safe development
- **Webpack**: Bundling with 'webworker' target
- **VS Code API**: FileSystem, Uri, workspace APIs only
- **Webview API**: For dashboard UI
### Key Design Decisions
1. **Separate Extension**: Created as standalone to avoid breaking changes to main extension
2. **No Node.js**: All file operations use `vscode.workspace.fs`
3. **No External Dependencies**: Minimal bundle size, faster loading
4. **Browser-Compatible**: Works in any VS Code environment
5. **Configuration Reuse**: Uses same settings structure as main extension
### File Structure
```
lite/
├── src/
│ ├── extension.ts # Main entry point
│ ├── DashboardProvider.ts # Webview dashboard
│ └── utils.ts # Helper utilities
├── assets/
│ └── frontmatter-teal-128x128.png
├── package.json # Web extension manifest
├── webpack.config.js # Web worker build config
├── tsconfig.json # TypeScript config
└── docs/ # Documentation files
```
## Features Implemented
### ✅ Core Features
| Feature | Status | Implementation |
|---------|--------|----------------|
| Register Folders | ✅ | Context menu command |
| Create Content | ✅ | Command palette + dashboard |
| Dashboard UI | ✅ | Webview with folder/file listing |
| Virtual Workspace Detection | ✅ | Scheme-based detection |
| Configuration Persistence | ✅ | VS Code settings |
| File Listing | ✅ | FindFiles API (max 100 files) |
| Open Files | ✅ | From dashboard |
### ❌ Intentionally Excluded
| Feature | Reason |
|---------|--------|
| Media Upload | Requires file system access |
| Git Operations | Requires child_process |
| Custom Scripts | Requires Node.js runtime |
| File Watching | Limited browser API support |
| Local Server | Requires process spawning |
## Code Quality
### Testing
- ✅ TypeScript compilation verified
- ✅ Webpack build successful
- ✅ Code review completed with feedback addressed
- ✅ Security scan passed (CodeQL)
- ⏳ Manual testing pending (requires github.dev access)
### Best Practices Applied
- Error handling with proper types
- Output channel for logging
- Extracted utility functions
- Documented limitations
- User-friendly error messages
- CSP-compliant webview
## User Experience
### First-Time Setup (3 steps)
1. Install extension in virtual workspace
2. Right-click folder → "Register Content Folder"
3. Use dashboard or command palette to create content
### Typical Workflow
1. Open repository in github.dev
2. Register content folders
3. View existing content in dashboard
4. Create new content as needed
5. Edit front matter and content
6. Commit changes
## Documentation
Created comprehensive docs:
- **README.md**: User-facing features and limitations
- **SETUP.md**: Step-by-step setup guide
- **TESTING.md**: Testing procedures
- **DEVELOPMENT.md**: Developer guide
- **CHANGELOG.md**: Version history
- **SUMMARY.md**: This document
## Migration Path
Users can use both versions:
- **Desktop VS Code**: Full Front Matter extension (all features)
- **Virtual Workspaces**: Front Matter Lite (core features)
Configuration is compatible between versions.
## Performance Considerations
### Optimizations
- Minimal bundle size (~12KB gzipped)
- Lazy-loaded webview content
- Limited file scanning (100 files max)
- No background processes
- Event-driven updates
### Known Limitations
- Manual refresh required (no auto-watch)
- 100 file limit per folder
- No caching of content metadata
- Simple front matter template only
## Future Enhancements
### Planned for v1.0
- [ ] Inline front matter editing in dashboard
- [ ] Better content search/filter
- [ ] Tags/categories management
- [ ] Content preview
- [ ] Keyboard shortcuts
### Future Considerations
- [ ] IndexedDB caching
- [ ] Background sync
- [ ] Collaborative editing awareness
- [ ] Template customization
- [ ] Export/import configurations
## Metrics
### Development Stats
- **Lines of Code**: ~500 (TypeScript)
- **Build Time**: ~2 seconds
- **Bundle Size**: ~12KB (minified)
- **Dependencies**: 3 (dev only)
- **Documentation**: 2000+ lines
### Compatibility
- ✅ github.dev
- ✅ vscode.dev
- ✅ GitHub Codespaces
- ✅ VS Code Desktop (file & virtual workspaces)
- ✅ All modern browsers
## Security
### Security Scan Results
- CodeQL: 0 vulnerabilities
- No external runtime dependencies
- CSP-compliant webview
- No eval() or unsafe operations
- Input validation on all user inputs
### Privacy
- No telemetry
- No external API calls
- All data stored in VS Code settings
- No file uploads to external services
## Conclusion
Front Matter Lite successfully addresses the original issue by providing a functional, secure, and well-documented web extension for virtual workspaces. It maintains the core value proposition of Front Matter CMS while working within browser constraints.
The implementation is production-ready pending manual testing in real-world virtual workspace scenarios.
## Next Steps
1. Manual testing in github.dev
2. User feedback collection
3. Iterate on UX based on feedback
4. Consider marketplace publishing strategy
5. Update main extension README to reference lite version
6. Create video demo/walkthrough

View File

@@ -1,202 +0,0 @@
# Testing Front Matter Lite
This guide explains how to test the Front Matter Lite extension in various environments.
## Prerequisites
- Built extension (run `npm run build`)
- VS Code installed locally OR
- Access to github.dev/vscode.dev
## Testing Methods
### 1. Testing in VS Code Extension Development Host
This is the fastest way to test during development:
1. Open the lite folder in VS Code
2. Build the extension: `npm run build`
3. Press F5 to launch Extension Development Host
4. In the new window, open a folder or workspace
5. The Front Matter Lite sidebar should appear in the Activity Bar
**To test virtual workspace features:**
1. In Extension Development Host, open Command Palette (F1)
2. Run "Open Remote Repository"
3. Enter a GitHub repository URL (e.g., `https://github.com/username/repo`)
4. The extension will activate in virtual workspace mode
### 2. Testing in github.dev
1. Package the extension:
```bash
npm install -g @vscode/vsce
vsce package
```
2. This creates a `.vsix` file
3. Navigate to github.dev:
- Go to any GitHub repository
- Press `.` (period key)
- OR change `github.com` to `github.dev` in the URL
4. Install the extension:
- Click Extensions icon in Activity Bar
- Click "..." menu
- Choose "Install from VSIX..."
- Select the generated `.vsix` file
5. Test the features
### 3. Testing in vscode.dev
Similar to github.dev:
1. Go to https://vscode.dev
2. Open a folder or repository
3. Install the extension from VSIX (as above)
## Test Scenarios
### Scenario 1: Register a Content Folder
1. Open a repository with markdown files
2. In Explorer, right-click on a folder
3. Select "Front Matter Lite > Register Content Folder (Lite)"
4. Enter a title
5. Verify:
- Success message appears
- Folder appears in Dashboard
- Configuration is saved
### Scenario 2: Create Content
1. Ensure at least one folder is registered
2. Click "Create Content" in the Dashboard OR
3. Run Command: "Front Matter Lite: Create Content (Lite)"
4. Select a content folder
5. Enter a file name
6. Verify:
- File is created with front matter
- File opens in editor
- Front matter includes title, date, tags
### Scenario 3: View Content in Dashboard
1. Register a folder with existing markdown files
2. Click "Refresh" in the Dashboard
3. Verify:
- Files are listed
- File names and folders are shown
- Clicking a file opens it
### Scenario 4: Virtual Workspace Detection
1. Open repository via "Open Remote Repository" or github.dev
2. Check Output channel "Front Matter Lite"
3. Verify message: "Running in virtual workspace mode"
4. Verify information message appears about limited features
## Expected Behavior
### Working Features ✅
- ✅ Register content folders
- ✅ Create new content files
- ✅ View registered folders in dashboard
- ✅ List content files in dashboard
- ✅ Open files from dashboard
- ✅ Basic front matter template
- ✅ Virtual workspace detection
### Known Limitations ❌
- ❌ Cannot edit front matter in UI (use editor)
- ❌ No media upload/management
- ❌ No git integration
- ❌ No custom scripts
- ❌ No file system watch (manual refresh needed)
- ❌ Limited to 100 files per folder
## Debugging
### Enable Logging
1. View > Output
2. Select "Front Matter Lite" from dropdown
3. Check for error messages
### Common Issues
**Extension not appearing:**
- Check that it's built: `npm run build`
- Verify `dist/extension-web.js` exists
- Check package.json has correct `browser` entry point
**Commands not working:**
- Check Output channel for errors
- Verify workspace has folders
- Ensure running in compatible environment
**Dashboard not loading:**
- Check browser console (if in github.dev/vscode.dev)
- Verify webview is enabled
- Check for Content Security Policy errors
### Browser Console (github.dev/vscode.dev)
1. Press F12 to open Developer Tools
2. Check Console tab for JavaScript errors
3. Check Network tab for failed requests
## Manual Testing Checklist
- [ ] Extension activates in virtual workspace
- [ ] Dashboard appears in Activity Bar
- [ ] Can register a folder via context menu
- [ ] Registered folders appear in dashboard
- [ ] Can create content via command
- [ ] Content file has correct front matter
- [ ] Files appear in dashboard after refresh
- [ ] Clicking file in dashboard opens it
- [ ] Virtual workspace mode detected
- [ ] Configuration persists
- [ ] Works in github.dev
- [ ] Works in vscode.dev
- [ ] Works in local VS Code
## Performance Testing
Test with different repository sizes:
1. Small repo (<10 files)
2. Medium repo (10-50 files)
3. Large repo (50-100 files)
Verify:
- Dashboard loads within 2 seconds
- File creation is responsive
- No UI freezing
## Reporting Issues
When reporting issues, include:
1. Environment (github.dev, vscode.dev, local)
2. Workspace type (virtual or file)
3. Steps to reproduce
4. Output channel logs
5. Browser console errors (if applicable)
6. Extension version
## Next Steps
After testing:
1. Document any issues found
2. Verify all test scenarios pass
3. Test in different browsers (Chrome, Firefox, Edge, Safari)
4. Get feedback from users
5. Iterate on improvements

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

1724
lite/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,164 +0,0 @@
{
"name": "vscode-front-matter-lite",
"displayName": "Front Matter CMS (Lite)",
"description": "Front Matter CMS lite version for virtual workspaces (github.dev, vscode.dev). Provides basic content management features with limited functionality compared to the full extension.",
"icon": "assets/frontmatter-teal-128x128.png",
"version": "10.9.0",
"preview": true,
"publisher": "eliostruyf",
"galleryBanner": {
"color": "#0e131f",
"theme": "dark"
},
"engines": {
"vscode": "^1.90.0"
},
"categories": [
"Other"
],
"keywords": [
"Front Matter",
"CMS",
"Markdown",
"Web Extension",
"Virtual Workspace"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/estruyf/vscode-front-matter"
},
"capabilities": {
"virtualWorkspaces": true,
"untrustedWorkspaces": {
"supported": true
}
},
"browser": "./dist/extension-web.js",
"activationEvents": [
"workspaceContains:**/.frontmatter",
"workspaceContains:**/frontmatter.json"
],
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "frontmatter-lite",
"title": "Front Matter Lite",
"icon": "$(file-text)"
}
]
},
"views": {
"frontmatter-lite": [
{
"type": "webview",
"id": "frontMatterLite.panel",
"name": "Metadata"
},
{
"type": "webview",
"id": "frontMatterLite.dashboard",
"name": "Dashboard"
}
]
},
"commands": [
{
"command": "frontMatter.lite.dashboard",
"title": "Open Dashboard (Lite)",
"category": "Front Matter Lite"
},
{
"command": "frontMatter.lite.registerFolder",
"title": "Register Content Folder (Lite)",
"category": "Front Matter Lite"
},
{
"command": "frontMatter.lite.createContent",
"title": "Create Content (Lite)",
"category": "Front Matter Lite"
}
],
"configuration": {
"title": "Front Matter Lite",
"type": "object",
"properties": {
"frontMatter.content.pageFolders": {
"type": "array",
"default": [],
"markdownDescription": "Configure the folders that contain your content",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The title of the folder"
},
"path": {
"type": "string",
"description": "The path to the folder"
}
},
"required": [
"title",
"path"
]
}
},
"frontMatter.taxonomy.contentTypes": {
"type": "array",
"default": [
{
"name": "default",
"fields": [
{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime"
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
}
]
}
],
"markdownDescription": "Configure your content types"
}
}
},
"menus": {
"explorer/context": [
{
"command": "frontMatter.lite.registerFolder",
"when": "explorerResourceIsFolder",
"group": "frontmatter@1"
}
]
}
},
"scripts": {
"vscode:prepublish": "npm run build",
"build": "webpack --mode production --config ./webpack.config.js",
"dev": "webpack --mode development --watch --config ./webpack.config.js"
},
"devDependencies": {
"@types/vscode": "^1.90.0",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^4.10.0"
}
}

View File

@@ -1,279 +0,0 @@
import * as vscode from 'vscode';
export class DashboardProvider implements vscode.WebviewViewProvider {
public static readonly viewType = 'frontMatterLite.dashboard';
private _view?: vscode.WebviewView;
private _outputChannel: vscode.OutputChannel;
constructor(
private readonly _extensionUri: vscode.Uri,
outputChannel: vscode.OutputChannel
) {
this._outputChannel = outputChannel;
}
public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken
) {
this._view = webviewView;
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this._extensionUri]
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
// Handle messages from the webview
webviewView.webview.onDidReceiveMessage(async (data) => {
switch (data.type) {
case 'createContent': {
await vscode.commands.executeCommand('frontMatter.lite.createContent');
break;
}
case 'registerFolder': {
vscode.window.showInformationMessage(
'Please right-click on a folder in the Explorer and select "Front Matter Lite > Register Content Folder"'
);
break;
}
case 'refreshContent': {
await this._refreshContent();
break;
}
case 'openFile': {
try {
const uri = vscode.Uri.parse(data.uri);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
} catch (error) {
vscode.window.showErrorMessage(`Failed to open file: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
break;
}
}
});
// Initial load
this._refreshContent();
}
private async _refreshContent() {
if (!this._view) {
return;
}
const config = vscode.workspace.getConfiguration('frontMatter');
const pageFolders = config.get<Array<{ title: string; path: string }>>('content.pageFolders') || [];
const contentFiles: Array<{ uri: string; name: string; folder: string }> = [];
// Scan all registered folders for markdown files
for (const folder of pageFolders) {
try {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) continue;
const folderUri = vscode.Uri.joinPath(workspaceFolders[0].uri, folder.path);
const pattern = new vscode.RelativePattern(folderUri, '**/*.{md,mdx,markdown}');
// Note: Limited to 100 files per folder to prevent performance issues in large repositories
const files = await vscode.workspace.findFiles(pattern, '**/node_modules/**', 100);
for (const file of files) {
const relativePath = vscode.workspace.asRelativePath(file);
const fileName = relativePath.split('/').pop() || '';
contentFiles.push({
uri: file.toString(),
name: fileName,
folder: folder.title
});
}
} catch (error) {
const errorMsg = `Error scanning folder ${folder.path}: ${error instanceof Error ? error.message : 'Unknown error'}`;
this._outputChannel.appendLine(errorMsg);
}
}
// Send data to webview
this._view.webview.postMessage({
type: 'updateContent',
folders: pageFolders,
files: contentFiles
});
}
private _getHtmlForWebview(webview: vscode.Webview) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Front Matter Lite</title>
<style>
body {
padding: 10px;
color: var(--vscode-foreground);
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
}
.header {
margin-bottom: 20px;
}
h2 {
font-size: 18px;
margin: 0 0 10px 0;
}
.button-group {
display: flex;
gap: 8px;
margin-bottom: 20px;
}
button {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
padding: 8px 12px;
cursor: pointer;
border-radius: 2px;
}
button:hover {
background: var(--vscode-button-hoverBackground);
}
.section {
margin-bottom: 20px;
}
.section-title {
font-weight: bold;
margin-bottom: 8px;
font-size: 14px;
}
.folder-list, .file-list {
list-style: none;
padding: 0;
margin: 0;
}
.folder-item, .file-item {
padding: 8px;
margin-bottom: 4px;
background: var(--vscode-list-inactiveSelectionBackground);
border-radius: 2px;
}
.file-item {
cursor: pointer;
}
.file-item:hover {
background: var(--vscode-list-hoverBackground);
}
.file-name {
font-weight: 500;
}
.file-folder {
font-size: 12px;
color: var(--vscode-descriptionForeground);
margin-top: 2px;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: var(--vscode-descriptionForeground);
}
.empty-state-icon {
font-size: 48px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="header">
<h2>Front Matter Lite</h2>
<div class="button-group">
<button id="createBtn">Create Content</button>
<button id="registerBtn">Register Folder</button>
<button id="refreshBtn">Refresh</button>
</div>
</div>
<div id="content">
<div class="empty-state">
<div class="empty-state-icon">📝</div>
<p>Loading content...</p>
</div>
</div>
<script>
const vscode = acquireVsCodeApi();
document.getElementById('createBtn').addEventListener('click', () => {
vscode.postMessage({ type: 'createContent' });
});
document.getElementById('registerBtn').addEventListener('click', () => {
vscode.postMessage({ type: 'registerFolder' });
});
document.getElementById('refreshBtn').addEventListener('click', () => {
vscode.postMessage({ type: 'refreshContent' });
});
window.addEventListener('message', event => {
const message = event.data;
switch (message.type) {
case 'updateContent': {
updateContent(message.folders, message.files);
break;
}
}
});
function updateContent(folders, files) {
const contentDiv = document.getElementById('content');
if (folders.length === 0) {
contentDiv.innerHTML = \`
<div class="empty-state">
<div class="empty-state-icon">📁</div>
<p>No content folders registered</p>
<p style="font-size: 12px;">Click "Register Folder" to get started</p>
</div>
\`;
return;
}
let html = '<div class="section"><div class="section-title">Content Folders</div><ul class="folder-list">';
folders.forEach(folder => {
html += \`<li class="folder-item">\${folder.title} <span style="color: var(--vscode-descriptionForeground);">(\${folder.path})</span></li>\`;
});
html += '</ul></div>';
if (files.length > 0) {
html += '<div class="section"><div class="section-title">Recent Content</div><ul class="file-list">';
files.forEach(file => {
html += \`
<li class="file-item" data-uri="\${file.uri}">
<div class="file-name">\${file.name}</div>
<div class="file-folder">\${file.folder}</div>
</li>
\`;
});
html += '</ul></div>';
} else {
html += '<div class="empty-state"><p>No content files found</p></div>';
}
contentDiv.innerHTML = html;
// Add click handlers to files
document.querySelectorAll('.file-item').forEach(item => {
item.addEventListener('click', () => {
const uri = item.getAttribute('data-uri');
vscode.postMessage({ type: 'openFile', uri });
});
});
}
</script>
</body>
</html>`;
}
}

View File

@@ -1,527 +0,0 @@
import * as vscode from 'vscode';
/**
* Panel provider for editing front matter metadata of the current file
*/
export class PanelProvider implements vscode.WebviewViewProvider {
public static readonly viewType = 'frontMatterLite.panel';
private _view?: vscode.WebviewView;
private _outputChannel: vscode.OutputChannel;
private _currentFileUri?: vscode.Uri;
constructor(
private readonly _extensionUri: vscode.Uri,
outputChannel: vscode.OutputChannel
) {
this._outputChannel = outputChannel;
}
public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken
) {
this._view = webviewView;
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this._extensionUri]
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
// Handle messages from the webview
webviewView.webview.onDidReceiveMessage(async (data) => {
switch (data.type) {
case 'updateField': {
await this._updateFrontMatterField(data.field, data.value);
break;
}
case 'refresh': {
await this._loadCurrentFile();
break;
}
}
});
// Listen for active editor changes
vscode.window.onDidChangeActiveTextEditor(() => {
this._loadCurrentFile();
});
// Initial load
this._loadCurrentFile();
}
private async _loadCurrentFile() {
if (!this._view) {
return;
}
const editor = vscode.window.activeTextEditor;
if (!editor) {
this._view.webview.postMessage({
type: 'noFile'
});
return;
}
const doc = editor.document;
const fileName = doc.uri.path.split('/').pop() || '';
// Only process markdown files
if (!fileName.match(/\.(md|mdx|markdown)$/i)) {
this._view.webview.postMessage({
type: 'notMarkdown'
});
return;
}
this._currentFileUri = doc.uri;
try {
const content = doc.getText();
const frontMatter = this._parseFrontMatter(content);
this._view.webview.postMessage({
type: 'fileLoaded',
fileName,
frontMatter
});
} catch (error) {
this._outputChannel.appendLine(`Error loading file: ${error}`);
this._view.webview.postMessage({
type: 'error',
message: error instanceof Error ? error.message : 'Unknown error'
});
}
}
/**
* Parse front matter from markdown content
* Note: This is a simplified YAML parser that handles basic key: value pairs
* Limitations:
* - Only supports bracket-style arrays: [item1, item2]
* - Does not support dash-style arrays (- item)
* - Does not handle multiline values
* - May not handle special YAML characters in strings
*/
private _parseFrontMatter(content: string): Record<string, any> {
const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---/;
const match = content.match(frontMatterRegex);
if (!match) {
return {};
}
const frontMatterText = match[1];
const frontMatter: Record<string, any> = {};
// Simple YAML parser (for basic key: value pairs)
const lines = frontMatterText.split('\n');
for (const line of lines) {
const colonIndex = line.indexOf(':');
if (colonIndex === -1) continue;
const key = line.substring(0, colonIndex).trim();
let valueStr = line.substring(colonIndex + 1).trim();
// Handle arrays
if (valueStr.startsWith('[') && valueStr.endsWith(']')) {
frontMatter[key] = valueStr.substring(1, valueStr.length - 1)
.split(',')
.map(v => v.trim().replace(/^['"]|['"]$/g, ''));
} else {
// Remove quotes
frontMatter[key] = valueStr.replace(/^['"]|['"]$/g, '');
}
}
return frontMatter;
}
private async _updateFrontMatterField(field: string, value: any) {
if (!this._currentFileUri) {
return;
}
try {
const doc = await vscode.workspace.openTextDocument(this._currentFileUri);
const content = doc.getText();
const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---/;
const match = content.match(frontMatterRegex);
if (!match) {
vscode.window.showErrorMessage('No front matter found in file');
return;
}
const frontMatterText = match[1];
const lines = frontMatterText.split('\n');
let updated = false;
// Update the field
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const colonIndex = line.indexOf(':');
if (colonIndex === -1) continue;
const key = line.substring(0, colonIndex).trim();
if (key === field) {
// Format the value
// Note: String values with special characters should ideally be quoted
// This simple implementation may not handle all YAML edge cases
let formattedValue: string;
if (Array.isArray(value)) {
formattedValue = `[${value.map(v => `"${v}"`).join(', ')}]`;
} else if (typeof value === 'string') {
// Add quotes if value contains special characters
formattedValue = value.match(/[:\[\]{}]/) ? `"${value}"` : value;
} else {
formattedValue = String(value);
}
lines[i] = `${key}: ${formattedValue}`;
updated = true;
break;
}
}
if (!updated) {
// Field doesn't exist, add it
let formattedValue: string;
if (Array.isArray(value)) {
formattedValue = `[${value.map(v => `"${v}"`).join(', ')}]`;
} else if (typeof value === 'string') {
// Add quotes if value contains special characters
formattedValue = value.match(/[:\[\]{}]/) ? `"${value}"` : value;
} else {
formattedValue = String(value);
}
lines.push(`${field}: ${formattedValue}`);
}
const newFrontMatter = lines.join('\n');
const newContent = content.replace(frontMatterRegex, `---\n${newFrontMatter}\n---`);
// Write the updated content
const edit = new vscode.WorkspaceEdit();
edit.replace(
this._currentFileUri,
new vscode.Range(0, 0, doc.lineCount, 0),
newContent
);
await vscode.workspace.applyEdit(edit);
// Reload to show updated values
await this._loadCurrentFile();
this._outputChannel.appendLine(`Updated field "${field}" with value: ${value}`);
} catch (error) {
const errorMsg = `Error updating front matter: ${error instanceof Error ? error.message : 'Unknown error'}`;
vscode.window.showErrorMessage(errorMsg);
this._outputChannel.appendLine(errorMsg);
}
}
private _getHtmlForWebview(webview: vscode.Webview) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Front Matter Panel</title>
<style>
body {
padding: 10px;
color: var(--vscode-foreground);
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
}
.header {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid var(--vscode-panel-border);
}
h2 {
font-size: 14px;
margin: 0 0 5px 0;
font-weight: 600;
}
.file-name {
font-size: 12px;
color: var(--vscode-descriptionForeground);
}
.field-group {
margin-bottom: 15px;
}
label {
display: block;
font-size: 12px;
font-weight: 500;
margin-bottom: 4px;
color: var(--vscode-foreground);
}
input[type="text"],
input[type="datetime-local"],
textarea {
width: 100%;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
padding: 6px 8px;
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
box-sizing: border-box;
}
input[type="text"]:focus,
input[type="datetime-local"]:focus,
textarea:focus {
outline: 1px solid var(--vscode-focusBorder);
outline-offset: -1px;
}
textarea {
resize: vertical;
min-height: 60px;
}
.tags-input {
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 4px;
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
min-height: 32px;
}
.tag {
background: var(--vscode-badge-background);
color: var(--vscode-badge-foreground);
padding: 2px 8px;
border-radius: 2px;
font-size: 11px;
display: flex;
align-items: center;
gap: 4px;
}
.tag-remove {
cursor: pointer;
font-weight: bold;
}
.tag-input {
border: none;
background: transparent;
color: var(--vscode-input-foreground);
flex: 1;
min-width: 100px;
padding: 4px;
outline: none;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: var(--vscode-descriptionForeground);
}
.empty-state-icon {
font-size: 48px;
margin-bottom: 10px;
}
button {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
padding: 6px 12px;
cursor: pointer;
font-size: 12px;
margin-top: 10px;
}
button:hover {
background: var(--vscode-button-hoverBackground);
}
</style>
</head>
<body>
<div id="content">
<div class="empty-state">
<div class="empty-state-icon">📄</div>
<p>Open a markdown file to edit its front matter</p>
</div>
</div>
<script>
const vscode = acquireVsCodeApi();
let currentFrontMatter = {};
let currentFileName = '';
window.addEventListener('message', event => {
const message = event.data;
switch (message.type) {
case 'fileLoaded': {
currentFrontMatter = message.frontMatter;
currentFileName = message.fileName;
renderFrontMatter();
break;
}
case 'noFile': {
renderEmptyState('No file open');
break;
}
case 'notMarkdown': {
renderEmptyState('Not a markdown file');
break;
}
case 'error': {
renderEmptyState(\`Error: \${message.message}\`);
break;
}
}
});
function renderEmptyState(message) {
const contentDiv = document.getElementById('content');
contentDiv.innerHTML = \`
<div class="empty-state">
<div class="empty-state-icon">📄</div>
<p>\${message}</p>
</div>
\`;
}
function renderFrontMatter() {
const contentDiv = document.getElementById('content');
let html = \`
<div class="header">
<h2>Front Matter</h2>
<div class="file-name">\${currentFileName}</div>
</div>
\`;
// Render common fields
const commonFields = ['title', 'description', 'date', 'tags', 'categories', 'draft'];
for (const field of commonFields) {
const value = currentFrontMatter[field];
if (value !== undefined) {
html += renderField(field, value);
}
}
// Render other fields
for (const [field, value] of Object.entries(currentFrontMatter)) {
if (!commonFields.includes(field)) {
html += renderField(field, value);
}
}
html += \`<button onclick="refreshPanel()">Refresh</button>\`;
contentDiv.innerHTML = html;
// Add event listeners
addFieldListeners();
}
function renderField(field, value) {
const fieldId = \`field-\${field}\`;
if (Array.isArray(value)) {
return \`
<div class="field-group">
<label>\${capitalizeFirst(field)}</label>
<div class="tags-input" id="\${fieldId}">
\${value.map(tag => \`<span class="tag">\${tag} <span class="tag-remove" onclick="removeTag('\${field}', '\${tag}')">×</span></span>\`).join('')}
<input type="text" class="tag-input" placeholder="Add \${field}..." onkeydown="handleTagInput(event, '\${field}')">
</div>
</div>
\`;
} else if (field === 'description') {
return \`
<div class="field-group">
<label>\${capitalizeFirst(field)}</label>
<textarea id="\${fieldId}" data-field="\${field}">\${value || ''}</textarea>
</div>
\`;
} else if (field === 'date') {
// Try to format date for datetime-local input
let dateValue = value;
if (value) {
try {
const d = new Date(value);
dateValue = d.toISOString().slice(0, 16);
} catch (e) {
dateValue = value;
}
}
return \`
<div class="field-group">
<label>\${capitalizeFirst(field)}</label>
<input type="datetime-local" id="\${fieldId}" data-field="\${field}" value="\${dateValue || ''}">
</div>
\`;
} else {
return \`
<div class="field-group">
<label>\${capitalizeFirst(field)}</label>
<input type="text" id="\${fieldId}" data-field="\${field}" value="\${value || ''}">
</div>
\`;
}
}
function capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function addFieldListeners() {
const inputs = document.querySelectorAll('input[data-field], textarea[data-field]');
inputs.forEach(input => {
input.addEventListener('change', (e) => {
const field = e.target.getAttribute('data-field');
let value = e.target.value;
// Convert datetime-local to ISO string
if (e.target.type === 'datetime-local' && value) {
value = new Date(value).toISOString();
}
updateField(field, value);
});
});
}
function handleTagInput(event, field) {
if (event.key === 'Enter' && event.target.value.trim()) {
const tag = event.target.value.trim();
const currentTags = currentFrontMatter[field] || [];
if (!currentTags.includes(tag)) {
const newTags = [...currentTags, tag];
updateField(field, newTags);
event.target.value = '';
}
}
}
function removeTag(field, tag) {
const currentTags = currentFrontMatter[field] || [];
const newTags = currentTags.filter(t => t !== tag);
updateField(field, newTags);
}
function updateField(field, value) {
vscode.postMessage({
type: 'updateField',
field,
value
});
}
function refreshPanel() {
vscode.postMessage({ type: 'refresh' });
}
</script>
</body>
</html>`;
}
}

View File

@@ -1,233 +0,0 @@
import * as vscode from 'vscode';
import { DashboardProvider } from './DashboardProvider';
import { PanelProvider } from './PanelProvider';
import { isVirtualWorkspace } from './utils';
/**
* Lite version of Front Matter CMS for virtual workspaces
* This version provides basic content management functionality using
* the VS Code FileSystem API which works in virtual workspaces like github.dev
*/
let outputChannel: vscode.OutputChannel;
export function activate(context: vscode.ExtensionContext) {
outputChannel = vscode.window.createOutputChannel('Front Matter Lite');
outputChannel.appendLine('Front Matter Lite activated for virtual workspace');
// Register Panel Webview Provider
const panelProvider = new PanelProvider(context.extensionUri, outputChannel);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
PanelProvider.viewType,
panelProvider
)
);
// Register Dashboard Webview Provider
const dashboardProvider = new DashboardProvider(context.extensionUri, outputChannel);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
DashboardProvider.viewType,
dashboardProvider
)
);
// Register Dashboard command
context.subscriptions.push(
vscode.commands.registerCommand('frontMatter.lite.dashboard', async () => {
// Focus on the dashboard view
vscode.commands.executeCommand('frontMatterLite.dashboard.focus');
})
);
// Register folder registration command
context.subscriptions.push(
vscode.commands.registerCommand('frontMatter.lite.registerFolder', async (uri: vscode.Uri) => {
try {
const config = vscode.workspace.getConfiguration('frontMatter');
const pageFolders = config.get<Array<{ title: string; path: string }>>('content.pageFolders') || [];
// Get workspace folder
const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
if (!workspaceFolder) {
vscode.window.showErrorMessage('No workspace folder found');
return;
}
// Calculate relative path
const relativePath = vscode.workspace.asRelativePath(uri, false);
// Check if folder is already registered
const exists = pageFolders.some(f => f.path === relativePath);
if (exists) {
vscode.window.showInformationMessage(`Folder "${relativePath}" is already registered`);
return;
}
// Prompt for folder title
const title = await vscode.window.showInputBox({
prompt: 'Enter a title for this content folder',
value: relativePath
});
if (!title) {
return;
}
// Add folder to configuration
pageFolders.push({
title,
path: relativePath
});
await config.update('content.pageFolders', pageFolders, vscode.ConfigurationTarget.Workspace);
vscode.window.showInformationMessage(
`Content folder "${title}" registered successfully!`,
'View Configuration'
).then(selection => {
if (selection === 'View Configuration') {
vscode.commands.executeCommand('workbench.action.openSettings', 'frontMatter.content.pageFolders');
}
});
outputChannel.appendLine(`Registered content folder: ${title} (${relativePath})`);
} catch (error) {
vscode.window.showErrorMessage(`Failed to register folder: ${error}`);
outputChannel.appendLine(`Error registering folder: ${error}`);
}
})
);
// Register create content command
context.subscriptions.push(
vscode.commands.registerCommand('frontMatter.lite.createContent', async () => {
try {
const config = vscode.workspace.getConfiguration('frontMatter');
const pageFolders = config.get<Array<{ title: string; path: string }>>('content.pageFolders') || [];
if (pageFolders.length === 0) {
const action = await vscode.window.showWarningMessage(
'No content folders configured. Please register a content folder first.',
'Register Folder'
);
if (action === 'Register Folder') {
vscode.window.showInformationMessage(
'Please right-click on a folder in the Explorer and select "Front Matter Lite > Register Content Folder"'
);
}
return;
}
// Select a content folder
const selectedFolder = await vscode.window.showQuickPick(
pageFolders.map(f => ({ label: f.title, description: f.path, folder: f })),
{ placeHolder: 'Select a content folder' }
);
if (!selectedFolder) {
return;
}
// Prompt for file name
const fileName = await vscode.window.showInputBox({
prompt: 'Enter the file name (without extension)',
validateInput: (value) => {
if (!value) {
return 'File name is required';
}
if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
return 'File name can only contain letters, numbers, hyphens, and underscores';
}
return null;
}
});
if (!fileName) {
return;
}
// Create the file
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) {
vscode.window.showErrorMessage('No workspace folder found');
return;
}
const folderUri = vscode.Uri.joinPath(
workspaceFolders[0].uri,
selectedFolder.folder.path
);
const fileUri = vscode.Uri.joinPath(folderUri, `${fileName}.md`);
// Check if file already exists
try {
await vscode.workspace.fs.stat(fileUri);
vscode.window.showErrorMessage(`File "${fileName}.md" already exists`);
return;
} catch (error) {
// Only proceed if the error is FileNotFound
if (error instanceof vscode.FileSystemError && error.code !== 'FileNotFound') {
vscode.window.showErrorMessage(`Error checking file: ${error.message}`);
outputChannel.appendLine(`Error checking file: ${error}`);
return;
}
// File doesn't exist, continue
}
// Create basic front matter content
const date = new Date().toISOString();
const content = `---
title: ${fileName}
description:
date: ${date}
tags: []
---
# ${fileName}
Your content here...
`;
const encoder = new TextEncoder();
await vscode.workspace.fs.writeFile(fileUri, encoder.encode(content));
// Open the file
const doc = await vscode.workspace.openTextDocument(fileUri);
await vscode.window.showTextDocument(doc);
vscode.window.showInformationMessage(`Content "${fileName}.md" created successfully!`);
outputChannel.appendLine(`Created content: ${fileUri.fsPath}`);
} catch (error) {
vscode.window.showErrorMessage(`Failed to create content: ${error}`);
outputChannel.appendLine(`Error creating content: ${error}`);
}
})
);
// Check if running in virtual workspace
if (isVirtualWorkspace()) {
outputChannel.appendLine('Running in virtual workspace mode');
vscode.window.showInformationMessage(
'Front Matter Lite is running in virtual workspace mode. Some features may be limited.',
'Learn More'
).then(selection => {
if (selection === 'Learn More') {
vscode.env.openExternal(
vscode.Uri.parse('https://frontmatter.codes/docs/virtual-workspaces')
);
}
});
}
outputChannel.appendLine('Front Matter Lite: All commands registered');
}
export function deactivate() {
if (outputChannel) {
outputChannel.dispose();
}
}

View File

@@ -1,15 +0,0 @@
import * as vscode from 'vscode';
/**
* Check if the current workspace is a virtual workspace
* Virtual workspaces use schemes other than 'file' (e.g., 'vscode-vfs', 'github')
*/
export function isVirtualWorkspace(): boolean {
if (!vscode.workspace.workspaceFolders) {
return false;
}
return vscode.workspace.workspaceFolders.some(
folder => folder.uri.scheme !== 'file'
);
}

View File

@@ -1,16 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "out",
"lib": ["ES2020"],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"strictNullChecks": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"exclude": ["node_modules", ".vscode-test"]
}

View File

@@ -1,53 +0,0 @@
//@ts-check
'use strict';
const path = require('path');
/**@type {import('webpack').Configuration}*/
const config = {
target: 'webworker', // Web extension target
entry: './src/extension.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'extension-web.js',
libraryTarget: 'commonjs2',
devtoolModuleFilenameTemplate: '../[resource-path]'
},
devtool: 'nosources-source-map',
externals: {
vscode: 'commonjs vscode' // The vscode-module is created on-the-fly and must be excluded
},
resolve: {
extensions: ['.ts', '.js'],
fallback: {
// Webpack 5 no longer polyfills Node.js core modules automatically
path: false,
fs: false,
os: false,
crypto: false,
stream: false,
assert: false,
buffer: false,
util: false
}
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
}
]
},
performance: {
hints: false
}
};
module.exports = config;

View File

@@ -1,68 +0,0 @@
import { XCircleIcon } from '@heroicons/react/24/solid';
import * as React from 'react';
export interface INumberFieldProps {
name: string;
value?: string;
placeholder?: string;
description?: string;
icon?: JSX.Element;
disabled?: boolean;
autoFocus?: boolean;
onChange?: (value: string) => void;
onReset?: () => void;
}
export const NumberField: React.FunctionComponent<INumberFieldProps> = ({
name,
value,
placeholder,
description,
icon,
autoFocus,
disabled,
onChange,
onReset
}: React.PropsWithChildren<INumberFieldProps>) => {
return (
<>
<div className="relative flex justify-center">
{
icon && (
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
{icon}
</div>
)
}
<input
type="number"
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
{(value && onReset) && (
<button onClick={onReset} className="absolute inset-y-0 right-0 pr-3 flex items-center text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]">
<XCircleIcon className={`h-5 w-5`} aria-hidden="true" />
</button>
)}
</div>
{
description && (
<p className="text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2">
{description}
</p>
)
}
</>
);
};

View File

@@ -17,8 +17,6 @@ export const List: React.FunctionComponent<IListProps> = ({
className = `grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5 gap-4`;
} else if (view === DashboardViewType.List) {
className = `-mx-4`;
} else if (view === DashboardViewType.Structure) {
className = `structure-view`;
}
return (

View File

@@ -9,7 +9,6 @@ import { GroupOption } from '../../constants/GroupOption';
import { GroupingSelector, PageAtom, PagedItems, ViewSelector } from '../../state';
import { Item } from './Item';
import { List } from './List';
import { StructureView } from './StructureView';
import usePagination from '../../hooks/usePagination';
import { LocalizationKey, localize } from '../../../localization';
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
@@ -146,21 +145,16 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
/>
{settings && settings?.contentFolders?.length > 0 ? (
<p className={`text-xl font-medium`}>{localize(LocalizationKey.dashboardContentsOverviewNoMarkdown)}</p>
) : (
<p className={`text-lg font-medium`}>{localize(LocalizationKey.dashboardContentsOverviewNoFolders)}</p>
)}
</div>
</div>
);
}
// Handle Structure view first - it overrides all other display modes
if (view === DashboardViewType.Structure) {
return <StructureView pages={pages} />;
}
if (grouping !== GroupOption.none) {
return (
<>
@@ -202,7 +196,7 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
<h1 className='text-xl flex space-x-2 items-center mb-4'>
<PinIcon className={`-rotate-45`} />
<span>{localize(LocalizationKey.dashboardContentsOverviewPinned)}</span>
</h1>
<List>
{pinnedPages.map((page, idx) => (

View File

@@ -1,79 +0,0 @@
import { useRecoilValue } from 'recoil';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
import { Page } from '../../models/Page';
import { SettingsSelector } from '../../state';
import { DateField } from '../Common/DateField';
import { ContentActions } from './ContentActions';
import { useMemo } from 'react';
import { Status } from './Status';
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import useCard from '../../hooks/useCard';
import { ItemSelection } from '../Common/ItemSelection';
import { openFile } from '../../utils';
import useSelectedItems from '../../hooks/useSelectedItems';
import { cn } from '../../../utils/cn';
export interface IStructureItemProps extends Page { }
export const StructureItem: React.FunctionComponent<IStructureItemProps> = ({
...pageData
}: React.PropsWithChildren<IStructureItemProps>) => {
const { selectedFiles } = useSelectedItems();
const settings = useRecoilValue(SettingsSelector);
const draftField = useMemo(() => settings?.draftField, [settings]);
const { escapedTitle } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
const isSelected = useMemo(() => selectedFiles.includes(pageData.fmFilePath), [selectedFiles, pageData.fmFilePath]);
const onOpenFile = React.useCallback(() => {
openFile(pageData.fmFilePath);
}, [pageData.fmFilePath]);
return (
<div className="relative">
<div
className={cn(
`flex items-center space-x-3 py-1 px-2 rounded cursor-pointer hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-editor-foreground)]`,
isSelected && `bg-[var(--vscode-list-activeSelectionBackground)]`
)}
>
<ItemSelection filePath={pageData.fmFilePath} show />
<MarkdownIcon className="w-4 h-4 text-[var(--vscode-symbolIcon-fileForeground)] flex-shrink-0" />
<button
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
onClick={onOpenFile}
className="flex-1 text-left truncate font-medium"
>
{escapedTitle}
</button>
<div className="flex items-center space-x-2 flex-shrink-0">
{pageData.date && (
<DateField
value={pageData.date}
format={pageData.fmDateFormat}
className="text-xs text-[var(--vscode-descriptionForeground)]"
/>
)}
{draftField && draftField.name && typeof pageData[draftField.name] !== "undefined" && (
<Status draft={pageData[draftField.name]} published={pageData.fmPublished} />
)}
<ContentActions
path={pageData.fmFilePath}
relPath={pageData.fmRelFileWsPath}
contentType={pageData.fmContentType}
scripts={settings?.scripts}
onOpen={onOpenFile}
listView
/>
</div>
</div>
</div>
);
};

View File

@@ -1,217 +0,0 @@
import { Disclosure } from '@headlessui/react';
import { ChevronRightIcon, FolderIcon } from '@heroicons/react/24/solid';
import * as React from 'react';
import { useMemo } from 'react';
import { Page } from '../../models';
import { StructureItem } from './StructureItem';
import { parseWinPath } from '../../../helpers/parseWinPath';
export interface IStructureViewProps {
pages: Page[];
}
interface FolderNode {
name: string;
path: string;
children: FolderNode[];
pages: Page[];
}
export const StructureView: React.FunctionComponent<IStructureViewProps> = ({
pages
}: React.PropsWithChildren<IStructureViewProps>) => {
const folderTree = useMemo(() => {
const root: FolderNode = {
name: '',
path: '',
children: [],
pages: []
};
const folderMap = new Map<string, FolderNode>();
folderMap.set('', root);
// Helper to compute the normalized folder path for a page.
// It ensures the page's folder starts with the `fmFolder` segment and
// preserves any subpaths after that segment (so subfolders are created).
const computeNormalizedFolderPath = (page: Page): string => {
if (!page.fmFolder) {
return '';
}
const fmFolder = page.fmFolder.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
// If we have a file path, use its directory (exclude the filename) to compute
// the relative path. This avoids treating filenames as folder segments.
const filePath = page.fmFilePath ? parseWinPath(page.fmFilePath).replace(/^\/+|\/+$/g, '') : '';
const fileDir = filePath && filePath.includes('/') ? filePath.substring(0, filePath.lastIndexOf('/')).replace(/^\/+|\/+$/g, '') : '';
if (fileDir) {
// If the content folder is known, and the file directory starts with it,
// replace that root with the fmFolder (preserving subfolders after it).
if (page.fmPageFolder?.path) {
const contentFolderPath = parseWinPath(page.fmPageFolder.path).replace(/^\/+|\/+$/g, '');
if (fileDir.startsWith(contentFolderPath)) {
const rel = fileDir.substring(contentFolderPath.length).replace(/^\/+|\/+$/g, '');
return rel ? `${fmFolder}/${rel}` : fmFolder;
}
}
// Otherwise try to find fmFolder as a directory segment in the fileDir
const segments = fileDir.split('/').filter(Boolean);
const fmIndex = segments.indexOf(fmFolder);
if (fmIndex >= 0) {
return segments.slice(fmIndex).join('/');
}
}
// Fallback: just use the fmFolder name
return fmFolder;
};
// First pass: create all folder nodes (ensure nodes exist even if a page lacks fmFilePath)
for (const page of pages) {
if (!page.fmFolder) {
continue;
}
const normalizedPath = computeNormalizedFolderPath(page).replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
if (!normalizedPath) {
continue;
}
const parts = normalizedPath.split('/').filter(part => part.length > 0);
let currentPath = '';
let currentNode = root;
for (const part of parts) {
const fullPath = currentPath ? `${currentPath}/${part}` : part;
if (!folderMap.has(fullPath)) {
const newNode: FolderNode = {
name: part,
path: fullPath,
children: [],
pages: []
};
folderMap.set(fullPath, newNode);
currentNode.children.push(newNode);
}
const nextNode = folderMap.get(fullPath);
if (nextNode) {
currentNode = nextNode;
}
currentPath = fullPath;
}
}
// Second pass: assign pages to their exact folder node (including subfolders)
for (const page of pages) {
if (!page.fmFolder) {
root.pages.push(page);
continue;
}
const normalizedPath = computeNormalizedFolderPath(page).replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
const folderNode = normalizedPath ? folderMap.get(normalizedPath) : folderMap.get(page.fmFolder.replace(/\\/g, '/').replace(/^\/+|\/+$/g, ''));
if (folderNode) {
folderNode.pages.push(page);
} else {
// If folder not found, add to root as fallback
root.pages.push(page);
}
}
return root;
}, [pages]);
const renderFolderNode = (node: FolderNode, depth = 0): React.ReactNode => {
const hasContent = node.pages.length > 0 || node.children.length > 0;
if (!hasContent) {
return null;
}
const isRoot = depth === 0;
const paddingLeft = depth * 20;
if (isRoot) {
// For root node, render children and pages directly
return (
<div className='space-y-4'>
{/* Root level folders */}
{node.children.map(child => renderFolderNode(child, depth + 1))}
{/* Root level pages */}
{node.pages.length > 0 && (
<div>
<h3 className="text-lg font-medium mb-3 text-[var(--vscode-editor-foreground)]">
Root Files
</h3>
<ul className="space-y-2">
{node.pages.map((page, idx) => (
<li key={`${page.slug}-${idx}`}>
<StructureItem {...page} />
</li>
))}
</ul>
</div>
)}
</div>
);
}
return (
<div key={node.path} className="mb-4">
<Disclosure defaultOpen={depth <= 1}>
{({ open }) => (
<>
<Disclosure.Button
className="flex items-center w-full text-left"
style={{ paddingLeft: `${paddingLeft}px` }}
>
<ChevronRightIcon
className={`w-4 h-4 mr-2 transform transition-transform ${open ? 'rotate-90' : ''
}`}
/>
<FolderIcon className="w-4 h-4 mr-2 text-[var(--vscode-symbolIcon-folderForeground)]" />
<span className="font-medium text-[var(--vscode-editor-foreground)]">
{node.name}
{node.pages.length > 0 && (
<span className="ml-2 text-sm text-[var(--vscode-descriptionForeground)]">
({node.pages.length} {node.pages.length === 1 ? 'file' : 'files'})
</span>
)}
</span>
</Disclosure.Button>
<Disclosure.Panel className="mt-2">
{/* Child folders */}
{node.children.map(child => renderFolderNode(child, depth + 1))}
{/* Pages in this folder */}
{node.pages.length > 0 && (
<ul className="space-y-1 mb-3">
{node.pages.map((page, idx) => (
<li key={`${page.slug}-${idx}`} style={{ paddingLeft: `${paddingLeft + 20}px` }}>
<StructureItem {...page} />
</li>
))}
</ul>
)}
</Disclosure.Panel>
</>
)}
</Disclosure>
</div>
);
};
return (
<div className="structure-view">
{renderFolderNode(folderTree)}
</div>
);
};

View File

@@ -2,11 +2,10 @@ import * as React from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import usePagination from '../../hooks/usePagination';
import { MediaTotalSelector, PageAtom, SettingsAtom, ViewSelector } from '../../state';
import { MediaTotalSelector, PageAtom, SettingsAtom } from '../../state';
import { PaginationButton } from './PaginationButton';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { DashboardViewType } from '../../models';
export interface IPaginationProps {
totalPages?: number;
@@ -18,7 +17,6 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
const [page, setPage] = useRecoilState(PageAtom);
const totalMedia = useRecoilValue(MediaTotalSelector);
const settings = useRecoilValue(SettingsAtom);
const view = useRecoilValue(ViewSelector);
const { pageSetNr, totalPagesNr } = usePagination(
settings?.dashboardState.contents.pagination,
totalPages,
@@ -35,17 +33,17 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
if (i >= 0 && i <= totalPagesNr) {
buttons.push(
<button
key={i}
disabled={i === page}
onClick={() => {
setPage(i);
}}
className={`max-h-8 rounded ${page === i
? `px-2 bg-[var(--vscode-list-activeSelectionBackground)] text-[var(--vscode-list-activeSelectionForeground)]`
: `text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}`}
>
{i + 1}
</button>
key={i}
disabled={i === page}
onClick={() => {
setPage(i);
}}
className={`max-h-8 rounded ${page === i
? `px-2 bg-[var(--vscode-list-activeSelectionBackground)] text-[var(--vscode-list-activeSelectionForeground)]`
: `text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}`}
>
{i + 1}
</button>
);
}
}
@@ -60,10 +58,6 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
setPage(0);
}, []);
if (view === DashboardViewType.Structure) {
return null;
}
return (
<div className="flex justify-between items-center sm:justify-end space-x-2 text-sm">
<PaginationButton

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { ViewAtom, SettingsSelector } from '../../state';
import { Bars4Icon, Squares2X2Icon, FolderIcon } from '@heroicons/react/24/solid';
import { Bars4Icon, Squares2X2Icon } from '@heroicons/react/24/solid';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { DashboardViewType } from '../../models';
@@ -16,7 +16,9 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (
const [view, setView] = useRecoilState(ViewAtom);
const settings = useRecoilValue(SettingsSelector);
const handleViewChange = (newView: DashboardViewType) => {
const toggleView = () => {
const newView =
view === DashboardViewType.Grid ? DashboardViewType.List : DashboardViewType.Grid;
setView(newView);
Messenger.send(DashboardMessage.setPageViewType, newView);
};
@@ -34,7 +36,7 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (
}`}
title={l10n.t(LocalizationKey.dashboardHeaderViewSwitchToGrid)}
type={`button`}
onClick={() => handleViewChange(DashboardViewType.Grid)}
onClick={toggleView}
>
<Squares2X2Icon className={`w-4 h-4`} />
<span className={`sr-only`}>
@@ -42,29 +44,17 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (
</span>
</button>
<button
className={`flex items-center px-2 py-1 ${view === DashboardViewType.List ? `bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)]` : 'text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]'
className={`flex items-center px-2 py-1 rounded-r-sm ${view === DashboardViewType.List ? `bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)]` : 'text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]'
}`}
title={l10n.t(LocalizationKey.dashboardHeaderViewSwitchToList)}
type={`button`}
onClick={() => handleViewChange(DashboardViewType.List)}
onClick={toggleView}
>
<Bars4Icon className={`w-4 h-4`} />
<span className={`sr-only`}>
{l10n.t(LocalizationKey.dashboardHeaderViewSwitchToList)}
</span>
</button>
<button
className={`flex items-center px-2 py-1 rounded-r-sm ${view === DashboardViewType.Structure ? `bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)]` : 'text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]'
}`}
title={l10n.t(LocalizationKey.dashboardHeaderViewSwitchToStructure)}
type={`button`}
onClick={() => handleViewChange(DashboardViewType.Structure)}
>
<FolderIcon className={`w-4 h-4`} />
<span className={`sr-only`}>
{l10n.t(LocalizationKey.dashboardHeaderViewSwitchToStructure)}
</span>
</button>
</div>
);
};

View File

@@ -3,7 +3,6 @@ import { ChevronDownIcon } from '@heroicons/react/24/outline';
import { Choice, SnippetField, SnippetInfoField } from '../../../models';
import { useEffect } from 'react';
import { TextField } from '../Common/TextField';
import { NumberField } from '../Common/NumberField';
export interface ISnippetInputFieldProps {
field: SnippetField;
@@ -79,17 +78,6 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
);
}
if (field.type === 'number') {
return (
<NumberField
name={field.name}
value={field.value as string || ''}
description={field.description}
onChange={(e) => onValueChange(field, e)}
/>
);
}
return (
<TextField
name={field.name}

View File

@@ -21,7 +21,9 @@ import { DEFAULT_DASHBOARD_FEATURE_FLAGS } from '../../../constants/DefaultFeatu
export interface ISnippetsProps { }
export const Snippets: React.FunctionComponent<ISnippetsProps> = () => {
export const Snippets: React.FunctionComponent<ISnippetsProps> = (
_: React.PropsWithChildren<ISnippetsProps>
) => {
const settings = useRecoilValue(SettingsSelector);
const viewData = useRecoilValue(ViewDataSelector);
const mode = useRecoilValue(ModeAtom);

View File

@@ -1,5 +1,4 @@
export enum DashboardViewType {
Grid = 1,
List,
Structure
List
}

View File

@@ -1,4 +1,4 @@
import { ContentFolder, I18nConfig } from '../../models';
import { I18nConfig } from '../../models';
export interface Page {
// Properties for caching
@@ -20,16 +20,15 @@ export interface Page {
fmCategories: string[];
fmContentType: string;
fmDateFormat: string | undefined;
fmPageFolder: ContentFolder | undefined;
// i18n fields
fmDefaultLocale?: boolean;
fmLocale?: I18nConfig;
fmTranslations?: {
fmTranslations?: {
[locale: string]: {
locale: I18nConfig;
path: string;
};
}
};
title: string;

View File

@@ -807,8 +807,7 @@ export class ArticleHelper {
const elms: Parent[] | Link[] = this.getAllElms(mdTree);
const headings = elms.filter((node) => node.type === 'heading');
const paragraphNodes = elms.filter((node) => node.type === 'paragraph');
const paragraphs = paragraphNodes.length;
const paragraphs = elms.filter((node) => node.type === 'paragraph').length;
const images = elms.filter((node) => node.type === 'image').length;
const links: string[] = elms
.filter((node) => node.type === 'link')
@@ -837,21 +836,6 @@ export class ArticleHelper {
}
}
// Extract first paragraph text for SEO keyword checking
let firstParagraph = '';
if (paragraphNodes.length > 0) {
const firstParagraphNode = paragraphNodes[0];
const extractTextFromNode = (node: any): string => {
if (node.type === 'text') {
return node.value || '';
} else if (node.children && Array.isArray(node.children)) {
return node.children.map(extractTextFromNode).join('');
}
return '';
};
firstParagraph = extractTextFromNode(firstParagraphNode);
}
const wordCount = this.wordCount(0, mdTree);
return {
@@ -862,8 +846,7 @@ export class ArticleHelper {
internalLinks,
externalLinks: externalLinks.length,
wordCount,
content: article.content,
firstParagraph
content: article.content
};
}

View File

@@ -727,10 +727,6 @@ export enum LocalizationKey {
* Change to list
*/
dashboardHeaderViewSwitchToList = 'dashboard.header.viewSwitch.toList',
/**
* Change to structure
*/
dashboardHeaderViewSwitchToStructure = 'dashboard.header.viewSwitch.toStructure',
/**
* Support Front Matter
*/
@@ -1112,7 +1108,7 @@ export enum LocalizationKey {
*/
dashboardStepsStepsToGetStartedGitName = 'dashboard.steps.stepsToGetStarted.git.name',
/**
* Enable Git synchronization to easily sync your changes with your repository.
* Enable Git synchronization to eaily sync your changes with your repository.
*/
dashboardStepsStepsToGetStartedGitDescription = 'dashboard.steps.stepsToGetStarted.git.description',
/**
@@ -1636,10 +1632,6 @@ export enum LocalizationKey {
* Content
*/
panelSeoKeywordInfoValidInfoContent = 'panel.seoKeywordInfo.validInfo.content',
/**
* First paragraph
*/
panelSeoKeywordInfoValidInfoFirstParagraph = 'panel.seoKeywordInfo.validInfo.firstParagraph',
/**
* Recommended frequency: 0.75% - 1.5%
*/

View File

@@ -11,7 +11,6 @@ export interface IArticleDetailsProps {
internalLinks: number;
externalLinks: number;
images: number;
firstParagraph?: string;
};
}

View File

@@ -40,13 +40,11 @@ export const DateTimeField: React.FunctionComponent<IDateTimeFieldProps> = ({
const onDateChange = React.useCallback((date: Date) => {
setDateValue(date);
if (format) {
// Always use DateHelper.formatInTimezone when a format is provided
onChange(DateHelper.formatInTimezone(date, format, timezone) || "");
} else {
// Only fallback to ISO string if no format is provided
onChange(date.toISOString());
}
}, [format, timezone, onChange]);
}, [format, onChange]);
const showRequiredState = useMemo(() => {
return required && !dateValue;

View File

@@ -16,7 +16,6 @@ export interface ISeoKeywordInfoProps {
content: string;
wordCount?: number;
headings?: string[];
firstParagraph?: string;
}
const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
@@ -27,8 +26,7 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
slug,
content,
wordCount,
headings,
firstParagraph
headings
}: React.PropsWithChildren<ISeoKeywordInfoProps>) => {
const density = () => {
@@ -92,10 +90,9 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
(slug.toLowerCase().includes(keyword.toLowerCase()) ||
slug.toLowerCase().includes(keyword.replace(/ /g, '-').toLowerCase())),
content: !!content && content.toLowerCase().includes(keyword.toLowerCase()),
heading: checkHeadings(),
firstParagraph: !!firstParagraph && firstParagraph.toLowerCase().includes(keyword.toLowerCase())
heading: checkHeadings()
};
}, [title, description, slug, content, headings, wordCount, firstParagraph]);
}, [title, description, slug, content, headings, wordCount]);
const tooltipContent = React.useMemo(() => {
return (
@@ -105,8 +102,7 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.description} /> {localize(LocalizationKey.commonDescription)}</span><br />
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.slug} /> {localize(LocalizationKey.commonSlug)}</span><br />
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.content} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoContent)}</span><br />
<span className='inline-flex items-center gap-1'><ValidInfo isValid={!!checks.heading} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoLabel)}</span><br />
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.firstParagraph} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoFirstParagraph)}</span>
<span className='inline-flex items-center gap-1'><ValidInfo isValid={!!checks.heading} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoLabel)}</span>
</>
)
}, [checks]);

View File

@@ -14,7 +14,6 @@ export interface ISeoKeywordsProps {
content: string;
headings?: string[];
wordCount?: number;
firstParagraph?: string;
}
const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({

View File

@@ -96,7 +96,6 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
headings={metadata?.articleDetails?.headingsText}
wordCount={metadata?.articleDetails?.wordCount}
content={metadata?.articleDetails?.content}
firstParagraph={metadata?.articleDetails?.firstParagraph}
/>
<FieldBoundary fieldName={`Keywords`}>

View File

@@ -33,8 +33,7 @@ export class Copilot {
}
const copilotExt = extensions.getExtension(`GitHub.copilot`);
const copilotChatExt = extensions.getExtension(`GitHub.copilot-chat`);
return !!copilotExt || !!copilotChatExt;
return !!copilotExt;
}
public static async suggestTitles(title: string): Promise<string[] | undefined> {
@@ -270,7 +269,7 @@ Example: SEO, website optimization, digital marketing.`
// console.log(models);
const [model] = await lm.selectChatModels({
vendor: 'copilot',
family: Settings.get<string>(SETTING_COPILOT_FAMILY) || 'gpt-4.1'
family: Settings.get<string>(SETTING_COPILOT_FAMILY) || 'gpt-4o-mini'
});
if ((!model || !model.sendRequest) && retry <= 5) {

View File

@@ -219,7 +219,6 @@ export class PagesParser {
const isDefaultLanguage = await i18n.isDefaultLanguage(filePath);
const locale = await i18n.getLocale(filePath);
const translations = await i18n.getTranslations(filePath);
const pageFolder = await Folders.getPageFolderByFilePath(filePath);
const page: Page = {
...article.data,
@@ -242,7 +241,6 @@ export class PagesParser {
fmContentType: contentType.name || DEFAULT_CONTENT_TYPE_NAME,
fmBody: article?.content || '',
fmDateFormat: dateFormat,
fmPageFolder: pageFolder,
// i18n properties
fmDefaultLocale: isDefaultLanguage,
fmLocale: locale,