Bhargava 6063bd1724 Help Project:
1. Initial Commit - a boiler plate code and POC to realize the concept of context
sensitive help
2. Frontend code written in ReactJS
3. Backend code written in Java, Spring Boot Framework
4. Frontend Start:
        pre-requisites : node, npm
	npm run dev  ==> to start the frontend vite server
5. Backend Start:
	pre-requisites : java, mvn
        mvn spring-boot:run  ==> to start the backend server
6. Visit http://localhost:5173/ for basic demo of help, press F1 in textboxes
7. Visit http://localhost:5173/editor and enter "admin123" to add/modify texts.

Happy Coding !!!

Thank you,
Bhargava.
2025-07-04 15:54:13 +05:30

339 lines
8.0 KiB
JavaScript

/**
* @typedef {import('hast').Comment} Comment
* @typedef {import('hast').Doctype} Doctype
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Nodes} Nodes
* @typedef {import('hast').Root} Root
* @typedef {import('hast').RootContent} RootContent
* @typedef {import('hast').Text} Text
*
* @typedef {import('parse5').DefaultTreeAdapterMap['document']} Parse5Document
* @typedef {import('parse5').DefaultTreeAdapterMap['documentFragment']} Parse5Fragment
* @typedef {import('parse5').DefaultTreeAdapterMap['element']} Parse5Element
* @typedef {import('parse5').DefaultTreeAdapterMap['node']} Parse5Nodes
* @typedef {import('parse5').DefaultTreeAdapterMap['documentType']} Parse5Doctype
* @typedef {import('parse5').DefaultTreeAdapterMap['commentNode']} Parse5Comment
* @typedef {import('parse5').DefaultTreeAdapterMap['textNode']} Parse5Text
* @typedef {import('parse5').DefaultTreeAdapterMap['parentNode']} Parse5Parent
* @typedef {import('parse5').Token.Attribute} Parse5Attribute
*
* @typedef {import('property-information').Schema} Schema
*/
/**
* @typedef Options
* Configuration.
* @property {Space | null | undefined} [space='html']
* Which space the document is in (default: `'html'`).
*
* When an `<svg>` element is found in the HTML space, this package already
* automatically switches to and from the SVG space when entering and exiting
* it.
*
* @typedef {Exclude<Parse5Nodes, Parse5Document | Parse5Fragment>} Parse5Content
*
* @typedef {'html' | 'svg'} Space
*/
import {stringify as commas} from 'comma-separated-tokens'
import {ok as assert} from 'devlop'
import {find, html, svg} from 'property-information'
import {stringify as spaces} from 'space-separated-tokens'
import {webNamespaces} from 'web-namespaces'
import {zwitch} from 'zwitch'
/** @type {Options} */
const emptyOptions = {}
const own = {}.hasOwnProperty
const one = zwitch('type', {handlers: {root, element, text, comment, doctype}})
/**
* Transform a hast tree to a `parse5` AST.
*
* @param {Nodes} tree
* Tree to transform.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {Parse5Nodes}
* `parse5` node.
*/
export function toParse5(tree, options) {
const settings = options || emptyOptions
const space = settings.space
return one(tree, space === 'svg' ? svg : html)
}
/**
* @param {Root} node
* Node (hast) to transform.
* @param {Schema} schema
* Current schema.
* @returns {Parse5Document}
* Parse5 node.
*/
function root(node, schema) {
/** @type {Parse5Document} */
const result = {
nodeName: '#document',
// @ts-expect-error: `parse5` uses enums, which are actually strings.
mode: (node.data || {}).quirksMode ? 'quirks' : 'no-quirks',
childNodes: []
}
result.childNodes = all(node.children, result, schema)
patch(node, result)
return result
}
/**
* @param {Root} node
* Node (hast) to transform.
* @param {Schema} schema
* Current schema.
* @returns {Parse5Fragment}
* Parse5 node.
*/
function fragment(node, schema) {
/** @type {Parse5Fragment} */
const result = {nodeName: '#document-fragment', childNodes: []}
result.childNodes = all(node.children, result, schema)
patch(node, result)
return result
}
/**
* @param {Doctype} node
* Node (hast) to transform.
* @returns {Parse5Doctype}
* Parse5 node.
*/
function doctype(node) {
/** @type {Parse5Doctype} */
const result = {
nodeName: '#documentType',
name: 'html',
publicId: '',
systemId: '',
parentNode: null
}
patch(node, result)
return result
}
/**
* @param {Text} node
* Node (hast) to transform.
* @returns {Parse5Text}
* Parse5 node.
*/
function text(node) {
/** @type {Parse5Text} */
const result = {
nodeName: '#text',
value: node.value,
parentNode: null
}
patch(node, result)
return result
}
/**
* @param {Comment} node
* Node (hast) to transform.
* @returns {Parse5Comment}
* Parse5 node.
*/
function comment(node) {
/** @type {Parse5Comment} */
const result = {
nodeName: '#comment',
data: node.value,
parentNode: null
}
patch(node, result)
return result
}
/**
* @param {Element} node
* Node (hast) to transform.
* @param {Schema} schema
* Current schema.
* @returns {Parse5Element}
* Parse5 node.
*/
function element(node, schema) {
const parentSchema = schema
let currentSchema = parentSchema
if (
node.type === 'element' &&
node.tagName.toLowerCase() === 'svg' &&
parentSchema.space === 'html'
) {
currentSchema = svg
}
/** @type {Array<Parse5Attribute>} */
const attrs = []
/** @type {string} */
let prop
if (node.properties) {
for (prop in node.properties) {
if (prop !== 'children' && own.call(node.properties, prop)) {
const result = createProperty(
currentSchema,
prop,
node.properties[prop]
)
if (result) {
attrs.push(result)
}
}
}
}
const space = currentSchema.space
// `html` and `svg` both have a space.
assert(space)
/** @type {Parse5Element} */
const result = {
nodeName: node.tagName,
tagName: node.tagName,
attrs,
// @ts-expect-error: `parse5` types are wrong.
namespaceURI: webNamespaces[space],
childNodes: [],
parentNode: null
}
result.childNodes = all(node.children, result, currentSchema)
patch(node, result)
if (node.tagName === 'template' && node.content) {
// @ts-expect-error: `parse5` types are wrong.
result.content = fragment(node.content, currentSchema)
}
return result
}
/**
* Handle a property.
*
* @param {Schema} schema
* Current schema.
* @param {string} prop
* Key.
* @param {Array<number | string> | boolean | number | string | null | undefined} value
* hast property value.
* @returns {Parse5Attribute | undefined}
* Field for runtime, optional.
*/
function createProperty(schema, prop, value) {
const info = find(schema, prop)
// Ignore nullish and `NaN` values.
if (
value === false ||
value === null ||
value === undefined ||
(typeof value === 'number' && Number.isNaN(value)) ||
(!value && info.boolean)
) {
return
}
if (Array.isArray(value)) {
// Accept `array`.
// Most props are space-separated.
value = info.commaSeparated ? commas(value) : spaces(value)
}
/** @type {Parse5Attribute} */
const attribute = {
name: info.attribute,
value: value === true ? '' : String(value)
}
if (info.space && info.space !== 'html' && info.space !== 'svg') {
const index = attribute.name.indexOf(':')
if (index < 0) {
attribute.prefix = ''
} else {
attribute.name = attribute.name.slice(index + 1)
attribute.prefix = info.attribute.slice(0, index)
}
attribute.namespace = webNamespaces[info.space]
}
return attribute
}
/**
* Transform all hast nodes.
*
* @param {Array<RootContent>} children
* List of children.
* @param {Parse5Parent} parentNode
* `parse5` parent node.
* @param {Schema} schema
* Current schema.
* @returns {Array<Parse5Content>}
* Transformed children.
*/
function all(children, parentNode, schema) {
let index = -1
/** @type {Array<Parse5Content>} */
const results = []
if (children) {
while (++index < children.length) {
/** @type {Parse5Content} */
const child = one(children[index], schema)
child.parentNode = parentNode
results.push(child)
}
}
return results
}
/**
* Add position info from `from` to `to`.
*
* @param {Nodes} from
* hast node.
* @param {Parse5Nodes} to
* `parse5` node.
* @returns {undefined}
* Nothing.
*/
function patch(from, to) {
const position = from.position
if (position && position.start && position.end) {
assert(typeof position.start.offset === 'number')
assert(typeof position.end.offset === 'number')
to.sourceCodeLocation = {
startLine: position.start.line,
startCol: position.start.column,
startOffset: position.start.offset,
endLine: position.end.line,
endCol: position.end.column,
endOffset: position.end.offset
}
}
}