[New Feature] Add web folder to support web documentation

This commit is contained in:
Rudy Haryanto
2022-10-04 20:17:17 +07:00
parent c27f624ca8
commit a54aa04c9b
251 changed files with 11079 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) AiCure, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const fs = require('fs-extra');
const glob = require('glob');
const path = require('path');
const reactDocs = require('@motiz88/react-native-docgen');
const GENERATE_ANNOTATION = '@' + 'generate-docs';
module.exports = extractDocsFromRN;
async function extractDocsFromRN(rnRoot) {
// TODO: make implementation async
const allComponentFiles = glob.sync(
path.join(rnRoot, '/Libraries/{Components,Image,}/**/*.js'),
{
nodir: true,
absolute: true,
}
);
const docs = [];
for (const file of allComponentFiles) {
const contents = fs.readFileSync(file, {encoding: 'utf-8'});
if (!contents.includes(GENERATE_ANNOTATION)) {
continue;
}
console.log(file);
const result = reactDocs.parse(
contents,
reactDocs.resolver.findAllComponentDefinitions,
reactDocs.defaultHandlers.filter(
handler => handler !== reactDocs.handlers.propTypeCompositionHandler
),
{filename: file}
);
const filteredResult = result.filter(item => {
if (item.description) return item;
});
docs.push({
file,
component: cleanComponentResult(...filteredResult),
});
}
// Make sure output is JSON-safe
const docsSerialized = JSON.parse(JSON.stringify(docs));
await fs.writeFile(
path.join(__dirname, 'extracted.json'),
JSON.stringify(docsSerialized, null, 2),
'utf8'
);
return docsSerialized;
}
function cleanComponentResult(component) {
return {
...component,
methods: component.methods.filter(method => !method.name.startsWith('_')),
};
}

View File

@@ -0,0 +1,326 @@
/**
* Copyright (c) AiCure, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const tokenizeComment = require('tokenize-comment');
const {formatTypeColumn, formatDefaultColumn} = require('./propFormatter');
const {
formatMethodType,
formatMethodName,
formatMethodDescription,
} = require('./methodFormatter');
const {
formatMultiplePlatform,
stringToInlineCodeForTable,
maybeLinkifyType,
maybeLinkifyTypeName,
formatType,
} = require('./utils');
// Formats an array of rows as a Markdown table
function generateTable(rows) {
const colWidths = new Map();
for (const row of rows) {
for (const col of Object.keys(row)) {
colWidths.set(
col,
Math.max(colWidths.get(col) || col.length, String(row[col]).length)
);
}
}
if (!colWidths.size) {
return '';
}
let header = '|',
divider = '|';
for (const [col, width] of colWidths) {
header += ' ' + col.padEnd(width + 1) + '|';
divider += ' ' + '-'.repeat(width) + ' ' + '|';
}
let result = header + '\n' + divider + '\n';
for (const row of rows) {
result += '|';
for (const [col, width] of colWidths) {
result += ' ' + String(row[col] || '').padEnd(width + 1) + '|';
}
result += '\n';
}
return result;
}
// Formats information about a prop
function generateProp(propName, prop) {
const infoTable = generateTable([
{
Type: formatTypeColumn(prop),
...formatDefaultColumn(prop),
},
]);
return (
'### ' +
(prop.required ? '<div class="label required basic">Required</div>' : '') +
'`' +
propName +
'`' +
(prop.rnTags && prop.rnTags.platform
? formatMultiplePlatform(prop.rnTags.platform)
: '') +
'\n' +
'\n' +
(prop.description ? prop.description + '\n\n' : '') +
infoTable
);
}
// Formats information about a prop
function generateMethod(method, component) {
let descriptionTokenized = '';
let header = 'Valid `params` keys are:';
let mdPoints = '';
if (method?.params[0]?.type?.raw) {
let desc = method?.params[0]?.type?.raw;
let len = method?.params[0]?.type?.signature?.properties?.length;
descriptionTokenized = tokenizeComment(desc);
if (
descriptionTokenized?.examples &&
descriptionTokenized?.examples.length === len
) {
let obj = [];
for (let i = 0; i < len; i++) {
let newObj = method?.params[0]?.type?.signature?.properties[i];
newObj['description'] = descriptionTokenized?.examples[i]?.value;
obj.push(newObj);
}
obj.map(item => {
if (item.description.trim() !== 'missing')
mdPoints += `- '${item.key}' (${item.value.name}) - ${item.description}`;
else mdPoints += `- '${item.key}' (${item.value.name})`;
});
}
}
if (method?.docblock) {
let dblock = method.docblock
.split('\n')
.map(line => {
return line.replace(/ /, '');
})
.join('\n');
const docblockTokenized = tokenizeComment(dblock);
dblock = dblock.replace(/@platform .*/g, '');
method.rnTags = {};
const platformTag = docblockTokenized.tags.find(
({key}) => key === 'platform'
);
if (platformTag) {
method.rnTags.platform = platformTag.value.split(',');
}
}
return (
'### `' +
method.name +
'()`' +
(method.rnTags && method.rnTags.platform
? formatMultiplePlatform(method.rnTags.platform)
: '') +
'\n' +
'\n' +
(method.description ? method.description + '\n\n' : '') +
generateMethodSignatureTable(method, component) +
(mdPoints && header + '\n' + mdPoints)
).trim();
}
function lowerFirst(s) {
return s[0].toLowerCase() + s.slice(1);
}
function generateMethodSignatureBlock(method, component) {
return (
'```jsx\n' +
(method.modifiers.includes('static')
? component.displayName + '.'
: lowerFirst(component.displayName + '.')) +
method.name +
'(' +
method.params
.map(param => (param.optional ? `[${param.name}]` : param.name))
.join(', ') +
');' +
'\n' +
'```\n\n'
);
}
function generateMethodSignatureTable(method, component) {
if (!method.params.length) {
return '';
}
return (
'**Parameters:**\n\n' +
generateTable(
method.params.map(param => {
return {
Name: formatMethodName(param),
Type: formatMethodType(param),
Required: param.optional ? 'No' : 'Yes',
...(param.description && {
Description: formatMethodDescription(param),
}),
};
})
)
);
}
// Formats information about props
function generateProps({props, composes}) {
if (!props || !Object.keys(props).length) {
return '';
}
return (
'## Props' +
'\n' +
'\n' +
(composes && composes.length
? composes
.map(parent => 'Inherits ' + maybeLinkifyTypeName(parent) + '.')
.join('\n\n') + '\n\n'
: '') +
Object.keys(props)
.sort((a, b) => a.localeCompare(b))
.sort((a, b) => props[b].required - props[a].required)
.map(function (propName) {
return generateProp(propName, props[propName]);
})
.join('\n\n---\n\n')
);
}
function generateMethods(component) {
const {methods} = component;
if (!methods || !methods.length) {
return '';
}
return (
'## Methods' +
'\n' +
'\n' +
[...methods]
.sort((a, b) =>
a.name.localeCompare(
b.name /* TODO @nocommit what's a neutral locale */
)
)
.map(function (method) {
return generateMethod(method, component);
})
.join('\n\n---\n\n')
);
}
// Generates a Docusaurus header for a component page
function generateHeader({id, title}) {
return (
'---' + '\n' + 'id: ' + id + '\n' + 'title: ' + title + '\n' + '---' + '\n'
);
}
// Function to process example contained description
function preprocessDescription(desc) {
// Playground tabs for the class and functional components
const playgroundTab = `<div class="toggler">
<ul role="tablist" class="toggle-syntax">
<li id="functional" class="button-functional" aria-selected="false" role="tab" tabindex="0" aria-controls="functionaltab" onclick="displayTabs('syntax', 'functional')">
Function Component Example
</li>
<li id="classical" class="button-classical" aria-selected="false" role="tab" tabindex="0" aria-controls="classicaltab" onclick="displayTabs('syntax', 'classical')">
Class Component Example
</li>
</ul>
</div>`;
//Blocks for different syntax sections
const functionalBlock = `<block class='functional syntax' />`;
const classBlock = `<block class='classical syntax' />`;
const endBlock = `<block class='endBlock syntax' />`;
desc = desc
.split('\n')
.map(line => {
return line.replace(/ /, '');
})
.join('\n');
const descriptionTokenized = tokenizeComment(desc);
// Tabs counter for examples
let tabs = 0;
descriptionTokenized.examples.map(item => {
const matchSnackPlayer = item.language.match(/(SnackPlayer name=).*/g);
if (matchSnackPlayer) {
const matchClassComp = matchSnackPlayer[0].match(
/Class%20Component%20Example/
);
const matchFuncComp = matchSnackPlayer[0].match(
/Function%20Component%20Example/
);
if (matchClassComp || matchFuncComp) tabs++;
}
});
if (tabs === 2) {
const firstExample = desc.slice(desc.search('```SnackPlayer') + 1);
const secondExample = firstExample.slice(
firstExample.search('```SnackPlayer') + 1
);
return (
desc.substring(0, desc.search('```SnackPlayer')) +
`\n## Example\n` +
`${playgroundTab}\n\n${functionalBlock}\n\n${
'`' + firstExample.slice(0, firstExample.search('```') + 3)
}\n\n${classBlock}\n\n${
'`' + secondExample.slice(0, secondExample.search('```') + 3)
}\n\n${endBlock}` +
secondExample.slice(secondExample.search('```') + 3)
);
} else {
if (desc.search('```SnackPlayer') !== -1) {
return (
desc.slice(0, desc.search('```SnackPlayer')) +
'\n' +
'\n## Example\n' +
'\n' +
desc.slice(desc.search('```SnackPlayer'))
);
} else return desc;
}
}
function generateMarkdown({id, title}, component) {
const markdownString =
generateHeader({id, title}) +
'\n' +
preprocessDescription(component.description) +
'\n\n' +
'---\n\n' +
'# Reference\n\n' +
generateProps(component) +
generateMethods(component);
return markdownString.replace(/\n{3,}/g, '\n\n');
}
module.exports = generateMarkdown;

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) AiCure, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// Hard-coded knowledge about the OpenDBM codebase and how to document it,
// beyond what is explicitly encoded in the react-docgen artifact
// (generatedComponentApiDocs.js)
// Ideally this file should go away.
module.exports = {
linkableTypeAliases: {
NativeColorValue: {
text: 'color',
url: 'colors.md',
},
ViewProps: {
text: 'View Props',
url: 'view.md#props',
},
PressEvent: {
text: 'PressEvent',
url: 'pressevent.md',
},
'RefreshLayoutConsts.SIZE.DEFAULT': {
text: 'RefreshControl.SIZE',
url: 'refreshcontrol.md#refreshlayoutconstssize',
},
StatusBarAnimation: {
text: 'StatusBarAnimation',
url: 'statusbar#statusbaranimation',
},
StatusBarStyle: {
text: 'StatusBarStyle',
url: 'statusbar#statusbarstyle',
},
ReactNode: {
text: 'React.Node',
url: 'react-node.md',
},
TextStyleProps: {
text: 'Text Style Props',
url: 'text-style-props',
},
SectionT: {
text: 'Section',
url: 'sectionlist#section',
},
ViewStyleProps: {
text: 'View Style Props',
url: 'view-style-props',
},
Text: {
text: 'Text',
url: 'text#style',
},
},
};

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) AiCure, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const {typeOf} = require('tokenize-comment/lib/utils');
const magic = require('./magic');
const {formatMultiplePlatform} = require('./utils');
function formatMethodType(param) {
let text, url;
if (param?.type?.name === 'union') {
if (param?.type?.alias) {
const {alias} = param.type;
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, alias)) {
({url, text} = magic.linkableTypeAliases[alias]);
}
if (url) return `[${text}](${url})`;
else return param.type.alias;
}
return param.type.name;
} else {
if (param?.type?.type) return param.type.type;
else return param.type.name;
}
}
function formatMethodName(param) {
let tag = param.description;
if (tag) {
const isMatch = tag.match(/{@platform [a-z ,]*}/);
if (isMatch) {
const platform = isMatch[0].match(/ [a-z ,]*/);
tag = tag.replace(/{@platform [a-z ,]*}/g, '');
tag = formatMultiplePlatform(platform[0].split(','));
return param.name + tag;
}
}
return param.name;
}
function formatMethodDescription(param) {
let tag = param.description;
const isMatch = tag.match(/{@platform [a-z ,]*}/);
if (isMatch) {
const platform = isMatch[0].match(/ [a-z ,]*/);
// Replaces @platform strings with empty string
// and appends type with formatted platform
tag = tag.replace(/{@platform [a-z ,]*}/g, '');
}
return tag;
}
module.exports = {
formatMethodType,
formatMethodName,
formatMethodDescription,
};

View File

@@ -0,0 +1,18 @@
{
"name": "@react-native-website/sync-api",
"version": "0.0.1",
"private": true,
"scripts": {
"prettier": "prettier --write \"**/*.js\"",
"sync": "node sync-api-docs ../../react-native"
},
"devDependencies": {
"@motiz88/react-native-docgen": "0.0.3",
"fs-extra": "^9.1.0",
"glob": "^7.1.6",
"he": "^1.2.0",
"path": "^0.12.7",
"react-docgen-markdown-renderer": "^2.1.3",
"tokenize-comment": "^3.0.1"
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) AiCure, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
// Preprocess the react-docgen artifact before rendering it to Markdown.
// This file may end up in the OpenDBM repo, as part of the
// `generate-api-docs` script.
const tokenizeComment = require('tokenize-comment');
const {typeOf} = require('tokenize-comment/lib/utils');
function joinDescriptionAndExamples(tokenized) {
let sections = [];
if (tokenized.description) {
sections.push(tokenized.description);
}
for (const {raw} of tokenized.examples) {
sections.push(raw);
}
if (tokenized.footer) {
sections.push(tokenized.footer);
}
return sections.join('\n\n');
}
function preprocessTagsInDescription(obj) {
if (obj && obj.description) {
obj.description = obj.description
.split('\n')
.map(line => {
return line.replace(/ /, '');
})
.join('\n');
const descriptionTokenized = tokenizeComment(obj.description);
obj.description = obj.description.replace(
/@platform .*|@default .*|@type .*/g,
''
);
obj.rnTags = {};
const platformTag = descriptionTokenized.tags.find(
({key}) => key === 'platform'
);
const defaultTag = descriptionTokenized.tags.filter(
tag => tag.key === 'default'
);
const typeTag = descriptionTokenized.tags.filter(tag => tag.key === 'type');
if (platformTag) {
obj.rnTags.platform = platformTag.value.split(',');
}
if (defaultTag.length) {
obj.rnTags.default = [];
defaultTag.forEach(tag => {
obj.rnTags.default.push(tag.value);
});
}
if (typeTag.length) {
obj.rnTags.type = [];
typeTag.forEach(tag => {
obj.rnTags.type.push(tag.value);
});
}
}
}
// NOTE: This function mutates `docs`.
function preprocessGeneratedApiDocs(docs) {
for (const {component} of docs) {
if (component.props && component.description) {
for (const prop of Object.values(component.props)) {
preprocessTagsInDescription(prop);
}
for (const prop of component.methods) {
preprocessTagsInDescription(prop);
}
}
}
}
module.exports = preprocessGeneratedApiDocs;

View File

@@ -0,0 +1,246 @@
/**
* Copyright (c) AiCure, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const {typeOf} = require('tokenize-comment/lib/utils');
const magic = require('./magic');
const {
formatMultiplePlatform,
stringToInlineCodeForTable,
maybeLinkifyType,
maybeLinkifyTypeName,
formatType,
} = require('./utils');
function formatTypeColumn(prop) {
// Checks for @type pragma comment
if (prop.rnTags && prop.rnTags.type) {
let tableRows = '';
const typeTags = prop.rnTags.type;
typeTags.forEach(tag => {
let url, text;
// Checks for @platform pragma in @type string
const isMatch = tag.match(/{@platform [a-z ,]*}/);
if (isMatch) {
// Extracts platforms from matched regex
const platform = isMatch[0].match(/ [a-z ,]*/);
// Replaces @platform strings with empty string
// and appends type with formatted platform
tag = tag.replace(/{@platform [a-z ,]*}/g, '');
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, tag)) {
({url, text} = magic.linkableTypeAliases[tag]);
if (url) tag = `[${text}](${url})`;
}
tag = tag + formatMultiplePlatform(platform[0].split(','));
} else {
// Check if there are multiple comma separated types in a single line
if (tag.match(/, /)) {
let newTag = '';
const tags = tag.split(', ');
tags.forEach(item => {
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, item)) {
({url, text} = magic.linkableTypeAliases[item]);
if (url) newTag += ', ' + `[${text}](${url})`;
} else newTag += ', ' + item;
});
//Trim comma from beginning
tag = newTag.replace(/^, /, '');
}
// If there is no comma separated types in rnTags
else if (Object.hasOwnProperty.call(magic.linkableTypeAliases, tag)) {
({url, text} = magic.linkableTypeAliases[tag]);
if (url) tag = `[${text}](${url})`;
}
}
tableRows = tableRows + tag + '<hr/>';
});
tableRows = tableRows.replace(/<hr\/>$/, '');
return tableRows;
}
// To extract type from prop flowType
else if (prop.flowType && Object.keys(prop.flowType).length >= 1) {
let text, url;
// Handles flowtype name for signatures
if (prop.flowType.name === 'signature') {
// Handles flowtype for function signature
if (prop.flowType.type === 'function') {
// Extracts EventType from the raw value
const isMatch = prop.flowType.raw.match(/: [a-zA-Z]*/);
if (isMatch) {
// Formats EventType
const eventType = isMatch[0].slice(2);
// Checks for aliases in magic and generates md url
if (
Object.hasOwnProperty.call(magic.linkableTypeAliases, eventType)
) {
({url, text} = magic.linkableTypeAliases[eventType]);
if (url) {
return `${prop.flowType.type}([${text}](${url}))`;
}
}
// TODO: Handling unknown function params
return `${prop.flowType.type}`;
} else {
return prop.flowType.type;
}
} else if (prop.flowType.type === 'object') {
return prop.flowType.type;
}
} else if (prop.flowType.name.includes('$ReadOnlyArray')) {
prop?.flowType?.elements[0]?.elements &&
prop?.flowType?.elements[0]?.elements.forEach(elem => {
if (
Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)
) {
({url, text} = magic.linkableTypeAliases[elem.name]);
}
});
if (url) return `array of [${text}](${url})`;
else if (prop?.flowType?.elements[0].name === 'union') {
const unionTypes = prop?.flowType?.elements[0]?.elements.reduce(
(acc, curr) => {
acc.push(curr.value);
return acc;
},
[]
);
return `array of enum(${unionTypes.join(', ')})`;
} else if (prop?.flowType?.elements[0]?.name) {
const typeName = prop.flowType.elements[0].name;
//array of number
if (typeName === 'number') return `array of ${typeName}`;
else if (
Object.hasOwnProperty.call(magic.linkableTypeAliases, typeName)
) {
({url, text} = magic.linkableTypeAliases[typeName]);
if (url) return `array of [${text}](${url})`;
}
//default array for all other types
else return 'array';
}
} else if (prop.flowType.name === '$ReadOnly') {
// Special Case: switch#trackcolor
let markdown = '';
if (prop.flowType.elements[0]?.type === 'object') {
prop?.flowType?.elements[0]?.signature?.properties.forEach(
({key, value}) => {
value?.elements?.forEach(elem => {
if (
Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)
) {
({url, text} = magic.linkableTypeAliases[elem.name]);
markdown += `${key}: [${text}](${url})` + ', ';
}
});
if (!url) markdown += `${key}: ${value.name}` + ', ';
}
);
if (markdown.match(/, $/)) markdown = markdown.replace(/, $/, '');
return `${prop.flowType.elements[0]?.type}: {${markdown}}`;
}
} else if (prop.flowType.name === 'union') {
let unionTypes = prop.flowType.raw.split('|');
// Trim whitespaces and remove any leftover `|` (to avoid table split)
unionTypes = unionTypes
.map(elem => {
return elem.trim().replace(/|/g, '');
})
.filter(item => {
if (item) return item;
});
// Get text and url from magic aliases
prop?.flowType?.elements?.forEach(elem => {
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)) {
({url, text} = magic.linkableTypeAliases[elem.name]);
}
});
if (url) return `[${text}](${url})`;
return `enum(${unionTypes.join(', ')})`;
} else if (prop.flowType.name === 'ReactElement') {
return 'element';
} else {
// Get text and url from magic aliases
prop?.flowType?.elements?.forEach(elem => {
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)) {
({url, text} = magic.linkableTypeAliases[elem.name]);
}
});
}
// If no text is found, get raw values as text
if (!text) {
// TO BE FIXED
text =
(prop.flowType.raw && stringToInlineCodeForTable(prop.flowType.raw)) ||
formatType(prop.flowType.name);
}
// If URL is found, return text and link in markdown format
if (url) {
return `[${text}](${url})`;
}
return text;
}
}
// Adds proper markdown formatting to component's default value.
function formatDefaultColumn(prop) {
if (prop?.rnTags?.default) {
// Parse from @default annotation
let tableRows = '';
prop.rnTags.default.forEach(tag => {
const isMatch = tag.match(/{@platform [a-z]*}/);
if (isMatch) {
const platform = isMatch[0].match(/ [a-z]*/);
tag = tag.replace(/{@platform [a-z]*}/g, '');
// Checks component for NativeColorValue in default
let colorBlock = '';
prop?.flowType?.elements.some(elem => {
if (elem.name === 'NativeColorValue' && !tag.includes('null')) {
colorBlock = `<ins style={{background: '${tag.replace(
/'/g,
''
)}'}} className="color-box" />`;
return true;
}
});
tag =
colorBlock +
(!tag.includes('null') ? '`' + tag + '`' : tag) +
formatMultiplePlatform(platform[0].split(','));
} else if (!tag.includes('`')) {
tag = '`' + tag + '`';
}
tableRows = tableRows + tag + '<hr/>';
});
tableRows = tableRows.replace(/<hr\/>$/, '');
return {Default: tableRows};
} else {
// Parse defaultValue if @default annotation not provided
return prop?.defaultValue?.value
? {Default: stringToInlineCodeForTable(prop?.defaultValue?.value)}
: '';
}
}
module.exports = {
formatTypeColumn,
formatDefaultColumn,
};

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) AiCure, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// ***** EXPERIMENTAL *****
// Updates the API docs from the OpenDBM source code.
'use strict';
const process = require('process');
const fs = require('fs-extra');
const path = require('path');
const extractDocsFromRN = require('./extractDocsFromRN');
const preprocessGeneratedApiDocs = require('./preprocessGeneratedApiDocs');
const generateMarkdown = require('./generateMarkdown');
const {titleToId} = require('./utils');
const DOCS_ROOT_DIR = path.resolve(__dirname, '..', 'docs');
async function generateApiDocs(rnPath) {
const apiDocs = await extractDocsFromRN(rnPath);
preprocessGeneratedApiDocs(apiDocs);
await Promise.all(
apiDocs.map(async ({component, file}, index) => {
if (!component.displayName) {
console.log(
`react-docgen data for ${path.basename(file)} was malformed, skipping`
);
return;
}
const id = titleToId(component.displayName);
const componentMarkdown = generateMarkdown(
{title: component.displayName, id: id},
component
);
const outFile = path.join(DOCS_ROOT_DIR, id + '.md');
console.log('Generated ' + outFile);
await fs.writeFile(outFile, componentMarkdown, 'utf8');
})
);
}
async function main(args) {
await generateApiDocs(args[0]);
}
main(process.argv.slice(2));

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) AiCure, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const he = require('he');
const magic = require('./magic');
// Adds multiple platform tags for prop name
function formatMultiplePlatform(platforms) {
let platformString = '';
platforms.forEach(platform => {
switch (platform.trim().toLowerCase()) {
case 'ios':
platformString += '<div class="label ios">' + 'iOS' + '</div> ';
break;
case 'android':
platformString += '<div class="label android">' + 'Android' + '</div>';
break;
case 'tv':
platformString += '<div class="label tv">' + 'TV' + '</div>';
break;
//TODO: Add a new CSS class for VR
case 'vr':
platformString += '<div class="label tv">' + 'VR' + '</div>';
}
});
return platformString;
}
// Wraps a string in an inline code block in a way that is safe to include in a
// table cell, by wrapping it as HTML <code> if necessary.
function stringToInlineCodeForTable(str) {
let useHtml = /[`|]/.test(str);
str = str.replace(/\n/g, ' ');
if (useHtml) {
return '<code>' + he.encode(str).replace(/\|/g, '&#124;') + '</code>';
}
return '`' + str + '`';
}
function maybeLinkifyType(flowType) {
let url, text;
flowType.elements?.forEach(elem => {
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)) {
({url, text} = magic.linkableTypeAliases[elem.name]);
}
});
if (!text) {
text = stringToInlineCodeForTable(
flowType.raw || formatType(flowType.name)
);
}
if (url) {
return `[${text}](${url})`;
}
return text;
}
function formatType(name) {
if (name.toLowerCase() === 'boolean') return 'bool';
if (name.toLowerCase() === 'stringish') return 'string';
if (name === '$ReadOnlyArray') return 'array';
return name;
}
function maybeLinkifyTypeName(name) {
let url, text;
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, name)) {
({url, text} = magic.linkableTypeAliases[name]);
}
if (!text) {
text = stringToInlineCodeForTable(name);
}
if (url) {
return `[${text}](${url})`;
}
return text;
}
function titleToId(title) {
return title.toLowerCase().replace(/[^a-z]+/g, '-');
}
module.exports = {
formatMultiplePlatform,
stringToInlineCodeForTable,
maybeLinkifyType,
maybeLinkifyTypeName,
formatType,
titleToId,
};