Compare commits

..

1 Commits

Author SHA1 Message Date
Jorge del Casar
0b16c5b6f3 feat: add generate import map 2021-02-07 09:15:25 +01:00
105 changed files with 1608 additions and 2291 deletions

View File

@@ -43,7 +43,7 @@
## The Goal for Rocket ## The Goal for Rocket
> Our goal is to provide developers with a meta framework for static websites with a sprinkle of JavaScript. > Our goal is to provide developers with a meta framework for static websites with a spricle of JavaScript.
Get a site up and running in no time and focus on the content. Get a site up and running in no time and focus on the content.
You can still tweak every detail of every underlying tool that gets used. You can still tweak every detail of every underlying tool that gets used.
@@ -54,7 +54,7 @@ Rocket is part of the [Modern Web Family](https://twitter.com/modern_web_dev).
We are always looking for contributors of all skill levels! If you're looking to ease your way into the project, try out a [good first issue](https://github.com/modernweb-dev/rocket/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). We are always looking for contributors of all skill levels! If you're looking to ease your way into the project, try out a [good first issue](https://github.com/modernweb-dev/rocket/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
If you are interested in helping contribute to Modern Web, please take a look at our [Contributing Guide](https://github.com/modernweb-dev/rocket/blob/main/CONTRIBUTING.md). Also, feel free to drop into [slack](https://rocket.modern-web.dev/about/slack/) and say hi. 👋 If you are interested in helping contribute to Modern Web, please take a look at our [Contributing Guide](https://github.com/modernweb-dev/rocket/blob/main/CONTRIBUTING.md). Also, feel free to drop into [slack](https://rocket.modern-web.dev/discover/slack/) and say hi. 👋
### Financial Contributors ### Financial Contributors

View File

@@ -1,10 +0,0 @@
html {
--demo-background-color: #eee;
--demo-color: #222;
}
html[theme="dark"] body {
background: #333;
--demo-background-color: #888;
--demo-color: #eee;
}

View File

@@ -7,5 +7,3 @@
rel="stylesheet" rel="stylesheet"
/> />
<meta name="twitter:creator" content="@modern_web_dev" /> <meta name="twitter:creator" content="@modern_web_dev" />
<link rel="stylesheet" href="{{ '/_assets/body.css' | asset | url }}" mdjs-use>

View File

@@ -1,69 +0,0 @@
class DemoElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.platform = 'the web';
this.language = 'en-US';
this.theme = 'light';
this.observer = new MutationObserver(this.updateData);
}
updateData = () => {
this.platform = document.documentElement.getAttribute('platform') || 'the web';
this.language = document.documentElement.getAttribute('data-lang') || 'en-US';
this.theme = document.documentElement.getAttribute('theme') || 'light';
this.requestUpdate();
};
connectedCallback() {
this.updateData();
this.observer.observe(document.documentElement, { attributes: true });
}
requestUpdate() {
this.shadowRoot.innerHTML = this.render();
}
render() {
return `
<style>
:host {
display: block;
background: var(--demo-background-color);
color: var(--demo-color);
padding: 10px;
}
:host[platform~="web"] {
border-bottom: 2px solid #333;
}
@media screen and (min-width: 640px) {
.about {
display: flex;
}
.about ul {
width: 50%;
}
}
</style>
<p>Hello I am DemoElement 👋</p>
<div class="about">
<ul>
<li>My purpose is to demonstrate how an element can adopt to different environments</li>
<li>I like <strong>${this.platform}</strong></li>
</ul>
<ul>
<li>My mother languages is <strong>${this.language}</strong></li>
<li>I feel very comfortable in the <strong>${this.theme}</strong></li>
</ul>
</div>
`;
}
}
customElements.define('demo-element', DemoElement);

View File

@@ -56,8 +56,8 @@ mdjs comes with some additional helpers you can choose to import:
````md ````md
```js script ```js script
import '@mdjs/mdjs-story/define'; import '@mdjs/mdjs-story/mdjs-story.js';
import '@mdjs/mdjs-preview/define'; import '@mdjs/mdjs-preview/mdjs-preview.js';
``` ```
```` ````
@@ -65,8 +65,8 @@ Once loaded you can use them like so:
````md ````md
```js script ```js script
import '@mdjs/mdjs-story/define'; import '@mdjs/mdjs-story/mdjs-story.js';
import '@mdjs/mdjs-preview/define'; import '@mdjs/mdjs-preview/mdjs-preview.js';
``` ```
```` ````
@@ -118,8 +118,8 @@ export const JsPreviewStory = () => html` <demo-wc-card>JS Preview Story</demo-w
Here is a live example from [demo-wc-card](https://www.npmjs.com/package/demo-wc-card). Here is a live example from [demo-wc-card](https://www.npmjs.com/package/demo-wc-card).
```js script ```js script
import '@mdjs/mdjs-story/define'; import '@mdjs/mdjs-story/mdjs-story.js';
import '@mdjs/mdjs-preview/define'; import '@mdjs/mdjs-preview/mdjs-preview.js';
import { html } from 'lit-html'; import { html } from 'lit-html';
``` ```
@@ -132,6 +132,31 @@ export const header = () => {
## Supported Systems ## Supported Systems
### es-dev-server
Preview your mdjs readme with live demos and auto reload.
- Add to your `package.json`:
```json
"scripts": {
"start": "es-dev-server",
}
```
- Create a `es-dev-server.config.js` in the root of your repository.
```js
const { mdjsTransformer } = require('@mdjs/core');
module.exports = {
nodeResolve: true,
open: 'README.md',
watch: true,
responseTransformers: [mdjsTransformer],
};
```
### Storybook ### Storybook
Please check out [@open-wc/demoing-storybook](https://open-wc.org/demoing/) for a fully integrated setup. Please check out [@open-wc/demoing-storybook](https://open-wc.org/demoing/) for a fully integrated setup.

View File

@@ -2,165 +2,18 @@
You can showcase live running code by annotating a code block with `js preview-story`. You can showcase live running code by annotating a code block with `js preview-story`.
## Features
- Shows components inside the page as they are
- You can enable “Simulation Mode” to break them out
- Simulation mode renders in an iframe to supporting media queries and isolated Simulation settings
- Simulation Settings
- Style (windows, mac, android, iOS)
- Size (small, medium, large, Galaxy S5, iPhone X, iPad …)
- Automatic Height
- Theme (light, dark)
- Language (en, nl, …)
- Settings are ”global” for all Simulators (e.g. changing one will change all)
- Settings can be remembered for other pages / return visits
```js script
import { html } from 'lit-html';
import './assets/demo-element.js';
```
## JavaScript Story
````md ````md
```js script
import { html } from 'lit-html';
import './assets/demo-element.js';
```
```js preview-story ```js preview-story
export const foo = () => html`<demo-element></demo-element>`; import { html } from 'lit-html';
export const foo = () => html` <p>my html</p> `;
``` ```
```` ````
will result in will result in
```js preview-story ```js preview-story
export const foo = () => html` <demo-element></demo-element> `; import { html } from 'lit-html';
```
export const foo = () => html` <p>my html</p> `;
## HTML Story
````md
```html preview-story
<demo-element></demo-element>
```
````
will result in
```html preview-story
<demo-element></demo-element>
```
## Setup Simulation Mode
For simulation mode we need a dedicated html file that will be used as an iframe target while loading stories.
The fastest way to create such a file is to use the `layout-simulator` layout.
Create a file `docs/simulator.md` with the following content.
```md
---
layout: layout-simulator
eleventyExcludeFromCollections: true
excludeFromSearch: true
---
```
Once you have that you need to configure it for the story renderer by setting it in your `rocket.config.js`.
```js
export default {
setupUnifiedPlugins: [
adjustPluginOptions('mdjsSetupCode', {
simulationSettings: { simulatorUrl: '/simulator/' },
}),
],
};
```
<inline-notification type="tip">
You can freely choose the path for the "simulator" by creating the md file in a different folder and adjusting the path in the config.
</inline-notification>
## Simulator states
To simulate these stats that usually come from the device itself we put those infos on the document tag.
We can simulate the following settings
1. `platform`
Adopting styles and behavior depending on which device platform you are.
```html
<html platform="web"></html>
<html platform="android"></html>
<html platform="ios"></html>
<!-- potentially later -->
<html platform="web-windows"></html>
<html platform="web-mac"></html>
```
2. `theme`
Adjust your styles based on a theme - light/dark are the default but you can add as many as you want.
```html
<html theme="light"></html>
<html theme="dark"></html>
```
3. `language`
Best to relay on `data-lang` and `lang` often gets changes by translations services which may interfere with you translation loading system.
```html
<html lang="en-US" data-lang="en-US"></html>
<html lang="de-DE" data-lang="de-DE"></html>
```
If you want to react to such document changes you can use an [MutationObserver](https://developer.mozilla.org/de/docs/Web/API/MutationObserver).
For a vanilla web component it could look something like this:
```js
class DemoElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.platform = 'the web';
this.language = 'en-US';
this.theme = 'light';
this.observer = new MutationObserver(this.updateData);
}
updateData = () => {
this.platform = document.documentElement.getAttribute('platform') || 'the web';
this.language = document.documentElement.getAttribute('data-lang') || 'en-US';
this.theme = document.documentElement.getAttribute('theme') || 'light';
this.requestUpdate();
};
connectedCallback() {
this.updateData();
this.observer.observe(document.documentElement, { attributes: true });
}
requestUpdate() {
this.shadowRoot.innerHTML = this.render();
}
render() {
return `
...
`;
}
}
customElements.define('demo-element', DemoElement);
```
```js script
import '@rocket/launch/inline-notification/inline-notification.js';
``` ```

View File

@@ -2,16 +2,10 @@
You can showcase live running code by annotating a code block with `js story`. You can showcase live running code by annotating a code block with `js story`.
```js script
import { html } from 'lit-html';
```
````md ````md
```js script
import { html } from 'lit-html';
```
```js story ```js story
import { html } from 'lit-html';
export const foo = () => html` <p>my html</p> `; export const foo = () => html` <p>my html</p> `;
``` ```
```` ````
@@ -19,5 +13,7 @@ export const foo = () => html` <p>my html</p> `;
will result in will result in
```js story ```js story
import { html } from 'lit-html';
export const foo = () => html` <p>my html</p> `; export const foo = () => html` <p>my html</p> `;
``` ```

View File

@@ -47,9 +47,3 @@ export const headlineConverter = () => html`
``` ```
How it then works is very similar to https://www.11ty.dev/docs/plugins/navigation/ How it then works is very similar to https://www.11ty.dev/docs/plugins/navigation/
## Sidebar redirects
By default, the sidebar nav redirects clicks on category headings to the first child page in that category.
To disable those redirects, override `_includes/_joiningBlocks/_layoutSidebar/sidebar/20-navigation.njk` and add the `no-redirects` attribute to the `<rocket-navigation>` element.

View File

@@ -1,5 +0,0 @@
---
layout: layout-simulator
eleventyExcludeFromCollections: true
excludeFromSearch: true
---

View File

@@ -38,6 +38,6 @@
"testing" "testing"
], ],
"dependencies": { "dependencies": {
"plugins-manager": "^0.2.1" "plugins-manager": "^0.2.0"
} }
} }

View File

@@ -1,17 +1,5 @@
# @rocket/building-rollup # @rocket/building-rollup
## 0.2.0
### Minor Changes
- bad4686: Preserve attributes on script tags. Preserve export names.
## 0.1.3
### Patch Changes
- be0d0b3: fix: add missing main entry to the packages
## 0.1.2 ## 0.1.2
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/building-rollup", "name": "@rocket/building-rollup",
"version": "0.2.0", "version": "0.1.2",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -13,7 +13,6 @@
}, },
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)", "author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/tools/building-rollup/", "homepage": "https://rocket.modern-web.dev/docs/tools/building-rollup/",
"main": "./index.js",
"type": "module", "type": "module",
"exports": { "exports": {
".": "./index.js" ".": "./index.js"
@@ -55,9 +54,9 @@
"@babel/preset-env": "^7.12.11", "@babel/preset-env": "^7.12.11",
"@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-node-resolve": "^11.0.1", "@rollup/plugin-node-resolve": "^11.0.1",
"@web/rollup-plugin-html": "^1.6.0", "@web/rollup-plugin-html": "^1.4.0",
"@web/rollup-plugin-import-meta-assets": "^1.0.4", "@web/rollup-plugin-import-meta-assets": "^1.0.4",
"@web/rollup-plugin-polyfills-loader": "^1.1.0", "@web/rollup-plugin-polyfills-loader": "^1.0.3",
"browserslist": "^4.16.1", "browserslist": "^4.16.1",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"rollup-plugin-workbox": "^6.1.0" "rollup-plugin-workbox": "^6.1.0"

View File

@@ -22,7 +22,7 @@ export function createBasicMetaConfig(userConfig = { output: {} }) {
const assetName = `[${developmentMode ? 'name' : 'hash'}][extname]`; const assetName = `[${developmentMode ? 'name' : 'hash'}][extname]`;
const config = { const config = {
preserveEntrySignatures: 'strict', preserveEntrySignatures: false,
treeshake: !developmentMode, treeshake: !developmentMode,
setupPlugins: [], setupPlugins: [],
...userConfig, ...userConfig,

View File

@@ -53,7 +53,7 @@ export function createSpaMetaConfig(userConfig = { output: {} }) {
// directory to match patterns against to be precached // directory to match patterns against to be precached
globDirectory: path.join(config.output.dir), globDirectory: path.join(config.output.dir),
// cache any html js and css by default // cache any html js and css by default
globPatterns: ['**/*.{html,js,css,webmanifest}', '**/*-search-index.json'], globPatterns: ['**/*.{html,js,css,webmanifest}'],
skipWaiting: true, skipWaiting: true,
clientsClaim: true, clientsClaim: true,
runtimeCaching: [ runtimeCaching: [

View File

@@ -75,14 +75,6 @@ describe('createMapConfig', () => {
'<h1>Has js in sub-js/index.html</h1>\n\n\n<script type="module" src="../sub-js.js"></script>', '<h1>Has js in sub-js/index.html</h1>\n\n\n<script type="module" src="../sub-js.js"></script>',
); );
const subJsAbsoluteIndexHtml = await readOutput('sub-js-absolute/index.html', {
stripToBody: true,
stripServiceWorker: true,
});
expect(subJsAbsoluteIndexHtml).to.equal(
'<h1>Has js in sub-js-absolute/index.html</h1>\n\n\n<script type="module" src="../sub-js-absolute.js"></script>',
);
const serviceWorkerJs = await readOutput('service-worker.js'); const serviceWorkerJs = await readOutput('service-worker.js');
expect(serviceWorkerJs).to.include('Promise'); // not empty string might be enough... expect(serviceWorkerJs).to.include('Promise'); // not empty string might be enough...
}); });

View File

@@ -1,2 +0,0 @@
<h1>Has js in sub-js-absolute/index.html</h1>
<script type="module" src="/sub-js-absolute/sub-js-absolute.js"></script>

View File

@@ -1,11 +1,5 @@
# check-html-links # check-html-links
## 0.2.1
### Patch Changes
- be0d0b3: fix: add missing main entry to the packages
## 0.2.0 ## 0.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "check-html-links", "name": "check-html-links",
"version": "0.2.1", "version": "0.2.0",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -13,7 +13,6 @@
}, },
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)", "author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/tools/check-html-links/", "homepage": "https://rocket.modern-web.dev/docs/tools/check-html-links/",
"main": "./index.js",
"bin": { "bin": {
"check-html-links": "src/cli.js" "check-html-links": "src/cli.js"
}, },

View File

@@ -1,45 +1,5 @@
# @rocket/cli # @rocket/cli
## 0.6.2
### Patch Changes
- ed86ff2: Do not set `data-localize-lang` in the simulator iframe html tag
- f4a0ab5: Pass document shadowRoot to iframe
- c675820: fix: windows path issue avoid filtering of index section of collections
## 0.6.1
### Patch Changes
- abc8a02: You can now use an absolute path for the rootDir
- Updated dependencies [bad4686]
- Updated dependencies [2267e72]
- @rocket/building-rollup@0.2.0
- @rocket/eleventy-plugin-mdjs-unified@0.4.0
## 0.6.0
### Minor Changes
- 5edc40f: Make sure each rocket instance has it's own eleventy config'
- ef8ebb0: To support dynamically created content to be part of the anchor navigation of the page we now analyze the final html output instead of `entry.templateContent`.
BREAKING CHANGE:
- only add anchors for the currently active pages (before it added anchor for every page)
### Patch Changes
- be0d0b3: fix: add missing main entry to the packages
- Updated dependencies [be0d0b3]
- Updated dependencies [ef8ebb0]
- @rocket/building-rollup@0.1.3
- check-html-links@0.2.1
- @rocket/core@0.1.2
- plugins-manager@0.2.1
- @rocket/eleventy-rocket-nav@0.3.0
## 0.5.2 ## 0.5.2
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/cli", "name": "@rocket/cli",
"version": "0.6.2", "version": "0.5.2",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -13,7 +13,6 @@
}, },
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)", "author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/", "homepage": "https://rocket.modern-web.dev/",
"main": "./index.cjs",
"bin": { "bin": {
"rocket": "src/cli.js" "rocket": "src/cli.js"
}, },
@@ -57,23 +56,22 @@
"dependencies": { "dependencies": {
"@11ty/eleventy": "^0.11.1", "@11ty/eleventy": "^0.11.1",
"@11ty/eleventy-img": "^0.7.4", "@11ty/eleventy-img": "^0.7.4",
"@rocket/building-rollup": "^0.2.0", "@rocket/building-rollup": "^0.1.2",
"@rocket/core": "^0.1.2", "@rocket/core": "^0.1.1",
"@rocket/eleventy-plugin-mdjs-unified": "^0.4.0", "@rocket/eleventy-plugin-mdjs-unified": "^0.3.1",
"@rocket/eleventy-rocket-nav": "^0.3.0", "@rocket/eleventy-rocket-nav": "^0.2.1",
"@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-node-resolve": "^11.0.1", "@rollup/plugin-node-resolve": "^11.0.1",
"@web/config-loader": "^0.1.3", "@web/config-loader": "^0.1.3",
"@web/dev-server": "^0.1.4", "@web/dev-server": "^0.1.4",
"@web/dev-server-rollup": "^0.3.2", "@web/dev-server-rollup": "^0.3.2",
"@web/rollup-plugin-copy": "^0.2.0", "@web/rollup-plugin-copy": "^0.2.0",
"check-html-links": "^0.2.1", "check-html-links": "^0.2.0",
"command-line-args": "^5.1.1", "command-line-args": "^5.1.1",
"command-line-usage": "^6.1.1", "command-line-usage": "^6.1.1",
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"micromatch": "^4.0.2", "micromatch": "^4.0.2",
"plugins-manager": "^0.2.1", "plugins-manager": "^0.2.0",
"slash": "^3.0.0",
"utf8": "^3.0.0" "utf8": "^3.0.0"
}, },
"types": "dist-types/index.d.ts" "types": "dist-types/index.d.ts"

View File

@@ -1,69 +0,0 @@
<html theme="light" platform="web" lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
margin: 0;
height: fit-content;
}
html[edge-distance] body {
padding: 8px;
}
</style>
<script type="module">
import { render } from 'lit-html';
async function onHashChange() {
const urlParts = new URLSearchParams(document.location.hash.substr(1));
if (urlParts.get('stylesheets')) {
for (const stylesheet of urlParts.getAll('stylesheets')) {
if (!document.querySelector(`link[rel="stylesheet"][href="${stylesheet}"]`)) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = stylesheet;
document.head.appendChild(link);
}
}
}
if (urlParts.get('theme')) {
document.documentElement.setAttribute('theme', urlParts.get('theme'));
}
if (urlParts.get('platform')) {
document.documentElement.setAttribute('platform', urlParts.get('platform'));
}
if (urlParts.get('language')) {
document.documentElement.setAttribute('lang', urlParts.get('language'));
document.documentElement.setAttribute('data-lang', urlParts.get('language'));
}
if (urlParts.get('story-key')) {
document.documentElement.setAttribute('story-key', urlParts.get('story-key'));
}
if (urlParts.get('edge-distance') === 'true') {
document.documentElement.setAttribute('edge-distance', '');
} else {
document.documentElement.removeAttribute('edge-distance');
}
const mod = await import(urlParts.get('story-file'));
render(mod[urlParts.get('story-key')]({ shadowRoot: document }), document.body);
}
window.addEventListener('hashchange', onHashChange, false);
onHashChange();
const observer = new ResizeObserver(() => {
const dimensions = document.body.getBoundingClientRect();
const data = {
action: 'mdjs-viewer-resize',
storyKey: document.documentElement.getAttribute('story-key'),
width: dimensions.width,
height: dimensions.height,
};
parent.postMessage(JSON.stringify(data), '*');
});
observer.observe(document.body);
</script>
</head>
<body></body>
</html>

View File

@@ -3,7 +3,6 @@
import { rollup } from 'rollup'; import { rollup } from 'rollup';
import fs from 'fs-extra'; import fs from 'fs-extra';
import path from 'path';
import { copy } from '@web/rollup-plugin-copy'; import { copy } from '@web/rollup-plugin-copy';
import { createMpaConfig } from '@rocket/building-rollup'; import { createMpaConfig } from '@rocket/building-rollup';
@@ -46,7 +45,7 @@ async function productionBuild(config) {
dir: config.outputDir, dir: config.outputDir,
}, },
// custom // custom
rootDir: path.resolve(config.outputDevDir), rootDir: config.outputDevDir,
absoluteBaseUrl: config.absoluteBaseUrl, absoluteBaseUrl: config.absoluteBaseUrl,
setupPlugins: [ setupPlugins: [
...defaultSetupPlugins, ...defaultSetupPlugins,

View File

@@ -10,7 +10,6 @@ import computedConfigPkg from './public/computedConfig.cjs';
import path from 'path'; import path from 'path';
import Eleventy from '@11ty/eleventy'; import Eleventy from '@11ty/eleventy';
import TemplateConfig from '@11ty/eleventy/src/TemplateConfig.js';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import fs from 'fs-extra'; import fs from 'fs-extra';
@@ -100,16 +99,11 @@ export class RocketCli {
await this.mergePresets(); await this.mergePresets();
const elev = new RocketEleventy(_inputDirCwdRelative, outputDevDir, this); const elev = new RocketEleventy(_inputDirCwdRelative, outputDevDir, this);
elev.isVerbose = false;
// 11ty always wants a relative path to cwd - why? // 11ty always wants a relative path to cwd - why?
const rel = path.relative(process.cwd(), path.join(__dirname)); const rel = path.relative(process.cwd(), path.join(__dirname));
const relCwdPathToConfig = path.join(rel, 'shared', '.eleventy.cjs'); const relCwdPathToConfig = path.join(rel, 'shared', '.eleventy.cjs');
const config = new TemplateConfig(null, relCwdPathToConfig);
elev.config = config.getConfig();
elev.resetConfig();
elev.setConfigPathOverride(relCwdPathToConfig); elev.setConfigPathOverride(relCwdPathToConfig);
elev.isVerbose = false;
await elev.init(); await elev.init();
this.eleventy = elev; this.eleventy = elev;

View File

@@ -1,6 +1,5 @@
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const slash = require('slash');
const { readdirSync } = require('fs'); const { readdirSync } = require('fs');
function getDirectories(source) { function getDirectories(source) {
@@ -24,7 +23,7 @@ const rocketCollections = {
let docs = [ let docs = [
...collection.getFilteredByGlob(`${_inputDirCwdRelative}/${section}/**/*.md`), ...collection.getFilteredByGlob(`${_inputDirCwdRelative}/${section}/**/*.md`),
]; ];
docs = docs.filter(page => page.inputPath !== `./${slash(indexSection)}`); docs = docs.filter(page => page.inputPath !== `./${indexSection}`);
return docs; return docs;
}); });

View File

@@ -35,7 +35,8 @@ describe('RocketCli e2e', () => {
}); });
describe('eleventy in config', () => { describe('eleventy in config', () => {
it('can modify eleventy via an elventy function in the config', async () => { // TODO: find out while this has a side effect and breaks other tests
it.skip('can modify eleventy via an elventy function in the config', async () => {
cli = await executeStart('e2e-fixtures/content/eleventy.rocket.config.js'); cli = await executeStart('e2e-fixtures/content/eleventy.rocket.config.js');
const indexHtml = await readStartOutput(cli, 'index.html'); const indexHtml = await readStartOutput(cli, 'index.html');
expect(indexHtml).to.equal( expect(indexHtml).to.equal(

View File

@@ -34,7 +34,12 @@ describe('RocketCli use cases', () => {
expect(aboutHtml).to.equal( expect(aboutHtml).to.equal(
[ [
'<p><code>about.md</code></p>', '<p><code>about.md</code></p>',
'<script type="module" src="/about/__mdjs-stories.js" mdjs-setup></script>', '<script type="module">',
' import { myData } from "../sub/assets/myData.js";',
' import("../sub/assets/myData.js");',
' const name = "myData";',
' import(`../sub/assets/${name}.js`);',
'</script>',
].join('\n'), ].join('\n'),
); );
@@ -42,7 +47,12 @@ describe('RocketCli use cases', () => {
expect(subHtml).to.equal( expect(subHtml).to.equal(
[ [
'<p><code>sub/index.md</code></p>', '<p><code>sub/index.md</code></p>',
'<script type="module" src="/sub/__mdjs-stories.js" mdjs-setup></script>', '<script type="module">',
' import { myData } from "./assets/myData.js";',
' import("./assets/myData.js");',
' const name = "myData";',
' import(`./assets/${name}.js`);',
'</script>',
].join('\n'), ].join('\n'),
); );
@@ -52,7 +62,12 @@ describe('RocketCli use cases', () => {
expect(subDetailsHtml).to.equal( expect(subDetailsHtml).to.equal(
[ [
'<p><code>sub/details.md</code></p>', '<p><code>sub/details.md</code></p>',
'<script type="module" src="/sub/details/__mdjs-stories.js" mdjs-setup></script>', '<script type="module">',
' import { myData } from "../assets/myData.js";',
' import("../assets/myData.js");',
' const name = "myData";',
' import(`../assets/${name}.js`);',
'</script>',
].join('\n'), ].join('\n'),
); );
@@ -60,7 +75,12 @@ describe('RocketCli use cases', () => {
expect(indexHtml).to.equal( expect(indexHtml).to.equal(
[ [
'<p><code>index.md</code></p>', '<p><code>index.md</code></p>',
'<script type="module" src="/__mdjs-stories.js" mdjs-setup></script>', '<script type="module">',
' import { myData } from "./sub/assets/myData.js";',
' import("./sub/assets/myData.js");',
' const name = "myData";',
' import(`./sub/assets/${name}.js`);',
'</script>',
].join('\n'), ].join('\n'),
); );
}); });

View File

@@ -1,11 +1,5 @@
# @rocket/core # @rocket/core
## 0.1.2
### Patch Changes
- be0d0b3: fix: add missing main entry to the packages
## 0.1.1 ## 0.1.1
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/core", "name": "@rocket/core",
"version": "0.1.2", "version": "0.1.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -13,7 +13,6 @@
}, },
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)", "author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/", "homepage": "https://rocket.modern-web.dev/",
"main": "./dist/title.cjs",
"type": "module", "type": "module",
"exports": { "exports": {
"./title": { "./title": {

View File

@@ -1,11 +1,5 @@
# @rocket/drawer # @rocket/drawer
## 0.1.3
### Patch Changes
- 0b64116: Update @lion dependencies
## 0.1.2 ## 0.1.2
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/drawer", "name": "@rocket/drawer",
"version": "0.1.3", "version": "0.1.2",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -33,7 +33,7 @@
"testing" "testing"
], ],
"dependencies": { "dependencies": {
"@lion/overlays": "^0.26.1", "@lion/overlays": "^0.23.2",
"lit-element": "^2.4.0" "lit-element": "^2.4.0"
}, },
"types": "dist-types/index.d.ts" "types": "dist-types/index.d.ts"

View File

@@ -18,7 +18,6 @@ export class RocketDrawer extends OverlayMixin(LitElement) {
return { return {
useOverlay: { type: Boolean, reflect: true }, useOverlay: { type: Boolean, reflect: true },
useOverlayMediaQuery: { type: String }, useOverlayMediaQuery: { type: String },
mediaMatcher: { type: Object },
}; };
} }
@@ -90,20 +89,6 @@ export class RocketDrawer extends OverlayMixin(LitElement) {
} }
} }
} }
if (changedProperties.has('useOverlayMediaQuery')) {
this.mediaMatcher.removeEventListener('change', this.onMatchMedia);
this.mediaMatcher = window.matchMedia(this.useOverlayMediaQuery);
this.mediaMatcher.addEventListener('change', this.onMatchMedia);
this.useOverlay = !!this.mediaMatcher.matches;
}
}
/**
* @param { MediaQueryListEvent } query
*/
onMatchMedia(query) {
this.useOverlay = !!query.matches;
} }
_setupOpenCloseListeners() { _setupOpenCloseListeners() {
@@ -133,15 +118,11 @@ export class RocketDrawer extends OverlayMixin(LitElement) {
this.__toggle = this.__toggle.bind(this); this.__toggle = this.__toggle.bind(this);
this.onMatchMedia = this.onMatchMedia.bind(this);
this.onGestureStart = this.onGestureStart.bind(this); this.onGestureStart = this.onGestureStart.bind(this);
this.onGestureMove = this.onGestureMove.bind(this); this.onGestureMove = this.onGestureMove.bind(this);
this.onGestureEnd = this.onGestureEnd.bind(this); this.onGestureEnd = this.onGestureEnd.bind(this);
this.updateFromTouch = this.updateFromTouch.bind(this); this.updateFromTouch = this.updateFromTouch.bind(this);
this.mediaMatcher = window.matchMedia(this.useOverlayMediaQuery);
this.mediaMatcher.addEventListener('change', this.onMatchMedia);
this._startX = 0; this._startX = 0;
this._currentX = 0; this._currentX = 0;
this._velocity = 0; this._velocity = 0;
@@ -152,7 +133,10 @@ export class RocketDrawer extends OverlayMixin(LitElement) {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this.useOverlay = !!this.mediaMatcher.matches; this.useOverlay = !!window.matchMedia(this.useOverlayMediaQuery).matches;
window.matchMedia(this.useOverlayMediaQuery).addListener(query => {
this.useOverlay = !!query.matches;
});
} }
render() { render() {

View File

@@ -1,18 +1,5 @@
# @rocket/eleventy-plugin-mdjs-unified # @rocket/eleventy-plugin-mdjs-unified
## 0.4.0
### Minor Changes
- 2267e72: Write the mdjs JavaScript code to a file and load it from there instead of an inline script
### Patch Changes
- Updated dependencies [a8e66d8]
- Updated dependencies [fe6a929]
- Updated dependencies [a8e66d8]
- @mdjs/core@0.7.0
## 0.3.1 ## 0.3.1
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/eleventy-plugin-mdjs-unified", "name": "@rocket/eleventy-plugin-mdjs-unified",
"version": "0.4.0", "version": "0.3.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -31,7 +31,7 @@
"mdjs" "mdjs"
], ],
"dependencies": { "dependencies": {
"@mdjs/core": "^0.7.0", "@mdjs/core": "^0.6.2",
"es-module-lexer": "^0.3.26", "es-module-lexer": "^0.3.26",
"unist-util-visit": "^2.0.3" "unist-util-visit": "^2.0.3"
}, },

View File

@@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
const path = require('path');
const fs = require('fs');
const { mdjsProcess } = require('@mdjs/core'); const { mdjsProcess } = require('@mdjs/core');
const visit = require('unist-util-visit'); const visit = require('unist-util-visit');
const { init, parse } = require('es-module-lexer'); const { init, parse } = require('es-module-lexer');
@@ -9,7 +7,7 @@ const { init, parse } = require('es-module-lexer');
const { parseTitle } = require('@rocket/core/title'); const { parseTitle } = require('@rocket/core/title');
/** @typedef {import('@mdjs/core').MdjsProcessPlugin} MdjsProcessPlugin */ /** @typedef {import('@mdjs/core').MdjsProcessPlugin} MdjsProcessPlugin */
/** @typedef {import('../types/code').EleventyPluginMdjsUnified} EleventyPluginMdjsUnified */ /** @typedef {import('../types/code').EleventPluginMdjsUnified} EleventPluginMdjsUnified */
/** @typedef {import('../types/code').NodeChildren} NodeChildren */ /** @typedef {import('../types/code').NodeChildren} NodeChildren */
/** @typedef {import('../types/code').NodeElement} NodeElement */ /** @typedef {import('../types/code').NodeElement} NodeElement */
/** @typedef {import('unist').Node} Node */ /** @typedef {import('unist').Node} Node */
@@ -95,7 +93,7 @@ async function processImports(source, inputPath) {
} }
/** /**
* @param {EleventyPluginMdjsUnified} pluginOptions * @param {EleventPluginMdjsUnified} pluginOptions
*/ */
function eleventyUnified(pluginOptions) { function eleventyUnified(pluginOptions) {
/** /**
@@ -141,12 +139,10 @@ function eleventyUnified(pluginOptions) {
let code = result.html; let code = result.html;
if (result.jsCode) { if (result.jsCode) {
const newFolder = path.dirname(eleventySettings.page.outputPath);
const newName = path.join(newFolder, '__mdjs-stories.js');
await fs.promises.mkdir(newFolder, { recursive: true });
await fs.promises.writeFile(newName, result.jsCode, 'utf8');
code += ` code += `
<script type="module" src="${eleventySettings.page.url}__mdjs-stories.js" mdjs-setup></script> <script type="module">
${result.jsCode}
</script>
`; `;
} }
return code; return code;
@@ -161,15 +157,15 @@ function eleventyUnified(pluginOptions) {
/** /**
* @param {*} eleventyConfig * @param {*} eleventyConfig
* @param {EleventyPluginMdjsUnified} [pluginOptions] * @param {EleventPluginMdjsUnified} [pluginOptions]
*/ */
function configFunction(eleventyConfig, pluginOptions = {}) { function configFunction(eleventyConfig, pluginOptions = {}) {
eleventyConfig.setLibrary('md', eleventyUnified(pluginOptions)); eleventyConfig.setLibrary('md', eleventyUnified(pluginOptions));
} }
const EleventyPluginMdjsUnified = { const eleventPluginMdjsUnified = {
initArguments: {}, initArguments: {},
configFunction, configFunction,
}; };
module.exports = EleventyPluginMdjsUnified; module.exports = eleventPluginMdjsUnified;

View File

@@ -53,12 +53,13 @@ describe('eleventy-plugin-mdjs-unified', () => {
it('renders markdown with javascript', async () => { it('renders markdown with javascript', async () => {
const files = await renderEleventy('./test-node/fixtures/mdjs'); const files = await renderEleventy('./test-node/fixtures/mdjs');
expect(files).to.deep.equal([
expect(files.length).to.equal(1); {
expect(files[0].name).to.equal('first/index.html'); html:
'<h1 id="first"><a aria-hidden="true" tabindex="-1" href="#first"><span class="icon icon-link"></span></a>First</h1>\n<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> foo <span class="token operator">=</span> <span class="token string">\'bar\'</span><span class="token punctuation">;</span>\n<span class="token keyword module">import</span> <span class="token punctuation">{</span> html <span class="token punctuation">}</span> <span class="token keyword module">from</span> <span class="token string">\'lit-html\'</span><span class="token punctuation">;</span>\n</code></pre>\n<mdjs-story mdjs-story-name="inline"></mdjs-story>\n<mdjs-preview mdjs-story-name="withBorder"></mdjs-preview>\n <script type="module">\n \nexport const inline = () => html` <p>main</p> `;\nexport const withBorder = () => html` <p>main</p> `;\nconst rootNode = document;\nconst stories = [{ key: \'inline\', story: inline, code: `<pre class="language-js"><code class="language-js"><span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function">inline</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">\\`</span><span class="token html language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>p</span><span class="token punctuation">></span></span>main<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>p</span><span class="token punctuation">></span></span> </span><span class="token template-punctuation string">\\`</span></span><span class="token punctuation">;</span>\n</code></pre>` }, { key: \'withBorder\', story: withBorder, code: `<pre class="language-js"><code class="language-js"><span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function">withBorder</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">\\`</span><span class="token html language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>p</span><span class="token punctuation">></span></span>main<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>p</span><span class="token punctuation">></span></span> </span><span class="token template-punctuation string">\\`</span></span><span class="token punctuation">;</span>\n</code></pre>` }];\nfor (const story of stories) {\n const storyEl = rootNode.querySelector(`[mdjs-story-name="${story.key}"]`);\n storyEl.codeHasHtml = true;\n storyEl.story = story.story;\n storyEl.code = story.code;\n};\nif (!customElements.get(\'mdjs-preview\')) { import(\'@mdjs/mdjs-preview/mdjs-preview.js\'); }\nif (!customElements.get(\'mdjs-story\')) { import(\'@mdjs/mdjs-story/mdjs-story.js\'); }\n </script>\n ',
expect(files[0].html).to.include('<script type="module"'); name: 'first/index.html',
expect(files[0].html).to.include('mdjs-setup>'); },
]);
}); });
it('rewrites relative import pathes', async () => { it('rewrites relative import pathes', async () => {
@@ -66,7 +67,7 @@ describe('eleventy-plugin-mdjs-unified', () => {
expect(files).to.deep.equal([ expect(files).to.deep.equal([
{ {
html: html:
'<p>first</p>\n <script type="module" src="/first/__mdjs-stories.js" mdjs-setup></script>\n ', "<p>first</p>\n <script type=\"module\">\n import '../import-me.js';\nimport('../import-me-too.js');\n </script>\n ",
name: 'first/index.html', name: 'first/index.html',
}, },
]); ]);
@@ -77,7 +78,7 @@ describe('eleventy-plugin-mdjs-unified', () => {
expect(files).to.deep.equal([ expect(files).to.deep.equal([
{ {
html: html:
'<p>first</p>\n <script type="module" src="/subpage/first/__mdjs-stories.js" mdjs-setup></script>\n ', "<p>first</p>\n <script type=\"module\">\n import '../../import-me.js';\nimport('../../import-me-too.js');\n </script>\n ",
name: 'subpage/first/index.html', name: 'subpage/first/index.html',
}, },
]); ]);
@@ -88,7 +89,7 @@ describe('eleventy-plugin-mdjs-unified', () => {
expect(files).to.deep.equal([ expect(files).to.deep.equal([
{ {
html: html:
'<p>index</p>\n <script type="module" src="/__mdjs-stories.js" mdjs-setup></script>\n ', "<p>index</p>\n <script type=\"module\">\n import './import-me.js';\nimport('./import-me-too.js');\n </script>\n ",
name: 'index.html', name: 'index.html',
}, },
]); ]);

View File

@@ -3,7 +3,7 @@ import { Node } from 'unist';
export const setupUnifiedPluginsFn: (plugins: MdjsProcessPlugin[]) => MdjsProcessPlugin[]; export const setupUnifiedPluginsFn: (plugins: MdjsProcessPlugin[]) => MdjsProcessPlugin[];
export interface EleventyPluginMdjsUnified { export interface EleventPluginMdjsUnified {
setupUnifiedPlugins?: setupUnifiedPluginsFn[]; setupUnifiedPlugins?: setupUnifiedPluginsFn[];
} }

View File

@@ -1,5 +1,4 @@
const RocketNav = require('./eleventy-rocket-nav'); const RocketNav = require('./eleventy-rocket-nav');
const { addPageAnchors } = require('./src/addPageAnchors.js');
// export the configuration function for plugin // export the configuration function for plugin
module.exports = function (eleventyConfig) { module.exports = function (eleventyConfig) {
@@ -9,10 +8,6 @@ module.exports = function (eleventyConfig) {
eleventyConfig.addNunjucksFilter('rocketNavToHtml', function (pages, options) { eleventyConfig.addNunjucksFilter('rocketNavToHtml', function (pages, options) {
return RocketNav.toHtml.call(eleventyConfig, pages, options); return RocketNav.toHtml.call(eleventyConfig, pages, options);
}); });
eleventyConfig.addTransform('rocket-nav-add-page-anchors', async function (content) {
const newContent = await addPageAnchors(content);
return newContent;
});
}; };
module.exports.navigation = { module.exports.navigation = {

View File

@@ -1,15 +1,5 @@
# @rocket/eleventy-rocket-nav # @rocket/eleventy-rocket-nav
## 0.3.0
### Minor Changes
- ef8ebb0: To support dynamically created content to be part of the anchor navigation of the page we now analyze the final html output instead of `entry.templateContent`.
BREAKING CHANGE:
- only add anchors for the currently active pages (before it added anchor for every page)
## 0.2.1 ## 0.2.1
### Patch Changes ### Patch Changes

View File

@@ -94,8 +94,21 @@ function findNavigationEntries(nodes = [], key = '') {
entry.title = entry.key; entry.title = entry.key;
} }
if (entry.key) { if (entry.key) {
if (!headingsCache.has(entry.templateContent)) {
headingsCache.set(entry.templateContent, getHeadingsOfHtml(entry.templateContent));
}
const headings = /** @type {Heading[]} */ (headingsCache.get(entry.templateContent));
const anchors = headings.map(heading => ({
key: heading.text + Math.random(),
parent: entry.key,
url: `${entry.url}#${heading.id}`,
pluginType: 'eleventy-navigation',
parentKey: entry.key,
title: heading.text,
anchor: true,
}));
// @ts-ignore // @ts-ignore
entry.children = findNavigationEntries(nodes, entry.key); entry.children = [...anchors, ...findNavigationEntries(nodes, entry.key)];
} }
return entry; return entry;
}); });
@@ -214,36 +227,43 @@ function navigationToHtml(pages, _options = {}) {
}>${pages }>${pages
.map(entry => { .map(entry => {
const liClass = []; const liClass = [];
const aClass = [];
if (options.listItemClass) { if (options.listItemClass) {
liClass.push(options.listItemClass); liClass.push(options.listItemClass);
} }
if (options.activeKey === entry.key && options.activeListItemClass) { if (options.anchorClass) {
liClass.push(options.activeListItemClass); aClass.push(options.anchorClass);
}
if (options.activeKey === entry.key) {
if (options.activeListItemClass) {
liClass.push(options.activeListItemClass);
}
if (options.activeAnchorClass) {
aClass.push(options.activeAnchorClass);
}
} }
if (options.activeTreeListClass && activePages && activePages.includes(entry.key)) { if (options.activeTreeListClass && activePages && activePages.includes(entry.key)) {
liClass.push(options.activeTreeListClass); liClass.push(options.activeTreeListClass);
} }
if (options.activeAnchorListClass && activePages && activePages.includes(entry.key)) {
aClass.push(options.activeAnchorListClass);
}
if (options.listItemHasChildrenClass && entry.children && entry.children.length) { if (options.listItemHasChildrenClass && entry.children && entry.children.length) {
liClass.push(options.listItemHasChildrenClass); liClass.push(options.listItemHasChildrenClass);
} }
const output = []; if (entry.anchor) {
output.push( liClass.push('anchor');
`<${options.listItemElement}${liClass.length ? ` class="${liClass.join(' ')}"` : ''}>`, aClass.push('anchor');
);
output.push(`<a href="${urlFilter(entry.url)}">${entry.title}</a>`);
if (options.showExcerpt && entry.excerpt) {
output.push(`: ${entry.excerpt}`);
} }
if (options.activeKey === entry.key && options.activeListItemClass) {
output.push('<!-- ADD PAGE ANCHORS -->');
}
if (entry.children) {
output.push(navigationToHtml(entry.children, options));
}
output.push(`</${options.listItemElement}>`);
return output.join('\n'); return `<${options.listItemElement}${
liClass.length ? ` class="${liClass.join(' ')}"` : ''
}><a href="${urlFilter(entry.url)}"${
aClass.length ? ` class="${aClass.join(' ')}"` : ''
}>${entry.title}</a>${options.showExcerpt && entry.excerpt ? `: ${entry.excerpt}` : ''}${
entry.children ? navigationToHtml(entry.children, options) : ''
}</${options.listItemElement}>`;
}) })
.join('\n')}</${options.listElement}>` .join('\n')}</${options.listElement}>`
: ''; : '';

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/eleventy-rocket-nav", "name": "@rocket/eleventy-rocket-nav",
"version": "0.3.0", "version": "0.2.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -13,7 +13,7 @@
}, },
"main": ".eleventy.js", "main": ".eleventy.js",
"scripts": { "scripts": {
"test": "mocha test-node/**/*.test.js test-node/*.test.js --timeout 5000", "test": "mocha test-node/**/*.test.js test-node/*.test.js",
"test:watch": "mocha test-node/**/*.test.js test-node/*.test.js --watch" "test:watch": "mocha test-node/**/*.test.js test-node/*.test.js --watch"
}, },
"files": [ "files": [

View File

@@ -1,136 +0,0 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
const fs = require('fs');
const { SaxEventType, SAXParser } = require('sax-wasm');
const saxPath = require.resolve('sax-wasm/lib/sax-wasm.wasm');
const saxWasmBuffer = fs.readFileSync(saxPath);
/** @typedef {import('../types').Heading} Heading */
/** @typedef {import('sax-wasm').Text} Text */
/** @typedef {import('sax-wasm').Tag} Tag */
/** @typedef {import('sax-wasm').Position} Position */
// Instantiate
const parser = new SAXParser(
SaxEventType.CloseTag | SaxEventType.Comment,
{ highWaterMark: 256 * 1024 }, // 256k chunks
);
/**
* @param {object} options
* @param {string} options.content
* @param {Position} options.start
* @param {Position} options.end
* @param {string} options.insert
*/
function removeBetween({ content, start, end, insert = '' }) {
const lines = content.split('\n');
const i = start.line;
const line = lines[i];
const upToChange = line.slice(0, start.character - 1);
const afterChange = line.slice(end.character + 2);
lines[i] = `${upToChange}${insert}${afterChange}`;
return lines.join('\n');
}
/**
* @param {Tag} data
* @param {string} name
*/
function getAttribute(data, name) {
if (data.attributes) {
const { attributes } = data;
const foundIndex = attributes.findIndex(entry => entry.name.value === name);
if (foundIndex !== -1) {
return attributes[foundIndex].value.value;
}
}
return null;
}
/**
* @param {Tag} data
*/
function getText(data) {
if (data.textNodes) {
return data.textNodes.map(textNode => textNode.value).join('');
}
return null;
}
/**
* @param {string} html
*/
function getHeadingsOfHtml(html) {
/** @type {Heading[]} */
const headings = [];
/** @type {Text} */
let insertPoint;
parser.eventHandler = (ev, _data) => {
if (ev === SaxEventType.Comment) {
const data = /** @type {Text} */ (/** @type {any} */ (_data));
// NOTE: we NEED to access data internal value so sax-wasm does not reuse it's value
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const tmp = data.start.line + data.end.line;
if (data.value.trim() === 'ADD PAGE ANCHORS' || data.value.trim() === '-->ADD PAGE ANCHORS') {
insertPoint = data;
}
}
if (ev === SaxEventType.CloseTag) {
const data = /** @type {Tag} */ (/** @type {any} */ (_data));
if (data.name === 'h2') {
const id = getAttribute(data, 'id');
const text = getText(data);
if (id && text) {
headings.push({ text, id });
}
}
}
};
parser.write(Buffer.from(html, 'utf8'));
parser.end();
// @ts-ignore
return { headings, insertPoint };
}
let isSetup = false;
/**
* @param {string} content
*/
async function addPageAnchors(content) {
if (!isSetup) {
await parser.prepareWasm(saxWasmBuffer);
isSetup = true;
}
const { headings, insertPoint } = getHeadingsOfHtml(content);
const pageAnchorsHtml = [];
if (headings.length > 0) {
pageAnchorsHtml.push('<ul>');
for (const heading of headings) {
pageAnchorsHtml.push(' <li class="menu-item anchor">');
pageAnchorsHtml.push(` <a href="#${heading.id}" class="anchor">${heading.text}</a>`);
pageAnchorsHtml.push(' </li>');
}
pageAnchorsHtml.push('</ul>');
}
if (insertPoint) {
return removeBetween({
content,
start: insertPoint.start,
end: insertPoint.end,
insert: pageAnchorsHtml.join('\n'),
});
}
return content;
}
module.exports = {
addPageAnchors,
};

View File

@@ -1,32 +0,0 @@
const { expect } = require('chai');
const prettier = require('prettier');
const { addPageAnchors } = require('../src/addPageAnchors.js');
const format = code => prettier.format(code, { parser: 'html' }).trim();
describe('addPageAnchors', () => {
it('finds and adds anchors for each h2 as an unordered list', async () => {
const input = [
'<body>',
' <!-- ADD PAGE ANCHORS -->',
' <div id="content">',
' <h2 id="first">👉 First Headline</h2>',
' </div>',
'</body>',
].join('\n');
const expected = [
'<body>',
' <ul>',
' <li class="menu-item anchor">',
' <a href="#first" class="anchor">👉 First Headline</a>',
' </li>',
' </ul>',
' <div id="content">',
' <h2 id="first">👉 First Headline</h2>',
' </div>',
'</body>',
].join('\n');
const result = await addPageAnchors(input);
expect(format(result)).to.deep.equal(expected);
});
});

View File

@@ -1,5 +0,0 @@
const eleventyNavigationPlugin = require('@rocket/eleventy-rocket-nav');
module.exports = function (eleventyConfig) {
eleventyConfig.addPlugin(eleventyNavigationPlugin);
};

View File

@@ -1 +0,0 @@
module.exports = 'layout.njk';

View File

@@ -1,9 +0,0 @@
<body>
{{ collections.all | rocketNav | rocketNavToHtml({
listItemClass: "menu-item",
activeListItemClass: "current",
activeKey: eleventyNavigation.key
}) | safe }}
{{ content | safe }}
</body>

View File

@@ -1,9 +0,0 @@
---
title: Bats
eleventyNavigation:
key: Bats
parent: Mammals
order: 2
---
🦇 can fly.

View File

@@ -1,12 +0,0 @@
---
title: Humans
eleventyNavigation:
key: Humans
parent: Mammals
order: 1
---
<h2 id="anatomy">Anatomy</h2>
<p>Has arms.</p>
<h2 id="age">📖 Age</h2>
<p>Up to 130 years.</p>

View File

@@ -1,7 +0,0 @@
---
title: Mammals
eleventyNavigation:
key: Mammals
---
Mammals need air.

View File

@@ -1,96 +0,0 @@
const path = require('path');
const fs = require('fs-extra');
const { expect } = require('chai');
const Eleventy = require('@11ty/eleventy');
const TemplateConfig = require('@11ty/eleventy/src/TemplateConfig');
const prettier = require('prettier');
async function execute(fixtureDir) {
const relPath = path.relative(process.cwd(), __dirname);
const relativeInputPath = path.join(relPath, fixtureDir.split('/').join(path.sep));
const relativeOutputPath = path.join(relPath, 'fixtures', '__output');
const relativeConfigPath = path.join(relativeInputPath, '.eleventy.js');
await fs.emptyDir(relativeOutputPath);
const elev = new Eleventy(relativeInputPath, relativeOutputPath);
const config = new TemplateConfig(null, relativeConfigPath);
elev.config = config.getConfig();
elev.setConfigPathOverride(relativeConfigPath);
elev.resetConfig();
await elev.init();
await elev.write();
return {
readOutput: async readPath => {
const relativeReadPath = path.join(relativeOutputPath, readPath);
let text = await fs.promises.readFile(relativeReadPath);
text = text.toString();
text = prettier.format(text, { parser: 'html', printWidth: 100 });
return text.trim();
},
};
}
describe('eleventy-rocket-nav', () => {
it('renders a menu with anchors for h2 content', async () => {
const { readOutput } = await execute('fixtures/three-pages');
const bats = await readOutput('bats/index.html');
expect(bats).to.deep.equal(
[
'<body>',
' <ul>',
' <li class="menu-item active">',
' <a href="/mammals/">Mammals</a>',
' <ul>',
' <li class="menu-item">',
' <a href="/humans/">Humans</a>',
' </li>',
' <li class="menu-item current">',
' <a href="/bats/">Bats</a>',
' </li>',
' </ul>',
' </li>',
' </ul>',
'',
' <p>🦇 can fly.</p>',
'</body>',
].join('\n'),
);
const humans = await readOutput('humans/index.html');
expect(humans).to.deep.equal(
[
'<body>',
' <ul>',
' <li class="menu-item active">',
' <a href="/mammals/">Mammals</a>',
' <ul>',
' <li class="menu-item current">',
' <a href="/humans/">Humans</a>',
' <ul>',
' <li class="menu-item anchor">',
' <a href="#anatomy" class="anchor">Anatomy</a>',
' </li>',
' <li class="menu-item anchor">',
' <a href="#age" class="anchor">📖 Age</a>',
' </li>',
' </ul>',
' </li>',
' <li class="menu-item">',
' <a href="/bats/">Bats</a>',
' </li>',
' </ul>',
' </li>',
' </ul>',
'',
' <h2 id="anatomy">Anatomy</h2>',
' <p>Has arms.</p>',
' <h2 id="age">📖 Age</h2>',
' <p>Up to 130 years.</p>',
'</body>',
].join('\n'),
);
});
});

View File

@@ -0,0 +1,28 @@
# Check HTML Links
A fast checker for broken links/references in HTML.
## Installation
```
npm i -D check-html-links
```
## Usage
```
npx check-html-links _site
```
For docs please see our homepage [https://rocket.modern-web.dev/docs/tools/check-html-links/](https://rocket.modern-web.dev/docs/tools/check-html-links/).
## Comparison
Checking the output of the [11ty-website](https://github.com/11ty/11ty-website) with 13 missing reference targets (used by 516 links) while checking 501 files. (on January 17, 2021)
| Tool | Lines printed | Times | Lang | Dependency Tree |
| ---------------- | ------------- | ------ | ---- | --------------- |
| check-html-links | 38 | ~2.5s | node | 19 |
| link-checker | 3000+ | ~11s | node | 106 |
| hyperlink | 68 | 4m 20s | node | 481 |
| htmltest | 1000+ | ~0.7s | GO | - |

View File

@@ -0,0 +1 @@
export { inspectFolder } from './src/inspectFolder.js';

View File

@@ -0,0 +1,47 @@
{
"name": "generate-import-map",
"version": "0.0.1",
"publishConfig": {
"access": "public"
},
"description": "A fast low dependency generator of import map",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/modernweb-dev/rocket.git",
"directory": "packages/generate-import-map"
},
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/tools/generate-import-map/",
"bin": {
"generate-import-map": "src/cli.js"
},
"type": "module",
"exports": {
".": "./index.js"
},
"scripts": {
"test": "mocha --timeout 5000 test-node/**/*.test.{js,cjs} test-node/*.test.{js,cjs}",
"test:watch": "onchange 'src/**/*.{js,cjs}' 'test-node/**/*.{js,cjs}' -- npm test",
"types:copy": "copyfiles \"./types/**/*.d.ts\" dist-types/"
},
"files": [
"*.js",
"dist",
"dist-types",
"src"
],
"dependencies": {
"chalk": "^4.0.0",
"es-module-lexer": "^0.3.26",
"glob": "^7.0.0",
"node-fetch": "^2.6.1",
"sax-wasm": "^2.0.0"
},
"devDependencies": {
"@types/es-module-lexer": "^0.3.0",
"@types/glob": "^7.0.0",
"@types/node-fetch": "^2.5.8"
},
"types": "dist-types/index.d.ts"
}

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env node
import path from 'path';
import chalk from 'chalk';
import { extractBareImports } from './inspectFolder.js';
import { checkRemoteSpecifiers, createImportMap } from './createImportMap.js';
import { listFiles } from './listFiles.js';
async function main() {
const userRootDir = process.argv[2];
const userImportMapDist = process.argv[3];
const remoteUrl = process.argv[4];
const rootDir = userRootDir ? path.resolve(userRootDir) : process.cwd();
const importMapDist = userImportMapDist
? path.resolve(userImportMapDist)
: `${process.cwd()}/import-map.json`;
const performanceInspectStart = process.hrtime();
console.log('👀 Looking for all imports...');
const files = await listFiles('**/*.html', rootDir);
const filesOutput =
files.length == 0
? '🧐 No files to check. Did you select the correct folder?'
: `🔥 Found a total of ${chalk.green.bold(files.length)} files to check!`;
console.log(filesOutput);
const { localSpecifiers, bareSpecifiers } = await extractBareImports(files, rootDir);
const performanceInspectEnd = process.hrtime(performanceInspectStart);
console.log(
`🔗 Found a total of ${chalk.green.bold(
localSpecifiers.length,
)} local imports and ${chalk.green.bold(bareSpecifiers.length)} bare imports!`,
);
console.log(
`✅ Files inspected. (executed in %ds %dms)`,
performanceInspectEnd[0],
performanceInspectEnd[1] / 1000000,
);
const performanceRemoteStart = process.hrtime();
const foundBareSpecifiers = await checkRemoteSpecifiers(bareSpecifiers, remoteUrl);
createImportMap(foundBareSpecifiers, remoteUrl, importMapDist);
const performanceRemoteEnd = process.hrtime(performanceRemoteStart);
console.log(
`✅ Remote CDN check and import map created. (executed in %ds %dms)`,
performanceRemoteEnd[0],
performanceRemoteEnd[1] / 1000000,
);
}
main();

View File

@@ -0,0 +1,48 @@
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
export const defaultRemoteUrl = 'https://cdn.skypack.dev/';
export const defaultImportMapDist = path.resolve('./import-map.json');
/**
*
* @param {string[]} bareSpecifiers
* @param {string} remoteUrl
*/
export async function checkRemoteSpecifiers(bareSpecifiers, remoteUrl = defaultRemoteUrl) {
/** @type {Promise<void>[]} */
const promises = [];
/** @type {string[]} */
const result = [];
bareSpecifiers.forEach(specifier => {
const remoteSpecifier = `${remoteUrl}${specifier}`;
const promise = fetch(remoteSpecifier).then(response => {
if (response.ok) {
result.push(specifier);
}
});
promises.push(promise);
});
await Promise.all(promises);
return result;
}
/**
*
* @param {string[]} bareSpecifiers
* @param {string} remoteUrl
* @param {string} importMapDist
*/
export function createImportMap(
bareSpecifiers,
remoteUrl = defaultRemoteUrl,
importMapDist = defaultImportMapDist,
) {
const imports = {};
bareSpecifiers.forEach(specifier => {
const remoteSpecifier = `${remoteUrl}${specifier}`;
imports[specifier] = remoteSpecifier;
});
fs.writeFileSync(importMapDist, JSON.stringify({ imports }, null, 2));
}

View File

@@ -0,0 +1,197 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import fs from 'fs';
import saxWasm from 'sax-wasm';
import { createRequire } from 'module';
import { init, parse } from 'es-module-lexer';
import { listFiles } from './listFiles.js';
import path from 'path';
/** @typedef {import('../types/main').Script} Script */
/** @typedef {import('../types/main').Import} Import */
/** @typedef {import('sax-wasm').Attribute} Attribute */
/** @typedef {import('sax-wasm').Tag} Tag */
/** @typedef {import('sax-wasm').Text} Text */
/** @typedef {import('es-module-lexer').ImportSpecifier} ImportSpecifier */
const require = createRequire(import.meta.url);
const { SaxEventType, SAXParser } = saxWasm;
const streamOptions = { highWaterMark: 256 * 1024 };
const saxPath = require.resolve('sax-wasm/lib/sax-wasm.wasm');
const saxWasmBuffer = fs.readFileSync(saxPath);
const parserSpecifiers = new SAXParser(
SaxEventType.OpenTag | SaxEventType.CloseTag | SaxEventType.Attribute | SaxEventType.Text,
streamOptions,
);
let localSpecifiers = new Set();
let bareSpecifiers = new Set();
/**
* @param {string} filePath
* @param {object} option
* @param {string} option.rootDir
*/
function analyzeFile(filePath, { rootDir }) {
if (fs.existsSync(filePath) && !fs.lstatSync(filePath).isDirectory()) {
const source = fs.readFileSync(filePath, { encoding: 'utf-8' });
const importDir = path.dirname(filePath);
console.log('Resolving specifier %s', filePath);
getSpecifiers(source).forEach(specifier => {
addSpecifier(specifier, { importDir, rootDir });
});
}
}
/**
* @param {string} source
*/
function getSpecifiers(source) {
/** @type {[ImportSpecifier[], string[]]} */
const [imports] = parse(source, 'optional-sourcename');
return imports.map(specifier => source.substring(specifier.s, specifier.e).replace(/'/g, ''));
}
/**
*
* @param {string} specifier
* @param {object} options
* @param {string} options.importDir
* @param {string} options.rootDir
*/
function addSpecifier(specifier, { importDir, rootDir }) {
let newSpecifier = specifier.trim();
if (newSpecifier.endsWith('/')) {
newSpecifier += 'index.js';
}
if (newSpecifier.startsWith('/')) {
// Absolute path specifier
newSpecifier = path.join(rootDir, newSpecifier);
} else if (newSpecifier.startsWith('../') || newSpecifier.startsWith('./')) {
// Relative path specifier
newSpecifier = path.join(importDir, newSpecifier);
}
if (newSpecifier.startsWith('/')) {
if (!localSpecifiers.has(newSpecifier)) {
localSpecifiers.add(newSpecifier);
analyzeFile(newSpecifier, { rootDir });
}
} else {
const [namespace, name] = newSpecifier.split('/');
if (namespace.startsWith('@')) {
newSpecifier = `${namespace}/${name}`;
} else {
newSpecifier = namespace;
}
bareSpecifiers.add(newSpecifier);
}
}
/**
*
* @param {Script[]} scripts
* @param {object} options
* @param {string} options.importDir
* @param {string} options.rootDir
*/
async function resolveSpecifiers(scripts, { importDir, rootDir }) {
for (const scriptObj of scripts) {
if (scriptObj.specifier) {
addSpecifier(scriptObj.specifier, { importDir, rootDir });
} else {
getSpecifiers(scriptObj.content).forEach(specifier => {
addSpecifier(specifier, { importDir, rootDir });
});
}
}
}
/**
* @param {string} filePath
*/
function extractScripts(filePath) {
/** @type {Script[]} */
const scripts = [];
let captureContent = false;
parserSpecifiers.eventHandler = (ev, /** @type {any} */ _data) => {
const data = /** @type {Tag | Text} */ _data;
if (captureContent && ev === SaxEventType.Text) {
const scriptObj = scripts[scripts.length - 1];
scriptObj.content += data.value;
} else if (ev === SaxEventType.CloseTag && data.name.toString() === 'script') {
captureContent = false;
} else if (ev === SaxEventType.OpenTag) {
const tagName = data.name.toString();
const type = data.attributes.find(
(/** @type Attribute */ attr) => attr.name.toString() === 'type',
);
if (tagName === 'script' && type?.value.toString() === 'module') {
const scriptObj = /** @type {Script} */ {
filePath,
content: '',
specifier: '',
};
const src = data.attributes.find(
(/** @type Attribute */ attr) => attr.name.toString() === 'src',
);
if (src) {
let srcPath = src.value.toString().trim();
if (!srcPath.startsWith('.') || !srcPath.startsWith('/')) {
srcPath = `./${srcPath}`;
}
scriptObj.specifier = srcPath;
} else {
captureContent = true;
}
scripts.push(scriptObj);
}
}
};
return new Promise(resolve => {
const readable = fs.createReadStream(filePath, streamOptions);
readable.on('data', chunk => {
// @ts-expect-error
parserSpecifiers.write(chunk);
});
readable.on('end', () => {
parserSpecifiers.end();
resolve(scripts);
});
});
}
/**
* @param {string[]} files
* @param {string} rootDir
*/
export async function extractBareImports(files, rootDir) {
await parserSpecifiers.prepareWasm(saxWasmBuffer);
await init;
localSpecifiers = new Set();
bareSpecifiers = new Set();
for (const filePath of files) {
const importDir = path.dirname(filePath);
const scripts = await extractScripts(filePath);
await resolveSpecifiers(scripts, { importDir, rootDir });
}
return {
localSpecifiers: Array.from(localSpecifiers),
bareSpecifiers: Array.from(bareSpecifiers),
};
}
/**
* @param {string} inRootDir
*/
export async function inspectFolder(inRootDir, extension = 'html') {
const rootDir = path.resolve(inRootDir);
const files = await listFiles(`**/*.${extension}`, rootDir);
return await extractBareImports(files, rootDir);
}

View File

@@ -0,0 +1,34 @@
import fs from 'fs';
import path from 'path';
import glob from 'glob';
/**
* Lists all files using the specified glob, starting from the given root directory.
*
* Will return all matching file paths fully resolved.
*
* @param {string} fromGlob
* @param {string} rootDir
*/
export function listFiles(fromGlob, rootDir) {
return new Promise(resolve => {
glob(
fromGlob,
{ cwd: rootDir },
/**
* @param {Error | null} er
* @param {string[]} files
*/
(er, files) => {
// remember, each filepath returned is relative to rootDir
resolve(
files
// fully resolve the filename relative to rootDir
.map(filePath => path.resolve(rootDir, filePath))
// filter out directories
.filter(filePath => !fs.lstatSync(filePath).isDirectory()),
);
},
);
});
}

View File

@@ -0,0 +1,12 @@
<script>
import "/empty.js";
</script>
<script>
import "./empty.js";
</script>
<script>
import "./empty.js ";
</script>
<script>
import " ./empty.js ";
</script>

View File

@@ -0,0 +1,4 @@
<script src="/empty.js"></script>
<script src="./empty.js"></script>
<script src="./empty.js "></script>
<script src=" ./empty.js "></script>

View File

@@ -0,0 +1 @@
export { TestComponent } from './src/TestComponent.js';

View File

@@ -0,0 +1,3 @@
import { LitElement } from 'lit-element';
export class TestElement extends LitElement {}

View File

@@ -0,0 +1,3 @@
import { TestComponent } from './src/TestComponent.js';
window.customElements.define('test-element', TestComponent);

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="scripts/main.js" type="module"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1,545 @@
<!doctype html>
<html lang="en" dir="ltr">
<head>
<link rel="stylesheet" href="/_merged_assets/rocket-blog.css">
<link
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap"
rel="stylesheet"
/>
<meta name="twitter:creator" content="@modern_web_dev" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Markdown JavaScript: Overview: Rocket</title>
<meta property="og:title" content="Markdown JavaScript: Overview: Rocket"/>
<meta name="generator" content="rocket 0.1">
<link rel="canonical" href="/docs/markdown-javascript/overview/"/>
<meta name="description" content="Rocket is the way to build fast static websites with a sprinkle of JavaScript"/>
<meta property="og:description" content="Rocket is the way to build fast static websites with a sprinkle of JavaScript"/>
<link rel="apple-touch-icon" sizes="180x180" href="/_merged_assets/_static/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/_merged_assets/_static/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/_merged_assets/_static/icons/favicon-16x16.png">
<link rel="manifest" href="/_merged_assets/webmanifest.json">
<link rel="mask-icon" href="/_merged_assets/_static/icons/safari-pinned-tab.svg" color="#3f93ce">
<meta name="msapplication-TileColor" content="#1d3557">
<meta name="theme-color" content="#1d3557">
<meta property="og:site_name" content="Rocket"/>
<meta property="og:type" content="website"/>
<meta property="og:image" content="/_merged_assets/11ty-img/f8d62426-1200.png"/>
<meta property="og:url" content="/docs/markdown-javascript/overview/"/>
<meta name="twitter:card" content="summary_large_image"/>
<link rel="stylesheet" href="/_merged_assets/variables.css">
<link rel="stylesheet" href="/_merged_assets/layout.css">
<link rel="stylesheet" href="/_merged_assets/markdown.css">
<link rel="stylesheet" href="/_merged_assets/style.css">
<noscript><link rel="stylesheet" href="/_merged_assets/_static/noscript.css"/></noscript>
</head>
<body layout="layout-sidebar">
<header id="main-header">
<div class="content-area">
<button id="mobile-menu-trigger" data-action="trigger-mobile-menu">
<span class="sr-only">Show Menu</span>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewbox="0 0 448 512" class="icon">
<path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path>
</svg>
</button>
<a class="logo-link" href="/">
<img src="/_merged_assets/logo.svg" alt="Rocket Logo" />
<span>Rocket</span>
</a>
<rocket-search class="search" json-url="/_merged_assets/_static/rocket-search-index.json">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" style="width: 25px;">
<path d="M51.6 96.7c11 0 21-3.9 28.8-10.5l35 35c.8.8 1.8 1.2 2.9 1.2s2.1-.4 2.9-1.2c1.6-1.6 1.6-4.2 0-5.8l-35-35c6.5-7.8 10.5-17.9 10.5-28.8 0-24.9-20.2-45.1-45.1-45.1-24.8 0-45.1 20.3-45.1 45.1 0 24.9 20.3 45.1 45.1 45.1zm0-82c20.4 0 36.9 16.6 36.9 36.9C88.5 72 72 88.5 51.6 88.5S14.7 71.9 14.7 51.6c0-20.3 16.6-36.9 36.9-36.9z"/>
</svg>
</rocket-search>
<script type="module">
import '@rocket/search/rocket-search.js';
</script>
<a href="/guides/" class="
">Guides</a>
<a href="/docs/" class="
active
">Docs</a>
<a href="/blog/" class="
">Blog</a>
<launch-dark-switch class="light-dark-switch" label="Toggle darkmode">Toggle darkmode</launch-dark-switch>
<a class="social-link" href="https://github.com/modernweb-dev/rocket" aria-label="Rocket on GitHub" rel="noopener noreferrer" target="_blank">
<span class="sr-only">GitHub</span>
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 17 16" fill="none">
<title>GitHub</title>
<path fill="currentColor" fill-rule="evenodd" d="M8.18391.249268C3.82241.249268.253906 3.81777.253906 8.17927c0 3.46933 2.279874 6.44313 5.451874 7.53353.3965.0991.49563-.1983.49563-.3965v-1.3878c-2.18075.4956-2.67638-.9912-2.67638-.9912-.3965-.8922-.89212-1.1895-.89212-1.1895-.69388-.4957.09912-.4957.09912-.4957.793.0992 1.1895.793 1.1895.793.69388 1.2887 1.88338.8922 2.27988.6939.09912-.4956.29737-.8921.49562-1.0904-1.78425-.1982-3.5685-.8921-3.5685-3.96496 0-.89212.29738-1.586.793-2.08162-.09912-.19825-.3965-.99125.09913-2.08163 0 0 .69387-.19825 2.18075.793.59475-.19825 1.28862-.29737 1.9825-.29737.69387 0 1.38775.09912 1.98249.29737 1.4869-.99125 2.1808-.793 2.1808-.793.3965 1.09038.1982 1.88338.0991 2.08163.4956.59475.793 1.28862.793 2.08162 0 3.07286-1.8834 3.66766-3.66764 3.86586.29737.3965.59474.8921.59474 1.586v2.1808c0 .1982.0991.4956.5948.3965 3.172-1.0904 5.4518-4.0642 5.4518-7.53353-.0991-4.3615-3.6676-7.930002-8.02909-7.930002z" clip-rule="evenodd"></path>
</svg>
</a>
</div>
</header>
<div id="content-wrapper">
<div class="content-area">
<rocket-drawer id="sidebar">
<nav slot="content" id="sidebar-nav">
<a class="logo-link" href="/">
<img src="/_merged_assets/logo.svg" alt="Rocket Logo" />
<span>Rocket</span>
</a>
<rocket-navigation>
<ul><li class="menu-item"><a href="/docs/configuration/">Configuration</a><ul><li class="menu-item"><a href="/docs/configuration/overview/">Overview</a><ul><li class="menu-item anchor"><a href="/docs/configuration/overview/#adding-rollup-plugins" class="anchor">Adding Rollup Plugins</a></li>
<li class="menu-item anchor"><a href="/docs/configuration/overview/#modifying-options-of-plugins" class="anchor">Modifying Options of Plugins</a></li></ul></li>
<li class="menu-item"><a href="/docs/configuration/computed-config/">Computed Config</a><ul><li class="menu-item anchor"><a href="/docs/configuration/computed-config/#set-your-own-data" class="anchor">Set Your Own Data</a></li>
<li class="menu-item anchor"><a href="/docs/configuration/computed-config/#default-available-configs" class="anchor">Default Available Configs</a></li></ul></li></ul></li>
<li class="menu-item"><a href="/docs/presets/">Presets</a><ul><li class="menu-item"><a href="/docs/presets/joining-blocks/">Joining Blocks</a><ul><li class="menu-item anchor"><a href="/docs/presets/joining-blocks/#adding-content-without-overriding" class="anchor">Adding content without overriding</a></li>
<li class="menu-item anchor"><a href="/docs/presets/joining-blocks/#overriding-content" class="anchor">Overriding Content</a></li>
<li class="menu-item anchor"><a href="/docs/presets/joining-blocks/#reordering-and-overriding" class="anchor">Reordering and Overriding</a></li></ul></li>
<li class="menu-item"><a href="/docs/presets/launch/">Launch</a><ul><li class="menu-item anchor"><a href="/docs/presets/launch/#installation" class="anchor">Installation</a></li>
<li class="menu-item anchor"><a href="/docs/presets/launch/#data" class="anchor">Data</a></li>
<li class="menu-item anchor"><a href="/docs/presets/launch/#inline-notification" class="anchor">Inline Notification</a></li></ul></li>
<li class="menu-item"><a href="/docs/presets/search/">Search</a><ul><li class="menu-item anchor"><a href="/docs/presets/search/#installation" class="anchor">Installation</a></li></ul></li>
<li class="menu-item"><a href="/docs/presets/blog/">Blog</a><ul><li class="menu-item anchor"><a href="/docs/presets/blog/#installation" class="anchor">Installation</a></li>
<li class="menu-item anchor"><a href="/docs/presets/blog/#usage" class="anchor">Usage</a></li></ul></li></ul></li>
<li class="menu-item active"><a href="/docs/markdown-javascript/" class="active">Markdown JavaScript</a><ul><li class="menu-item current"><a href="/docs/markdown-javascript/overview/">Overview</a><ul><li class="menu-item anchor"><a href="/docs/markdown-javascript/overview/#web-components" class="anchor">Web Components</a></li>
<li class="menu-item anchor"><a href="/docs/markdown-javascript/overview/#demo-support-story" class="anchor">Demo Support (Story)</a></li>
<li class="menu-item anchor"><a href="/docs/markdown-javascript/overview/#supported-systems" class="anchor">Supported Systems</a></li>
<li class="menu-item anchor"><a href="/docs/markdown-javascript/overview/#build-mdjs" class="anchor">Build mdjs</a></li></ul></li>
<li class="menu-item"><a href="/docs/markdown-javascript/preview/">Preview</a></li>
<li class="menu-item"><a href="/docs/markdown-javascript/story/">Story</a></li></ul></li>
<li class="menu-item"><a href="/docs/eleventy-plugins/">Eleventy Plugins</a><ul><li class="menu-item"><a href="/docs/eleventy-plugins/mdjs-unified/">Markdown JavaScript (mdjs)</a><ul><li class="menu-item anchor"><a href="/docs/eleventy-plugins/mdjs-unified/#setup" class="anchor">Setup</a></li>
<li class="menu-item anchor"><a href="/docs/eleventy-plugins/mdjs-unified/#configure-a-unified-or-remark-plugin-with-mdjs" class="anchor">Configure a unified or remark Plugin with mdjs</a></li>
<li class="menu-item anchor"><a href="/docs/eleventy-plugins/mdjs-unified/#add-a-unified-or-remark-plugin" class="anchor">Add a unified or remark Plugin</a></li></ul></li></ul></li>
<li class="menu-item"><a href="/docs/tools/">Tools</a><ul><li class="menu-item"><a href="/docs/tools/plugins-manager/">Plugins Manager</a><ul><li class="menu-item anchor"><a href="/docs/tools/plugins-manager/#problem" class="anchor">Problem</a></li>
<li class="menu-item anchor"><a href="/docs/tools/plugins-manager/#problem-statement" class="anchor">Problem Statement</a></li>
<li class="menu-item anchor"><a href="/docs/tools/plugins-manager/#solution" class="anchor">Solution</a></li>
<li class="menu-item anchor"><a href="/docs/tools/plugins-manager/#adding-a-plugin" class="anchor">Adding a Plugin</a></li>
<li class="menu-item anchor"><a href="/docs/tools/plugins-manager/#adjusting-plugin-options" class="anchor">Adjusting Plugin Options</a></li>
<li class="menu-item anchor"><a href="/docs/tools/plugins-manager/#converting-metaplugins-to-an-actual-plugin" class="anchor">Converting metaPlugins to an Actual Plugin</a></li></ul></li>
<li class="menu-item"><a href="/docs/tools/rollup-config/">Rollup Config</a><ul><li class="menu-item anchor"><a href="/docs/tools/rollup-config/#features" class="anchor">Features</a></li>
<li class="menu-item anchor"><a href="/docs/tools/rollup-config/#setup" class="anchor">Setup</a></li>
<li class="menu-item anchor"><a href="/docs/tools/rollup-config/#customizations" class="anchor">Customizations</a></li></ul></li>
<li class="menu-item"><a href="/docs/tools/check-html-links/">Check HTML Links</a><ul><li class="menu-item anchor"><a href="/docs/tools/check-html-links/#features" class="anchor">Features</a></li>
<li class="menu-item anchor"><a href="/docs/tools/check-html-links/#installation" class="anchor">Installation</a></li>
<li class="menu-item anchor"><a href="/docs/tools/check-html-links/#usage" class="anchor">Usage</a></li>
<li class="menu-item anchor"><a href="/docs/tools/check-html-links/#example-output" class="anchor">Example Output</a></li>
<li class="menu-item anchor"><a href="/docs/tools/check-html-links/#comparison" class="anchor">Comparison</a></li></ul></li></ul></li></ul>
<div class="sidebar-bottom">
<hr>
<launch-dark-switch class="light-dark-switch" label="Toggle darkmode">Toggle darkmode</launch-dark-switch>
<a href="https://github.com/modernweb-dev/rocket/issues">Help and Feedback</a>
</div>
</rocket-navigation>
</nav>
</rocket-drawer>
<main class="markdown-body">
<h1 id="markdown-javascript-overview"><a class="anchor" href="#markdown-javascript-overview"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Markdown JavaScript: Overview</h1>
<p>Markdown JavaScript (mdjs) is a format that allows you to use JavaScript with Markdown, to create interactive demos. It does so by "annotating" JavaScript that should be executed in Markdown.</p>
<p>To annotate we use a code block with <code>js script</code>.</p>
<pre class="language-md"><code class="language-md"><span class="token code"><span class="token punctuation">```</span><span class="token code-language">js script</span>
<span class="token code-block language-js"><span class="token comment">// execute me</span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<h2 id="web-components"><a class="anchor" href="#web-components"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Web Components</h2>
<p>One very good use case for that can be web components.
HTML already works in Markdown so all you need is to load a web components definition file.</p>
<p>You could even do so within the same Markdown file.</p>
<pre class="language-md"><code class="language-md"><span class="token title important"><span class="token punctuation">##</span> This is my-card</span>
Here's an example of the component:
<span class="token code"><span class="token punctuation">```</span><span class="token code-language">html preview-story</span>
<span class="token code-block language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>my-card</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>h2</span><span class="token punctuation">></span></span>Hello world!<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>h2</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>button</span><span class="token punctuation">></span></span>Click me!<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>my-card</span><span class="token punctuation">></span></span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<p>You can even execute some JavaScript:</p>
<pre class="language-md"><code class="language-md"><span class="token title important"><span class="token punctuation">##</span> This is my-el</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>my-el</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>my-el</span><span class="token punctuation">></span></span>
<span class="token code"><span class="token punctuation">```</span><span class="token code-language">js script</span>
<span class="token code-block language-js"><span class="token keyword module">import</span> <span class="token punctuation">{</span> <span class="token maybe-class-name">LitElement</span><span class="token punctuation">,</span> html <span class="token punctuation">}</span> <span class="token keyword module">from</span> <span class="token string">'https://unpkg.com/lit-element?module'</span><span class="token punctuation">;</span>
<span class="token keyword">class</span> <span class="token class-name">MyEl</span> <span class="token keyword">extends</span> <span class="token class-name">LitElement</span> <span class="token punctuation">{</span>
<span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">innerHTML</span> <span class="token operator">=</span> <span class="token string">'&#x3C;p style="color: red">I am alive&#x3C;/p>'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
customElements<span class="token punctuation">.</span><span class="token method function property-access">define</span><span class="token punctuation">(</span><span class="token string">'my-el'</span><span class="token punctuation">,</span> <span class="token maybe-class-name">MyEl</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<h2 id="demo-support-story"><a class="anchor" href="#demo-support-story"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Demo Support (Story)</h2>
<p>mdjs comes with some additional helpers you can choose to import:</p>
<pre class="language-md"><code class="language-md"><span class="token code"><span class="token punctuation">```</span><span class="token code-language">js script</span>
<span class="token code-block language-js"><span class="token keyword module">import</span> <span class="token string">'@mdjs/mdjs-story/mdjs-story.js'</span><span class="token punctuation">;</span>
<span class="token keyword module">import</span> <span class="token string">'@mdjs/mdjs-preview/mdjs-preview.js'</span><span class="token punctuation">;</span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<p>Once loaded you can use them like so:</p>
<pre class="language-md"><code class="language-md"><span class="token code"><span class="token punctuation">```</span><span class="token code-language">js script</span>
<span class="token code-block language-js"><span class="token keyword module">import</span> <span class="token string">'@mdjs/mdjs-story/mdjs-story.js'</span><span class="token punctuation">;</span>
<span class="token keyword module">import</span> <span class="token string">'@mdjs/mdjs-preview/mdjs-preview.js'</span><span class="token punctuation">;</span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<h3 id="story"><a class="anchor" href="#story"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Story</h3>
<p>The code snippet will actually get executed at that place and you will have a live demo</p>
<pre class="language-md"><code class="language-md"><span class="token code"><span class="token punctuation">```</span><span class="token code-language">js story</span>
<span class="token code-block language-js"><span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">JsStory</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token embedded-code html"> &#x3C;demo-wc-card>JS Story&#x3C;/demo-wc-card> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<pre class="language-md"><code class="language-md"><span class="token code"><span class="token punctuation">```</span><span class="token code-language">html story</span>
<span class="token code-block language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>demo-wc-card</span><span class="token punctuation">></span></span>HTML Story<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>demo-wc-card</span><span class="token punctuation">></span></span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<h4 id="full-code-support"><a class="anchor" href="#full-code-support"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Full Code Support</h4>
<pre class="language-md"><code class="language-md"><span class="token code"><span class="token punctuation">```</span><span class="token code-language">js story</span>
<span class="token code-block language-js"><span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">JsStory</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> calculateSomething <span class="token operator">=</span> <span class="token number">12</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token embedded-code html">
&#x3C;demo-wc-card .header=${`Something: ${calculateSomething}`}>JS Story&#x3C;/demo-wc-card>
</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<h3 id="preview-story"><a class="anchor" href="#preview-story"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Preview Story</h3>
<p>Will become a live demo wrapped in a container with a show code button.</p>
<pre class="language-md"><code class="language-md"><span class="token code"><span class="token punctuation">```</span><span class="token code-language">js preview-story</span>
<span class="token code-block language-js"><span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">JsPreviewStory</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token embedded-code html"> &#x3C;demo-wc-card>JS Preview Story&#x3C;/demo-wc-card> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<pre class="language-md"><code class="language-md"><span class="token code"><span class="token punctuation">```</span><span class="token code-language">html preview-story</span>
<span class="token code-block language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>demo-wc-card</span><span class="token punctuation">></span></span>HTML Preview Story<span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>demo-wc-card</span><span class="token punctuation">></span></span></span>
<span class="token punctuation">```</span></span>
</code></pre>
<p>Here is a live example from <a href="https://www.npmjs.com/package/demo-wc-card">demo-wc-card</a>.</p>
<mdjs-preview mdjs-story-name="header"></mdjs-preview>
<h2 id="supported-systems"><a class="anchor" href="#supported-systems"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Supported Systems</h2>
<h3 id="es-dev-server"><a class="anchor" href="#es-dev-server"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>es-dev-server</h3>
<p>Preview your mdjs readme with live demos and auto reload.</p>
<ul>
<li>
<p>Add to your <code>package.json</code>:</p>
<pre class="language-json"><code class="language-json"><span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"start"</span><span class="token operator">:</span> <span class="token string">"es-dev-server"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre>
</li>
<li>
<p>Create a <code>es-dev-server.config.js</code> in the root of your repository.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">{</span> mdjsTransformer <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@mdjs/core'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
module<span class="token punctuation">.</span><span class="token property-access">exports</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
nodeResolve<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
open<span class="token punctuation">:</span> <span class="token string">'README.md'</span><span class="token punctuation">,</span>
watch<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
responseTransformers<span class="token punctuation">:</span> <span class="token punctuation">[</span>mdjsTransformer<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</li>
</ul>
<h3 id="storybook"><a class="anchor" href="#storybook"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Storybook</h3>
<p>Please check out <a href="https://open-wc.org/demoing/">@open-wc/demoing-storybook</a> for a fully integrated setup.</p>
<p>It includes <a href="https://open-wc.org/demoing/storybook-addon-markdown-docs.html">storybook-addon-markdown-docs</a> which uses mdjs under the hood.</p>
<h3 id="chrome-extension-currently-only-for-github"><a class="anchor" href="#chrome-extension-currently-only-for-github"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Chrome Extension (currently only for GitHub)</h3>
<p>See live demos directly inside GitHub pages, Markdown files, issues, pull requests...</p>
<p>Please check out <a href="https://github.com/open-wc/mdjs-viewer">mdjs-viewer</a>.</p>
<h2 id="build-mdjs"><a class="anchor" href="#build-mdjs"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Build mdjs</h2>
<h3 id="basic"><a class="anchor" href="#basic"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Basic</h3>
<p>mdjs offers two more "basic" integrations</p>
<h4 id="mdjsdocpage"><a class="anchor" href="#mdjsdocpage"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><code>mdjsDocPage</code></h4>
<p>Creates a full blown HTML page by passing in the Markdown.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">{</span> mdjsDocPage <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@mdjs/core'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> page <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">mdjsDocPage</span><span class="token punctuation">(</span>markdownString<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">/*
&#x3C;html>
... // load styles/components for mdjs, start stories
&#x3C;body>
&#x3C;h1>Some Markdown&#x3C;/h1>
&#x3C;/body>
&#x3C;/html>
*/</span>
</code></pre>
<h4 id="mdjsprocess"><a class="anchor" href="#mdjsprocess"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><code>mdjsProcess</code></h4>
<p>Pass in multiple Markdown documents and you get back all the JavaScript code and rendered HTML.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">{</span> mdjsProcess <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@mdjs/core'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">mdjsProcess</span><span class="token punctuation">(</span>markdownString<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">/*
{
jsCode: "
import '@mdjs/mdjs-story/mdjs-story.js';
...
",
html: '&#x3C;h1>Markdown One&#x3C;/h1>',
}
*/</span>
</code></pre>
<h3 id="advanced"><a class="anchor" href="#advanced"><svg class="octicon octicon-link" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>Advanced</h3>
<p>mdjs is build to be integrated within the <a href="https://unifiedjs.com/">unifiedjs</a> system.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> unified <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'unified'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> markdown <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'remark-parse'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> htmlStringify <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'remark-html'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> mdjsParse <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@mdjs/core'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> parser <span class="token operator">=</span> <span class="token function">unified</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">use</span><span class="token punctuation">(</span>markdown<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">use</span><span class="token punctuation">(</span>mdjsParse<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">use</span><span class="token punctuation">(</span>htmlStringify<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> parser<span class="token punctuation">.</span><span class="token method function property-access">process</span><span class="token punctuation">(</span>body<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> jsCode <span class="token punctuation">}</span> <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">;</span>
<span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token property-access">contents</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// &#x3C;h1>This is my-el>&#x3C;/h1></span>
<span class="token comment">// &#x3C;my-el>&#x3C;/my-el></span>
<span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span>jsCode<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// customElements.define('my-el', class extends HTMLElement {</span>
<span class="token comment">// ...</span>
</code></pre>
<script type="module">
import '@mdjs/mdjs-story/mdjs-story.js';
import '@mdjs/mdjs-preview/mdjs-preview.js';
import { html } from 'lit-html';
import 'demo-wc-card/demo-wc-card.js';
export const header = () => {
return html`<demo-wc-card .header=${'my new header'}></demo-wc-card>`;
};
const rootNode = document;
const stories = [{ key: 'header', story: header, code: `<pre class="language-js"><code class="language-js"><span class="token keyword module">import</span> <span class="token string">'demo-wc-card/demo-wc-card.js'</span><span class="token punctuation">;</span>
<span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function">header</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">\`</span><span class="token html language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>demo-wc-card</span> <span class="token attr-name">.header</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">\${</span><span class="token string">'my new header'</span><span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>demo-wc-card</span><span class="token punctuation">></span></span> </span><span class="token template-punctuation string">\`</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>` }];
for (const story of stories) {
const storyEl = rootNode.querySelector(`[mdjs-story-name="${story.key}"]`);
storyEl.codeHasHtml = true;
storyEl.story = story.story;
storyEl.code = story.code;
};
if (!customElements.get('mdjs-preview')) { import('@mdjs/mdjs-preview/mdjs-preview.js'); }
if (!customElements.get('mdjs-story')) { import('@mdjs/mdjs-story/mdjs-story.js'); }
</script>
<div class="content-footer">
<p>
Caught a mistake or want to contribute to the documentation?
<a href="https://github.com/modernweb-dev/rocket/edit/main/./docs/docs/markdown-javascript/overview.md">Edit this page on GitHub!</a>
</p>
</div>
</main>
</div>
</div>
<footer id="main-footer">
<div id="footer-menu">
<div class="content-area">
<nav>
<h3>Discover</h3>
<ul>
<li>
<a href="/blog/">Blog</a>
</li>
<li>
<a href="https://github.com/modernweb-dev/rocket/issues">Help and Feedback</a>
</li>
</ul>
</nav>
<nav>
<h3>Follow</h3>
<ul>
<li>
<a href="https://github.com/modernweb-dev/rocket">GitHub</a>
</li>
<li>
<a href="https://twitter.com/modern_web_dev">Twitter</a>
</li>
<li>
<a href="/about/slack/">Slack</a>
</li>
</ul>
</nav>
<nav>
<h3>Support</h3>
<ul>
<li>
<a href="/about/sponsor/">Sponsor</a>
</li>
<li>
<a href="https://github.com/modernweb-dev/rocket/blob/main/CONTRIBUTING.md">Contribute</a>
</li>
</ul>
</nav>
</div>
</div>
</footer>
<script type="module">
import '@rocket/navigation/rocket-navigation.js';
import '@rocket/drawer/rocket-drawer.js';
const drawer = document.querySelector('#sidebar');
// Toggle button
const triggers = document.querySelectorAll('[data-action="trigger-mobile-menu"]');
for (const trigger of [...triggers]) {
trigger.addEventListener('click', function () {
drawer.opened = true;
});
}
</script>
<script>
async function serviceWorkerUpdate() {
if ('serviceWorker' in navigator) {
const oldReg = await navigator.serviceWorker.getRegistration();
let oldSwState;
if (oldReg && oldReg.active) {
oldSwState = oldReg.active.state;
}
let refreshing;
navigator.serviceWorker.addEventListener('controllerchange', async () => {
if (refreshing) {
return;
}
const newReg = await navigator.serviceWorker.getRegistration();
let newSwState;
if (newReg && newReg.active) {
newSwState = newReg.active.state;
}
if (oldSwState === 'activated' && newSwState === 'activating') {
refreshing = true;
window.location.reload();
}
});
}
}
serviceWorkerUpdate();
</script>
</body>
</html>

View File

@@ -0,0 +1,4 @@
import { html, render } from 'lit-html';
import '../components/test-component.js';
render(html`<test-element></test-element>`, document.body);

View File

@@ -0,0 +1,24 @@
import chai from 'chai';
import { execute } from './test-helpers.js';
const { expect } = chai;
describe('inspectFolder', () => {
it.skip('can handle script src', async () => {
const { localSpecifiers, bareSpecifiers } = await execute('fixtures/script-src');
expect(localSpecifiers).lengthOf(1);
expect(bareSpecifiers).lengthOf(0);
});
it.skip('can handle script content', async () => {
const { localSpecifiers, bareSpecifiers } = await execute('fixtures/script-content');
expect(localSpecifiers).lengthOf(1);
expect(bareSpecifiers).lengthOf(0);
});
it('can handle multiple levels', async () => {
const { localSpecifiers, bareSpecifiers } = await execute('fixtures/test-case');
expect(localSpecifiers).lengthOf(3);
expect(bareSpecifiers).lengthOf(2);
});
});

View File

@@ -0,0 +1,11 @@
import path from 'path';
import { fileURLToPath } from 'url';
import { inspectFolder } from 'generate-import-map';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export async function execute(inPath) {
const testDir = path.join(__dirname, inPath.split('/').join(path.sep));
return await inspectFolder(testDir);
}

View File

@@ -0,0 +1,24 @@
// Don't edit this file directly. It is generated by /scripts/update-package-configs.ts
{
"extends": "../../tsconfig.node-base.json",
"compilerOptions": {
"module": "ESNext",
"outDir": "./dist-types",
"rootDir": ".",
"composite": true,
"allowJs": true,
"checkJs": true,
"emitDeclarationOnly": true
},
"references": [],
"include": [
"src",
"*.js",
"types"
],
"exclude": [
"dist",
"dist-types"
]
}

View File

@@ -0,0 +1,10 @@
export interface Script {
specifier: string;
content: string;
filePath: string;
}
export interface Import {
s: number;
e: number;
}

View File

@@ -35,7 +35,7 @@
"preset" "preset"
], ],
"dependencies": { "dependencies": {
"@rocket/drawer": "^0.1.3", "@rocket/drawer": "^0.1.2",
"@rocket/navigation": "^0.2.1" "@rocket/navigation": "^0.2.0"
} }
} }

View File

@@ -1,20 +1,5 @@
# Change Log # Change Log
## 0.7.0
### Minor Changes
- a8e66d8: Extract building of the JavaScript setup code into a unified plugin called mdjsSetupCode
- fe6a929: For the story preview keep the original code block around to get code highlighting from the main document. This enables styling and reduces the code complexity.
### Patch Changes
- a8e66d8: You can provide a highlightCode function to the mdjsSetupCode unified plugin
- Updated dependencies [edb1abf]
- Updated dependencies [604a80e]
- @mdjs/mdjs-preview@0.4.0
- @mdjs/mdjs-story@0.2.0
## 0.6.2 ## 0.6.2
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mdjs/core", "name": "@mdjs/core",
"version": "0.7.0", "version": "0.6.2",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -43,12 +43,12 @@
"remark" "remark"
], ],
"dependencies": { "dependencies": {
"@mdjs/mdjs-preview": "^0.4.0", "@mdjs/mdjs-preview": "^0.3.0",
"@mdjs/mdjs-story": "^0.2.0", "@mdjs/mdjs-story": "^0.1.0",
"@types/unist": "^2.0.3", "@types/unist": "^2.0.3",
"es-module-lexer": "^0.3.26", "es-module-lexer": "^0.3.26",
"github-markdown-css": "^4.0.0", "github-markdown-css": "^4.0.0",
"plugins-manager": "^0.2.1", "plugins-manager": "^0.2.0",
"rehype-autolink-headings": "^5.0.1", "rehype-autolink-headings": "^5.0.1",
"rehype-prism-template": "^0.4.1", "rehype-prism-template": "^0.4.1",
"rehype-raw": "^5.0.0", "rehype-raw": "^5.0.0",

View File

@@ -18,7 +18,6 @@ const { executeSetupFunctions } = require('plugins-manager');
const { mdjsParse } = require('./mdjsParse.js'); const { mdjsParse } = require('./mdjsParse.js');
const { mdjsStoryParse } = require('./mdjsStoryParse.js'); const { mdjsStoryParse } = require('./mdjsStoryParse.js');
const { mdjsSetupCode } = require('./mdjsSetupCode.js');
/** @type {MdjsProcessPlugin[]} */ /** @type {MdjsProcessPlugin[]} */
const defaultMetaPlugins = [ const defaultMetaPlugins = [
@@ -26,7 +25,6 @@ const defaultMetaPlugins = [
{ name: 'gfm', plugin: gfm }, { name: 'gfm', plugin: gfm },
{ name: 'mdjsParse', plugin: mdjsParse }, { name: 'mdjsParse', plugin: mdjsParse },
{ name: 'mdjsStoryParse', plugin: mdjsStoryParse }, { name: 'mdjsStoryParse', plugin: mdjsStoryParse },
{ name: 'mdjsSetupCode', plugin: mdjsSetupCode },
// @ts-ignore // @ts-ignore
{ name: 'remark2rehype', plugin: remark2rehype, options: { allowDangerousHtml: true } }, { name: 'remark2rehype', plugin: remark2rehype, options: { allowDangerousHtml: true } },
// @ts-ignore // @ts-ignore
@@ -52,15 +50,29 @@ const defaultMetaPlugins = [
* @param {function[]} [options.setupUnifiedPlugins] * @param {function[]} [options.setupUnifiedPlugins]
* @param {MdjsProcessPlugin[]} [options.plugins] deprecated option use setupUnifiedPlugins instead * @param {MdjsProcessPlugin[]} [options.plugins] deprecated option use setupUnifiedPlugins instead
*/ */
async function mdjsProcess(mdjs, { setupUnifiedPlugins = [] } = {}) { async function mdjsProcess(
mdjs,
{ rootNodeQueryCode = 'document', setupUnifiedPlugins = [] } = {},
) {
const parser = unified(); const parser = unified();
const metaPlugins = executeSetupFunctions(setupUnifiedPlugins, defaultMetaPlugins); const metaPlugins = executeSetupFunctions(setupUnifiedPlugins, defaultMetaPlugins);
/** // @ts-ignore
* @param {string} code for (const pluginObj of metaPlugins) {
*/ parser.use(pluginObj.plugin, pluginObj.options);
async function highlightCode(code) { }
/** @type {unknown} */
const parseResult = await parser.process(mdjs);
const result = /** @type {ParseResult} */ (parseResult);
const { stories, jsCode } = result.data;
let fullJsCode = jsCode;
if (stories && stories.length > 0) {
const storiesCode = stories.map(story => story.code).join('\n');
// @ts-ignore // @ts-ignore
const codePlugins = metaPlugins.filter(pluginObj => const codePlugins = metaPlugins.filter(pluginObj =>
['markdown', 'remark2rehype', 'rehypePrism', 'htmlStringify'].includes(pluginObj.name), ['markdown', 'remark2rehype', 'rehypePrism', 'htmlStringify'].includes(pluginObj.name),
@@ -70,30 +82,46 @@ async function mdjsProcess(mdjs, { setupUnifiedPlugins = [] } = {}) {
for (const pluginObj of codePlugins) { for (const pluginObj of codePlugins) {
codeParser.use(pluginObj.plugin, pluginObj.options); codeParser.use(pluginObj.plugin, pluginObj.options);
} }
const codeResult = await codeParser.process(code);
return codeResult.contents;
}
// @ts-ignore const invokeStoriesCode = [];
for (const pluginObj of metaPlugins) { for (const story of stories) {
if (pluginObj.name === 'mdjsSetupCode') { let code = '';
if (pluginObj.options && !pluginObj.options.highlightCode) { switch (story.type) {
pluginObj.options.highlightCode = highlightCode; case 'html':
} code = `\`\`\`html\n${story.code.split('`')[1]}\n\`\`\``;
if (!pluginObj.options) { break;
pluginObj.options = { highlightCode }; case 'js':
code = `\`\`\`js\n${story.code}\n\`\`\``;
break;
default:
break;
} }
const codeResult = await codeParser.process(code);
const highlightedCode = /** @type {string} */ (codeResult.contents)
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
invokeStoriesCode.push(
`{ key: '${story.key}', story: ${story.key}, code: \`${highlightedCode}\` }`,
);
} }
parser.use(pluginObj.plugin, pluginObj.options);
fullJsCode = [
jsCode,
storiesCode,
`const rootNode = ${rootNodeQueryCode};`,
`const stories = [${invokeStoriesCode.join(', ')}];`,
`for (const story of stories) {`,
// eslint-disable-next-line no-template-curly-in-string
' const storyEl = rootNode.querySelector(`[mdjs-story-name="${story.key}"]`);',
` storyEl.codeHasHtml = true;`,
` storyEl.story = story.story;`,
` storyEl.code = story.code;`,
`};`,
`if (!customElements.get('mdjs-preview')) { import('@mdjs/mdjs-preview/mdjs-preview.js'); }`,
`if (!customElements.get('mdjs-story')) { import('@mdjs/mdjs-story/mdjs-story.js'); }`,
].join('\n');
} }
return { stories, jsCode: fullJsCode, html: result.contents };
/** @type {unknown} */
const parseResult = await parser.process(mdjs);
const result = /** @type {ParseResult} */ (parseResult);
const { stories, setupJsCode } = result.data;
return { stories, jsCode: setupJsCode, html: result.contents };
} }
module.exports = { module.exports = {

View File

@@ -1,57 +0,0 @@
/** @typedef {import('vfile').VFileOptions} VFileOptions */
/** @typedef {import('unist').Node} Node */
/** @typedef {import('@mdjs/core/types/code').Story} Story */
function mdjsSetupCode({ rootNodeQueryCode = 'document', simulationSettings = {} } = {}) {
/**
* @param {Node} tree
* @param {VFileOptions} file
*/
async function transformer(tree, file) {
const { stories, jsCode } = file.data;
file.data.setupJsCode = jsCode;
if (stories && stories.length > 0) {
const storiesCode = stories.map(/** @param {Story} story */ story => story.code).join('\n');
const invokeStoriesCode = [];
for (const story of stories) {
invokeStoriesCode.push(`{ key: '${story.key}', story: ${story.key} }`);
}
file.data.setupJsCode = [
'/** script code **/',
jsCode,
'/** stories code **/',
storiesCode,
'/** stories setup code **/',
`const rootNode = ${rootNodeQueryCode};`,
`const stories = [${invokeStoriesCode.join(', ')}];`,
'let needsMdjsElements = false;',
`for (const story of stories) {`,
// eslint-disable-next-line no-template-curly-in-string
' const storyEl = rootNode.querySelector(`[mdjs-story-name="${story.key}"]`);',
' if (storyEl) {',
` storyEl.story = story.story;`,
` storyEl.key = story.key;`,
` needsMdjsElements = true;`,
` Object.assign(storyEl, ${JSON.stringify(simulationSettings)});`,
' }',
`};`,
'if (needsMdjsElements) {',
` if (!customElements.get('mdjs-preview')) { import('@mdjs/mdjs-preview/define'); }`,
` if (!customElements.get('mdjs-story')) { import('@mdjs/mdjs-story/define'); }`,
'}',
].join('\n');
}
return tree;
}
return transformer;
}
module.exports = {
mdjsSetupCode,
};

View File

@@ -3,7 +3,6 @@
/** @typedef {import('@mdjs/core/types/code').StoryTypes} StoryTypes */ /** @typedef {import('@mdjs/core/types/code').StoryTypes} StoryTypes */
/** @typedef {(name: string) => string} TagFunction */ /** @typedef {(name: string) => string} TagFunction */
/** @typedef {import('unist').Node} UnistNode */ /** @typedef {import('unist').Node} UnistNode */
/** @typedef {import('unist').Parent} UnistParent */
/** @typedef {import('vfile').VFileOptions} VFileOptions */ /** @typedef {import('vfile').VFileOptions} VFileOptions */
const visit = require('unist-util-visit'); const visit = require('unist-util-visit');
@@ -41,7 +40,7 @@ function defaultStoryTag(name) {
* @param {string} name * @param {string} name
*/ */
function defaultPreviewStoryTag(name) { function defaultPreviewStoryTag(name) {
return `<mdjs-preview mdjs-story-name="${name}">[[CODE SLOT]]</mdjs-preview>`; return `<mdjs-preview mdjs-story-name="${name}"></mdjs-preview>`;
} }
/** /**
@@ -56,16 +55,14 @@ function mdjsStoryParse({
} = {}) { } = {}) {
/** @type {Story[]} */ /** @type {Story[]} */
const stories = []; const stories = [];
let htmlIndex = 0; let index = 0;
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/** /**
* @param {UnistNode} node * @param {UnistNode} node
* @param {number} index
* @param {UnistParent} parent
*/ */
const nodeCodeVisitor = (node, index, parent) => { const nodeCodeVisitor = node => {
if (node.lang === 'js' && node.meta === 'story' && typeof node.value === 'string') { if (node.lang === 'js' && node.meta === 'story' && typeof node.value === 'string') {
const storyData = extractStoryData(node.value); const storyData = extractStoryData(node.value);
node.type = 'html'; node.type = 'html';
@@ -74,65 +71,30 @@ function mdjsStoryParse({
} }
if (node.lang === 'js' && node.meta === 'preview-story' && typeof node.value === 'string') { if (node.lang === 'js' && node.meta === 'preview-story' && typeof node.value === 'string') {
const storyData = extractStoryData(node.value); const storyData = extractStoryData(node.value);
const newValue = previewStoryTag(storyData.name); node.type = 'html';
if (newValue.includes('[[CODE SLOT]]')) { node.value = previewStoryTag(storyData.name);
const tagParts = newValue.split('[[CODE SLOT]]');
node = {
type: 'root',
children: [
{ type: 'html', value: tagParts[0] },
{ type: 'text', value: '\n\n' },
node,
{ type: 'text', value: '\n\n' },
{ type: 'html', value: tagParts[1] },
],
};
parent.children.splice(index, 1, node);
} else {
node.type = 'html';
node.value = previewStoryTag(storyData.name);
}
stories.push(storyData); stories.push(storyData);
} }
if (node.lang === 'html' && node.meta === 'story') { if (node.lang === 'html' && node.meta === 'story') {
const storyData = extractStoryData( const storyData = extractStoryData(
`export const HtmlStory${htmlIndex} = () => html\`${node.value}\`;`, `export const HtmlStory${index} = () => html\`${node.value}\`;`,
{ type: 'html' }, { type: 'html' },
); );
node.type = 'html'; node.type = 'html';
node.value = storyTag(storyData.name); node.value = storyTag(storyData.name);
stories.push(storyData); stories.push(storyData);
htmlIndex += 1; index += 1;
} }
if (node.lang === 'html' && node.meta === 'preview-story') { if (node.lang === 'html' && node.meta === 'preview-story') {
const storyData = extractStoryData( const storyData = extractStoryData(
`export const HtmlStory${htmlIndex} = () => html\`${node.value}\`;`, `export const HtmlStory${index} = () => html\`${node.value}\`;`,
{ type: 'html' }, { type: 'html' },
); );
node.type = 'html';
const newValue = previewStoryTag(storyData.name); node.value = previewStoryTag(storyData.name);
if (newValue.includes('[[CODE SLOT]]')) {
const tagParts = newValue.split('[[CODE SLOT]]');
node = {
type: 'root',
children: [
{ type: 'html', value: tagParts[0] },
{ type: 'text', value: '\n\n' },
node,
{ type: 'text', value: '\n\n' },
{ type: 'html', value: tagParts[1] },
],
};
parent.children.splice(index, 1, node);
} else {
node.type = 'html';
node.value = previewStoryTag(storyData.name);
}
stories.push(storyData); stories.push(storyData);
htmlIndex += 1; index += 1;
} }
}; };

View File

@@ -45,16 +45,7 @@ describe('Integration', () => {
'<pre><code class="language-js">const foo = 1;', '<pre><code class="language-js">const foo = 1;',
'</code></pre>', '</code></pre>',
'<mdjs-story mdjs-story-name="fooStory"></mdjs-story>', '<mdjs-story mdjs-story-name="fooStory"></mdjs-story>',
'<mdjs-preview mdjs-story-name="fooPreviewStory">', '<mdjs-preview mdjs-story-name="fooPreviewStory"></mdjs-preview>',
'',
'',
'',
'<pre><code class="language-js">export const fooPreviewStory = () => {}',
'</code></pre>',
'',
'',
'',
'</mdjs-preview>',
]; ];
const parser = unified() const parser = unified()

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-template-curly-in-string */ /* eslint-disable no-template-curly-in-string */
const chai = require('chai'); const chai = require('chai');
const { adjustPluginOptions } = require('plugins-manager');
const { mdjsProcess } = require('../src/mdjsProcess.js'); const { mdjsProcess } = require('../src/mdjsProcess.js');
const { expect } = chai; const { expect } = chai;
@@ -29,44 +28,27 @@ describe('mdjsProcess', () => {
'<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> foo <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>', '<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> foo <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>',
'</code></pre>', '</code></pre>',
'<mdjs-story mdjs-story-name="fooStory"></mdjs-story>', '<mdjs-story mdjs-story-name="fooStory"></mdjs-story>',
'<mdjs-preview mdjs-story-name="fooPreviewStory">', '<mdjs-preview mdjs-story-name="fooPreviewStory"></mdjs-preview>',
'',
'',
'',
'<pre class="language-js"><code class="language-js"><span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function">fooPreviewStory</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span>',
'</code></pre>',
'',
'',
'',
'</mdjs-preview>',
].join('\n'); ].join('\n');
const expectedJsCode = [ const expectedJsCode = [
'/** script code **/',
'const bar = 2;', 'const bar = 2;',
'/** stories code **/',
'export const fooStory = () => {}', 'export const fooStory = () => {}',
'export const fooPreviewStory = () => {}', 'export const fooPreviewStory = () => {}',
'/** stories setup code **/',
'const rootNode = document;', 'const rootNode = document;',
`const stories = [{ key: 'fooStory', story: fooStory }, { key: 'fooPreviewStory', story: fooPreviewStory }];`, `const stories = [{ key: 'fooStory', story: fooStory, code: \`<pre class="language-js"><code class="language-js"><span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function">fooStory</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span>`,
'let needsMdjsElements = false;', `</code></pre>\` }, { key: 'fooPreviewStory', story: fooPreviewStory, code: \`<pre class="language-js"><code class="language-js"><span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function">fooPreviewStory</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span>`,
`</code></pre>\` }];`,
'for (const story of stories) {', 'for (const story of stories) {',
' const storyEl = rootNode.querySelector(`[mdjs-story-name="${story.key}"]`);', ' const storyEl = rootNode.querySelector(`[mdjs-story-name="${story.key}"]`);',
' if (storyEl) {', ' storyEl.codeHasHtml = true;',
' storyEl.story = story.story;', ' storyEl.story = story.story;',
' storyEl.key = story.key;', ' storyEl.code = story.code;',
' needsMdjsElements = true;',
' Object.assign(storyEl, {});',
' }',
'};', '};',
'if (needsMdjsElements) {', `if (!customElements.get('mdjs-preview')) { import('@mdjs/mdjs-preview/mdjs-preview.js'); }`,
` if (!customElements.get('mdjs-preview')) { import('@mdjs/mdjs-preview/define'); }`, `if (!customElements.get('mdjs-story')) { import('@mdjs/mdjs-story/mdjs-story.js'); }`,
` if (!customElements.get('mdjs-story')) { import('@mdjs/mdjs-story/define'); }`,
'}',
].join('\n'); ].join('\n');
const result = await mdjsProcess(input); const result = await mdjsProcess(input);
expect(result.html).to.equal(expected); expect(result.html).to.equal(expected);
expect(result.jsCode).to.equal(expectedJsCode); expect(result.jsCode).to.equal(expectedJsCode);
}); });
@@ -168,45 +150,4 @@ describe('mdjsProcess', () => {
const result = await mdjsProcess(input); const result = await mdjsProcess(input);
expect(result.html.trim()).to.equal(expected); expect(result.html.trim()).to.equal(expected);
}); });
it('can adjust languages for story preview', async () => {
const input = [
'Intro',
'```js preview-story',
'export const fooPreviewStory = () => {}',
'```',
].join('\n');
const expected = [
`/** script code **/`,
``,
`/** stories code **/`,
`export const fooPreviewStory = () => {}`,
`/** stories setup code **/`,
`const rootNode = document;`,
`const stories = [{ key: 'fooPreviewStory', story: fooPreviewStory }];`,
`let needsMdjsElements = false;`,
`for (const story of stories) {`,
' const storyEl = rootNode.querySelector(`[mdjs-story-name="${story.key}"]`);',
` if (storyEl) {`,
` storyEl.story = story.story;`,
` storyEl.key = story.key;`,
` needsMdjsElements = true;`,
' Object.assign(storyEl, {"languages":[{"key":"en","name":"English"}]});',
` }`,
`};`,
`if (needsMdjsElements) {`,
` if (!customElements.get('mdjs-preview')) { import('@mdjs/mdjs-preview/define'); }`,
` if (!customElements.get('mdjs-story')) { import('@mdjs/mdjs-story/define'); }`,
`}`,
].join('\n');
const result = await mdjsProcess(input, {
setupUnifiedPlugins: [
adjustPluginOptions('mdjsSetupCode', {
simulationSettings: { languages: [{ key: 'en', name: 'English' }] },
}),
],
});
expect(result.jsCode.trim()).to.equal(expected);
});
}); });

View File

@@ -28,7 +28,7 @@ describe('mdjsParse', () => {
expect(/** @type {MDJSVFileData} */ (result.data).jsCode).to.equal('const bar = 22;'); expect(/** @type {MDJSVFileData} */ (result.data).jsCode).to.equal('const bar = 22;');
}); });
// TODO: fix this bug - maybe something in unified itself 🤔 // TODO: fix this bug
it.skip('handling only "js script" code blocks', async () => { it.skip('handling only "js script" code blocks', async () => {
const input = [ const input = [
// //

View File

@@ -37,27 +37,9 @@ describe('mdjsStoryParse', () => {
'<pre><code class="language-js">const foo = 1;', '<pre><code class="language-js">const foo = 1;',
'</code></pre>', '</code></pre>',
'<mdjs-story mdjs-story-name="fooStory"></mdjs-story>', '<mdjs-story mdjs-story-name="fooStory"></mdjs-story>',
'<mdjs-preview mdjs-story-name="fooPreviewStory">', '<mdjs-preview mdjs-story-name="fooPreviewStory"></mdjs-preview>',
'',
'',
'',
'<pre><code class="language-js">export const fooPreviewStory = () => {}',
'</code></pre>',
'',
'',
'',
'</mdjs-preview>',
'<mdjs-story mdjs-story-name="HtmlStory0"></mdjs-story>', '<mdjs-story mdjs-story-name="HtmlStory0"></mdjs-story>',
'<mdjs-preview mdjs-story-name="HtmlStory1">', '<mdjs-preview mdjs-story-name="HtmlStory1"></mdjs-preview>',
'',
'',
'',
'<pre><code class="language-html">&#x3C;demo-el>&#x3C;/demo-el>',
'</code></pre>',
'',
'',
'',
'</mdjs-preview>',
'', '',
].join('\n'); ].join('\n');

View File

@@ -25,7 +25,6 @@ export interface ParseResult {
data: { data: {
stories: Story[]; stories: Story[];
jsCode: string; jsCode: string;
setupJsCode: string;
}; };
} }

View File

@@ -1,30 +1,5 @@
# @mdjs/mdjs-preview # @mdjs/mdjs-preview
## 0.4.1
### Patch Changes
- 0f6709a: Make sure initial settings are taken from the element if nothing is yet stored
## 0.4.0
### Minor Changes
- edb1abf: Reworking completely by
- slotting in the highlighted code
- open story in dedicated window
- enabling an simulation mode that can render the story in an iframe
- share settings between all simulators
- option to remember simulator settings
- force side effect import via `/define`
## 0.3.2
### Patch Changes
- be0d0b3: fix: add missing main entry to the packages
## 0.3.1 ## 0.3.1
### Patch Changes ### Patch Changes

View File

@@ -1,3 +1,3 @@
import { MdJsPreview } from '../MdJsPreview.js'; import { MdJsPreview } from './src/MdJsPreview.js';
customElements.define('mdjs-preview', MdJsPreview); customElements.define('mdjs-preview', MdJsPreview);

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mdjs/mdjs-preview", "name": "@mdjs/mdjs-preview",
"version": "0.4.1", "version": "0.3.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -13,11 +13,10 @@
}, },
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)", "author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/markdown-javascript/preview/", "homepage": "https://rocket.modern-web.dev/docs/markdown-javascript/preview/",
"main": "./index.js",
"type": "module", "type": "module",
"exports": { "exports": {
".": "./index.js", ".": "./index.js",
"./define": "./src/define/define.js" "./mdjs-preview.js": "./mdjs-preview.js"
}, },
"scripts": { "scripts": {
"debug": "cd ../../ && npm run debug -- --group mdjs-preview", "debug": "cd ../../ && npm run debug -- --group mdjs-preview",
@@ -32,8 +31,8 @@
"src" "src"
], ],
"dependencies": { "dependencies": {
"@lion/accordion": "^0.4.2", "lit-element": "^2.4.0",
"lit-element": "^2.4.0" "lit-html": "^1.3.0"
}, },
"types": "dist-types/index.d.ts" "types": "dist-types/index.d.ts"
} }

View File

@@ -1,13 +1,5 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from 'lit-element';
import '@lion/accordion/define'; import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
import {
subscribe,
unSubscribe,
saveToSharedStates,
applySharedStates,
} from './mdjsViewerSharedStates.js';
import { addResizeHandler } from './resizeHandler.js';
/** /**
* @typedef {object} StoryOptions * @typedef {object} StoryOptions
@@ -28,731 +20,160 @@ export class MdJsPreview extends LitElement {
story: { story: {
attribute: false, attribute: false,
}, },
key: { code: {
type: String, type: String,
}, },
deviceMode: { codeHasHtml: {
type: Boolean, type: Boolean,
attribute: 'device-mode', },
showCode: {
type: Boolean,
attribute: 'show-code',
reflect: true, reflect: true,
}, },
sameSettings: { type: Boolean },
contentHeight: { type: Number },
simulatorUrl: { type: String },
// page settings
platform: { type: String },
platforms: { type: Array },
size: { type: String },
sizes: { type: Array },
theme: { type: String, reflect: true },
themes: { type: Array },
language: { type: String },
languages: { type: Array },
edgeDistance: { type: Boolean },
autoHeight: { type: Boolean },
rememberSettings: { type: Boolean },
}; };
} }
toggleShowCode() {
this.showCode = !this.showCode;
}
constructor() { constructor() {
super(); super();
this.code = '';
/** @type {LitHtmlStoryFn} */ /** @type {LitHtmlStoryFn} */
this.story = () => html` <p>Loading...</p> `; this.story = () => html` <p>Loading...</p> `;
this.key = ''; this.codeHasHtml = false;
this.contentHeight = 0;
this.simulatorUrl = '';
this.theme = 'light';
this.themes = [
{ key: 'light', name: 'Light' },
{ key: 'dark', name: 'Dark' },
];
this.language = 'en-US';
this.languages = [
{ key: 'en', name: 'English' },
{ key: 'en-US', name: 'English (United States)' },
{ key: 'en-GB', name: 'English (United Kingdom)' },
{ key: 'de', name: 'German' },
{ key: 'es', name: 'Spanish' },
{ key: 'fi', name: 'Finnish' },
{ key: 'fr', name: 'French' },
{ key: 'it', name: 'Italian' },
{ key: 'nl', name: 'Dutch' },
{ key: 'pl', name: 'Polish' },
{ key: 'pt', name: 'Portuguese' },
{ key: 'ro', name: 'Romanian' },
{ key: 'sv', name: 'Swedish' },
{ key: 'sv', name: 'Swedish' },
];
this.platform = 'web';
this.platforms = [
{ key: 'web', name: 'Web' },
// { key: 'web-windows', name: 'Windows' },
// { key: 'web-mac', name: 'Mac' },
{ key: 'android', name: 'Android' },
{ key: 'ios', name: 'iOS' },
];
this.size = 'webSmall';
this.sizes = [
{
key: 'webSmall',
name: 'Small',
platform: 'web',
width: 360,
height: 640,
dpr: 1,
},
{
key: 'webMedium',
name: 'Medium',
platform: 'web',
width: 640,
height: 640,
dpr: 1,
},
{
key: 'webLarge',
name: 'Large',
platform: 'web',
width: 1024,
height: 640,
dpr: 1,
},
{
key: 'pixel2',
name: 'Pixel 2',
platform: 'android',
width: 411,
height: 731,
dpr: 2.6,
},
{
key: 'galaxyS5',
name: 'Galaxy S5',
platform: 'android',
width: 360,
height: 640,
dpr: 3,
},
{
key: 'iphoneX',
name: 'iPhone X',
platform: 'ios',
width: 375,
height: 812,
dpr: 3,
},
{
key: 'iPad',
name: 'iPad',
platform: 'ios',
width: 768,
height: 1024,
dpr: 2,
},
];
this.deviceMode = false;
this.autoHeight = true;
this.edgeDistance = true;
this.sameSettings = true;
this.rememberSettings = false;
this.__firstRun = true;
this.__syncUp = false;
}
connectedCallback() {
super.connectedCallback();
if (this.sameSettings) {
applySharedStates(this);
}
addResizeHandler();
}
get baseUrl() {
return document.location.origin;
}
get deviceHeight() {
if (this.autoHeight) {
return this.contentHeight + 10;
}
return this.sizeData?.height || 50;
}
/**
* @param {string} platform
*/
getSizesFor(platform) {
return this.sizes.filter(el => el.platform === platform);
}
get sizeData() {
return (
this.sizes.find(el => el.key === this.size) || { width: 50, height: 50, name: 'default' }
);
}
onSubscribe = () => {
this.__syncUp = false;
applySharedStates(this);
this.__syncUp = true;
};
/**
* @param {import('lit-element').PropertyValues} changeProps
*/
update(changeProps) {
super.update(changeProps);
if (this.sameSettings && this.__syncUp) {
saveToSharedStates(this, this.onSubscribe);
}
if (changeProps.has('sameSettings')) {
if (this.sameSettings) {
subscribe(this.onSubscribe);
} else {
unSubscribe(this.onSubscribe);
}
}
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.sameSettings) {
unSubscribe(this.onSubscribe);
}
}
firstUpdated() {
this.__syncUp = true;
}
get iframeUrl() {
const mdjsSetupScript = /** @type {HTMLScriptElement} */ (document.querySelector(
'script[type=module][mdjs-setup]',
));
if (!mdjsSetupScript) {
throw new Error('Could not find a <script type="module" src="..." mdjs-setup></script>');
}
const params = new URLSearchParams();
params.set('story-file', mdjsSetupScript.src);
params.set('story-key', this.key);
params.set('theme', this.theme);
params.set('platform', this.platform);
params.set('language', this.language);
params.set('edge-distance', this.edgeDistance.toString());
const links = /** @type {HTMLLinkElement[]} */ ([
...document.querySelectorAll('link[mdjs-use]'),
]);
for (const link of links) {
if (link.href) {
params.append('stylesheets', link.href);
}
}
return `${this.simulatorUrl}#?${params.toString()}`;
}
/**
* @param {string} platform
*/
changePlatform(platform) {
this.platform = platform;
const sizes = this.getSizesFor(this.platform);
this.size = sizes[0].key;
}
renderPlatforms() {
return html`
<div>
<h3>Platform</h3>
<h4>Platform</h4>
<div
class="segmented-control"
@change=${
/** @param {Event} ev */ ev => {
if (ev.target) {
this.changePlatform(/** @type {HTMLInputElement} */ (ev.target).value);
}
}
}
>
${this.platforms.map(
platform => html`
<label class="${this.platform === platform.key ? 'selected' : ''}">
<span>${platform.name}</span>
<input
type="radio"
name="platform"
value="${platform.key}"
?checked=${this.platform === platform.key}
/>
</label>
`,
)}
</div>
</div>
`;
}
renderSizes() {
return html`
<div>
<h3>Viewport</h3>
<h4>Size</h4>
<div
class="segmented-control"
@change=${
/** @param {Event} ev */ ev => {
if (ev.target) {
this.size = /** @type {HTMLInputElement} */ (ev.target).value;
}
}
}
>
${this.getSizesFor(this.platform).map(
size => html`
<label class="${this.size === size.key ? 'selected' : ''}">
<span>${size.name}</span>
<input
type="radio"
name="size"
value="${size.key}"
.checked=${this.size === size.key}
/>
</label>
`,
)}
</div>
${this.renderAutoHeight()}
</div>
`;
}
renderThemes() {
return html`
<div>
<h3>Visual</h3>
<h4>Theme</h4>
<div
class="segmented-control"
@change=${
/** @param {Event} ev */ ev => {
if (ev.target) {
this.theme = /** @type {HTMLInputElement} */ (ev.target).value;
}
}
}
>
${this.themes.map(
theme => html`
<label class="${this.theme === theme.key ? 'selected' : ''}">
<span>${theme.name}</span>
<input
type="radio"
name="theme"
value="${theme.key}"
?checked=${this.theme === theme.key}
/>
</label>
`,
)}
</div>
${this.renderEdgeDistance()}
</div>
`;
}
renderLanguages() {
return html`
<div>
<h3>Localization</h3>
<label>
Language
<select
@change=${
/** @param {Event} ev */ ev => {
if (ev.target) {
this.language = /** @type {HTMLInputElement} */ (ev.target).value;
}
}
}
>
${this.languages.map(
language => html`
<option value="${language.key}" ?selected=${this.language === language.key}>
${language.name}
</option>
`,
)}
</select>
</label>
</div>
`;
}
renderEdgeDistance() {
return html`
<div>
<label class="${this.edgeDistance ? 'switch selected' : 'switch'}">
Apply distance to edge
<span class="switch-button"></span>
<input
type="checkbox"
?checked=${this.edgeDistance}
@change=${
/** @param {Event} ev */ ev => {
if (ev.target) {
this.edgeDistance = /** @type {HTMLInputElement} */ (ev.target).checked;
}
}
}
/>
</label>
</div>
`;
}
renderAutoHeight() {
return html`
<div>
<label class="${this.autoHeight ? 'switch selected' : 'switch'}">
Fit height to content
<span class="switch-button"></span>
<input
type="checkbox"
?checked=${this.autoHeight}
@change=${
/** @param {Event} ev */ ev => {
if (ev.target) {
this.autoHeight = /** @type {HTMLInputElement} */ (ev.target).checked;
}
}
}
/>
</label>
</div>
`;
}
renderSameSettings() {
return html`
<div>
<label class="${this.sameSettings ? 'switch selected' : 'switch'}">
Same settings for all simulations
<span class="switch-button"></span>
<input
type="checkbox"
?checked=${this.sameSettings}
@change=${
/** @param {Event} ev */ ev => {
if (ev.target) {
this.sameSettings = /** @type {HTMLInputElement} */ (ev.target).checked;
}
}
}
/>
</label>
</div>
`;
}
renderRememberSettings() {
if (!this.sameSettings) {
return html``;
}
return html`
<div>
<label class="${this.rememberSettings ? 'switch selected' : 'switch'}">
Remember settings
<span class="switch-button"></span>
<input
type="checkbox"
?checked=${this.rememberSettings}
@change=${
/** @param {Event} ev */ ev => {
if (ev.target) {
this.rememberSettings = /** @type {HTMLInputElement} */ (ev.target).checked;
}
}
}
/>
</label>
</div>
`;
}
renderSyncSettings() {
return html`
<div>
<h3>Global</h3>
${this.renderSameSettings()} ${this.renderRememberSettings()}
</div>
`;
} }
render() { render() {
return html` return html`
<div id="wrapper"> <div id="wrapper">
${this.deviceMode === false <div>${this.story({ shadowRoot: this.shadowRoot })}</div>
? html`<div>${this.story({ shadowRoot: this.shadowRoot })}</div>` <button id="showCodeButton" @click=${this.toggleShowCode}>show code</button>
: html`
<iframe
csp=${`script-src ${document.location.origin} 'unsafe-inline'; connect-src ws://${document.location.host}/`}
.src=${this.iframeUrl}
style=${`width: ${this.sizeData.width}px; height: ${this.deviceHeight}px;`}
></iframe>
<p class="frame-description" style=${`width: ${this.sizeData.width + 4}px;`}>
${this.sizeData.name} - ${this.deviceHeight}x${this.sizeData.width}
</p>
`}
</div> </div>
<lion-accordion class="options"> ${this.codeHasHtml ? unsafeHTML(this.code) : html`<pre><code>${this.code}</code></pre>`}
${this.deviceMode
? html`
<h3 slot="invoker">
<button>Settings</button>
</h3>
<div slot="content">
<div class="settings-wrapper">
${this.renderPlatforms()} ${this.renderSizes()} ${this.renderThemes()}
${this.renderLanguages()} ${this.renderSyncSettings()}
</div>
</div>
`
: ''}
<h3 slot="invoker">
<button>Code</button>
</h3>
<div slot="content">
<slot></slot>
</div>
</lion-accordion>
${this.simulatorUrl
? html`
<div class="controls">
<a href=${this.iframeUrl} target="_blank">Open simulation in new window</a>
<button
@click=${() => (this.deviceMode = !this.deviceMode)}
class="simulation-toggle"
>
${this.deviceMode ? html`Disable` : html`Enable`} device simulation
</button>
</div>
`
: ''}
`; `;
} }
static get styles() { static get styles() {
return css` return css`
:host { :host {
border: 1px solid #ccc;
display: block; display: block;
padding-bottom: 10px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px;
} }
iframe { #showCodeButton {
border: 2px solid #4caf50;
background: #fff;
}
.switch {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.switch:focus-within .switch-button {
box-shadow: 0 0 0 1px hsl(0deg 0% 100% / 40%), 0 0 0 4px rgb(31 117 203 / 48%);
}
.switch-button {
display: inline-block;
width: 44px;
background: grey;
height: 25px;
border-radius: 15px;
position: relative;
}
.switch-button::after {
content: ' ';
width: 18px;
height: 18px;
border-radius: 10px;
background: rgb(255, 255, 255);
display: block;
position: absolute; position: absolute;
top: 3px; right: 5px;
left: 4px; bottom: 5px;
}
.switch.selected .switch-button {
background: green;
}
.switch.selected .switch-button::after {
left: auto;
right: 4px;
}
.frame-description {
margin: -5px 0 10px 0;
text-align: right;
font-size: 12px; font-size: 12px;
color: #333; line-height: 20px;
color: #24292e;
background-color: #eff3f6;
background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%);
display: inline-block;
padding: 3px 12px;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-repeat: repeat-x;
background-position: -1px -1px;
background-size: 110% 110%;
border: 1px solid rgba(27, 31, 35, 0.2);
border-radius: 0.25em;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
} }
.settings-wrapper { #showCodeButton:hover {
display: grid; background-color: #e6ebf1;
grid-template-columns: 1fr; background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%);
grid-gap: 20px 40px; background-position: -0.5em;
max-width: 650px; border-color: rgba(27, 31, 35, 0.35);
text-decoration: none;
background-repeat: repeat-x;
} }
@media (min-width: 640px) { #wrapper {
.settings-wrapper {
grid-template-columns: repeat(2, 1fr);
}
}
.settings-wrapper h3 {
margin: 10px 0;
font-size: 16px;
}
.options {
display: block;
padding: 15px 0;
}
.controls {
display: flex;
justify-content: space-between;
}
.controls a {
color: #3f51b5;
font-size: 14px;
line-height: 37px;
}
.simulation-toggle {
border: 1px solid #3f51b5;
border-radius: 9px;
padding: 10px;
background: none;
font-weight: bold;
color: #3f51b5;
text-align: center;
}
.simulation-toggle:hover {
background-color: #3f51b5;
color: #fff;
}
h3[slot='invoker'] button {
font-size: 16px;
display: block;
position: relative; position: relative;
padding: 10px; padding: 10px;
border: none;
border-bottom: 1px solid #bbb;
width: 100%;
background: none;
text-align: left;
font-weight: bold;
} }
h3[slot='invoker'] button::after { :host > pre {
content: '>'; display: none;
right: 20px; margin: 0;
top: 10px; padding: 16px;
position: absolute; overflow: auto;
transform: rotate(90deg); font-size: 85%;
} line-height: 1.45;
background-color: #f6f8fa;
h3[slot='invoker'][expanded='true'] button::after {
transform: rotate(-90deg);
}
h3[slot='invoker'][expanded='true'] button {
border-bottom: none;
}
[slot='content'] {
border-bottom: 1px solid #bbb;
padding: 10px;
}
h3[slot='invoker']:first-child button {
border-top: 1px solid #bbb;
}
h4 {
font-weight: normal;
font-size: 14px;
margin: 5px 0;
}
.segmented-control {
border: 1px solid #3f51b5;
border-radius: 18px;
display: inline-block;
font-size: 14px;
margin-bottom: 10px;
}
.segmented-control span {
padding: 5px 10px;
display: inline-block;
border-radius: 18px;
margin: 2px 0;
}
.segmented-control label:first-child span {
margin-left: 2px;
}
.segmented-control label:last-child span {
margin-right: 2px;
}
.segmented-control label.selected span {
background: #3f51b5;
color: #fff;
}
.segmented-control label:focus-within span {
box-shadow: 0 0 0 1px hsl(0deg 0% 100% / 40%), 0 0 0 4px rgb(31 117 203 / 48%);
}
.segmented-control input,
.switch input {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
select {
display: block;
padding: 5px;
border: 1px solid #333;
border-radius: 3px; border-radius: 3px;
} }
:host([show-code]) > pre {
display: block;
}
code[class*='language-'],
pre[class*='language-'] {
color: #393a34;
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
tab-size: 4;
hyphens: none;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f6f8fa;
border-radius: 3px;
}
.token.function,
.token.class-name {
color: #6f42c1;
}
.token.tag,
.token.selector,
.language-autohotkey .token.keyword {
color: #22863a;
}
.token.entity,
.token.url,
.token.symbol,
.token.number,
.token.boolean,
.token.variable,
.token.constant,
.token.property,
.token.inserted,
.token.punctuation,
.token.operator {
color: #005cc5;
}
.token.regex {
color: #032f62;
}
.token.atrule,
.token.keyword,
.token.attr-name,
.language-autohotkey .token.selector {
color: #d73a49;
}
`; `;
} }
} }

View File

@@ -1,132 +0,0 @@
const _sharedStates = {
platform: 'web',
size: 'webSmall',
theme: 'light',
language: 'en',
autoHeight: true,
deviceMode: false,
rememberSettings: false,
edgeDistance: true,
};
/** @type {Function[]} */
let subscribeFns = [];
/**
* @param {Function} subscribeFn
*/
export function subscribe(subscribeFn) {
subscribeFns.push(subscribeFn);
}
/**
* @param {Function} subscribeFn
*/
export function unSubscribe(subscribeFn) {
subscribeFns = subscribeFns.filter(fn => fn !== subscribeFn);
}
function storeSettings() {
for (const _sharedStateKey of Object.keys(_sharedStates)) {
const sharedStateKey = /** @type {keyof _sharedStates} */ (_sharedStateKey);
if (_sharedStates.rememberSettings) {
localStorage.setItem(
`mdjsViewerSharedStates-${sharedStateKey}`,
_sharedStates[sharedStateKey].toString(),
);
} else {
localStorage.removeItem(`mdjsViewerSharedStates-${sharedStateKey}`);
}
}
}
let hasGlobalStateBeenSetBefore = false;
function restoreSettings() {
for (const _sharedStateKey of Object.keys(_sharedStates)) {
const sharedStateKey = /** @type {keyof _sharedStates} */ (_sharedStateKey);
const restoredValue = localStorage.getItem(`mdjsViewerSharedStates-${sharedStateKey}`);
if (restoredValue !== null) {
switch (sharedStateKey) {
case 'autoHeight':
case 'deviceMode':
case 'rememberSettings':
case 'edgeDistance':
_sharedStates[sharedStateKey] = restoredValue === 'true' ? true : false;
break;
default:
_sharedStates[sharedStateKey] = restoredValue;
}
hasGlobalStateBeenSetBefore = true;
}
}
}
restoreSettings();
/**
*
* @param {import('./MdJsPreview.js').MdJsPreview} target
*/
export function applySharedStates(target) {
if (hasGlobalStateBeenSetBefore) {
for (const _sharedStateKey of Object.keys(_sharedStates)) {
const sharedStateKey = /** @type {keyof _sharedStates} */ (_sharedStateKey);
switch (sharedStateKey) {
case 'autoHeight':
case 'deviceMode':
case 'rememberSettings':
case 'edgeDistance':
target[sharedStateKey] = _sharedStates[sharedStateKey];
break;
default:
target[sharedStateKey] = _sharedStates[sharedStateKey];
}
}
} else {
_saveToSharedStates(target);
}
}
/**
*
* @param {import('./MdJsPreview.js').MdJsPreview} target
*/
function _saveToSharedStates(target) {
let updated = false;
for (const _sharedStateKey of Object.keys(_sharedStates)) {
const sharedStateKey = /** @type {keyof _sharedStates} */ (_sharedStateKey);
if (_sharedStates[sharedStateKey] !== target[sharedStateKey]) {
switch (sharedStateKey) {
case 'autoHeight':
case 'deviceMode':
case 'rememberSettings':
case 'edgeDistance':
_sharedStates[sharedStateKey] = target[sharedStateKey];
break;
default:
_sharedStates[sharedStateKey] = target[sharedStateKey];
}
updated = true;
hasGlobalStateBeenSetBefore = true;
}
}
return updated;
}
/**
*
* @param {import('./MdJsPreview.js').MdJsPreview} target
* @param {Function} subscribedFn
*/
export function saveToSharedStates(target, subscribedFn) {
const updated = _saveToSharedStates(target);
if (updated) {
storeSettings();
for (const subscribeFn of subscribeFns) {
if (subscribeFn !== subscribedFn) {
subscribeFn();
}
}
}
}

View File

@@ -1,19 +0,0 @@
let addedCount = 0;
export function addResizeHandler() {
if (addedCount > 0) {
addedCount += 1;
return;
}
window.addEventListener('message', ev => {
const data = JSON.parse(ev.data);
if (data.action === 'mdjs-viewer-resize') {
const viewer = /** @type {import('./MdJsPreview.js').MdJsPreview} */ (document.body.querySelector(
`[mdjs-story-name="${data.storyKey}"]`,
));
if (viewer) {
viewer.contentHeight = data.height;
}
}
});
}

View File

@@ -1,38 +0,0 @@
import { expect, fixture, html } from '@open-wc/testing';
import '@mdjs/mdjs-preview/define';
/** @typedef {import('@mdjs/mdjs-preview').MdJsPreview} MdJsPreview */
describe('mdjs-preview', () => {
it('will render the element into the shadow root by default', async () => {
const el = await fixture(html`
<mdjs-preview .story=${() => html`<p id="testing"></p>`}></mdjs-preview>
`);
expect(el.shadowRoot.querySelectorAll('#testing').length).to.equal(1);
});
it('sync simulator states between instances', async () => {
const el = await fixture(html`
<div>
<mdjs-preview .story=${() => html`<p></p>`}></mdjs-preview>
<mdjs-preview .story=${() => html`<p></p>`}></mdjs-preview>
</div>
`);
const [preview1, preview2] = /** @type {MdJsPreview[]} */ (el.children);
preview1.platform = 'web';
preview1.edgeDistance = true;
await preview1.updateComplete;
expect(preview1.platform).to.equal('web');
expect(preview2.platform).to.equal('web');
expect(preview1.edgeDistance).to.be.true;
expect(preview2.edgeDistance).to.be.true;
preview1.platform = 'android';
preview1.edgeDistance = false;
await preview1.updateComplete;
expect(preview1.platform).to.equal('android');
expect(preview2.platform).to.equal('android');
expect(preview1.edgeDistance).to.be.false;
expect(preview2.edgeDistance).to.be.false;
});
});

View File

@@ -1,17 +1,5 @@
# @mdjs/mdjs-story # @mdjs/mdjs-story
## 0.2.0
### Minor Changes
- 604a80e: Force `/define`Ï entrypoint via export map
## 0.1.2
### Patch Changes
- be0d0b3: fix: add missing main entry to the packages
## 0.1.1 ## 0.1.1
### Patch Changes ### Patch Changes

View File

@@ -1,3 +1,3 @@
import { MdJsStory } from './MdJsStory.js'; import { MdJsStory } from './src/MdJsStory.js';
customElements.define('mdjs-story', MdJsStory); customElements.define('mdjs-story', MdJsStory);

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mdjs/mdjs-story", "name": "@mdjs/mdjs-story",
"version": "0.2.0", "version": "0.1.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -13,11 +13,10 @@
}, },
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)", "author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/markdown-javascript/story/", "homepage": "https://rocket.modern-web.dev/docs/markdown-javascript/story/",
"main": "./index.js",
"type": "module", "type": "module",
"exports": { "exports": {
".": "./index.js", ".": "./index.js",
"./define": "./src/define.js" "./mdjs-story.js": "./mdjs-story.js"
}, },
"scripts": { "scripts": {
"debug": "cd ../../ && npm run debug -- --group mdjs-story", "debug": "cd ../../ && npm run debug -- --group mdjs-story",

View File

@@ -1,19 +1,5 @@
# @rocket/navigation # @rocket/navigation
## 0.2.1
### Patch Changes
- 728a205: feat(navigation): add no-redirects attribute
By default, the sidebar nav redirects clicks on category headings to
their first child.
To disable those redirects, override
\_includes/\_joiningBlocks/\_layoutSidebar/sidebar/20-navigation.njk
and add the no-redirects attribute to the <rocket-navigation>
element.
## 0.2.0 ## 0.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/navigation", "name": "@rocket/navigation",
"version": "0.2.1", "version": "0.2.0",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },

View File

@@ -1,27 +1,3 @@
/**
* Debounce a function
* @template {(this: any, ...args: any[]) => void} T
* @param {T} func function
* @param {number} wait time in milliseconds to debounce
* @param {boolean} immediate when true, run immediately and on the leading edge
* @return {T} debounced function
*/
function debounce(func, wait, immediate) {
/** @type {number|undefined} */
let timeout;
return /** @type {typeof func}*/ (function () {
let args = /** @type {Parameters<typeof func>} */ (/** @type {unknown}*/ (arguments));
const later = () => {
timeout = undefined;
if (!immediate) func.apply(this, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(this, args);
});
}
/** /**
* @typedef {object} NavigationListItem * @typedef {object} NavigationListItem
* @property {HTMLElement} headline * @property {HTMLElement} headline
@@ -29,17 +5,12 @@ function debounce(func, wait, immediate) {
* @property {number} top * @property {number} top
*/ */
/**
* @element rocket-navigation
* @attr {Boolean} no-redirects - if set, will not redirect to first child of nav category when clicking on category header.
*/
export class RocketNavigation extends HTMLElement { export class RocketNavigation extends HTMLElement {
constructor() { constructor() {
super(); super();
/** @type NavigationListItem[] */ /** @type NavigationListItem[] */
this.list = []; this.list = [];
this.__clickHandler = this.__clickHandler.bind(this); this.__scrollHandler = this.__scrollHandler.bind(this);
this.__scrollHandler = debounce(this.__scrollHandler.bind(this), 25, true);
this.__isSetup = false; this.__isSetup = false;
} }
@@ -49,7 +20,27 @@ export class RocketNavigation extends HTMLElement {
} }
this.__isSetup = true; this.__isSetup = true;
this.addEventListener('click', this.__clickHandler); this.addEventListener('click', ev => {
const el = /** @type {HTMLElement} */ (ev.target);
if (el.classList.contains('anchor')) {
const anchor = /** @type {HTMLAnchorElement} */ (el);
ev.preventDefault();
this.dispatchEvent(new Event('close-overlay', { bubbles: true }));
// wait for closing animation to finish before start scrolling
setTimeout(() => {
const parsedUrl = new URL(anchor.href);
document.location.hash = parsedUrl.hash;
}, 250);
}
const links = el.parentElement?.querySelectorAll('ul a');
if (links && links.length > 1) {
const subLink = /** @type {HTMLAnchorElement} */ (links[1]);
if (!subLink.classList.contains('anchor')) {
ev.preventDefault();
subLink.click();
}
}
});
const anchors = /** @type {NodeListOf<HTMLAnchorElement>} */ (this.querySelectorAll( const anchors = /** @type {NodeListOf<HTMLAnchorElement>} */ (this.querySelectorAll(
'li.current a.anchor', 'li.current a.anchor',
@@ -66,41 +57,12 @@ export class RocketNavigation extends HTMLElement {
} }
} }
window.addEventListener('scroll', this.__scrollHandler, { passive: true }); // TODO: debounce
window.addEventListener('scroll', this.__scrollHandler);
this.__scrollHandler(); this.__scrollHandler();
} }
/**
* @param {Event} ev
*/
__clickHandler(ev) {
const el = /** @type {HTMLElement} */ (ev.target);
if (el.classList.contains('anchor')) {
const anchor =
el instanceof HTMLAnchorElement
? el
: /** @type{HTMLAnchorElement} */ (el.querySelector('a.anchor'));
ev.preventDefault();
this.dispatchEvent(new Event('close-overlay', { bubbles: true }));
// wait for closing animation to finish before start scrolling
setTimeout(() => {
const parsedUrl = new URL(anchor.href);
document.location.hash = parsedUrl.hash;
}, 250);
}
if (!this.hasAttribute('no-redirects')) {
const links = el.parentElement?.querySelectorAll('ul a');
if (links && links.length > 1) {
const subLink = /** @type {HTMLAnchorElement} */ (links[1]);
if (!subLink.classList.contains('anchor')) {
ev.preventDefault();
subLink.click();
}
}
}
}
__scrollHandler() { __scrollHandler() {
for (const listObj of this.list) { for (const listObj of this.list) {
listObj.top = listObj.headline.getBoundingClientRect().top; listObj.top = listObj.headline.getBoundingClientRect().top;

View File

@@ -58,8 +58,7 @@ describe('rocket-navigation', () => {
expect(anchorSpy).to.not.be.called; expect(anchorSpy).to.not.be.called;
}); });
it('will mark the currently "active" headline in the menu', async function () { it('will mark the currently "active" headline in the menu', async () => {
this.timeout(5000);
function addBlock(headline, length = 5) { function addBlock(headline, length = 5) {
return html` return html`
<h2 id="${headline}">${headline}</h2> <h2 id="${headline}">${headline}</h2>
@@ -97,20 +96,20 @@ describe('rocket-navigation', () => {
} }
</style> </style>
`); `);
await aTimeout(50); await aTimeout(0);
const anchorLis = wrapper.querySelectorAll('.menu-item.anchor'); const anchorLis = wrapper.querySelectorAll('.menu-item.anchor');
expect(anchorLis[0]).to.have.class('current'); expect(anchorLis[0]).to.have.class('current');
expect(anchorLis[1]).to.not.have.class('current'); expect(anchorLis[1]).to.not.have.class('current');
expect(anchorLis[2]).to.not.have.class('current'); expect(anchorLis[2]).to.not.have.class('current');
document.querySelector('#middle').scrollIntoView(); document.querySelector('#middle').scrollIntoView();
await aTimeout(100); await aTimeout(20);
expect(anchorLis[0]).to.not.have.class('current'); expect(anchorLis[0]).to.not.have.class('current');
expect(anchorLis[1]).to.have.class('current'); expect(anchorLis[1]).to.have.class('current');
expect(anchorLis[2]).to.not.have.class('current'); expect(anchorLis[2]).to.not.have.class('current');
document.querySelector('#bottom').scrollIntoView(); document.querySelector('#bottom').scrollIntoView();
await aTimeout(100); await aTimeout(20);
expect(anchorLis[0]).to.not.have.class('current'); expect(anchorLis[0]).to.not.have.class('current');
expect(anchorLis[1]).to.not.have.class('current'); expect(anchorLis[1]).to.not.have.class('current');
expect(anchorLis[2]).to.have.class('current'); expect(anchorLis[2]).to.have.class('current');

View File

@@ -1,11 +1,5 @@
# plugins-manager # plugins-manager
## 0.2.1
### Patch Changes
- be0d0b3: fix: add missing main entry to the packages
## 0.2.0 ## 0.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "plugins-manager", "name": "plugins-manager",
"version": "0.2.1", "version": "0.2.0",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -13,7 +13,6 @@
}, },
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)", "author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/tools/plugins-manager/", "homepage": "https://rocket.modern-web.dev/docs/tools/plugins-manager/",
"main": "./dist/index.cjs",
"type": "module", "type": "module",
"exports": { "exports": {
".": { ".": {

View File

@@ -1,26 +1,5 @@
# @rocket/search # @rocket/search
## 0.3.3
### Patch Changes
- 818caad: chore: generalize label & add alt when no img
- 0b64116: Update @lion dependencies
## 0.3.2
### Patch Changes
- 302227e: Add variable for border-radius of SearchCombobox
## 0.3.1
### Patch Changes
- be0d0b3: fix: add missing main entry to the packages
- Updated dependencies [be0d0b3]
- plugins-manager@0.2.1
## 0.3.0 ## 0.3.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/search", "name": "@rocket/search",
"version": "0.3.3", "version": "0.3.0",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -13,7 +13,6 @@
}, },
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)", "author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/presets/search/", "homepage": "https://rocket.modern-web.dev/docs/presets/search/",
"main": "./node.js",
"type": "module", "type": "module",
"exports": { "exports": {
".": "./node.js", ".": "./node.js",
@@ -41,11 +40,11 @@
"search" "search"
], ],
"dependencies": { "dependencies": {
"@lion/combobox": "^0.5.1", "@lion/combobox": "^0.1.20",
"@open-wc/scoped-elements": "^1.3.2", "@open-wc/scoped-elements": "^1.3.2",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"minisearch": "^3.0.2", "minisearch": "^3.0.2",
"plugins-manager": "^0.2.1", "plugins-manager": "^0.2.0",
"sax-wasm": "^2.0.0" "sax-wasm": "^2.0.0"
} }
} }

View File

@@ -29,7 +29,7 @@ function getTitle({ result, search }) {
function getText({ result, search }) { function getText({ result, search }) {
const { terms, body } = result; const { terms, body } = result;
return highlightSearchTerms({ text: body, search, terms, addEllipsis: true }); return highlightSearchTerms({ text: body, search, terms });
} }
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110 // @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110
@@ -40,7 +40,6 @@ export class RocketSearch extends ScopedElementsMixin(LitElement) {
search: { type: String }, search: { type: String },
results: { type: Array }, results: { type: Array },
maxResults: { type: Number, attribute: 'max-results' }, maxResults: { type: Number, attribute: 'max-results' },
noResultsText: { type: String },
}; };
} }
@@ -56,7 +55,6 @@ export class RocketSearch extends ScopedElementsMixin(LitElement) {
this.jsonUrl = ''; this.jsonUrl = '';
this.search = ''; this.search = '';
this.maxResults = 10; this.maxResults = 10;
this.noResultsText = 'No results found';
/** /**
* @type {RocketSearchResult[]} * @type {RocketSearchResult[]}
*/ */
@@ -112,7 +110,7 @@ export class RocketSearch extends ScopedElementsMixin(LitElement) {
return html` return html`
<rocket-search-combobox <rocket-search-combobox
name="combo" name="combo"
label="Search" label="Rocket Search"
@input=${ev => { @input=${ev => {
this.search = ev.target.value; this.search = ev.target.value;
}} }}
@@ -134,9 +132,6 @@ export class RocketSearch extends ScopedElementsMixin(LitElement) {
></rocket-search-option> ></rocket-search-option>
`, `,
)} )}
${this.results.length <= 0 && this.search.length > 0
? html` <rocket-search-option .title=${this.noResultsText}></rocket-search-option> `
: ''}
</rocket-search-combobox> </rocket-search-combobox>
`; `;
} }

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