Compare commits

..

24 Commits

Author SHA1 Message Date
Thomas Allmer
00ebeea10f wip: POC 2021-04-04 18:02:12 +02:00
Thomas Allmer
818caad7cb Create chilled-turkeys-help.md 2021-04-04 18:01:06 +02:00
Konstantinos Norgias
672b7e893e chore: generalize label & add alt when no img 2021-04-04 18:01:06 +02:00
Thomas Allmer
a8e66d84f4 feat(mdjs-core): extract mdjsSetupCode function which support a highlightCode fn 2021-04-04 18:00:26 +02:00
github-actions[bot]
e9090d64b9 Version Packages 2021-04-01 20:01:47 +02:00
Benny Powers
728a205b7b chore: add changeset 2021-04-01 19:44:43 +02:00
Benny Powers
67ba29d45a 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.
2021-04-01 19:44:43 +02:00
github-actions[bot]
758caffdf9 Version Packages 2021-03-25 07:14:15 +01:00
qa46hx
302227e8a9 feat(search): add variable for border-radius of SearchCombobox 2021-03-24 23:20:56 +01:00
Thomas Allmer
04a31220fb chore: align versions across the mono repo 2021-03-15 21:03:07 +01:00
Benny Powers
d44a23af0c Merge pull request #83 from modernweb-dev/changeset-release/main
Version Packages
2021-03-07 10:14:39 +02:00
github-actions[bot]
18a79589c2 Version Packages 2021-03-06 19:28:35 +00:00
Thomas Allmer
b7727b0e10 chore: add rocket nav upgrade to cli 2021-03-06 20:26:44 +01:00
Thomas Allmer
5edc40fed5 feat(cli): make sure each instance has its own eleventy config 2021-03-06 19:58:09 +01:00
Amin Yahyaabadi
be0d0b3ca1 fix: add missing main entry to the packages (#81)
This allows the tools to work properly. For example, eslint-plugin-import, TypeScript LSP hyperclick, and many other tools rely on main.
2021-03-06 19:10:49 +01:00
Thomas Allmer
ef8ebb0098 feat(eleventy-rocket-nav): support dynamically created content 2021-03-06 19:05:00 +01:00
djlauk
2fa61e1377 chore: tiny fixes to the README (#74) 2021-02-23 21:45:41 +01:00
Matsuuu
b23e37f38e feat(search): Precache search results to service worker 2021-02-23 21:44:53 +01:00
Matsuuu
cf45e32702 feat(search): Add ellipsis as prefix when truncating 2021-02-23 21:44:53 +01:00
Matsuuu
b5965c6c37 feat(search): Set cursor to pointer on result hover 2021-02-23 21:44:53 +01:00
Matsuuu
e39cc45d23 fix(search): Center search icon 2021-02-23 21:44:53 +01:00
Matsuuu
f0434cb12c feat(search): Add feedback when no results found 2021-02-23 21:44:53 +01:00
Matsuuu
c87caaed2d feat: Allow overlay query modification in Drawer (#73) 2021-02-23 21:31:12 +01:00
Thomas Allmer
04af7ecf53 chore: align dependency versions 2021-02-23 20:39:37 +01:00
76 changed files with 1223 additions and 206 deletions

View File

@@ -0,0 +1,5 @@
---
'@rocket/search': patch
---
chore: generalize label & add alt when no img

View File

@@ -0,0 +1,5 @@
---
'@mdjs/core': minor
---
Extract building of the JavaScript setup code into a unified plugin called mdjsSetupCode

View File

@@ -0,0 +1,5 @@
---
'@mdjs/core': patch
---
You can provide a highlightCode function to the mdjsSetupCode unified plugin

View File

@@ -43,7 +43,7 @@
## The Goal for Rocket
> Our goal is to provide developers with a meta framework for static websites with a spricle of JavaScript.
> Our goal is to provide developers with a meta framework for static websites with a sprinkle of JavaScript.
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.
@@ -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).
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. 👋
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. 👋
### Financial Contributors

View File

@@ -0,0 +1,14 @@
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Playground</title>
<body>
<rocket-playground></rocket-playground>
<script src="https://unpkg.com/wasm-flate@0.1.12-alpha/dist/bootstrap.js"></script>
<script type="module">
import '@rocket/playground/rocket-playground';
</script>
</body>
</html>

View File

@@ -132,31 +132,6 @@ export const header = () => {
## 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
Please check out [@open-wc/demoing-storybook](https://open-wc.org/demoing/) for a fully integrated setup.

View File

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

View File

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

View File

@@ -47,3 +47,9 @@ export const headlineConverter = () => html`
```
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.

3
docs/playground.md Normal file
View File

@@ -0,0 +1,3 @@
---
layout: layout-playground
---

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@rocket/building-rollup",
"version": "0.1.2",
"version": "0.1.3",
"publishConfig": {
"access": "public"
},
@@ -14,6 +14,7 @@
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/tools/building-rollup/",
"type": "module",
"main": "./index.js",
"exports": {
".": "./index.js"
},

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "check-html-links",
"version": "0.2.0",
"version": "0.2.1",
"publishConfig": {
"access": "public"
},
@@ -17,6 +17,7 @@
"check-html-links": "src/cli.js"
},
"type": "module",
"main": "./index.js",
"exports": {
".": "./index.js"
},

View File

@@ -1,5 +1,27 @@
# @rocket/cli
## 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
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@rocket/cli",
"version": "0.5.2",
"version": "0.6.0",
"publishConfig": {
"access": "public"
},
@@ -17,6 +17,7 @@
"rocket": "src/cli.js"
},
"type": "module",
"main": "./index.cjs",
"exports": {
".": {
"require": "./index.cjs",
@@ -56,22 +57,22 @@
"dependencies": {
"@11ty/eleventy": "^0.11.1",
"@11ty/eleventy-img": "^0.7.4",
"@rocket/building-rollup": "^0.1.2",
"@rocket/core": "^0.1.1",
"@rocket/building-rollup": "^0.1.3",
"@rocket/core": "^0.1.2",
"@rocket/eleventy-plugin-mdjs-unified": "^0.3.1",
"@rocket/eleventy-rocket-nav": "^0.2.1",
"@rocket/eleventy-rocket-nav": "^0.3.0",
"@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-node-resolve": "^11.0.1",
"@web/config-loader": "^0.1.3",
"@web/dev-server": "^0.1.4",
"@web/dev-server-rollup": "^0.3.2",
"@web/rollup-plugin-copy": "^0.2.0",
"check-html-links": "^0.2.0",
"check-html-links": "^0.2.1",
"command-line-args": "^5.1.1",
"command-line-usage": "^6.1.1",
"fs-extra": "^9.0.1",
"micromatch": "^4.0.2",
"plugins-manager": "^0.2.0",
"plugins-manager": "^0.2.1",
"utf8": "^3.0.0"
},
"types": "dist-types/index.d.ts"

View File

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

View File

@@ -58,6 +58,12 @@ export class RocketStart {
setupRollupPlugins: this.config.setupDevAndBuildPlugins,
setupPlugins: this.config.setupDevPlugins,
middleware: [
function rewriteIndex(context, next) {
context.set('Access-Control-Allow-Origin', '*');
return next();
},
],
},
[],
{ rollupWrapperFunction: fromRollup },

View File

@@ -35,8 +35,7 @@ describe('RocketCli e2e', () => {
});
describe('eleventy in config', () => {
// 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 () => {
it('can modify eleventy via an elventy function in the config', async () => {
cli = await executeStart('e2e-fixtures/content/eleventy.rocket.config.js');
const indexHtml = await readStartOutput(cli, 'index.html');
expect(indexHtml).to.equal(

View File

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

View File

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

View File

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

View File

@@ -53,13 +53,12 @@ describe('eleventy-plugin-mdjs-unified', () => {
it('renders markdown with javascript', async () => {
const files = await renderEleventy('./test-node/fixtures/mdjs');
expect(files).to.deep.equal([
{
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 ',
name: 'first/index.html',
},
]);
expect(files.length).to.equal(1);
expect(files[0].name).to.equal('first/index.html');
expect(files[0].html).to.include('<script type="module">');
expect(files[0].html).to.include('for (const story of stories)');
});
it('rewrites relative import pathes', async () => {

View File

@@ -1,4 +1,5 @@
const RocketNav = require('./eleventy-rocket-nav');
const { addPageAnchors } = require('./src/addPageAnchors.js');
// export the configuration function for plugin
module.exports = function (eleventyConfig) {
@@ -8,6 +9,10 @@ module.exports = function (eleventyConfig) {
eleventyConfig.addNunjucksFilter('rocketNavToHtml', function (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 = {

View File

@@ -1,5 +1,15 @@
# @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
### Patch Changes

View File

@@ -94,21 +94,8 @@ function findNavigationEntries(nodes = [], key = '') {
entry.title = 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
entry.children = [...anchors, ...findNavigationEntries(nodes, entry.key)];
entry.children = findNavigationEntries(nodes, entry.key);
}
return entry;
});
@@ -227,43 +214,36 @@ function navigationToHtml(pages, _options = {}) {
}>${pages
.map(entry => {
const liClass = [];
const aClass = [];
if (options.listItemClass) {
liClass.push(options.listItemClass);
}
if (options.anchorClass) {
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.activeKey === entry.key && options.activeListItemClass) {
liClass.push(options.activeListItemClass);
}
if (options.activeTreeListClass && activePages && activePages.includes(entry.key)) {
liClass.push(options.activeTreeListClass);
}
if (options.activeAnchorListClass && activePages && activePages.includes(entry.key)) {
aClass.push(options.activeAnchorListClass);
}
if (options.listItemHasChildrenClass && entry.children && entry.children.length) {
liClass.push(options.listItemHasChildrenClass);
}
if (entry.anchor) {
liClass.push('anchor');
aClass.push('anchor');
const output = [];
output.push(
`<${options.listItemElement}${liClass.length ? ` class="${liClass.join(' ')}"` : ''}>`,
);
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 `<${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}>`;
return output.join('\n');
})
.join('\n')}</${options.listElement}>`
: '';

View File

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

View File

@@ -0,0 +1,136 @@
/* 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

@@ -0,0 +1,32 @@
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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
---
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

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

View File

@@ -0,0 +1,96 @@
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

@@ -19,6 +19,7 @@
".": "./index.js",
"./preset/": "./preset/",
"./inline-notification": "./inline-notification/index.js",
"./inline-notification-element": "./inline-notification/inline-notification.js",
"./inline-notification/inline-notification.js": "./inline-notification/inline-notification.js"
},
"scripts": {
@@ -36,6 +37,6 @@
],
"dependencies": {
"@rocket/drawer": "^0.1.2",
"@rocket/navigation": "^0.2.0"
"@rocket/navigation": "^0.2.1"
}
}

View File

@@ -43,12 +43,12 @@
"remark"
],
"dependencies": {
"@mdjs/mdjs-preview": "^0.3.0",
"@mdjs/mdjs-story": "^0.1.0",
"@mdjs/mdjs-preview": "^0.3.2",
"@mdjs/mdjs-story": "^0.1.2",
"@types/unist": "^2.0.3",
"es-module-lexer": "^0.3.26",
"github-markdown-css": "^4.0.0",
"plugins-manager": "^0.2.0",
"plugins-manager": "^0.2.1",
"rehype-autolink-headings": "^5.0.1",
"rehype-prism-template": "^0.4.1",
"rehype-raw": "^5.0.0",

View File

@@ -18,6 +18,7 @@ const { executeSetupFunctions } = require('plugins-manager');
const { mdjsParse } = require('./mdjsParse.js');
const { mdjsStoryParse } = require('./mdjsStoryParse.js');
const { mdjsSetupCode } = require('./mdjsSetupCode.js');
/** @type {MdjsProcessPlugin[]} */
const defaultMetaPlugins = [
@@ -25,6 +26,7 @@ const defaultMetaPlugins = [
{ name: 'gfm', plugin: gfm },
{ name: 'mdjsParse', plugin: mdjsParse },
{ name: 'mdjsStoryParse', plugin: mdjsStoryParse },
{ name: 'mdjsSetupCode', plugin: mdjsSetupCode },
// @ts-ignore
{ name: 'remark2rehype', plugin: remark2rehype, options: { allowDangerousHtml: true } },
// @ts-ignore
@@ -50,29 +52,15 @@ const defaultMetaPlugins = [
* @param {function[]} [options.setupUnifiedPlugins]
* @param {MdjsProcessPlugin[]} [options.plugins] deprecated option use setupUnifiedPlugins instead
*/
async function mdjsProcess(
mdjs,
{ rootNodeQueryCode = 'document', setupUnifiedPlugins = [] } = {},
) {
async function mdjsProcess(mdjs, { setupUnifiedPlugins = [] } = {}) {
const parser = unified();
const metaPlugins = executeSetupFunctions(setupUnifiedPlugins, defaultMetaPlugins);
// @ts-ignore
for (const pluginObj of metaPlugins) {
parser.use(pluginObj.plugin, pluginObj.options);
}
/** @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');
/**
* @param {string} code
*/
async function highlightCode(code) {
// @ts-ignore
const codePlugins = metaPlugins.filter(pluginObj =>
['markdown', 'remark2rehype', 'rehypePrism', 'htmlStringify'].includes(pluginObj.name),
@@ -82,46 +70,30 @@ async function mdjsProcess(
for (const pluginObj of codePlugins) {
codeParser.use(pluginObj.plugin, pluginObj.options);
}
const invokeStoriesCode = [];
for (const story of stories) {
let code = '';
switch (story.type) {
case 'html':
code = `\`\`\`html\n${story.code.split('`')[1]}\n\`\`\``;
break;
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}\` }`,
);
}
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');
const codeResult = await codeParser.process(code);
return codeResult.contents;
}
return { stories, jsCode: fullJsCode, html: result.contents };
// @ts-ignore
for (const pluginObj of metaPlugins) {
if (pluginObj.name === 'mdjsSetupCode') {
if (pluginObj.options && !pluginObj.options.highlightCode) {
pluginObj.options.highlightCode = highlightCode;
}
if (!pluginObj.options) {
pluginObj.options = { highlightCode };
}
}
parser.use(pluginObj.plugin, pluginObj.options);
}
/** @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 = {

View File

@@ -0,0 +1,68 @@
/** @typedef {import('vfile').VFileOptions} VFileOptions */
/** @typedef {import('unist').Node} Node */
/** @typedef {import('@mdjs/core/types/code').Story} Story */
function mdjsSetupCode({
rootNodeQueryCode = 'document',
highlightCode = /** @param {string} code */ code => code,
} = {}) {
/**
* @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) {
let code = '';
switch (story.type) {
case 'html':
code = `\`\`\`html\n${story.code.split('`')[1]}\n\`\`\``;
break;
case 'js':
code = `\`\`\`js\n${story.code}\n\`\`\``;
break;
default:
break;
}
let highlightedCode = await highlightCode(code);
highlightedCode = highlightedCode.replace(/`/g, '\\`').replace(/\$/g, '\\$');
invokeStoriesCode.push(
`{ key: '${story.key}', story: ${story.key}, code: \`${highlightedCode}\` }`,
);
}
file.data.setupJsCode = [
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;`,
` storyEl.jsCode = \`${jsCode.replace(/`/g, '\\`')}\`;`,
`};`,
`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 tree;
}
return transformer;
}
module.exports = {
mdjsSetupCode,
};

View File

@@ -43,12 +43,14 @@ describe('mdjsProcess', () => {
' storyEl.codeHasHtml = true;',
' storyEl.story = story.story;',
' storyEl.code = story.code;',
' storyEl.jsCode = `const bar = 2;`;',
'};',
`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');
const result = await mdjsProcess(input);
expect(result.html).to.equal(expected);
expect(result.jsCode).to.equal(expectedJsCode);
});

View File

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

View File

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

View File

@@ -1,5 +1,11 @@
# @mdjs/mdjs-preview
## 0.3.2
### Patch Changes
- be0d0b3: fix: add missing main entry to the packages
## 0.3.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@mdjs/mdjs-preview",
"version": "0.3.1",
"version": "0.3.2",
"publishConfig": {
"access": "public"
},
@@ -13,6 +13,7 @@
},
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/markdown-javascript/preview/",
"main": "./index.js",
"type": "module",
"exports": {
".": "./index.js",

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@mdjs/mdjs-story",
"version": "0.1.1",
"version": "0.1.2",
"publishConfig": {
"access": "public"
},
@@ -13,6 +13,7 @@
},
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/markdown-javascript/story/",
"main": "./index.js",
"type": "module",
"exports": {
".": "./index.js",

View File

@@ -1,5 +1,19 @@
# @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
### Minor Changes

View File

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

View File

@@ -1,3 +1,27 @@
/**
* 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
* @property {HTMLElement} headline
@@ -5,12 +29,17 @@
* @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 {
constructor() {
super();
/** @type NavigationListItem[] */
this.list = [];
this.__scrollHandler = this.__scrollHandler.bind(this);
this.__clickHandler = this.__clickHandler.bind(this);
this.__scrollHandler = debounce(this.__scrollHandler.bind(this), 25, true);
this.__isSetup = false;
}
@@ -20,27 +49,7 @@ export class RocketNavigation extends HTMLElement {
}
this.__isSetup = true;
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();
}
}
});
this.addEventListener('click', this.__clickHandler);
const anchors = /** @type {NodeListOf<HTMLAnchorElement>} */ (this.querySelectorAll(
'li.current a.anchor',
@@ -57,12 +66,41 @@ export class RocketNavigation extends HTMLElement {
}
}
// TODO: debounce
window.addEventListener('scroll', this.__scrollHandler);
window.addEventListener('scroll', this.__scrollHandler, { passive: true });
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() {
for (const listObj of this.list) {
listObj.top = listObj.headline.getBoundingClientRect().top;

View File

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

View File

@@ -0,0 +1,3 @@
# Story element for mdjs
For docs please see our homepage [https://rocket.modern-web.dev/docs/markdown-javascript/story/](https://rocket.modern-web.dev/docs/markdown-javascript/story/).

View File

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

View File

@@ -0,0 +1,40 @@
{
"name": "@rocket/playground",
"version": "0.1.1",
"publishConfig": {
"access": "public"
},
"description": "Rendering storybook story functions inside a story window with show code capabilities",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/modernweb-dev/rocket.git",
"directory": "packages/mdjs-story"
},
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/markdown-javascript/story/",
"type": "module",
"exports": {
".": "./index.js",
"./rocket-playground": "./rocket-playground.js"
},
"scripts": {
"debug": "cd ../../ && npm run debug -- --group mdjs-story",
"test": "npm run test:web",
"test:watch": "onchange 'src/**/*.{js,cjs}' 'test-node/**/*.js' -- npm run test:node",
"test:node": "mocha --timeout 5000 test-node/**/*.test.{js,cjs} test-node/*.test.{js,cjs}",
"test:web": "cd ../../ && npm run test:web -- --group mdjs-story"
},
"files": [
"*.js",
"assets",
"dist-types",
"src"
],
"dependencies": {
"@vanillawc/wc-monaco-editor": "^1.10.12",
"lit-element": "^2.4.0",
"wasm-flate": "^1.0.2-browser"
},
"types": "dist-types/index.d.ts"
}

View File

@@ -0,0 +1,3 @@
import { RocketPlayground } from './src/RocketPlayground.js';
customElements.define('rocket-playground', RocketPlayground);

View File

@@ -0,0 +1,133 @@
import { LitElement, html, css } from 'lit-element';
import frame from './frame.svg.js';
const DEVICES = {
contentFlow: {
name: 'Content Flow',
system: 'web',
width: 'auto',
height: 'auto',
dpr: 1,
},
webSmall: {
name: 'Web Small',
system: 'web',
width: 360,
height: 640,
dpr: 1,
},
pixel2: {
name: 'Pixel 2',
system: 'android',
width: 411,
height: 731,
dpr: 2.6,
},
galaxyS5: {
name: 'Galaxy 5',
system: 'android',
width: 360,
height: 640,
dpr: 3,
},
iphoneX: {
name: 'iPhone X',
system: 'ios',
width: 375,
height: 812,
dpr: 3,
},
};
export class DevicePreview extends LitElement {
static get properties() {
return {
jsCode: { type: String },
device: { type: String, reflect: true },
};
}
constructor() {
super();
this.jsCode = '';
this.device = 'pixel2';
this.iframe = null;
}
static get styles() {
return [
css`
:host {
display: block;
position: relative;
}
iframe {
border: none;
background: #fff;
background: green;
}
:host([device='pixel2']) iframe {
width: 411px;
height: 640px;
}
div,
iframe {
position: absolute;
}
iframe {
top: 100px;
left: 100px;
}
svg {
width: 587px;
}
`,
];
}
update(changedProperties) {
super.update(changedProperties);
if (this.iframe) {
const iframeContent = `
<html>
<head>
<script type="importmap">
{
"imports": {
"lit-html": "http://localhost:8000/__wds-outside-root__/1/node_modules/lit-html/lit-html.js",
"@rocket/launch/inline-notification-element": "http://localhost:8000/__wds-outside-root__/1/packages/launch/inline-notification/inline-notification.js"
}
}
</script>
</head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
}
</style>
<script type="module">
${this.jsCode}
</script>
<body></body>
</html>
`;
this.iframe.src = `data:text/html;charset=utf-8,${encodeURIComponent(iframeContent)}`;
}
}
firstUpdated() {
this.iframe = this.shadowRoot.querySelector('iframe');
}
render() {
return html`
<iframe
sandbox="allow-scripts"
csp="script-src localhost:8000 'unsafe-inline'; connect-src 'none'"
></iframe>
<div>${frame(html)}</div>
`;
}
}

View File

@@ -0,0 +1,170 @@
import { LitElement, html, css } from 'lit-element';
import '@vanillawc/wc-monaco-editor';
import './device-preview.js';
/**
* @typedef {object} StoryOptions
* @property {ShadowRoot | null} StoryOptions.shadowRoot
*/
/** @typedef {(options?: StoryOptions) => ReturnType<LitElement['render']>} LitHtmlStoryFn */
/** @typedef {import('./devices.js').devices} devices */
export function createViewer(jsCode) {
const iframeViewer = document.createElement('iframe');
const iframeContent = `
<html>
<head>
<script type="importmap">
{
"imports": {
"lit-html": "http://localhost:8000/__wds-outside-root__/1/node_modules/lit-html/lit-html.js"
}
}
</script>
</head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
}
</style>
<script type="module">
${jsCode}
</script>
<body></body>
</html>
`;
iframeViewer.setAttribute('style', `border: none; background: #fff;`);
iframeViewer.setAttribute('sandbox', 'allow-scripts');
iframeViewer.setAttribute('csp', "script-src localhost:8000 'unsafe-inline'; connect-src 'none'");
// Uses a data url as when using srcdoc the iframe csp rules get ignored?
// iframeViewer.setAttribute('srcdoc', iframeContent);
iframeViewer.src = `data:text/html;charset=utf-8,${escape(iframeContent)}`;
return iframeViewer;
}
/**
* Renders a story
*
* @element mdjs-story
* @prop {StoryFn} [story=(() => TemplateResult)] Function that returns the story
*/
export class RocketPlayground extends LitElement {
static get properties() {
return {
story: {
attribute: false,
},
jsCode: {
type: String,
},
showDevices: {
type: Array,
},
};
}
constructor() {
super();
/** @type {LitHtmlStoryFn} */
this.story = () => html` <p>Loading...</p> `;
this.jsCode = '';
// this.story({ shadowRoot: this.shadowRoot });
this.urlState = new URLSearchParams(document.location.search);
// this.urlState.append('jsCode', '');
/**
* @type {Array<keyof devices>}
*/
this.showDevices = ['pixel2'];
}
setJsCode(jsCode) {
const encoded = flate.gzip_encode(jsCode);
this.urlState.set('jsCode', encoded);
this.updateUrl();
this.jsCode = jsCode;
}
updateUrl() {
history.pushState('', '', '?' + this.urlState.toString());
}
// createRenderRoot() {
// return this;
// }
setup() {
this.editorWc = this.querySelector('wc-monaco-editor');
if (this.editorWc) {
this.editorWc.tabSize = 2;
if (this.urlState.get('jsCode')) {
this.jsCode = flate.gzip_decode(this.urlState.get('jsCode'));
}
const value = [
"import { html, render } from 'lit-html';",
'export const foo = () => html`',
' <p>hey there</p>',
'`;',
"render(foo(), document.querySelector('body'))",
].join('\n');
this.editorWc.value = this.jsCode || value;
this.editorWc.editor.getModel().onDidChangeRawContentFast(() => {
this.setJsCode(this.editorWc.value);
});
}
}
firstUpdated() {
setTimeout(() => {
this.setup();
}, 100);
}
connectedCallback() {
super.connectedCallback();
this.editorWc = document.createElement('wc-monaco-editor');
this.editorWc.slot = 'editor';
this.editorWc.setAttribute('language', 'javascript');
this.appendChild(this.editorWc);
}
static get styles() {
return [
css`
:host {
display: flex;
height: 100%;
}
#editor-wrapper,
#devices-wrapper {
width: 50%;
}
`,
];
}
render() {
return html`
<div id="editor-wrapper">
<slot name="editor"></slot>
</div>
<div id="devices-wrapper">
${this.showDevices.map(
device => html` <device-preview .jsCode=${this.jsCode} .device=${device}></device-preview> `,
)}
</div>
`;
}
}

View File

@@ -0,0 +1,7 @@
export async function createImportMapForLocalPackages(packages) {
for (const pkg of packages) {
const pkgJson = await import(pkg);
console.log({pkg, pkgJson});
}
}

View File

@@ -0,0 +1,3 @@
import { DevicePreview } from './DevicePreview.js';
customElements.define('device-preview', DevicePreview);

View File

@@ -0,0 +1,37 @@
export const devices = {
contentFlow: {
name: 'Content Flow',
system: 'web',
width: 'auto',
height: 'auto',
dpr: 1,
},
webSmall: {
name: 'Web Small',
system: 'web',
width: 360,
height: 640,
dpr: 1,
},
pixel2: {
name: 'Pixel 2',
system: 'android',
width: 411,
height: 731,
dpr: 2.6,
},
galaxyS5: {
name: 'Galaxy 5',
system: 'android',
width: 360,
height: 640,
dpr: 3,
},
iphoneX: {
name: 'iPhone X',
system: 'ios',
width: 375,
height: 812,
dpr: 3,
},
};

View File

@@ -0,0 +1,27 @@
export default tag => tag`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 583.782 992.748">
<defs>
<path id="reuse-0" d="M-328.071 77.934h87.321c3.158 3.241 3.966 6.64 0 10.357h-87.321c-2.388-2.89-5.443-5.624 0-10.357z" filter="url(#c)"/>
</defs>
<defs>
<filter id="c" width="1.02" height="1.18" x="-.01" y="-.09" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation=".388"/>
</filter>
<filter id="b" width="1.146" height="1.072" x="-.073" y="-.036" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="14.425"/>
</filter>
<linearGradient id="a">
<stop offset="0" stop-color="#ececec"/>
<stop offset="1" stop-color="#ececec" stop-opacity="0"/>
</linearGradient>
</defs>
<g transform="translate(-83.389 -12.472)">
<path fill-rule="evenodd" d="M121.945 32.979c-25.884 7.036-44.412 19.814-46.138 45.707l-.543 819.843c.093 25.7 10.39 46.875 47.222 56.291l413.614-.48c29.367-6.096 48.726-21.391 48.851-54.367l.543-818.4c-3.437-20.23-8.931-39.683-45.05-47.632zm431.2 46.298a3.26 3.26 0 013.267 3.258l.572 825.24a3.26 3.26 0 01-3.263 3.262l-459.512-.562a3.26 3.26 0 01-3.256-3.264l.567-823.549a3.26 3.26 0 013.251-3.258z" filter="url(#b)" opacity=".45" transform="translate(83.389 12.471) scale(.9375)"/>
<path fill="none" d="M325.826 41.2v964.02h-197.99c-23.97-6.931-42.562-21.032-44.447-57.409l.505-854.589c1.73-26.554 13.299-45.975 41.921-53.033zm-.052 0v964.02h197.99c23.97-6.931 42.562-21.032 44.447-57.409l-.505-854.589c-1.73-26.554-13.299-45.975-41.922-53.033z"/>
<path fill="#151515" fill-rule="evenodd" d="M193.16 44.552c-24.085 6.596-41.324 18.577-42.93 42.852l-.506 768.63c.088 24.094 9.67 43.949 43.942 52.777l384.868-.45c27.326-5.715 45.34-20.057 45.456-50.973l.505-767.278c-3.198-18.965-8.31-37.204-41.92-44.656zm395.232 66.864a2.863 2.863 0 012.871 2.862l.504 724.803a2.863 2.863 0 01-2.868 2.866l-403.586-.495a2.863 2.863 0 01-2.86-2.865l.498-723.318a2.863 2.863 0 012.856-2.862z"/>
<use filter="url(#c)" transform="matrix(1.00398 0 0 1.14592 671.843 -16.339)" xlink:href="#reuse-0"/>
<ellipse cx="532.319" cy="77.836" rx="8.207" ry="7.955"/>
<use filter="url(#c)" transform="matrix(1.00398 0 0 1.14592 671.843 778.763)" xlink:href="#reuse-0"/>
</g>
</svg>
`;

View File

View File

@@ -0,0 +1,19 @@
import chai from 'chai';
import path from 'path';
import { fileURLToPath } from 'url';
import { createImportMapForLocalPackages } from '../src/createImportMapForLocalPackages.js';
const { expect } = chai;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
describe('normalizeConfig', () => {
it.only('makes sure essential settings are there', async () => {
const importMap = await createImportMapForLocalPackages(['@rocket/launch']);
expect(importMap).to.deep.equal({
imports: {
"@rocket/launch/inline-notification-element": "http://localhost:8000/__wds-outside-root__/1/packages/launch/inline-notification/inline-notification.js"
}
});
});
});

View File

@@ -0,0 +1,24 @@
// Don't edit this file directly. It is generated by /scripts/update-package-configs.ts
{
"extends": "../../tsconfig.browser-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

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

View File

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

View File

@@ -1,5 +1,19 @@
# @rocket/search
## 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
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@rocket/search",
"version": "0.3.0",
"version": "0.3.2",
"publishConfig": {
"access": "public"
},
@@ -14,6 +14,7 @@
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
"homepage": "https://rocket.modern-web.dev/docs/presets/search/",
"type": "module",
"main": "./node.js",
"exports": {
".": "./node.js",
"./node": "./node.js",
@@ -44,7 +45,7 @@
"@open-wc/scoped-elements": "^1.3.2",
"chalk": "^4.0.0",
"minisearch": "^3.0.2",
"plugins-manager": "^0.2.0",
"plugins-manager": "^0.2.1",
"sax-wasm": "^2.0.0"
}
}

View File

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

View File

@@ -43,6 +43,8 @@ export class RocketSearchCombobox extends LionCombobox {
font: inherit;
cursor: pointer;
fill: var(--rocket-search-fill-color, #000);
display: flex;
align-items: center;
}
::slotted([slot='prefix'][close-btn]) {
@@ -136,7 +138,7 @@ export class RocketSearchCombobox extends LionCombobox {
display: flex;
border: 1px solid var(--rocket-search-input-border-color, #dfe1e5);
box-shadow: none;
border-radius: 24px;
border-radius: var(--rocket-search-input-border-radius, 24px);
padding: 5px 0;
}

View File

@@ -65,7 +65,7 @@ export class RocketSearchOption extends LinkMixin(LionOption) {
:host {
display: block;
cursor: default;
cursor: pointer;
position: relative;
padding: 12px 10px;
display: flex;
@@ -103,7 +103,7 @@ export class RocketSearchOption extends LinkMixin(LionOption) {
render() {
return html`
<img class="icon" src=${getIcon(this.section)} />
<img class="icon" src=${getIcon(this.section)} alt=${this.section} />
<div class="choice-field__label">
<div class="title">${unsafeHTML(this.title)}</div>
<div class="text">${unsafeHTML(this.text)}</div>

View File

@@ -26,6 +26,7 @@ function defaultHighlight(term) {
* @param {number} [options.before]
* @param {number} [options.length]
* @param {function} [options.highlight]
* @param {boolean} [options.addEllipsis]
*/
export function highlightSearchTerms({
search,
@@ -34,6 +35,7 @@ export function highlightSearchTerms({
before = 15,
length = 100,
highlight = defaultHighlight,
addEllipsis = false,
}) {
if (!search || !text) {
return '';
@@ -70,5 +72,9 @@ export function highlightSearchTerms({
} while (startIndex !== -1);
}
return newText.substr(truncateStart, length + extraLength);
let textResult = newText.substr(truncateStart, length + extraLength);
if (addEllipsis && truncateStart > 0) {
textResult = `...${textResult}`;
}
return textResult;
}

View File

@@ -3,9 +3,10 @@ import { rocketBlog } from '@rocket/blog';
import { rocketSearch } from '@rocket/search';
import { absoluteBaseUrlNetlify } from '@rocket/core/helpers';
export default {
/** @type {Partial<import("./packages/cli/types/main").RocketCliOptions>} */
const config = {
presets: [rocketLaunch(), rocketBlog(), rocketSearch()],
absoluteBaseUrl: absoluteBaseUrlNetlify('http://localhost:8080'),
// emptyOutputDir: false,
};
}
export default config;

View File

@@ -1778,6 +1778,11 @@
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
"@vanillawc/wc-monaco-editor@^1.10.12":
version "1.10.12"
resolved "https://registry.yarnpkg.com/@vanillawc/wc-monaco-editor/-/wc-monaco-editor-1.10.12.tgz#73ae976b27fecfbef034df0cd2ea3608f4457180"
integrity sha512-UOFs6eCf30qWQE8J4+e6axlcoZAKfa/rOgcqMB70s7UvAzQYnE0zpbWmTyRPCG6ARmuHXDkaxjkG8DXywZfJDA==
"@web/browser-logs@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@web/browser-logs/-/browser-logs-0.2.0.tgz#3f39d59154bf668f0bce467026354ff2b9f3e06b"
@@ -8744,6 +8749,11 @@ void-elements@^2.0.1:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
wasm-flate@^1.0.2-browser:
version "1.0.2-browser"
resolved "https://registry.yarnpkg.com/wasm-flate/-/wasm-flate-1.0.2-browser.tgz#e10e758b37c3d38829b54809fa2fef853b48131c"
integrity sha512-qpqOzvbKtgG/2Mb0DYXVPQ2nOrkKYoAQtpr4QZzgO8SMfKieeODUy2vomMnimQjH/K2RGWnw+kZmO8yf+Yh9qA==
wcwidth@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"