frontend: add webcomponent documentation generation and update command model annotations
This commit is contained in:
parent
b92aec9d30
commit
3530e557c4
@ -2,6 +2,7 @@ import { inject } from '@angular/core';
|
|||||||
import { ActivatedRouteSnapshot, CanActivateFn } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivateFn } from '@angular/router';
|
||||||
import { ErrorReason } from '@lib/models';
|
import { ErrorReason } from '@lib/models';
|
||||||
import { NavigationService, ParticipantTokenService, RoomService, SessionStorageService } from '@lib/services';
|
import { NavigationService, ParticipantTokenService, RoomService, SessionStorageService } from '@lib/services';
|
||||||
|
import { WebComponentProperty } from '@lib/typings/ce/webcomponent/properties.model';
|
||||||
|
|
||||||
export const extractRoomQueryParamsGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
export const extractRoomQueryParamsGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
||||||
const navigationService = inject(NavigationService);
|
const navigationService = inject(NavigationService);
|
||||||
@ -52,12 +53,12 @@ export const extractRecordingQueryParamsGuard: CanActivateFn = (route: Activated
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractParams = (route: ActivatedRouteSnapshot) => ({
|
const extractParams = ({ params, queryParams }: ActivatedRouteSnapshot) => ({
|
||||||
roomId: route.params['room-id'],
|
roomId: params['room-id'],
|
||||||
participantName: route.queryParams['participant-name'],
|
participantName: queryParams[WebComponentProperty.PARTICIPANT_NAME],
|
||||||
secret: route.queryParams['secret'],
|
secret: queryParams['secret'],
|
||||||
leaveRedirectUrl: route.queryParams['leave-redirect-url'],
|
leaveRedirectUrl: queryParams[WebComponentProperty.LEAVE_REDIRECT_URL],
|
||||||
showOnlyRecordings: route.queryParams['show-only-recordings']
|
showOnlyRecordings: queryParams[WebComponentProperty.SHOW_ONLY_RECORDINGS] || 'false'
|
||||||
});
|
});
|
||||||
|
|
||||||
const isValidUrl = (url: string) => {
|
const isValidUrl = (url: string) => {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { CommandsManager } from './CommandsManager';
|
|||||||
import { EventsManager } from './EventsManager';
|
import { EventsManager } from './EventsManager';
|
||||||
import { WebComponentEvent } from '../typings/ce/event.model';
|
import { WebComponentEvent } from '../typings/ce/event.model';
|
||||||
import styles from '../assets/css/styles.css';
|
import styles from '../assets/css/styles.css';
|
||||||
|
import { WebComponentProperty } from '../typings/ce/properties.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `OpenViduMeet` web component provides an interface for embedding an OpenVidu Meet room within a web page.
|
* The `OpenViduMeet` web component provides an interface for embedding an OpenVidu Meet room within a web page.
|
||||||
@ -134,9 +135,12 @@ export class OpenViduMeet extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateIframeSrc() {
|
private updateIframeSrc() {
|
||||||
const baseUrl = this.getAttribute('room-url') || this.getAttribute('recording-url');
|
const baseUrl =
|
||||||
|
this.getAttribute(WebComponentProperty.ROOM_URL) || this.getAttribute(WebComponentProperty.RECORDING_URL);
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
console.error('The "room-url" or "recording-url" attribute is required.');
|
console.error(
|
||||||
|
`The "${WebComponentProperty.ROOM_URL}" or "${WebComponentProperty.RECORDING_URL}" attribute is required.`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +151,7 @@ export class OpenViduMeet extends HTMLElement {
|
|||||||
|
|
||||||
// Update query params
|
// Update query params
|
||||||
Array.from(this.attributes).forEach((attr) => {
|
Array.from(this.attributes).forEach((attr) => {
|
||||||
if (attr.name !== 'room-url' && attr.name !== 'recording-url') {
|
if (attr.name !== WebComponentProperty.ROOM_URL && attr.name !== WebComponentProperty.RECORDING_URL) {
|
||||||
url.searchParams.set(attr.name, attr.value);
|
url.searchParams.set(attr.name, attr.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
33
frontend/webcomponent/src/typings/ce/properties.model.ts
Normal file
33
frontend/webcomponent/src/typings/ce/properties.model.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* THIS HEADER IS AUTOGENERATED. DO NOT MODIFY MANUALLY.
|
||||||
|
* ! For any changes, please update the '/openvidu-meet/typings' directory.
|
||||||
|
**/
|
||||||
|
|
||||||
|
export enum WebComponentProperty {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base URL of the OpenVidu Meet room.
|
||||||
|
* @required This attribute is required unless `recording-url` is provided.
|
||||||
|
*/
|
||||||
|
ROOM_URL = 'room-url',
|
||||||
|
/**
|
||||||
|
* The URL of a recording to view.
|
||||||
|
* @required This attribute is required if `room-url` is not provided.
|
||||||
|
*/
|
||||||
|
RECORDING_URL = 'recording-url',
|
||||||
|
/**
|
||||||
|
* Display name for the local participant.
|
||||||
|
*/
|
||||||
|
PARTICIPANT_NAME = 'participant-name',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL to redirect to when leaving the meeting.
|
||||||
|
* Redirection occurs after the `CLOSED` event fires.
|
||||||
|
*/
|
||||||
|
LEAVE_REDIRECT_URL = 'leave-redirect-url',
|
||||||
|
/**
|
||||||
|
* Whether to show only recordings instead of live meetings.
|
||||||
|
*/
|
||||||
|
SHOW_ONLY_RECORDINGS = 'show-only-recordings'
|
||||||
|
}
|
||||||
21
prepare.sh
21
prepare.sh
@ -14,6 +14,7 @@ BUILD_FRONTEND=false
|
|||||||
BUILD_BACKEND=false
|
BUILD_BACKEND=false
|
||||||
BUILD_WEBCOMPONENT=false
|
BUILD_WEBCOMPONENT=false
|
||||||
BUILD_TESTAPP=false
|
BUILD_TESTAPP=false
|
||||||
|
BUILD_WC_DOC=false
|
||||||
|
|
||||||
# Function to display help
|
# Function to display help
|
||||||
show_help() {
|
show_help() {
|
||||||
@ -25,12 +26,14 @@ show_help() {
|
|||||||
echo " --backend Build backend"
|
echo " --backend Build backend"
|
||||||
echo " --webcomponent Build webcomponent"
|
echo " --webcomponent Build webcomponent"
|
||||||
echo " --testapp Build testapp"
|
echo " --testapp Build testapp"
|
||||||
|
echo " --wc-doc Generate webcomponent documentation"
|
||||||
echo " --all Build all artifacts (default)"
|
echo " --all Build all artifacts (default)"
|
||||||
echo " --help Show this help"
|
echo " --help Show this help"
|
||||||
echo
|
echo
|
||||||
echo "If no arguments are provided, all artifacts will be built."
|
echo "If no arguments are provided, all artifacts will be built."
|
||||||
echo
|
echo
|
||||||
echo -e "${YELLOW}Example:${NC} ./prepare.sh --frontend --backend"
|
echo -e "${YELLOW}Example:${NC} ./prepare.sh --frontend --backend"
|
||||||
|
echo -e "${YELLOW}Example:${NC} ./prepare.sh --wc-doc"
|
||||||
}
|
}
|
||||||
|
|
||||||
# If no arguments, build everything
|
# If no arguments, build everything
|
||||||
@ -60,6 +63,9 @@ else
|
|||||||
--testapp)
|
--testapp)
|
||||||
BUILD_TESTAPP=true
|
BUILD_TESTAPP=true
|
||||||
;;
|
;;
|
||||||
|
--wc-doc)
|
||||||
|
BUILD_WC_DOC=true
|
||||||
|
;;
|
||||||
--all)
|
--all)
|
||||||
BUILD_TYPINGS=true
|
BUILD_TYPINGS=true
|
||||||
BUILD_FRONTEND=true
|
BUILD_FRONTEND=true
|
||||||
@ -125,4 +131,19 @@ if [ "$BUILD_TESTAPP" = true ]; then
|
|||||||
cd ..
|
cd ..
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Generate webcomponent documentation if selected
|
||||||
|
if [ "$BUILD_WC_DOC" = true ]; then
|
||||||
|
echo -e "${GREEN}Generating webcomponent documentation...${NC}"
|
||||||
|
node scripts/generate-webcomponent-docs.js docs
|
||||||
|
|
||||||
|
# Copy the generated documentation to the openvidu.io directory
|
||||||
|
echo -e "${GREEN}Copying webcomponent documentation to openvidu.io...${NC}"
|
||||||
|
|
||||||
|
cp docs/webcomponent-events.md ../openvidu.io/shared/meet/webcomponent-events.md
|
||||||
|
cp docs/webcomponent-commands.md ../openvidu.io/shared/meet/webcomponent-commands.md
|
||||||
|
cp docs/webcomponent-attributes.md ../openvidu.io/shared/meet/webcomponent-attributes.md
|
||||||
|
|
||||||
|
echo -e "${GREEN}Webcomponent documentation generated successfully!${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "${BLUE}Preparation completed!${NC}"
|
echo -e "${BLUE}Preparation completed!${NC}"
|
||||||
488
scripts/generate-webcomponent-docs.js
Normal file
488
scripts/generate-webcomponent-docs.js
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates documentation for the OpenVidu Meet WebComponent
|
||||||
|
*/
|
||||||
|
class WebComponentDocGenerator {
|
||||||
|
constructor() {
|
||||||
|
this.typingsPath = path.join(__dirname, '../typings/src/webcomponent');
|
||||||
|
this.webComponentPath = path.join(__dirname, '../frontend/webcomponent/src/components/OpenViduMeet.ts');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and parses a TypeScript file to extract enum documentation
|
||||||
|
*/
|
||||||
|
parseEnumFile(filePath) {
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const lines = content.split('\n');
|
||||||
|
|
||||||
|
const enums = [];
|
||||||
|
let currentEnum = null;
|
||||||
|
let currentItem = null;
|
||||||
|
let inEnum = false;
|
||||||
|
let inComment = false;
|
||||||
|
let commentLines = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
|
||||||
|
// Detect start of enum
|
||||||
|
if (line.startsWith('export enum')) {
|
||||||
|
inEnum = true;
|
||||||
|
currentEnum = {
|
||||||
|
name: line.match(/export enum (\w+)/)[1],
|
||||||
|
items: []
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect end of enum
|
||||||
|
if (inEnum && line === '}') {
|
||||||
|
if (currentItem) {
|
||||||
|
currentEnum.items.push(currentItem);
|
||||||
|
}
|
||||||
|
enums.push(currentEnum);
|
||||||
|
inEnum = false;
|
||||||
|
currentEnum = null;
|
||||||
|
currentItem = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inEnum) continue;
|
||||||
|
|
||||||
|
// Handle multi-line comments
|
||||||
|
if (line.startsWith('/**')) {
|
||||||
|
inComment = true;
|
||||||
|
commentLines = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inComment) {
|
||||||
|
if (line.endsWith('*/')) {
|
||||||
|
inComment = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract comment content
|
||||||
|
const commentContent = line.replace(/^\*\s?/, '').trim();
|
||||||
|
if (commentContent) {
|
||||||
|
commentLines.push(commentContent);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse enum item
|
||||||
|
if (line.includes('=') && !line.startsWith('//')) {
|
||||||
|
// Save previous item if exists
|
||||||
|
if (currentItem) {
|
||||||
|
currentEnum.items.push(currentItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = line.match(/(\w+)\s*=\s*'([^']+)'/);
|
||||||
|
if (match) {
|
||||||
|
// Extract @required text if present
|
||||||
|
const requiredComment = commentLines.find(c => c.includes('@required'));
|
||||||
|
let requiredText = '';
|
||||||
|
if (requiredComment) {
|
||||||
|
const requiredMatch = requiredComment.match(/@required\s*(.*)/);
|
||||||
|
requiredText = requiredMatch ? requiredMatch[1].trim() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
currentItem = {
|
||||||
|
name: match[1],
|
||||||
|
value: match[2],
|
||||||
|
description: commentLines.filter(line => !line.includes('@')).join(' '),
|
||||||
|
isPrivate: commentLines.some(c => c.includes('@private')),
|
||||||
|
isModerator: commentLines.some(c => c.includes('@moderator')),
|
||||||
|
isRequired: commentLines.some(c => c.includes('@required')),
|
||||||
|
requiredText: requiredText
|
||||||
|
};
|
||||||
|
commentLines = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts payload information from interface definitions
|
||||||
|
*/
|
||||||
|
extractPayloads(filePath) {
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const payloads = {};
|
||||||
|
|
||||||
|
// Find the payload interface
|
||||||
|
const interfaceMatch = content.match(/export interface \w+Payloads\s*{([\s\S]*?)^}/m);
|
||||||
|
if (!interfaceMatch) return payloads;
|
||||||
|
|
||||||
|
const interfaceContent = interfaceMatch[1];
|
||||||
|
const lines = interfaceContent.split('\n');
|
||||||
|
|
||||||
|
let currentKey = null;
|
||||||
|
let inComment = false;
|
||||||
|
let commentLines = [];
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
|
||||||
|
if (trimmed.startsWith('/**')) {
|
||||||
|
inComment = true;
|
||||||
|
commentLines = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inComment) {
|
||||||
|
if (trimmed.endsWith('*/')) {
|
||||||
|
inComment = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentContent = trimmed.replace(/^\*\s?/, '').trim();
|
||||||
|
if (commentContent && !commentContent.includes('@')) {
|
||||||
|
commentLines.push(commentContent);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse payload property - looking for patterns like [WebComponentEvent.JOIN]: {
|
||||||
|
const propMatch = trimmed.match(/\[\w+\.(\w+)\]:\s*({[\s\S]*?}|[^,;]+)[,;]?/);
|
||||||
|
if (propMatch) {
|
||||||
|
const enumValue = propMatch[1];
|
||||||
|
let type = propMatch[2].trim();
|
||||||
|
|
||||||
|
// If it's a multi-line object, we need to collect the full definition
|
||||||
|
if (type.startsWith('{') && !type.endsWith('}')) {
|
||||||
|
// Find the closing brace
|
||||||
|
let braceCount = 1;
|
||||||
|
let i = lines.indexOf(line) + 1;
|
||||||
|
while (i < lines.length && braceCount > 0) {
|
||||||
|
const nextLine = lines[i].trim();
|
||||||
|
type += '\n' + nextLine;
|
||||||
|
for (const char of nextLine) {
|
||||||
|
if (char === '{') braceCount++;
|
||||||
|
if (char === '}') braceCount--;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payloads[enumValue] = {
|
||||||
|
type: type.replace(/[,;]$/, ''), // Remove trailing comma or semicolon
|
||||||
|
description: commentLines.join(' ')
|
||||||
|
};
|
||||||
|
commentLines = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts WebComponent attributes from the OpenViduMeet.ts file
|
||||||
|
*/
|
||||||
|
extractWebComponentAttributes() {
|
||||||
|
const content = fs.readFileSync(this.webComponentPath, 'utf8');
|
||||||
|
const attributes = [];
|
||||||
|
|
||||||
|
// Look for @attribute JSDoc comments
|
||||||
|
const attributeMatches = content.match(/@attribute\s+([^\s]+)\s*-\s*([^\n]+)/g);
|
||||||
|
if (attributeMatches) {
|
||||||
|
attributeMatches.forEach(match => {
|
||||||
|
const parts = match.match(/@attribute\s+([^\s]+)\s*-\s*(.+)/);
|
||||||
|
if (parts) {
|
||||||
|
attributes.push({
|
||||||
|
name: parts[1],
|
||||||
|
description: parts[2].trim()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates markdown table for events (only public events)
|
||||||
|
*/
|
||||||
|
generateEventsTable() {
|
||||||
|
const enums = this.parseEnumFile(path.join(this.typingsPath, 'event.model.ts'));
|
||||||
|
const payloads = this.extractPayloads(path.join(this.typingsPath, 'event.model.ts'));
|
||||||
|
|
||||||
|
const eventEnum = enums.find(e => e.name === 'WebComponentEvent');
|
||||||
|
if (!eventEnum) return '';
|
||||||
|
|
||||||
|
let markdown = '| Event | Description | Payload |\n';
|
||||||
|
markdown += '|-------|-------------|------------|\n';
|
||||||
|
|
||||||
|
for (const item of eventEnum.items) {
|
||||||
|
// Skip private events
|
||||||
|
if (item.isPrivate) continue;
|
||||||
|
|
||||||
|
const payload = payloads[item.name];
|
||||||
|
const payloadInfo = payload ? this.formatPayload(payload.type) : '-';
|
||||||
|
|
||||||
|
markdown += `| \`${item.value}\` | ${item.description || 'No description available'} | ${payloadInfo} |\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates markdown table for commands/methods (only public methods)
|
||||||
|
*/
|
||||||
|
generateCommandsTable() {
|
||||||
|
const enums = this.parseEnumFile(path.join(this.typingsPath, 'command.model.ts'));
|
||||||
|
const payloads = this.extractPayloads(path.join(this.typingsPath, 'command.model.ts'));
|
||||||
|
|
||||||
|
const commandEnum = enums.find(e => e.name === 'WebComponentCommand');
|
||||||
|
if (!commandEnum) return '';
|
||||||
|
|
||||||
|
let markdown = '| Method | Description | Parameters | Access Level |\n';
|
||||||
|
markdown += '|--------|-------------|------------|-------------|\n';
|
||||||
|
|
||||||
|
for (const item of commandEnum.items) {
|
||||||
|
// Skip private commands
|
||||||
|
if (item.isPrivate) continue;
|
||||||
|
|
||||||
|
const payload = payloads[item.name];
|
||||||
|
|
||||||
|
// Generate method name from command name and payload
|
||||||
|
const methodName = this.generateMethodName(item.name, item.value, payload);
|
||||||
|
|
||||||
|
const params = payload ? this.formatMethodParameters(payload.type) : '-';
|
||||||
|
|
||||||
|
// Determine access level based on @moderator annotation
|
||||||
|
const accessLevel = this.getAccessLevel(item);
|
||||||
|
|
||||||
|
markdown += `| \`${methodName}\` | ${item.description || 'No description available'} | ${params} | ${accessLevel} |\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates method name and signature from command enum
|
||||||
|
*/
|
||||||
|
generateMethodName(commandName, commandValue, payload) {
|
||||||
|
// Convert COMMAND_NAME to camelCase method name
|
||||||
|
const methodName = commandName
|
||||||
|
.toLowerCase()
|
||||||
|
.split('_')
|
||||||
|
.map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
// If there's no payload or payload is void, no parameters needed
|
||||||
|
if (!payload || payload.type === 'void') {
|
||||||
|
return `${methodName}()`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract parameter names from payload type
|
||||||
|
if (payload.type.includes('{') && payload.type.includes('}')) {
|
||||||
|
// Remove comments (both single-line // and multi-line /* */)
|
||||||
|
const cleanedType = payload.type
|
||||||
|
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove /* */ comments
|
||||||
|
.replace(/\/\/.*$/gm, ''); // Remove // comments
|
||||||
|
|
||||||
|
const properties = cleanedType
|
||||||
|
.replace(/[{}]/g, '')
|
||||||
|
.split(';')
|
||||||
|
.map(prop => prop.trim())
|
||||||
|
.filter(prop => prop && !prop.startsWith('//') && !prop.startsWith('/*'))
|
||||||
|
.map(prop => {
|
||||||
|
const [key] = prop.split(':').map(s => s.trim());
|
||||||
|
return key;
|
||||||
|
})
|
||||||
|
.filter(key => key); // Remove empty keys
|
||||||
|
|
||||||
|
if (properties.length > 0) {
|
||||||
|
return `${methodName}(${properties.join(', ')})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: no parameters
|
||||||
|
return `${methodName}()`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the access level of a command based on its @moderator annotation
|
||||||
|
*/
|
||||||
|
getAccessLevel(item) {
|
||||||
|
return item.isModerator ? 'Moderator' : 'All';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates markdown table for attributes/properties
|
||||||
|
*/
|
||||||
|
generateAttributesTable() {
|
||||||
|
const propertyEnums = this.parseEnumFile(path.join(this.typingsPath, 'properties.model.ts'));
|
||||||
|
const propertyEnum = propertyEnums.find(e => e.name === 'WebComponentProperty');
|
||||||
|
|
||||||
|
let markdown = '| Attribute | Description | Required |\n';
|
||||||
|
markdown += '|-----------|-------------|----------|\n';
|
||||||
|
|
||||||
|
// Add attributes from the properties enum only
|
||||||
|
if (propertyEnum) {
|
||||||
|
for (const item of propertyEnum.items) {
|
||||||
|
// Format required column with additional text if present
|
||||||
|
let requiredColumn = 'No';
|
||||||
|
if (item.isRequired) {
|
||||||
|
requiredColumn = item.requiredText ? `Yes (${item.requiredText})` : 'Yes';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use description from JSDoc comments, fallback to hardcoded if not available
|
||||||
|
const description = item.description || this.getDescriptionForAttribute(item.value);
|
||||||
|
|
||||||
|
markdown += `| \`${item.value}\` | ${description} | ${requiredColumn} |\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats payload type information for display in events table
|
||||||
|
*/
|
||||||
|
formatPayload(type) {
|
||||||
|
if (type === 'void' || type === '{}') {
|
||||||
|
return 'None';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle object types
|
||||||
|
if (type.includes('{') && type.includes('}')) {
|
||||||
|
const properties = type
|
||||||
|
.replace(/[{}]/g, '')
|
||||||
|
.split(';')
|
||||||
|
.map(prop => prop.trim())
|
||||||
|
.filter(prop => prop)
|
||||||
|
.map(prop => {
|
||||||
|
const [key, value] = prop.split(':').map(s => s.trim());
|
||||||
|
return `"${key}": "${value}"`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (properties.length > 0) {
|
||||||
|
const tab = ' ';
|
||||||
|
const jsonContent = '{ <br>' + tab + properties.join(',<br>' + tab) + '<br>}';
|
||||||
|
return `<pre><code>${jsonContent}</code></pre>`;
|
||||||
|
} else {
|
||||||
|
return '<pre><code>{}</code></pre>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `\`${type}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats method parameters for display
|
||||||
|
*/
|
||||||
|
formatMethodParameters(type) {
|
||||||
|
if (type === 'void') {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle object types
|
||||||
|
if (type.includes('{') && type.includes('}')) {
|
||||||
|
// Remove comments (both single-line // and multi-line /* */)
|
||||||
|
const cleanedType = type
|
||||||
|
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove /* */ comments
|
||||||
|
.replace(/\/\/.*$/gm, ''); // Remove // comments
|
||||||
|
|
||||||
|
const properties = cleanedType
|
||||||
|
.replace(/[{}]/g, '')
|
||||||
|
.split(';')
|
||||||
|
.map(prop => prop.trim())
|
||||||
|
.filter(prop => prop && !prop.startsWith('//') && !prop.startsWith('/*'))
|
||||||
|
.map(prop => {
|
||||||
|
const [key, value] = prop.split(':').map(s => s.trim());
|
||||||
|
return `• \`${key}\`: ${value}`;
|
||||||
|
})
|
||||||
|
.filter(param => param && !param.includes('undefined')); // Remove malformed parameters
|
||||||
|
|
||||||
|
return properties.length > 0 ? properties.join('<br>') : 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets description for an attribute
|
||||||
|
*/
|
||||||
|
getDescriptionForAttribute(attributeName) {
|
||||||
|
const descriptions = {
|
||||||
|
'room-id': 'Unique identifier for the meeting room',
|
||||||
|
'participant-name': 'Display name for the local participant',
|
||||||
|
'leave-redirect-url': 'URL to redirect to when leaving the meeting'
|
||||||
|
};
|
||||||
|
return descriptions[attributeName] || 'No description available';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates separate documentation files
|
||||||
|
*/
|
||||||
|
generateSeparateDocuments(outputDir = './docs') {
|
||||||
|
// Ensure output directory exists
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header comment for all generated files
|
||||||
|
const headerComment = `<!-- This file is auto-generated. Do not edit manually. -->\n<!-- Generated by openvidu-meet/scripts/generate-webcomponent-docs.js -->\n\n`;
|
||||||
|
|
||||||
|
const eventsTable = this.generateEventsTable();
|
||||||
|
const commandsTable = this.generateCommandsTable();
|
||||||
|
const attributesTable = this.generateAttributesTable();
|
||||||
|
|
||||||
|
// Write separate files with header comments
|
||||||
|
const eventsPath = path.join(outputDir, 'webcomponent-events.md');
|
||||||
|
const commandsPath = path.join(outputDir, 'webcomponent-commands.md');
|
||||||
|
const attributesPath = path.join(outputDir, 'webcomponent-attributes.md');
|
||||||
|
|
||||||
|
fs.writeFileSync(eventsPath, headerComment + eventsTable, 'utf8');
|
||||||
|
fs.writeFileSync(commandsPath, headerComment + commandsTable, 'utf8');
|
||||||
|
fs.writeFileSync(attributesPath, headerComment + attributesTable, 'utf8');
|
||||||
|
|
||||||
|
return {
|
||||||
|
events: eventsPath,
|
||||||
|
commands: commandsPath,
|
||||||
|
attributes: attributesPath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the generated documentation to separate files
|
||||||
|
*/
|
||||||
|
saveDocumentation(outputDir = './docs') {
|
||||||
|
const files = this.generateSeparateDocuments(outputDir);
|
||||||
|
|
||||||
|
console.log('✅ Documentation generated successfully:');
|
||||||
|
console.log(`📄 Events: ${files.events}`);
|
||||||
|
console.log(`🔧 Commands: ${files.commands}`);
|
||||||
|
console.log(`⚙️ Attributes: ${files.attributes}`);
|
||||||
|
|
||||||
|
// Display summary
|
||||||
|
console.log('\n📊 Documentation Summary:');
|
||||||
|
console.log('- Only public/non-private elements included');
|
||||||
|
console.log('- Three separate markdown files generated');
|
||||||
|
console.log('- Tables only, no additional content');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main execution
|
||||||
|
if (require.main === module) {
|
||||||
|
const generator = new WebComponentDocGenerator();
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const outputDir = args[0] || './docs';
|
||||||
|
|
||||||
|
try {
|
||||||
|
generator.saveDocumentation(outputDir);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error generating documentation:', error.message);
|
||||||
|
console.error('Stack trace:', error.stack);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WebComponentDocGenerator;
|
||||||
@ -10,7 +10,7 @@ export enum WebComponentCommand {
|
|||||||
INITIALIZE = 'INITIALIZE',
|
INITIALIZE = 'INITIALIZE',
|
||||||
/**
|
/**
|
||||||
* Ends the current meeting for all participants.
|
* Ends the current meeting for all participants.
|
||||||
* This command is only available for the moderator.
|
* @moderator
|
||||||
*/
|
*/
|
||||||
END_MEETING = 'END_MEETING',
|
END_MEETING = 'END_MEETING',
|
||||||
/**
|
/**
|
||||||
@ -19,7 +19,7 @@ export enum WebComponentCommand {
|
|||||||
LEAVE_ROOM = 'LEAVE_ROOM',
|
LEAVE_ROOM = 'LEAVE_ROOM',
|
||||||
/**
|
/**
|
||||||
* Kicks a participant from the meeting.
|
* Kicks a participant from the meeting.
|
||||||
* This command is only available for the moderator.
|
* @moderator
|
||||||
*/
|
*/
|
||||||
KICK_PARTICIPANT = 'KICK_PARTICIPANT'
|
KICK_PARTICIPANT = 'KICK_PARTICIPANT'
|
||||||
}
|
}
|
||||||
|
|||||||
27
typings/src/webcomponent/properties.model.ts
Normal file
27
typings/src/webcomponent/properties.model.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export enum WebComponentProperty {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The OpenVidu Meet room URL to connect to (moderator or publisher url)
|
||||||
|
* @required This attribute is required unless `recording-url` is provided.
|
||||||
|
*/
|
||||||
|
ROOM_URL = 'room-url',
|
||||||
|
/**
|
||||||
|
* The URL of a recording to view.
|
||||||
|
* @required This attribute is required unless `room-url` is provided.
|
||||||
|
*/
|
||||||
|
RECORDING_URL = 'recording-url',
|
||||||
|
/**
|
||||||
|
* Display name for the local participant.
|
||||||
|
*/
|
||||||
|
PARTICIPANT_NAME = 'participant-name',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL to redirect to when leaving the meeting.
|
||||||
|
* Redirection occurs after the **`CLOSED` event** fires.
|
||||||
|
*/
|
||||||
|
LEAVE_REDIRECT_URL = 'leave-redirect-url',
|
||||||
|
/**
|
||||||
|
* Whether to show only recordings instead of live meetings.
|
||||||
|
*/
|
||||||
|
SHOW_ONLY_RECORDINGS = 'show-only-recordings'
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user