mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
Merge branch 'next' into hints
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -13995,7 +13995,7 @@
|
||||
},
|
||||
"packages/webawesome-pro": {
|
||||
"name": "@shoelace-style/webawesome-pro",
|
||||
"version": "3.0.0-beta.1",
|
||||
"version": "3.0.0-beta.2",
|
||||
"license": "TODO",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
|
||||
@@ -1,30 +1,63 @@
|
||||
import { parse as HTMLParse } from 'node-html-parser';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { parse } from 'path';
|
||||
import { anchorHeadingsPlugin } from './_utils/anchor-headings.js';
|
||||
import { codeExamplesPlugin } from './_utils/code-examples.js';
|
||||
import { copyCodePlugin } from './_utils/copy-code.js';
|
||||
import { currentLink } from './_utils/current-link.js';
|
||||
import { highlightCodePlugin } from './_utils/highlight-code.js';
|
||||
import { anchorHeadingsTransformer } from './_transformers/anchor-headings.js';
|
||||
import { codeExamplesTransformer } from './_transformers/code-examples.js';
|
||||
import { copyCodeTransformer } from './_transformers/copy-code.js';
|
||||
import { currentLinkTransformer } from './_transformers/current-link.js';
|
||||
import { highlightCodeTransformer } from './_transformers/highlight-code.js';
|
||||
import { outlineTransformer } from './_transformers/outline.js';
|
||||
import { getComponents } from './_utils/manifest.js';
|
||||
import { markdown } from './_utils/markdown.js';
|
||||
// import { formatCodePlugin } from './_utils/format-code.js';
|
||||
import { SimulateWebAwesomeApp } from './_utils/simulate-webawesome-app.js';
|
||||
// import { formatCodePlugin } from './_plugins/format-code.js';
|
||||
// import litPlugin from '@lit-labs/eleventy-plugin-lit';
|
||||
import { readFile } from 'fs/promises';
|
||||
import nunjucks from 'nunjucks';
|
||||
import process from 'process';
|
||||
import * as url from 'url';
|
||||
import { outlinePlugin } from './_utils/outline.js';
|
||||
import { replaceTextPlugin } from './_utils/replace-text.js';
|
||||
import { searchPlugin } from './_utils/search.js';
|
||||
import { replaceTextPlugin } from './_plugins/replace-text.js';
|
||||
import { searchPlugin } from './_plugins/search.js';
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
const isDev = process.argv.includes('--develop');
|
||||
const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4'];
|
||||
|
||||
async function getPackageData() {
|
||||
return JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
||||
}
|
||||
|
||||
export default async function (eleventyConfig) {
|
||||
const packageData = JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
||||
const docsDir = path.join(process.env.BASE_DIR || '.', 'docs');
|
||||
const allComponents = getComponents();
|
||||
let packageData = await getPackageData();
|
||||
let allComponents = getComponents();
|
||||
|
||||
const distDir = process.env.UNBUNDLED_DIST_DIRECTORY || path.resolve(__dirname, '../dist');
|
||||
const customElementsManifest = path.join(distDir, 'custom-elements.json');
|
||||
const stylesheets = path.join(distDir, 'styles');
|
||||
|
||||
eleventyConfig.addWatchTarget(customElementsManifest);
|
||||
eleventyConfig.setWatchThrottleWaitTime(10); // in milliseconds
|
||||
|
||||
eleventyConfig.on('eleventy.beforeWatch', async function (changedFiles) {
|
||||
let updatePackageData = false;
|
||||
let updateComponentData = false;
|
||||
changedFiles.forEach(file => {
|
||||
if (file.includes('package.json')) {
|
||||
updatePackageData = true;
|
||||
}
|
||||
|
||||
if (file.includes('custom-elements.json')) {
|
||||
updateComponentData = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (updatePackageData) {
|
||||
packageData = await getPackageData();
|
||||
}
|
||||
|
||||
if (updateComponentData) {
|
||||
allComponents = getComponents();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* If you plan to add or remove any of these extensions, make sure to let either Konnor or Cory know as these
|
||||
@@ -52,7 +85,7 @@ export default async function (eleventyConfig) {
|
||||
// Template filters - {{ content | filter }}
|
||||
eleventyConfig.addFilter('inlineMarkdown', content => markdown.renderInline(content || ''));
|
||||
eleventyConfig.addFilter('markdown', content => markdown.render(content || ''));
|
||||
eleventyConfig.addFilter('stripExtension', string => parse(string + '').name);
|
||||
eleventyConfig.addFilter('stripExtension', string => path.parse(string + '').name);
|
||||
eleventyConfig.addFilter('stripPrefix', content => content.replace(/^wa-/, ''));
|
||||
// Trims whitespace and pipes from the start and end of a string. Useful for CEM types, which can be pipe-delimited.
|
||||
// With Prettier 3, this means a leading pipe will exist be present when the line wraps.
|
||||
@@ -109,31 +142,6 @@ export default async function (eleventyConfig) {
|
||||
return '';
|
||||
});
|
||||
|
||||
eleventyConfig.addTransform('second-nunjucks-transform', function NunjucksTransform(content) {
|
||||
// For a server build, we expect a server to run the second transform.
|
||||
if (serverBuild) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Only run the transform on files nunjucks would transform.
|
||||
if (!this.page.inputPath.match(/.(md|html|njk)$/)) {
|
||||
return content;
|
||||
}
|
||||
|
||||
/** This largely mimics what an app would do and just stubs out what we don't care about. */
|
||||
return nunjucks.renderString(content, {
|
||||
// Stub the server EJS shortcodes.
|
||||
currentUser: {
|
||||
hasPro: false,
|
||||
},
|
||||
server: {
|
||||
head: '',
|
||||
loginOrAvatar: '',
|
||||
flashes: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Paired shortcodes - {% shortCode %}content{% endShortCode %}
|
||||
eleventyConfig.addPairedShortcode('markdown', content => markdown.render(content || ''));
|
||||
|
||||
@@ -152,33 +160,33 @@ export default async function (eleventyConfig) {
|
||||
eleventyConfig.setLibrary('md', markdown);
|
||||
|
||||
// Add anchors to headings
|
||||
eleventyConfig.addPlugin(anchorHeadingsPlugin({ container: '#content' }));
|
||||
eleventyConfig.addTransform('doc-transforms', function (content) {
|
||||
let doc = HTMLParse(content, { blockTextElements: { code: true } });
|
||||
|
||||
// Add an outline to the page
|
||||
eleventyConfig.addPlugin(
|
||||
outlinePlugin({
|
||||
container: '#content',
|
||||
target: '.outline-links',
|
||||
selector: 'h2, h3',
|
||||
ifEmpty: doc => {
|
||||
doc.querySelector('#outline')?.remove();
|
||||
},
|
||||
}),
|
||||
);
|
||||
const transformers = [
|
||||
anchorHeadingsTransformer({ container: '#content' }),
|
||||
outlineTransformer({
|
||||
container: '#content',
|
||||
target: '.outline-links',
|
||||
selector: 'h2, h3',
|
||||
ifEmpty: doc => {
|
||||
doc.querySelector('#outline')?.remove();
|
||||
},
|
||||
}),
|
||||
// Add current link classes
|
||||
currentLinkTransformer(),
|
||||
codeExamplesTransformer(),
|
||||
highlightCodeTransformer(),
|
||||
copyCodeTransformer(),
|
||||
];
|
||||
|
||||
// Add current link classes
|
||||
eleventyConfig.addPlugin(currentLink());
|
||||
for (const transformer of transformers) {
|
||||
transformer.call(this, doc);
|
||||
}
|
||||
|
||||
// Add code examples for `<code class="example">` blocks
|
||||
eleventyConfig.addPlugin(codeExamplesPlugin());
|
||||
return doc.toString();
|
||||
});
|
||||
|
||||
// Highlight code blocks with Prism
|
||||
eleventyConfig.addPlugin(highlightCodePlugin());
|
||||
|
||||
// Add copy code buttons to code blocks
|
||||
eleventyConfig.addPlugin(copyCodePlugin);
|
||||
|
||||
// Various text replacements
|
||||
eleventyConfig.addPlugin(
|
||||
replaceTextPlugin([
|
||||
{
|
||||
@@ -222,15 +230,14 @@ export default async function (eleventyConfig) {
|
||||
// }
|
||||
|
||||
let assetsDir = path.join(process.env.BASE_DIR || 'docs', 'assets');
|
||||
fs.cpSync(assetsDir, path.join(eleventyConfig.directories.output, 'assets'), { recursive: true });
|
||||
const siteAssetsDir = path.join(eleventyConfig.directories.output, 'assets');
|
||||
fs.cpSync(assetsDir, siteAssetsDir, { recursive: true });
|
||||
|
||||
for (let glob of passThrough) {
|
||||
eleventyConfig.addPassthroughCopy(glob);
|
||||
}
|
||||
|
||||
// // SSR plugin
|
||||
// // Make sure this is the last thing, we don't want to run the risk of accidentally transforming shadow roots with
|
||||
// // the nunjucks 2nd transform.
|
||||
// if (!isDev) {
|
||||
// //
|
||||
// // Problematic components in SSR land:
|
||||
@@ -253,6 +260,23 @@ export default async function (eleventyConfig) {
|
||||
// componentModules,
|
||||
// });
|
||||
// }
|
||||
|
||||
if (!isDev) {
|
||||
eleventyConfig.addTransform('simulate-webawesome-app', function (content) {
|
||||
// For a server build, we expect a server to run the second transform.
|
||||
if (serverBuild) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Only run the transform on files nunjucks would transform.
|
||||
if (!this.page.inputPath.match(/.(md|html|njk)$/)) {
|
||||
return content;
|
||||
}
|
||||
|
||||
/** This largely mimics what an app would do and just stubs out what we don't care about. */
|
||||
return SimulateWebAwesomeApp(content);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable no-invalid-this */
|
||||
import { readFileSync } from 'fs';
|
||||
import { mkdir, writeFile } from 'fs/promises';
|
||||
import lunr from 'lunr';
|
||||
import { parse } from 'node-html-parser';
|
||||
@@ -23,19 +24,22 @@ export function searchPlugin(options = {}) {
|
||||
...options,
|
||||
};
|
||||
|
||||
// Hoist above so that it can "cache" properly for incremental builds.
|
||||
return function (eleventyConfig) {
|
||||
const pagesToIndex = new Map();
|
||||
let pagesToIndex = new Map();
|
||||
|
||||
eleventyConfig.addPreprocessor('exclude-unlisted-from-search', '*', function (data, content) {
|
||||
if (data.unlisted) {
|
||||
// no-op
|
||||
pagesToIndex.delete(data.page.inputPath);
|
||||
} else {
|
||||
pagesToIndex.set(data.page.inputPath, {});
|
||||
pagesToIndex.set(data.page.inputPath, true);
|
||||
}
|
||||
|
||||
return content;
|
||||
});
|
||||
|
||||
// With incremental builds we need this to be last in case stuff was added from metadata. _BUT_ in incremental builds, not every page is added to the "transform".
|
||||
eleventyConfig.addTransform('search', function (content) {
|
||||
if (!pagesToIndex.has(this.page.inputPath)) {
|
||||
return content;
|
||||
@@ -67,11 +71,30 @@ export function searchPlugin(options = {}) {
|
||||
return content;
|
||||
});
|
||||
|
||||
eleventyConfig.on('eleventy.after', ({ directories }) => {
|
||||
eleventyConfig.on('eleventy.after', async ({ directories }) => {
|
||||
const { output } = directories;
|
||||
const outputFilename = path.resolve(join(output, 'search.json'));
|
||||
const cachedPages = path.resolve(join(output, 'cached_pages.json'));
|
||||
|
||||
function getCachedPages() {
|
||||
let content = { pages: [] };
|
||||
try {
|
||||
content = JSON.parse(readFileSync(cachedPages));
|
||||
} catch (e) {}
|
||||
|
||||
const cachedPagesMap = new Map(content.pages);
|
||||
for (const [key, value] of cachedPagesMap.entries()) {
|
||||
// A page uses a cached value if `true` and it didnt get its value set in the "transform" hook. This is to get around the limitation of incremental builds not going over every file in transform.
|
||||
if (pagesToIndex.get(key) === true) {
|
||||
pagesToIndex.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const map = [];
|
||||
const searchIndex = lunr(async function () {
|
||||
|
||||
getCachedPages();
|
||||
const searchIndex = lunr(function () {
|
||||
let index = 0;
|
||||
|
||||
this.ref('id');
|
||||
@@ -84,9 +107,11 @@ export function searchPlugin(options = {}) {
|
||||
map[index] = { title: page.title, description: page.description, url: page.url };
|
||||
index++;
|
||||
}
|
||||
await mkdir(dirname(outputFilename), { recursive: true });
|
||||
await writeFile(outputFilename, JSON.stringify({ searchIndex, map }), 'utf-8');
|
||||
});
|
||||
|
||||
await mkdir(dirname(outputFilename), { recursive: true });
|
||||
await writeFile(outputFilename, JSON.stringify({ searchIndex, map }), 'utf-8');
|
||||
await writeFile(cachedPages, JSON.stringify({ pages: [...pagesToIndex.entries()] }, null, 2));
|
||||
});
|
||||
};
|
||||
}
|
||||
81
packages/webawesome/docs/_transformers/anchor-headings.js
Normal file
81
packages/webawesome/docs/_transformers/anchor-headings.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { parse } from 'node-html-parser';
|
||||
import slugify from 'slugify';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
function createId(text) {
|
||||
let slug = slugify(String(text), {
|
||||
remove: /[^\w|\s]/g,
|
||||
lower: true,
|
||||
});
|
||||
|
||||
// ids must start with a letter
|
||||
if (!/^[a-z]/i.test(slug)) {
|
||||
slug = `wa_${slug}`;
|
||||
}
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eleventy plugin to add anchors to headings to content.
|
||||
*/
|
||||
export function anchorHeadingsTransformer(options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
headingSelector: 'h2, h3, h4, h5, h6',
|
||||
anchorLabel: 'Jump to heading',
|
||||
...options,
|
||||
};
|
||||
|
||||
/** doc is a parsed HTML document */
|
||||
return function (doc) {
|
||||
const container = doc.querySelector(options.container);
|
||||
|
||||
if (!container) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
// Look for headings
|
||||
let selector = `:is(${options.headingSelector}):not([data-no-anchor], [data-no-anchor] *)`;
|
||||
container.querySelectorAll(selector).forEach(heading => {
|
||||
const hasAnchor = heading.querySelector('a');
|
||||
const existingId = heading.getAttribute('id');
|
||||
const clone = parse(heading.outerHTML);
|
||||
|
||||
// Create a clone of the heading so we can remove [data-no-anchor] elements from the text content
|
||||
clone.querySelectorAll('[data-no-anchor]').forEach(el => el.remove());
|
||||
|
||||
if (hasAnchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = existingId;
|
||||
if (!id) {
|
||||
const slug = createId(clone.textContent ?? '') ?? uuid().slice(-12);
|
||||
id = slug;
|
||||
let suffix = 1;
|
||||
|
||||
// Make sure the slug is unique in the document
|
||||
while (doc.getElementById(id) !== null) {
|
||||
id = `${slug}-${++suffix}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the anchor
|
||||
const anchor = parse(`
|
||||
<a href="#${encodeURIComponent(id)}">
|
||||
<span class="wa-visually-hidden"></span>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
`);
|
||||
anchor.querySelector('.wa-visually-hidden').textContent = options.anchorLabel;
|
||||
|
||||
// Update the heading
|
||||
if (!existingId) {
|
||||
heading.setAttribute('id', id);
|
||||
}
|
||||
heading.classList.add('anchor-heading');
|
||||
heading.appendChild(anchor);
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,40 +1,46 @@
|
||||
import { parse } from 'node-html-parser';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { markdown } from '../_utils/markdown.js';
|
||||
import { copyCode } from './copy-code.js';
|
||||
import { highlightCode } from './highlight-code.js';
|
||||
|
||||
/**
|
||||
* Eleventy plugin to turn `<code class="example">` blocks into live examples.
|
||||
*/
|
||||
export function codeExamplesPlugin(options = {}) {
|
||||
export function codeExamplesTransformer(options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
...options,
|
||||
};
|
||||
|
||||
return function (eleventyConfig) {
|
||||
eleventyConfig.addTransform('code-examples', content => {
|
||||
const doc = parse(content, { blockTextElements: { code: true } });
|
||||
const container = doc.querySelector(options.container);
|
||||
return function (doc) {
|
||||
const container = doc.querySelector(options.container);
|
||||
|
||||
if (!container) {
|
||||
return content;
|
||||
}
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for external links
|
||||
container.querySelectorAll('code.example').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
const hasButtons = !code.classList.contains('no-buttons');
|
||||
const isOpen = code.classList.contains('open') || !hasButtons;
|
||||
const noEdit = code.classList.contains('no-edit');
|
||||
const id = `code-example-${uuid().slice(-12)}`;
|
||||
let preview = pre.textContent;
|
||||
// Look for external links
|
||||
container.querySelectorAll('code.example').forEach(code => {
|
||||
let pre = code.closest('pre');
|
||||
const hasButtons = !code.classList.contains('no-buttons');
|
||||
const isOpen = code.classList.contains('open') || !hasButtons;
|
||||
const noEdit = code.classList.contains('no-edit');
|
||||
const id = `code-example-${uuid().slice(-12)}`;
|
||||
let preview = pre.textContent;
|
||||
|
||||
// Run preview scripts as modules to prevent collisions
|
||||
const root = parse(preview, { blockTextElements: { script: true } });
|
||||
root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module'));
|
||||
preview = root.toString();
|
||||
const langClass = [...code.classList.values()].find(val => val.startsWith('language-'));
|
||||
const lang = langClass ? langClass.replace(/^language-/, '') : 'plain';
|
||||
|
||||
const codeExample = parse(`
|
||||
code.innerHTML = highlightCode(code.textContent ?? '', lang);
|
||||
|
||||
// Run preview scripts as modules to prevent collisions
|
||||
const root = parse(preview, { blockTextElements: { script: true } });
|
||||
root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module'));
|
||||
preview = root.toString();
|
||||
|
||||
copyCode(code);
|
||||
|
||||
const codeExample = parse(`
|
||||
<div class="code-example ${isOpen ? 'open' : ''}">
|
||||
<div class="code-example-preview">
|
||||
<div>
|
||||
@@ -79,10 +85,7 @@ export function codeExamplesPlugin(options = {}) {
|
||||
</div>
|
||||
`);
|
||||
|
||||
pre.replaceWith(codeExample);
|
||||
});
|
||||
|
||||
return doc.toString();
|
||||
pre.replaceWith(codeExample);
|
||||
});
|
||||
};
|
||||
}
|
||||
38
packages/webawesome/docs/_transformers/copy-code.js
Normal file
38
packages/webawesome/docs/_transformers/copy-code.js
Normal file
@@ -0,0 +1,38 @@
|
||||
export function copyCode(code) {
|
||||
const pre = code.closest('pre');
|
||||
let preId = pre.getAttribute('id') || `code-block-${crypto.randomUUID()}`;
|
||||
let codeId = code.getAttribute('id') || `${preId}-inner`;
|
||||
|
||||
if (!code.getAttribute('id')) {
|
||||
code.setAttribute('id', codeId);
|
||||
}
|
||||
if (!pre.getAttribute('id')) {
|
||||
pre.setAttribute('id', preId);
|
||||
}
|
||||
|
||||
// Add a copy button
|
||||
pre.innerHTML += `<wa-copy-button from="${codeId}" class="copy-button wa-dark"></wa-copy-button>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eleventy plugin to add copy buttons to code blocks.
|
||||
*/
|
||||
export function copyCodeTransformer(options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
...options,
|
||||
};
|
||||
|
||||
return function (doc) {
|
||||
const container = doc.querySelector(options.container);
|
||||
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for code blocks
|
||||
container.querySelectorAll('pre > code').forEach(code => {
|
||||
copyCode(code);
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -24,30 +24,25 @@ function normalize(pathname) {
|
||||
/**
|
||||
* Eleventy plugin to decorate current links with a custom class.
|
||||
*/
|
||||
export function currentLink(options = {}) {
|
||||
export function currentLinkTransformer(options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
className: 'current',
|
||||
...options,
|
||||
};
|
||||
|
||||
return function (eleventyConfig) {
|
||||
eleventyConfig.addTransform('current-link', function (content) {
|
||||
const doc = parse(content);
|
||||
const container = doc.querySelector(options.container);
|
||||
return function (doc) {
|
||||
const container = doc.querySelector(options.container);
|
||||
|
||||
if (!container) {
|
||||
return content;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare the href attribute to 11ty's page URL
|
||||
container.querySelectorAll('a[href]').forEach(a => {
|
||||
if (normalize(a.getAttribute('href')) === normalize(this.page.url)) {
|
||||
a.classList.add(options.className);
|
||||
}
|
||||
|
||||
// Compare the href attribute to 11ty's page URL
|
||||
container.querySelectorAll('a[href]').forEach(a => {
|
||||
if (normalize(a.getAttribute('href')) === normalize(this.page.url)) {
|
||||
a.classList.add(options.className);
|
||||
}
|
||||
});
|
||||
|
||||
return doc.toString();
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -37,36 +37,31 @@ export function highlightCode(code, language = 'plain') {
|
||||
* Eleventy plugin to highlight code blocks with the `language-*` attribute using Prism.js. Unlike most plugins, this
|
||||
* works on the entire document — not just markdown content.
|
||||
*/
|
||||
export function highlightCodePlugin(options = {}) {
|
||||
export function highlightCodeTransformer(options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
...options,
|
||||
};
|
||||
|
||||
return function (eleventyConfig) {
|
||||
eleventyConfig.addTransform('highlight-code', content => {
|
||||
const doc = parse(content, { blockTextElements: { code: true } });
|
||||
const container = doc.querySelector(options.container);
|
||||
return function (doc) {
|
||||
const container = doc.querySelector(options.container);
|
||||
|
||||
if (!container) {
|
||||
return content;
|
||||
}
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for <code class="language-*"> and highlight each one
|
||||
container.querySelectorAll('code[class*="language-"]').forEach(code => {
|
||||
const langClass = [...code.classList.values()].find(val => val.startsWith('language-'));
|
||||
const lang = langClass ? langClass.replace(/^language-/, '') : 'plain';
|
||||
// Look for <code class="language-*"> and highlight each one
|
||||
container.querySelectorAll('code[class*="language-"]').forEach(code => {
|
||||
const langClass = [...code.classList.values()].find(val => val.startsWith('language-'));
|
||||
const lang = langClass ? langClass.replace(/^language-/, '') : 'plain';
|
||||
|
||||
try {
|
||||
code.innerHTML = highlightCode(code.textContent ?? '', lang);
|
||||
} catch (err) {
|
||||
if (!options.ignoreMissingLangs) {
|
||||
throw new Error(err.message);
|
||||
}
|
||||
try {
|
||||
code.innerHTML = highlightCode(code.textContent ?? '', lang);
|
||||
} catch (err) {
|
||||
if (!options.ignoreMissingLangs) {
|
||||
throw new Error(err.message);
|
||||
}
|
||||
});
|
||||
|
||||
return doc.toString();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
64
packages/webawesome/docs/_transformers/outline.js
Normal file
64
packages/webawesome/docs/_transformers/outline.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import { parse } from 'node-html-parser';
|
||||
|
||||
/**
|
||||
* Eleventy plugin to add an outline (table of contents) to the page. Headings must have an id, otherwise they won't be
|
||||
* included in the outline. An unordered list containing links will be appended to the target element.
|
||||
*
|
||||
* If no headings are found for the outline, the `ifEmpty()` function will be called with a `node-html-parser` object as
|
||||
* the first argument. This can be used to toggle classes or remove elements when the outline is empty.
|
||||
*
|
||||
* See the `node-html-parser` docs for more details: https://www.npmjs.com/package/node-html-parser
|
||||
*/
|
||||
export function outlineTransformer(options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
target: '.outline',
|
||||
selector: 'h2,h3',
|
||||
ifEmpty: () => null,
|
||||
...options,
|
||||
};
|
||||
|
||||
return function (doc) {
|
||||
const container = doc.querySelector(options.container);
|
||||
const ul = parse('<ul></ul>');
|
||||
let numLinks = 0;
|
||||
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.querySelectorAll(options.selector).forEach(heading => {
|
||||
const id = heading.getAttribute('id');
|
||||
const level = heading.tagName.slice(1);
|
||||
const clone = parse(heading.outerHTML);
|
||||
|
||||
if (heading.closest('[data-no-outline]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content
|
||||
clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove());
|
||||
clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove());
|
||||
|
||||
// Generate the link
|
||||
const li = parse(`<li data-level="${level}"><a></a></li>`);
|
||||
const a = li.querySelector('a');
|
||||
a.setAttribute('href', `#${encodeURIComponent(id)}`);
|
||||
a.textContent = clone.textContent.trim().replace(/#$/, '');
|
||||
|
||||
// Add it to the list
|
||||
ul.firstChild.appendChild(li);
|
||||
numLinks++;
|
||||
});
|
||||
|
||||
if (numLinks > 0) {
|
||||
// Append the list to all matching targets
|
||||
doc.querySelectorAll(options.target).forEach(target => {
|
||||
target.appendChild(parse(ul.outerHTML));
|
||||
});
|
||||
} else {
|
||||
// Remove if empty
|
||||
options.ifEmpty(doc);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { parse } from 'node-html-parser';
|
||||
import slugify from 'slugify';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
function createId(text) {
|
||||
let slug = slugify(String(text), {
|
||||
remove: /[^\w|\s]/g,
|
||||
lower: true,
|
||||
});
|
||||
|
||||
// ids must start with a letter
|
||||
if (!/^[a-z]/i.test(slug)) {
|
||||
slug = `wa_${slug}`;
|
||||
}
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eleventy plugin to add anchors to headings to content.
|
||||
*/
|
||||
export function anchorHeadingsPlugin(options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
headingSelector: 'h2, h3, h4, h5, h6',
|
||||
anchorLabel: 'Jump to heading',
|
||||
...options,
|
||||
};
|
||||
|
||||
return function (eleventyConfig) {
|
||||
eleventyConfig.addTransform('anchor-headings', content => {
|
||||
const doc = parse(content);
|
||||
const container = doc.querySelector(options.container);
|
||||
|
||||
if (!container) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Look for headings
|
||||
let selector = `:is(${options.headingSelector}):not([data-no-anchor], [data-no-anchor] *)`;
|
||||
container.querySelectorAll(selector).forEach(heading => {
|
||||
const hasAnchor = heading.querySelector('a');
|
||||
const existingId = heading.getAttribute('id');
|
||||
const clone = parse(heading.outerHTML);
|
||||
|
||||
// Create a clone of the heading so we can remove [data-no-anchor] elements from the text content
|
||||
clone.querySelectorAll('[data-no-anchor]').forEach(el => el.remove());
|
||||
|
||||
if (hasAnchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = existingId;
|
||||
if (!id) {
|
||||
const slug = createId(clone.textContent ?? '') ?? uuid().slice(-12);
|
||||
id = slug;
|
||||
let suffix = 1;
|
||||
|
||||
// Make sure the slug is unique in the document
|
||||
while (doc.getElementById(id) !== null) {
|
||||
id = `${slug}-${++suffix}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the anchor
|
||||
const anchor = parse(`
|
||||
<a href="#${encodeURIComponent(id)}">
|
||||
<span class="wa-visually-hidden"></span>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
`);
|
||||
anchor.querySelector('.wa-visually-hidden').textContent = options.anchorLabel;
|
||||
|
||||
// Update the heading
|
||||
if (!existingId) {
|
||||
heading.setAttribute('id', id);
|
||||
}
|
||||
heading.classList.add('anchor-heading');
|
||||
heading.appendChild(anchor);
|
||||
});
|
||||
|
||||
return doc.toString();
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { parse } from 'node-html-parser';
|
||||
|
||||
/**
|
||||
* Eleventy plugin to add copy buttons to code blocks.
|
||||
*/
|
||||
export function copyCodePlugin(eleventyConfig, options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
...options,
|
||||
};
|
||||
|
||||
let codeCount = 0;
|
||||
eleventyConfig.addTransform('copy-code', content => {
|
||||
const doc = parse(content, { blockTextElements: { code: true } });
|
||||
const container = doc.querySelector(options.container);
|
||||
|
||||
if (!container) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Look for code blocks
|
||||
container.querySelectorAll('pre > code').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
let preId = pre.getAttribute('id') || `code-block-${++codeCount}`;
|
||||
let codeId = code.getAttribute('id') || `${preId}-inner`;
|
||||
|
||||
if (!code.getAttribute('id')) {
|
||||
code.setAttribute('id', codeId);
|
||||
}
|
||||
if (!pre.getAttribute('id')) {
|
||||
pre.setAttribute('id', preId);
|
||||
}
|
||||
|
||||
// Add a copy button
|
||||
pre.innerHTML += `<wa-copy-button from="${codeId}" class="copy-button wa-dark"></wa-copy-button>`;
|
||||
});
|
||||
|
||||
return doc.toString();
|
||||
});
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { parse } from 'node-html-parser';
|
||||
|
||||
/**
|
||||
* Eleventy plugin to add an outline (table of contents) to the page. Headings must have an id, otherwise they won't be
|
||||
* included in the outline. An unordered list containing links will be appended to the target element.
|
||||
*
|
||||
* If no headings are found for the outline, the `ifEmpty()` function will be called with a `node-html-parser` object as
|
||||
* the first argument. This can be used to toggle classes or remove elements when the outline is empty.
|
||||
*
|
||||
* See the `node-html-parser` docs for more details: https://www.npmjs.com/package/node-html-parser
|
||||
*/
|
||||
export function outlinePlugin(options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
target: '.outline',
|
||||
selector: 'h2,h3',
|
||||
ifEmpty: () => null,
|
||||
...options,
|
||||
};
|
||||
|
||||
return function (eleventyConfig) {
|
||||
eleventyConfig.addTransform('outline', content => {
|
||||
const doc = parse(content);
|
||||
const container = doc.querySelector(options.container);
|
||||
const ul = parse('<ul></ul>');
|
||||
let numLinks = 0;
|
||||
|
||||
if (!container) {
|
||||
return content;
|
||||
}
|
||||
|
||||
container.querySelectorAll(options.selector).forEach(heading => {
|
||||
const id = heading.getAttribute('id');
|
||||
const level = heading.tagName.slice(1);
|
||||
const clone = parse(heading.outerHTML);
|
||||
|
||||
if (heading.closest('[data-no-outline]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content
|
||||
clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove());
|
||||
clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove());
|
||||
|
||||
// Generate the link
|
||||
const li = parse(`<li data-level="${level}"><a></a></li>`);
|
||||
const a = li.querySelector('a');
|
||||
a.setAttribute('href', `#${encodeURIComponent(id)}`);
|
||||
a.textContent = clone.textContent.trim().replace(/#$/, '');
|
||||
|
||||
// Add it to the list
|
||||
ul.firstChild.appendChild(li);
|
||||
numLinks++;
|
||||
});
|
||||
|
||||
if (numLinks > 0) {
|
||||
// Append the list to all matching targets
|
||||
doc.querySelectorAll(options.target).forEach(target => {
|
||||
target.appendChild(parse(ul.outerHTML));
|
||||
});
|
||||
} else {
|
||||
// Remove if empty
|
||||
options.ifEmpty(doc);
|
||||
}
|
||||
|
||||
return doc.toString();
|
||||
});
|
||||
};
|
||||
}
|
||||
18
packages/webawesome/docs/_utils/simulate-webawesome-app.js
Normal file
18
packages/webawesome/docs/_utils/simulate-webawesome-app.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import nunjucks from 'nunjucks';
|
||||
|
||||
/**
|
||||
* This function simulates what a server would do running "on top" of eleventy.
|
||||
*/
|
||||
export function SimulateWebAwesomeApp(str) {
|
||||
return nunjucks.renderString(str, {
|
||||
// Stub the server EJS shortcodes.
|
||||
currentUser: {
|
||||
hasPro: false,
|
||||
},
|
||||
server: {
|
||||
head: '',
|
||||
loginOrAvatar: '',
|
||||
flashes: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -55,7 +55,7 @@ it is rarely a good idea to mix sizes within the same button group.
|
||||
Set the `orientation` attribute to `vertical` to make a vertical button group.
|
||||
|
||||
```html {.example}
|
||||
<wa-button-group orientation="vertical" label="Options" style="max-width: 120px;">
|
||||
<wa-button-group orientation="vertical" label="Options">
|
||||
<wa-button>
|
||||
<wa-icon slot="start" name="plus"></wa-icon>
|
||||
New
|
||||
|
||||
@@ -251,4 +251,4 @@ This example demonstrates how to style buttons using a custom class. This is the
|
||||
outline-offset: 4px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
```
|
||||
@@ -90,6 +90,7 @@ Copy buttons can be disabled by adding the `disabled` attribute.
|
||||
|
||||
A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute.
|
||||
|
||||
|
||||
```html {.example}
|
||||
<wa-copy-button value="Web Awesome rocks!" feedback-duration="250"></wa-copy-button>
|
||||
```
|
||||
@@ -134,4 +135,4 @@ You can customize the button to your liking with CSS.
|
||||
outline-offset: 4px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
```
|
||||
@@ -23,6 +23,9 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh
|
||||
- Fixed a bug in `<wa-slider>` that prevented the hint from showing up [discuss:1172]
|
||||
- Fixed a bug in `<wa-textarea>` where setting `resize="auto"` caused the height of the textarea to double [issue:1155]
|
||||
- Fixed a bug in `<wa-color-picker>`, `<wa-checkbox>`, `<wa-input>`, `<wa-radio-group>`, `<wa-switch>`, and `<wa-textarea>` that prevented screen readers from announcing hints [issue:1186]
|
||||
- Fixed a bug in `<wa-card>` that caused slotted media to have incorrectly rounded corners [issue:1107]
|
||||
- Fixed a bug in `<wa-button-group>` that prevented pill buttons from rendering corners properly [issue:1165]
|
||||
- Fixed a bug in `<wa-button-group>` that caused some vertical groups to appear horizontal [issue:1152]
|
||||
|
||||
## 3.0.0-beta.2
|
||||
|
||||
|
||||
@@ -4,19 +4,20 @@ import { execSync } from 'child_process';
|
||||
import { deleteAsync } from 'del';
|
||||
import esbuild from 'esbuild';
|
||||
import { replace } from 'esbuild-plugin-replace';
|
||||
|
||||
import { mkdir, readFile } from 'fs/promises';
|
||||
import getPort, { portNumbers } from 'get-port';
|
||||
import { globby } from 'globby';
|
||||
import { dirname, join, posix, relative } from 'node:path';
|
||||
import { dirname, extname, join, posix, relative } from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ora from 'ora';
|
||||
import copy from 'recursive-copy';
|
||||
import { getCdnDir, getDistDir, getDocsDir, getRootDir, getSiteDir, runScript } from './utils.js';
|
||||
import { SimulateWebAwesomeApp } from '../docs/_utils/simulate-webawesome-app.js';
|
||||
import { generateDocs } from './docs.js';
|
||||
import { getCdnDir, getDistDir, getDocsDir, getRootDir, getSiteDir } from './utils.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const isDeveloping = process.argv.includes('--develop');
|
||||
|
||||
const spinner = ora({ text: 'Web Awesome', color: 'cyan' }).start();
|
||||
const getPackageData = async () => JSON.parse(await readFile(join(getRootDir(), 'package.json'), 'utf-8'));
|
||||
const getVersion = async () => JSON.stringify((await getPackageData()).version.toString());
|
||||
@@ -25,6 +26,10 @@ let buildContexts = {
|
||||
unbundledContext: {},
|
||||
};
|
||||
|
||||
const debugPerf = process.env.DEBUG_PERFORMANCE === '1';
|
||||
|
||||
const isDeveloping = process.argv.includes('--develop');
|
||||
|
||||
/**
|
||||
* @typedef {Object} BuildOptions
|
||||
* @property {Array<string>} [watchedSrcDirectories]
|
||||
@@ -44,6 +49,8 @@ export async function build(options = {}) {
|
||||
options.watchedDocsDirectories = [getDocsDir()];
|
||||
}
|
||||
|
||||
function measureStep() {}
|
||||
|
||||
/**
|
||||
* Runs the full build.
|
||||
*/
|
||||
@@ -51,17 +58,24 @@ export async function build(options = {}) {
|
||||
const start = Date.now();
|
||||
|
||||
try {
|
||||
await cleanup();
|
||||
await generateManifest();
|
||||
await generateReactWrappers();
|
||||
await generateTypes();
|
||||
await generateStyles();
|
||||
const steps = [cleanup, generateManifest, generateReactWrappers, generateTypes, generateStyles];
|
||||
|
||||
for (const step of steps) {
|
||||
if (debugPerf) {
|
||||
const stepStart = Date.now();
|
||||
await step();
|
||||
const elapsedTime = (Date.now() - stepStart) / 1000 + 's';
|
||||
spinner.succeed(`${step.name}: ${elapsedTime}`);
|
||||
} else {
|
||||
await step();
|
||||
}
|
||||
}
|
||||
|
||||
// copy everything to unbundled before we generate bundles.
|
||||
await copy(getCdnDir(), getDistDir(), { overwrite: true });
|
||||
|
||||
await generateBundle();
|
||||
await generateDocs();
|
||||
await generateDocs({ spinner });
|
||||
|
||||
const time = (Date.now() - start) / 1000 + 's';
|
||||
spinner.succeed(`The build is complete ${chalk.gray(`(finished in ${time})`)}`);
|
||||
@@ -258,49 +272,6 @@ export async function build(options = {}) {
|
||||
spinner.succeed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the documentation site.
|
||||
*/
|
||||
async function generateDocs() {
|
||||
/**
|
||||
* Used by the webawesome-app to skip doc generation since it will do its own.
|
||||
*/
|
||||
if (process.env.SKIP_ELEVENTY === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
spinner.start('Writing the docs');
|
||||
|
||||
const args = [];
|
||||
if (isDeveloping) args.push('--develop');
|
||||
|
||||
let output;
|
||||
try {
|
||||
// 11ty
|
||||
output = (await runScript(join(__dirname, 'docs.js'), args, { env: process.env }))
|
||||
// Cleanup the output
|
||||
.replace('[11ty]', '')
|
||||
.replace(' seconds', 's')
|
||||
.replace(/\(.*?\)/, '')
|
||||
.toLowerCase()
|
||||
.trim();
|
||||
|
||||
// Copy dist (production only)
|
||||
if (!isDeveloping) {
|
||||
await copy(getCdnDir(), join(getSiteDir(), 'dist'));
|
||||
}
|
||||
|
||||
spinner.succeed(`Writing the docs ${chalk.gray(`(${output}`)})`);
|
||||
} catch (error) {
|
||||
console.error('\n\n' + chalk.red(error) + '\n');
|
||||
|
||||
spinner.fail(chalk.red(`Error while writing the docs.`));
|
||||
if (!isDeveloping) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initial build
|
||||
await buildAll();
|
||||
|
||||
@@ -338,6 +309,46 @@ export async function build(options = {}) {
|
||||
'/dist/': './dist-cdn/',
|
||||
},
|
||||
},
|
||||
middleware: [
|
||||
function simulateWebawesomeApp(req, res, next) {
|
||||
// Accumulator for strings so we can pass them through nunjucks a second time similar to how the webawesome-app
|
||||
// will be running nunjucks twice.
|
||||
const finalString = [];
|
||||
const encoding = 'utf-8';
|
||||
|
||||
if (!next) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.url) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const extension = extname(req.url);
|
||||
if (extension !== '' && extension !== '.html') {
|
||||
// Assume its something like .svg / .png / .css etc. that we don't want to transform.
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const _write = res.write;
|
||||
|
||||
res.write = function (chunk, encoding) {
|
||||
// Buffer chunks into an array so that we do a single transform.
|
||||
finalString.push(chunk.toString());
|
||||
};
|
||||
|
||||
const _end = res.end;
|
||||
res.end = function (...args) {
|
||||
const transformedStr = SimulateWebAwesomeApp(finalString.join(''));
|
||||
_write.call(res, transformedStr, encoding);
|
||||
_end.call(res, ...args);
|
||||
};
|
||||
|
||||
next();
|
||||
},
|
||||
],
|
||||
callbacks: {
|
||||
ready: (_err, instance) => {
|
||||
// 404 errors
|
||||
@@ -397,7 +408,6 @@ export async function build(options = {}) {
|
||||
if (typeof options.onWatchEvent === 'function') {
|
||||
await options.onWatchEvent(evt, filename);
|
||||
}
|
||||
await regenerateBundle();
|
||||
|
||||
// Copy stylesheets when CSS files change
|
||||
if (isCssStylesheet) {
|
||||
@@ -409,8 +419,12 @@ export async function build(options = {}) {
|
||||
await generateManifest();
|
||||
}
|
||||
|
||||
// copy everything to unbundled before we generate bundles.
|
||||
await copy(getCdnDir(), getDistDir(), { overwrite: true });
|
||||
await regenerateBundle();
|
||||
|
||||
// This needs to be outside of "isComponent" check because SSR needs to run on CSS files too.
|
||||
await generateDocs();
|
||||
await generateDocs({ spinner });
|
||||
|
||||
reload();
|
||||
} catch (err) {
|
||||
@@ -438,7 +452,7 @@ export async function build(options = {}) {
|
||||
if (typeof options.onWatchEvent === 'function') {
|
||||
await options.onWatchEvent(evt, filename);
|
||||
}
|
||||
await generateDocs();
|
||||
await generateDocs({ spinner });
|
||||
reload();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,257 @@
|
||||
import Eleventy from '@11ty/eleventy';
|
||||
|
||||
import copy from 'recursive-copy';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { deleteAsync } from 'del';
|
||||
import { getDocsDir, getEleventyConfigPath, getSiteDir } from './utils.js';
|
||||
import { join } from 'path';
|
||||
import { getCdnDir, getDocsDir, getEleventyConfigPath, getSiteDir } from './utils.js';
|
||||
|
||||
const elev = new Eleventy(getDocsDir(), getSiteDir(), {
|
||||
quietMode: true,
|
||||
configPath: getEleventyConfigPath(),
|
||||
});
|
||||
let eleventyBuildResolver;
|
||||
let eleventyBuildPromise;
|
||||
|
||||
// Cleanup
|
||||
await deleteAsync(getSiteDir());
|
||||
function queueBuild() {
|
||||
eleventyBuildPromise = new Promise(resolve => {
|
||||
eleventyBuildResolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
// Write it
|
||||
await elev.write();
|
||||
// 11ty
|
||||
export async function createEleventy(options = {}) {
|
||||
let { isIncremental, isDeveloping, rootDir } = options;
|
||||
|
||||
isDeveloping ??= process.argv.includes('--develop');
|
||||
isIncremental ??= isDeveloping && !process.argv.includes('--no-incremental');
|
||||
|
||||
const eleventy = new Eleventy(rootDir || getDocsDir(), getSiteDir(), {
|
||||
quietMode: true,
|
||||
configPath: getEleventyConfigPath(),
|
||||
config: eleventyConfig => {
|
||||
if (isDeveloping || isIncremental) {
|
||||
eleventyConfig.setUseTemplateCache(false);
|
||||
|
||||
eleventyConfig.on('eleventy.before', function () {
|
||||
queueBuild();
|
||||
});
|
||||
eleventyConfig.on('eleventy.beforeWatch', async function () {
|
||||
queueBuild();
|
||||
});
|
||||
eleventyConfig.on('eleventy.after', async function () {
|
||||
eleventyBuildResolver();
|
||||
});
|
||||
}
|
||||
},
|
||||
source: 'script',
|
||||
runMode: isIncremental ? 'watch' : 'build',
|
||||
});
|
||||
eleventy.setIncrementalBuild(isIncremental);
|
||||
|
||||
await eleventy.init();
|
||||
|
||||
eleventy.logger.isChalkEnabled = false;
|
||||
eleventy.logger.overrideLogger(new CustomLogger());
|
||||
|
||||
if (isIncremental) {
|
||||
await eleventy.watch();
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
await eleventy.stopWatch();
|
||||
process.exitCode = 0;
|
||||
});
|
||||
}
|
||||
|
||||
return eleventy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the documentation site.
|
||||
*/
|
||||
export async function generateDocs(options = {}) {
|
||||
let { spinner, isIncremental, isDeveloping } = options;
|
||||
|
||||
isDeveloping ??= process.argv.includes('--develop');
|
||||
isIncremental ??= isDeveloping && !process.argv.includes('--no-incremental');
|
||||
|
||||
let eleventy = globalThis.eleventy;
|
||||
/**
|
||||
* Used by the webawesome-app to skip doc generation since it will do its own.
|
||||
*/
|
||||
if (process.env.SKIP_ELEVENTY === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
spinner?.start?.('Writing the docs');
|
||||
|
||||
const outputs = {
|
||||
warn: [],
|
||||
};
|
||||
|
||||
function stubConsole(key) {
|
||||
const originalFn = console[key];
|
||||
console[key] = function (...args) {
|
||||
outputs[key].push(...args);
|
||||
};
|
||||
return originalFn;
|
||||
}
|
||||
|
||||
// Works around a bug in 11ty where it still prints warnings despite the logger being overriden and in quietMode.
|
||||
const originalWarn = stubConsole('warn');
|
||||
|
||||
let output = '';
|
||||
|
||||
try {
|
||||
if (isIncremental) {
|
||||
if (!globalThis.eleventy) {
|
||||
// First run
|
||||
globalThis.eleventy = await createEleventy(options);
|
||||
eleventy = globalThis.eleventy;
|
||||
output = chalk.gray(`(${eleventy.logFinished()})`);
|
||||
} else {
|
||||
// eleventy incremental does its own writing, so we just kinda trust it for right now.
|
||||
eleventy = globalThis.eleventy;
|
||||
|
||||
await eleventyBuildPromise;
|
||||
let info = eleventy.logger.logger.outputs.log;
|
||||
|
||||
// TODO: The first write with incremental seems to be 1 behind. Not sure why. But its good enough for now.
|
||||
info = info.filter(line => {
|
||||
return !line.includes('Watching');
|
||||
});
|
||||
const lastLine = info[info.length - 1];
|
||||
output = chalk.gray(`(${lastLine})`);
|
||||
eleventy.logger.logger.reset();
|
||||
}
|
||||
} else {
|
||||
// Cleanup
|
||||
await deleteAsync(getSiteDir());
|
||||
|
||||
globalThis.eleventy = await createEleventy(options);
|
||||
eleventy = globalThis.eleventy;
|
||||
|
||||
// Write it
|
||||
await eleventy.write();
|
||||
output = chalk.gray(`(${eleventy.logFinished()})`);
|
||||
}
|
||||
|
||||
// Copy dist (production only)
|
||||
if (!isDeveloping) {
|
||||
await copy(getCdnDir(), join(getSiteDir(), 'dist'));
|
||||
}
|
||||
spinner?.succeed?.(`Writing the docs ${output}`);
|
||||
} catch (error) {
|
||||
console.warn = originalWarn;
|
||||
|
||||
console.error('\n\n' + chalk.red(error) + '\n');
|
||||
|
||||
spinner?.fail?.(chalk.red(`Error while writing the docs.`));
|
||||
if (!isDeveloping) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Much of this code is taken from 11ty's ConsoleLogger here:
|
||||
* https://github.com/11ty/eleventy/blob/main/src/Util/ConsoleLogger.js
|
||||
*
|
||||
* Patches 11ty logger so it doesnt log everything, but we can still use its output for our own build.
|
||||
* @typedef {'error'|'log'|'warn'|'info'} LogType
|
||||
*/
|
||||
class CustomLogger {
|
||||
#outputStream;
|
||||
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
flush() {
|
||||
Object.keys(this.outputs).forEach(outputType => {
|
||||
console[outputType](this.outputs[outputType].join(''));
|
||||
});
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.outputs = {
|
||||
log: [],
|
||||
info: [],
|
||||
warn: [],
|
||||
error: [],
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
log(msg) {
|
||||
this.message(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef LogOptions
|
||||
* @property {string} message
|
||||
* @property {string=} prefix
|
||||
* @property {LogType=} type
|
||||
* @property {string=} color
|
||||
* @property {boolean=} force
|
||||
* @param {LogOptions} options
|
||||
*/
|
||||
logWithOptions({ message, type, prefix, color, force }) {
|
||||
this.message(message, type, color, force, prefix);
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
forceLog(msg) {
|
||||
this.message(msg, undefined, undefined, true);
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
info(msg) {
|
||||
this.message(msg, 'info', 'blue');
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
warn(msg) {
|
||||
this.message(msg, 'warn', 'yellow');
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
error(msg) {
|
||||
this.message(msg, 'error', 'red');
|
||||
}
|
||||
|
||||
get outputStream() {
|
||||
if (!this.#outputStream) {
|
||||
this.#outputStream = new Readable({
|
||||
read() {},
|
||||
});
|
||||
}
|
||||
return this.#outputStream;
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
toStream(msg) {
|
||||
this.outputStream.push(msg);
|
||||
}
|
||||
|
||||
closeStream() {
|
||||
this.outputStream.push(null);
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the message to log.
|
||||
*
|
||||
* @param {string} message - The raw message to log.
|
||||
* @param {LogType} [type='log'] - The error level to log.
|
||||
* @param {string|undefined} [chalkColor=undefined] - Color name or falsy to disable
|
||||
* @param {boolean} [forceToConsole=false] - Enforce a log on console instead of specified target.
|
||||
*/
|
||||
message(message, type = 'log', chalkColor = undefined, _forceToConsole = false, prefix = '') {
|
||||
// if (chalkColor && this.isChalkEnabled) {
|
||||
// message = `${chalk.gray(prefix)} ${message.split("\n").join(`\n${chalk.gray(prefix)} `)}`;
|
||||
// this.outputs[type].push(chalk[chalkColor](message));
|
||||
// } else {
|
||||
message = `${prefix}${message.split('\n').join(`\n${prefix}`)}`;
|
||||
this.outputs[type].push(message);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
z-index: 2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
:host([orientation='vertical']) {
|
||||
:host([orientation='vertical']) .button-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,10 @@ export default class WaButtonGroup extends WebAwesomeElement {
|
||||
return html`
|
||||
<slot
|
||||
part="base"
|
||||
class=${classMap({ 'button-group': true, 'has-outlined': this.hasOutlined })}
|
||||
class=${classMap({
|
||||
'button-group': true,
|
||||
'has-outlined': this.hasOutlined,
|
||||
})}
|
||||
role="${this.disableRole ? 'presentation' : 'group'}"
|
||||
aria-label=${this.label}
|
||||
aria-orientation=${this.orientation}
|
||||
|
||||
@@ -309,22 +309,22 @@ slot[name='end']::slotted(*),
|
||||
}
|
||||
|
||||
/* Handle pill modifier for button groups */
|
||||
:host([pill]) .wa-button-group__horizontal.wa-button-group__button-first {
|
||||
:host([pill].wa-button-group__horizontal.wa-button-group__button-first) .button {
|
||||
border-start-start-radius: var(--wa-border-radius-pill);
|
||||
border-end-start-radius: var(--wa-border-radius-pill);
|
||||
}
|
||||
|
||||
:host([pill]) .wa-button-group__horizontal.wa-button-group__button-last {
|
||||
:host([pill].wa-button-group__horizontal.wa-button-group__button-last) .button {
|
||||
border-start-end-radius: var(--wa-border-radius-pill);
|
||||
border-end-end-radius: var(--wa-border-radius-pill);
|
||||
}
|
||||
|
||||
:host([pill]) .wa-button-group__vertical.wa-button-group__button-first {
|
||||
:host([pill].wa-button-group__vertical.wa-button-group__button-first) .button {
|
||||
border-start-start-radius: var(--wa-border-radius-pill);
|
||||
border-start-end-radius: var(--wa-border-radius-pill);
|
||||
}
|
||||
|
||||
:host([pill]) .wa-button-group__vertical.wa-button-group__button-last {
|
||||
:host([pill].wa-button-group__vertical.wa-button-group__button-last) .button {
|
||||
border-end-start-radius: var(--wa-border-radius-pill);
|
||||
border-end-end-radius: var(--wa-border-radius-pill);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
&::slotted(*) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user