Compare commits

..

5 Commits

Author SHA1 Message Date
github-actions[bot]
ff8b4c5cd5 Version Packages 2022-08-15 23:04:55 +02:00
Thomas Allmer
5122ea8639 chore: add docs & tests for link-text for headlines 2022-08-15 23:02:29 +02:00
Thomas Allmer
3032ba9b82 feat(engine): menus now support special characters in markdown headings 2022-08-15 23:02:29 +02:00
Thomas Allmer
93503ed309 feat(engine): HTML in headings will be ignored for the menu 2022-08-15 23:02:29 +02:00
George Raptis
77646abbee Fix documentation link 2022-08-15 14:54:42 +02:00
19 changed files with 364 additions and 29 deletions

View File

@@ -28,7 +28,7 @@
<p align="center">
<a href="https://rocket.modern-web.dev">Website</a>
·
<a href="https://rocket.modern-web.dev/doc/">Documentation</a>
<a href="https://rocket.modern-web.dev/docs/">Documentation</a>
·
<a href="https://rocket.modern-web.dev/chat">Discord Community</a>
</p>

View File

@@ -1,5 +1,25 @@
# @rocket/engine
## 0.2.5
### Patch Changes
- 93503ed: HTML in headings will be ignored for the menu
Some examples:
- `<h1>Hello <em>Word</em></h1>` => `Hello Word`
- `<h1>Hello <strong>World</strong> of <span>JS <em>(JavaScript)</em></span>!</h1>` => `Hello World of JS (JavaScript)!`
- 3032ba9: Menus now support special characters in markdown headings.
Examples:
```md
# Fun Errors & Feedback
# &lt;some-button>
```
## 0.2.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@rocket/engine",
"version": "0.2.4",
"version": "0.2.5",
"publishConfig": {
"access": "public"
},

View File

@@ -29,9 +29,23 @@ export function getHtmlMetaData(htmlFilePath) {
// headlinesWithId: [],
};
/** @type {string | undefined} */
let capturedHeadlineText = undefined;
parser.eventHandler = (ev, _data) => {
if (ev === SaxEventType.OpenTag) {
const data = /** @type {Tag} */ (/** @type {any} */ (_data));
if (isHeadline(data)) {
capturedHeadlineText = '';
}
}
if (capturedHeadlineText !== undefined && ev === SaxEventType.Text) {
const data = /** @type {Text} */ (/** @type {any} */ (_data));
capturedHeadlineText += data.value;
}
if (ev === SaxEventType.CloseTag) {
const data = /** @type {Tag} */ (/** @type {any} */ (_data));
// ********** <meta name="*" content="*">
if (data.name === 'meta') {
const metaName = getAttribute(data, 'name');
if (metaName === 'menu:link.text') {
@@ -63,26 +77,32 @@ export function getHtmlMetaData(htmlFilePath) {
if (!metaData.title && data.name === 'title') {
metaData.title = getText(data);
}
if (!metaData.h1 && data.name === 'h1') {
metaData.h1 = getText(data);
}
// ********** <h1> - <h6>
if (isHeadline(data)) {
const id = getAttribute(data, 'id');
const rawText = getText(data);
const linkText = getAttribute(data, 'link-text');
if (id && rawText) {
const processedCapturedHeadlineText = capturedHeadlineText
?.replace(/&#x3C;/g, '&lt;')
.replace(/&#x26;/g, '&')
.trim();
const text = linkText || processedCapturedHeadlineText || '';
if (data.name === 'h1') {
metaData.h1 = text;
}
if (id && text) {
if (!metaData.headlinesWithId) {
metaData.headlinesWithId = [];
}
const rawTextObj = linkText ? { rawText } : {};
const rawTextObj = linkText ? { rawText: processedCapturedHeadlineText } : {};
metaData.headlinesWithId.push({
text: linkText || rawText,
text,
id,
level: parseInt(data.name[1], 10),
...rawTextObj,
});
}
capturedHeadlineText = undefined;
}
}
};
@@ -97,6 +117,7 @@ export function getHtmlMetaData(htmlFilePath) {
});
readable.on('end', () => {
parser.end();
capturedHeadlineText = undefined;
resolve(metaData);
});

View File

@@ -9,6 +9,9 @@ const require = createRequire(import.meta.url);
export const streamOptions = { highWaterMark: 128 * 1024 };
const saxPath = require.resolve('sax-wasm/lib/sax-wasm.wasm');
const saxWasmBuffer = await readFile(saxPath);
export const parser = new SAXParser(SaxEventType.CloseTag, streamOptions);
export const parser = new SAXParser(
SaxEventType.OpenTag | SaxEventType.CloseTag | SaxEventType.Text,
streamOptions,
);
await parser.prepareWasm(saxWasmBuffer);

View File

@@ -834,4 +834,99 @@ describe('Engine menus', () => {
await cleanup();
});
it('14: get-all-text-but-strip-html', async () => {
const { build, readSource } = await setupTestEngine(
'fixtures/05-menu/14-get-all-text-but-strip-html/docs',
);
await build();
expect(JSON.parse(readSource('pageTreeData.rocketGenerated.json'))).to.deep.equal({
h1: 'Hello World of JS (JavaScript)!',
name: 'Hello World of JS (JavaScript)!',
menuLinkText: 'Hello World of JS (JavaScript)!',
url: '/',
outputRelativeFilePath: 'index.html',
sourceRelativeFilePath: 'index.rocket.js',
level: 0,
});
});
it('15: markdown special characters', async () => {
const { build, readSource } = await setupTestEngine(
'fixtures/05-menu/15-md-special-characters/docs',
);
await build();
expect(JSON.parse(readSource('pageTreeData.rocketGenerated.json'))).to.deep.equal({
children: [
{
h1: '&lt;some-button>',
headlinesWithId: [
{
id: 'some-button',
level: 1,
text: '&lt;some-button>',
},
],
level: 1,
menuLinkText: '&lt;some-button>',
name: '&lt;some-button>',
outputRelativeFilePath: 'component/index.html',
sourceRelativeFilePath: 'component.rocket.md',
url: '/component/',
},
],
h1: 'Fun Errors & Feedback',
headlinesWithId: [
{
id: 'fun-errors--feedback',
level: 1,
text: 'Fun Errors & Feedback',
},
],
level: 0,
menuLinkText: 'Fun Errors & Feedback',
name: 'Fun Errors & Feedback',
outputRelativeFilePath: 'index.html',
sourceRelativeFilePath: 'index.rocket.md',
url: '/',
});
});
it('15: link-text attribute', async () => {
const { build, readSource } = await setupTestEngine(
'fixtures/05-menu/16-link-text-attribute/docs',
);
await build();
expect(JSON.parse(readSource('pageTreeData.rocketGenerated.json'))).to.deep.equal({
h1: 'Home',
headlinesWithId: [
{
id: 'home',
level: 1,
rawText: 'Welcome to Rocket',
text: 'Home',
},
{
id: 'first',
level: 2,
text: 'First',
},
{
id: 'second',
level: 2,
rawText: 'Second is best',
text: 'Second',
},
],
level: 0,
menuLinkText: 'Home',
name: 'Home',
outputRelativeFilePath: 'index.html',
sourceRelativeFilePath: 'index.rocket.js',
url: '/',
});
});
});

View File

@@ -116,10 +116,21 @@ describe('Engine start error handling', () => {
});
it('04: update-header-while-rendering', async () => {
const { readOutput, writeSource, cleanup, engine, setAsOpenedInBrowser, outputExists } =
await setupTestEngine(
const {
readOutput,
writeSource,
cleanup,
engine,
setAsOpenedInBrowser,
outputExists,
anEngineEvent,
} = await setupTestEngine(
'fixtures/09b-watch-error-handling/04-update-header-while-rendering/docs',
);
expect(outputExists('index.html')).to.be.false;
await engine.start();
setAsOpenedInBrowser('index.rocket.js');
await writeSource(
'index.rocket.js',
[
@@ -140,13 +151,7 @@ describe('Engine start error handling', () => {
'`;',
].join('\n'),
);
await engine.start();
setAsOpenedInBrowser('index.rocket.js');
const { port } = engine.devServer.config;
expect(outputExists('index.html')).to.be.false;
await fetch(`http://localhost:${port}/`);
await anEngineEvent('rocketUpdated');
expect(readOutput('index.html')).to.equal(
[

View File

@@ -1,15 +1,15 @@
{
"title": "Fixed Title",
"h1": "\n Welcome Members:\n ",
"h1": "Welcome Members:",
"headlinesWithId": [
{
"text": "\n Welcome Members:\n ",
"text": "Welcome Members:",
"id": "welcome-members",
"level": 1
}
],
"name": "Fixed Title",
"menuLinkText": "\n Welcome Members:\n ",
"menuLinkText": "Welcome Members:",
"url": "/",
"outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.js",

View File

@@ -0,0 +1,11 @@
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = 'index.rocket.js';
import { layout, html } from './recursive.data.js';
export { layout, html };
/* END - Rocket auto generated - do not touch */
export default () =>
html`<h1>
Hello <strong>World</strong> of <span>JS <em>(JavaScript)</em></span
>!
</h1>`;

View File

@@ -0,0 +1,9 @@
{
"h1": "Hello World of JS (JavaScript)!",
"name": "Hello World of JS (JavaScript)!",
"menuLinkText": "Hello World of JS (JavaScript)!",
"url": "/",
"outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.js",
"level": 0
}

View File

@@ -0,0 +1,18 @@
import { PageTree, SiteMenu } from '@rocket/engine';
import { html } from 'lit';
const pageTree = new PageTree({
inputDir: new URL('./', import.meta.url),
outputDir: new URL('../__output', import.meta.url),
});
await pageTree.restore();
export const layout = data => {
return html`
${pageTree.renderMenu(new SiteMenu(), data.sourceRelativeFilePath)}
<main>${data.content()}</main>
`;
};
export { html };

View File

@@ -0,0 +1,9 @@
```js server
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = 'component.rocket.md';
import { layout, html } from './recursive.data.js';
export { layout, html };
/* END - Rocket auto generated - do not touch */
```
# &lt;some-button>

View File

@@ -0,0 +1,9 @@
```js server
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = 'index.rocket.md';
import { layout, html } from './recursive.data.js';
export { layout, html };
/* END - Rocket auto generated - do not touch */
```
# Fun Errors & Feedback

View File

@@ -0,0 +1,34 @@
{
"h1": "Fun Errors & Feedback",
"headlinesWithId": [
{
"text": "Fun Errors & Feedback",
"id": "fun-errors--feedback",
"level": 1
}
],
"name": "Fun Errors & Feedback",
"menuLinkText": "Fun Errors & Feedback",
"url": "/",
"outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.md",
"level": 0,
"children": [
{
"h1": "&lt;some-button>",
"headlinesWithId": [
{
"text": "&lt;some-button>",
"id": "some-button",
"level": 1
}
],
"name": "&lt;some-button>",
"menuLinkText": "&lt;some-button>",
"url": "/component/",
"outputRelativeFilePath": "component/index.html",
"sourceRelativeFilePath": "component.rocket.md",
"level": 1
}
]
}

View File

@@ -0,0 +1,18 @@
import { PageTree, SiteMenu } from '@rocket/engine';
import { html } from 'lit';
const pageTree = new PageTree({
inputDir: new URL('./', import.meta.url),
outputDir: new URL('../__output', import.meta.url),
});
await pageTree.restore();
export const layout = data => {
return html`
${pageTree.renderMenu(new SiteMenu(), data.sourceRelativeFilePath)}
<main>${data.content()}</main>
`;
};
export { html };

View File

@@ -0,0 +1,11 @@
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = 'index.rocket.js';
import { layout, html } from './recursive.data.js';
export { layout, html };
/* END - Rocket auto generated - do not touch */
export default () => html`
<h1 id="home" link-text="Home">Welcome to Rocket</h1>
<h2 id="first">First</h2>
<h2 link-text="Second" id="second">Second is best</h2>
`;

View File

@@ -0,0 +1,28 @@
{
"h1": "Home",
"headlinesWithId": [
{
"text": "Home",
"id": "home",
"level": 1,
"rawText": "Welcome to Rocket"
},
{
"text": "First",
"id": "first",
"level": 2
},
{
"text": "Second",
"id": "second",
"level": 2,
"rawText": "Second is best"
}
],
"name": "Home",
"menuLinkText": "Home",
"url": "/",
"outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.js",
"level": 0
}

View File

@@ -0,0 +1,18 @@
import { PageTree, SiteMenu } from '@rocket/engine';
import { html } from 'lit';
const pageTree = new PageTree({
inputDir: new URL('./', import.meta.url),
outputDir: new URL('../__output', import.meta.url),
});
await pageTree.restore();
export const layout = data => {
return html`
${pageTree.renderMenu(new SiteMenu(), data.sourceRelativeFilePath)}
<main>${data.content()}</main>
`;
};
export { html };

View File

@@ -85,22 +85,22 @@ In most cases you probably will not need to do anything as it will take the text
So if you have a page like this:
```md
# Hello World
# Learning Rocket
```
then it will be called "Hello World" in the menu.
then it will be called "Learning Rocket" in the menu.
You can overwrite that by using the property `menuLinkText`;
````md
```js server
export const menuLinkText = 'Hello';
export const menuLinkText = 'Docs';
```
# Hello World
# Learning Rocket
````
Now the menu will be called "Hello".
Now the menu will be called "Docs".
Within a menu the text of the links is defined by the following priority:
@@ -111,6 +111,32 @@ Within a menu the text of the links is defined by the following priority:
You can influence that data that gets provided to the menu by setting exports.
### link-text="..."
If you want to rename the menu text you can use the attribute `link-text`.
It works on your h1 for the page title as well as on your h2-h6 for a table of contents menu.
Examples:
```html
<h1 link-text="Docs">Learning Rocket</h1>
<h2 link-text="Contact">Write us a message</h2>
```
<inline-notification>
You can use HTML within markdown too!
</inline-notification>
## Headings with HTML
HTML in headings will be ignored for the menu
Some examples:
- `<h1>Hello <em>Word</em></h1>` => `Hello Word`
- `<h1>Hello <strong>World</strong> of <span>JS <em>(JavaScript)</em></span>!</h1>` => `Hello World of JS (JavaScript)!`
## Menu No Link
Often you have sections or groups of pages which you want to provide a heading for.