mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(site): API reference pages for media elements (#1342)
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Centralized media element API subsection definitions.
|
||||
*
|
||||
* Mirrors componentReferenceModel.js for media elements. Produces heading/id
|
||||
* data consumed by both MediaReference.astro and remarkConditionalHeadings.
|
||||
*/
|
||||
|
||||
const MEDIA_REFERENCE_SUBSECTIONS = Object.freeze([
|
||||
{
|
||||
key: 'hostProperties',
|
||||
title: 'Host Properties',
|
||||
id: 'host-properties',
|
||||
isEmpty: (ref) => Object.keys(ref.hostProperties ?? {}).length === 0,
|
||||
},
|
||||
{
|
||||
key: 'nativeAttributes',
|
||||
title: 'Attributes',
|
||||
id: 'attributes',
|
||||
isEmpty: (ref) => (ref.nativeAttributes ?? []).length === 0,
|
||||
},
|
||||
{
|
||||
key: 'events',
|
||||
title: 'Events',
|
||||
id: 'events',
|
||||
isEmpty: (ref) => (ref.events?.native ?? []).length === 0 && (ref.events?.elementSpecific ?? []).length === 0,
|
||||
},
|
||||
{
|
||||
key: 'methods',
|
||||
title: 'Methods',
|
||||
id: 'methods',
|
||||
isEmpty: (ref) => (ref.methods ?? []).length === 0,
|
||||
},
|
||||
{
|
||||
key: 'cssCustomProperties',
|
||||
title: 'CSS Custom Properties',
|
||||
id: 'css-custom-properties',
|
||||
isEmpty: (ref) => Object.keys(ref.cssCustomProperties ?? {}).length === 0,
|
||||
},
|
||||
]);
|
||||
|
||||
export function createMediaReferenceModel(mediaName, ref) {
|
||||
if (!ref) return null;
|
||||
|
||||
const sections = MEDIA_REFERENCE_SUBSECTIONS.flatMap((definition) => {
|
||||
if (definition.isEmpty(ref)) return [];
|
||||
return [
|
||||
{
|
||||
key: definition.key,
|
||||
title: definition.title,
|
||||
id: definition.id,
|
||||
depth: 3,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
return {
|
||||
mediaName,
|
||||
heading: {
|
||||
id: 'api-reference',
|
||||
depth: 2,
|
||||
text: 'API Reference',
|
||||
},
|
||||
sections,
|
||||
data: ref,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMediaReferenceTocHeadings(model) {
|
||||
if (!model) return [];
|
||||
|
||||
const headings = [
|
||||
{
|
||||
depth: model.heading.depth,
|
||||
text: model.heading.text,
|
||||
slug: model.heading.id,
|
||||
},
|
||||
];
|
||||
|
||||
for (const section of model.sections) {
|
||||
headings.push({
|
||||
depth: section.depth,
|
||||
text: section.title,
|
||||
slug: section.id,
|
||||
});
|
||||
}
|
||||
|
||||
return headings;
|
||||
}
|
||||
@@ -5,12 +5,14 @@ import { kebabCase } from 'es-toolkit/string';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { buildComponentReferenceTocHeadings, createComponentReferenceModel } from './componentReferenceModel';
|
||||
import { buildFeatureReferenceTocHeadings, createFeatureReferenceModel } from './featureReferenceModel';
|
||||
import { buildMediaReferenceTocHeadings, createMediaReferenceModel } from './mediaReferenceModel';
|
||||
import { buildUtilReferenceTocHeadings, createUtilReferenceModel } from './utilReferenceModel';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const COMPONENT_REF_DIR = path.resolve(__dirname, '../content/generated-component-reference');
|
||||
const FEATURE_REF_DIR = path.resolve(__dirname, '../content/generated-feature-reference');
|
||||
const UTIL_REF_DIR = path.resolve(__dirname, '../content/generated-util-reference');
|
||||
const MEDIA_REF_DIR = path.resolve(__dirname, '../content/generated-media-reference');
|
||||
|
||||
function readComponentRefJson(componentName) {
|
||||
const kebab = kebabCase(componentName);
|
||||
@@ -72,6 +74,9 @@ export default function remarkConditionalHeadings() {
|
||||
} else if (node.name === 'UtilReference') {
|
||||
injectUtilReferenceHeadings(node, headingsWithMetadata, reservedSlugs);
|
||||
return;
|
||||
} else if (node.name === 'MediaReference') {
|
||||
injectMediaReferenceHeadings(node, headingsWithMetadata, reservedSlugs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +212,32 @@ function injectUtilReferenceHeadings(node, headingsWithMetadata, reservedSlugs)
|
||||
}
|
||||
}
|
||||
|
||||
function readMediaRefJson(tagName) {
|
||||
const filePath = path.join(MEDIA_REF_DIR, `${tagName}.json`);
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function injectMediaReferenceHeadings(node, headingsWithMetadata, reservedSlugs) {
|
||||
const mediaAttr = node.attributes?.find((a) => a.name === 'media');
|
||||
const mediaName = typeof mediaAttr?.value === 'string' ? mediaAttr.value : null;
|
||||
if (!mediaName) return;
|
||||
|
||||
const json = readMediaRefJson(kebabCase(mediaName));
|
||||
if (!json) return;
|
||||
|
||||
const mediaModel = createMediaReferenceModel(mediaName, json);
|
||||
const mediaHeadings = buildMediaReferenceTocHeadings(mediaModel);
|
||||
|
||||
headingsWithMetadata.push(...mediaHeadings);
|
||||
for (const heading of mediaHeadings) {
|
||||
reservedSlugs.add(heading.slug);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract array value from JSX attribute like frameworks={["react", "html"]}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// @ts-nocheck — the model is plain JS shared with remarkConditionalHeadings
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildMediaReferenceTocHeadings, createMediaReferenceModel } from '../mediaReferenceModel';
|
||||
|
||||
function makeRef(overrides = {}) {
|
||||
return {
|
||||
name: 'HlsVideo',
|
||||
tagName: 'hls-video',
|
||||
hostProperties: {
|
||||
src: { type: 'string', readonly: false },
|
||||
streamType: { type: 'string', readonly: true },
|
||||
},
|
||||
nativeAttributes: ['src', 'autoplay', 'controls', 'loop', 'muted', 'playsinline', 'poster'],
|
||||
events: {
|
||||
native: ['play', 'pause'],
|
||||
elementSpecific: [{ name: 'streamtypechange', description: 'Fired when the stream type changes.' }],
|
||||
},
|
||||
methods: ['canPlayType', 'load', 'pause', 'play', 'requestFullscreen'],
|
||||
cssCustomProperties: { '--media-object-fit': { description: 'Object fit.' } },
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('createMediaReferenceModel', () => {
|
||||
it('returns null without a reference', () => {
|
||||
expect(createMediaReferenceModel('HlsVideo', null)).toBeNull();
|
||||
});
|
||||
|
||||
it('titles the attributes section "Attributes"', () => {
|
||||
const model = createMediaReferenceModel('HlsVideo', makeRef());
|
||||
const attrs = model.sections.find((s) => s.key === 'nativeAttributes');
|
||||
expect(attrs).toMatchObject({ title: 'Attributes', id: 'attributes' });
|
||||
});
|
||||
|
||||
it('drops sections with no data', () => {
|
||||
const model = createMediaReferenceModel('HlsVideo', makeRef({ hostProperties: {} }));
|
||||
const keys = model.sections.map((s) => s.key);
|
||||
expect(keys).not.toContain('hostProperties');
|
||||
});
|
||||
|
||||
it('keeps the events section when only native events exist', () => {
|
||||
const model = createMediaReferenceModel(
|
||||
'DashVideo',
|
||||
makeRef({ events: { native: ['play'], elementSpecific: [] } })
|
||||
);
|
||||
expect(model.sections.some((s) => s.key === 'events')).toBe(true);
|
||||
});
|
||||
|
||||
it('includes a methods section after events when methods exist', () => {
|
||||
const model = createMediaReferenceModel('HlsVideo', makeRef());
|
||||
const keys = model.sections.map((s) => s.key);
|
||||
expect(keys).toContain('methods');
|
||||
expect(keys.indexOf('methods')).toBeGreaterThan(keys.indexOf('events'));
|
||||
expect(keys.indexOf('methods')).toBeLessThan(keys.indexOf('cssCustomProperties'));
|
||||
const methods = model.sections.find((s) => s.key === 'methods');
|
||||
expect(methods).toMatchObject({ title: 'Methods', id: 'methods' });
|
||||
});
|
||||
|
||||
it('drops the methods section when there are no methods', () => {
|
||||
const model = createMediaReferenceModel('HlsVideo', makeRef({ methods: [] }));
|
||||
expect(model.sections.some((s) => s.key === 'methods')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildMediaReferenceTocHeadings', () => {
|
||||
it('returns empty for a null model', () => {
|
||||
expect(buildMediaReferenceTocHeadings(null)).toEqual([]);
|
||||
});
|
||||
|
||||
it('emits the API Reference heading followed by section headings', () => {
|
||||
const model = createMediaReferenceModel('HlsVideo', makeRef());
|
||||
const headings = buildMediaReferenceTocHeadings(model);
|
||||
expect(headings[0]).toEqual({ depth: 2, text: 'API Reference', slug: 'api-reference' });
|
||||
expect(headings).toContainEqual({ depth: 3, text: 'Attributes', slug: 'attributes' });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user