forked from FINAKON/HelpProject
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.
644 lines
17 KiB
JavaScript
644 lines
17 KiB
JavaScript
/**
|
||
* @import {Node, Point, Position} from 'unist'
|
||
* @import {Options as MessageOptions} from 'vfile-message'
|
||
* @import {Compatible, Data, Map, Options, Value} from 'vfile'
|
||
*/
|
||
|
||
/**
|
||
* @typedef {object & {type: string, position?: Position | undefined}} NodeLike
|
||
*/
|
||
|
||
import {VFileMessage} from 'vfile-message'
|
||
import {minpath} from '#minpath'
|
||
import {minproc} from '#minproc'
|
||
import {urlToPath, isUrl} from '#minurl'
|
||
|
||
/**
|
||
* Order of setting (least specific to most), we need this because otherwise
|
||
* `{stem: 'a', path: '~/b.js'}` would throw, as a path is needed before a
|
||
* stem can be set.
|
||
*/
|
||
const order = /** @type {const} */ ([
|
||
'history',
|
||
'path',
|
||
'basename',
|
||
'stem',
|
||
'extname',
|
||
'dirname'
|
||
])
|
||
|
||
export class VFile {
|
||
/**
|
||
* Create a new virtual file.
|
||
*
|
||
* `options` is treated as:
|
||
*
|
||
* * `string` or `Uint8Array` — `{value: options}`
|
||
* * `URL` — `{path: options}`
|
||
* * `VFile` — shallow copies its data over to the new file
|
||
* * `object` — all fields are shallow copied over to the new file
|
||
*
|
||
* Path related fields are set in the following order (least specific to
|
||
* most specific): `history`, `path`, `basename`, `stem`, `extname`,
|
||
* `dirname`.
|
||
*
|
||
* You cannot set `dirname` or `extname` without setting either `history`,
|
||
* `path`, `basename`, or `stem` too.
|
||
*
|
||
* @param {Compatible | null | undefined} [value]
|
||
* File value.
|
||
* @returns
|
||
* New instance.
|
||
*/
|
||
constructor(value) {
|
||
/** @type {Options | VFile} */
|
||
let options
|
||
|
||
if (!value) {
|
||
options = {}
|
||
} else if (isUrl(value)) {
|
||
options = {path: value}
|
||
} else if (typeof value === 'string' || isUint8Array(value)) {
|
||
options = {value}
|
||
} else {
|
||
options = value
|
||
}
|
||
|
||
/* eslint-disable no-unused-expressions */
|
||
|
||
/**
|
||
* Base of `path` (default: `process.cwd()` or `'/'` in browsers).
|
||
*
|
||
* @type {string}
|
||
*/
|
||
// Prevent calling `cwd` (which could be expensive) if it’s not needed;
|
||
// the empty string will be overridden in the next block.
|
||
this.cwd = 'cwd' in options ? '' : minproc.cwd()
|
||
|
||
/**
|
||
* Place to store custom info (default: `{}`).
|
||
*
|
||
* It’s OK to store custom data directly on the file but moving it to
|
||
* `data` is recommended.
|
||
*
|
||
* @type {Data}
|
||
*/
|
||
this.data = {}
|
||
|
||
/**
|
||
* List of file paths the file moved between.
|
||
*
|
||
* The first is the original path and the last is the current path.
|
||
*
|
||
* @type {Array<string>}
|
||
*/
|
||
this.history = []
|
||
|
||
/**
|
||
* List of messages associated with the file.
|
||
*
|
||
* @type {Array<VFileMessage>}
|
||
*/
|
||
this.messages = []
|
||
|
||
/**
|
||
* Raw value.
|
||
*
|
||
* @type {Value}
|
||
*/
|
||
this.value
|
||
|
||
// The below are non-standard, they are “well-known”.
|
||
// As in, used in several tools.
|
||
/**
|
||
* Source map.
|
||
*
|
||
* This type is equivalent to the `RawSourceMap` type from the `source-map`
|
||
* module.
|
||
*
|
||
* @type {Map | null | undefined}
|
||
*/
|
||
this.map
|
||
|
||
/**
|
||
* Custom, non-string, compiled, representation.
|
||
*
|
||
* This is used by unified to store non-string results.
|
||
* One example is when turning markdown into React nodes.
|
||
*
|
||
* @type {unknown}
|
||
*/
|
||
this.result
|
||
|
||
/**
|
||
* Whether a file was saved to disk.
|
||
*
|
||
* This is used by vfile reporters.
|
||
*
|
||
* @type {boolean}
|
||
*/
|
||
this.stored
|
||
/* eslint-enable no-unused-expressions */
|
||
|
||
// Set path related properties in the correct order.
|
||
let index = -1
|
||
|
||
while (++index < order.length) {
|
||
const field = order[index]
|
||
|
||
// Note: we specifically use `in` instead of `hasOwnProperty` to accept
|
||
// `vfile`s too.
|
||
if (
|
||
field in options &&
|
||
options[field] !== undefined &&
|
||
options[field] !== null
|
||
) {
|
||
// @ts-expect-error: TS doesn’t understand basic reality.
|
||
this[field] = field === 'history' ? [...options[field]] : options[field]
|
||
}
|
||
}
|
||
|
||
/** @type {string} */
|
||
let field
|
||
|
||
// Set non-path related properties.
|
||
for (field in options) {
|
||
// @ts-expect-error: fine to set other things.
|
||
if (!order.includes(field)) {
|
||
// @ts-expect-error: fine to set other things.
|
||
this[field] = options[field]
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get the basename (including extname) (example: `'index.min.js'`).
|
||
*
|
||
* @returns {string | undefined}
|
||
* Basename.
|
||
*/
|
||
get basename() {
|
||
return typeof this.path === 'string'
|
||
? minpath.basename(this.path)
|
||
: undefined
|
||
}
|
||
|
||
/**
|
||
* Set basename (including extname) (`'index.min.js'`).
|
||
*
|
||
* Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
|
||
* on windows).
|
||
* Cannot be nullified (use `file.path = file.dirname` instead).
|
||
*
|
||
* @param {string} basename
|
||
* Basename.
|
||
* @returns {undefined}
|
||
* Nothing.
|
||
*/
|
||
set basename(basename) {
|
||
assertNonEmpty(basename, 'basename')
|
||
assertPart(basename, 'basename')
|
||
this.path = minpath.join(this.dirname || '', basename)
|
||
}
|
||
|
||
/**
|
||
* Get the parent path (example: `'~'`).
|
||
*
|
||
* @returns {string | undefined}
|
||
* Dirname.
|
||
*/
|
||
get dirname() {
|
||
return typeof this.path === 'string'
|
||
? minpath.dirname(this.path)
|
||
: undefined
|
||
}
|
||
|
||
/**
|
||
* Set the parent path (example: `'~'`).
|
||
*
|
||
* Cannot be set if there’s no `path` yet.
|
||
*
|
||
* @param {string | undefined} dirname
|
||
* Dirname.
|
||
* @returns {undefined}
|
||
* Nothing.
|
||
*/
|
||
set dirname(dirname) {
|
||
assertPath(this.basename, 'dirname')
|
||
this.path = minpath.join(dirname || '', this.basename)
|
||
}
|
||
|
||
/**
|
||
* Get the extname (including dot) (example: `'.js'`).
|
||
*
|
||
* @returns {string | undefined}
|
||
* Extname.
|
||
*/
|
||
get extname() {
|
||
return typeof this.path === 'string'
|
||
? minpath.extname(this.path)
|
||
: undefined
|
||
}
|
||
|
||
/**
|
||
* Set the extname (including dot) (example: `'.js'`).
|
||
*
|
||
* Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
|
||
* on windows).
|
||
* Cannot be set if there’s no `path` yet.
|
||
*
|
||
* @param {string | undefined} extname
|
||
* Extname.
|
||
* @returns {undefined}
|
||
* Nothing.
|
||
*/
|
||
set extname(extname) {
|
||
assertPart(extname, 'extname')
|
||
assertPath(this.dirname, 'extname')
|
||
|
||
if (extname) {
|
||
if (extname.codePointAt(0) !== 46 /* `.` */) {
|
||
throw new Error('`extname` must start with `.`')
|
||
}
|
||
|
||
if (extname.includes('.', 1)) {
|
||
throw new Error('`extname` cannot contain multiple dots')
|
||
}
|
||
}
|
||
|
||
this.path = minpath.join(this.dirname, this.stem + (extname || ''))
|
||
}
|
||
|
||
/**
|
||
* Get the full path (example: `'~/index.min.js'`).
|
||
*
|
||
* @returns {string}
|
||
* Path.
|
||
*/
|
||
get path() {
|
||
return this.history[this.history.length - 1]
|
||
}
|
||
|
||
/**
|
||
* Set the full path (example: `'~/index.min.js'`).
|
||
*
|
||
* Cannot be nullified.
|
||
* You can set a file URL (a `URL` object with a `file:` protocol) which will
|
||
* be turned into a path with `url.fileURLToPath`.
|
||
*
|
||
* @param {URL | string} path
|
||
* Path.
|
||
* @returns {undefined}
|
||
* Nothing.
|
||
*/
|
||
set path(path) {
|
||
if (isUrl(path)) {
|
||
path = urlToPath(path)
|
||
}
|
||
|
||
assertNonEmpty(path, 'path')
|
||
|
||
if (this.path !== path) {
|
||
this.history.push(path)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get the stem (basename w/o extname) (example: `'index.min'`).
|
||
*
|
||
* @returns {string | undefined}
|
||
* Stem.
|
||
*/
|
||
get stem() {
|
||
return typeof this.path === 'string'
|
||
? minpath.basename(this.path, this.extname)
|
||
: undefined
|
||
}
|
||
|
||
/**
|
||
* Set the stem (basename w/o extname) (example: `'index.min'`).
|
||
*
|
||
* Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
|
||
* on windows).
|
||
* Cannot be nullified (use `file.path = file.dirname` instead).
|
||
*
|
||
* @param {string} stem
|
||
* Stem.
|
||
* @returns {undefined}
|
||
* Nothing.
|
||
*/
|
||
set stem(stem) {
|
||
assertNonEmpty(stem, 'stem')
|
||
assertPart(stem, 'stem')
|
||
this.path = minpath.join(this.dirname || '', stem + (this.extname || ''))
|
||
}
|
||
|
||
// Normal prototypal methods.
|
||
/**
|
||
* Create a fatal message for `reason` associated with the file.
|
||
*
|
||
* The `fatal` field of the message is set to `true` (error; file not usable)
|
||
* and the `file` field is set to the current file path.
|
||
* The message is added to the `messages` field on `file`.
|
||
*
|
||
* > 🪦 **Note**: also has obsolete signatures.
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {MessageOptions | null | undefined} [options]
|
||
* @returns {never}
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {Node | NodeLike | null | undefined} parent
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {never}
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {Point | Position | null | undefined} place
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {never}
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {never}
|
||
*
|
||
* @overload
|
||
* @param {Error | VFileMessage} cause
|
||
* @param {Node | NodeLike | null | undefined} parent
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {never}
|
||
*
|
||
* @overload
|
||
* @param {Error | VFileMessage} cause
|
||
* @param {Point | Position | null | undefined} place
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {never}
|
||
*
|
||
* @overload
|
||
* @param {Error | VFileMessage} cause
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {never}
|
||
*
|
||
* @param {Error | VFileMessage | string} causeOrReason
|
||
* Reason for message, should use markdown.
|
||
* @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
|
||
* Configuration (optional).
|
||
* @param {string | null | undefined} [origin]
|
||
* Place in code where the message originates (example:
|
||
* `'my-package:my-rule'` or `'my-rule'`).
|
||
* @returns {never}
|
||
* Never.
|
||
* @throws {VFileMessage}
|
||
* Message.
|
||
*/
|
||
fail(causeOrReason, optionsOrParentOrPlace, origin) {
|
||
// @ts-expect-error: the overloads are fine.
|
||
const message = this.message(causeOrReason, optionsOrParentOrPlace, origin)
|
||
|
||
message.fatal = true
|
||
|
||
throw message
|
||
}
|
||
|
||
/**
|
||
* Create an info message for `reason` associated with the file.
|
||
*
|
||
* The `fatal` field of the message is set to `undefined` (info; change
|
||
* likely not needed) and the `file` field is set to the current file path.
|
||
* The message is added to the `messages` field on `file`.
|
||
*
|
||
* > 🪦 **Note**: also has obsolete signatures.
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {MessageOptions | null | undefined} [options]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {Node | NodeLike | null | undefined} parent
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {Point | Position | null | undefined} place
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {Error | VFileMessage} cause
|
||
* @param {Node | NodeLike | null | undefined} parent
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {Error | VFileMessage} cause
|
||
* @param {Point | Position | null | undefined} place
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {Error | VFileMessage} cause
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @param {Error | VFileMessage | string} causeOrReason
|
||
* Reason for message, should use markdown.
|
||
* @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
|
||
* Configuration (optional).
|
||
* @param {string | null | undefined} [origin]
|
||
* Place in code where the message originates (example:
|
||
* `'my-package:my-rule'` or `'my-rule'`).
|
||
* @returns {VFileMessage}
|
||
* Message.
|
||
*/
|
||
info(causeOrReason, optionsOrParentOrPlace, origin) {
|
||
// @ts-expect-error: the overloads are fine.
|
||
const message = this.message(causeOrReason, optionsOrParentOrPlace, origin)
|
||
|
||
message.fatal = undefined
|
||
|
||
return message
|
||
}
|
||
|
||
/**
|
||
* Create a message for `reason` associated with the file.
|
||
*
|
||
* The `fatal` field of the message is set to `false` (warning; change may be
|
||
* needed) and the `file` field is set to the current file path.
|
||
* The message is added to the `messages` field on `file`.
|
||
*
|
||
* > 🪦 **Note**: also has obsolete signatures.
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {MessageOptions | null | undefined} [options]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {Node | NodeLike | null | undefined} parent
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {Point | Position | null | undefined} place
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {string} reason
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {Error | VFileMessage} cause
|
||
* @param {Node | NodeLike | null | undefined} parent
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {Error | VFileMessage} cause
|
||
* @param {Point | Position | null | undefined} place
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @overload
|
||
* @param {Error | VFileMessage} cause
|
||
* @param {string | null | undefined} [origin]
|
||
* @returns {VFileMessage}
|
||
*
|
||
* @param {Error | VFileMessage | string} causeOrReason
|
||
* Reason for message, should use markdown.
|
||
* @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
|
||
* Configuration (optional).
|
||
* @param {string | null | undefined} [origin]
|
||
* Place in code where the message originates (example:
|
||
* `'my-package:my-rule'` or `'my-rule'`).
|
||
* @returns {VFileMessage}
|
||
* Message.
|
||
*/
|
||
message(causeOrReason, optionsOrParentOrPlace, origin) {
|
||
const message = new VFileMessage(
|
||
// @ts-expect-error: the overloads are fine.
|
||
causeOrReason,
|
||
optionsOrParentOrPlace,
|
||
origin
|
||
)
|
||
|
||
if (this.path) {
|
||
message.name = this.path + ':' + message.name
|
||
message.file = this.path
|
||
}
|
||
|
||
message.fatal = false
|
||
|
||
this.messages.push(message)
|
||
|
||
return message
|
||
}
|
||
|
||
/**
|
||
* Serialize the file.
|
||
*
|
||
* > **Note**: which encodings are supported depends on the engine.
|
||
* > For info on Node.js, see:
|
||
* > <https://nodejs.org/api/util.html#whatwg-supported-encodings>.
|
||
*
|
||
* @param {string | null | undefined} [encoding='utf8']
|
||
* Character encoding to understand `value` as when it’s a `Uint8Array`
|
||
* (default: `'utf-8'`).
|
||
* @returns {string}
|
||
* Serialized file.
|
||
*/
|
||
toString(encoding) {
|
||
if (this.value === undefined) {
|
||
return ''
|
||
}
|
||
|
||
if (typeof this.value === 'string') {
|
||
return this.value
|
||
}
|
||
|
||
const decoder = new TextDecoder(encoding || undefined)
|
||
return decoder.decode(this.value)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Assert that `part` is not a path (as in, does not contain `path.sep`).
|
||
*
|
||
* @param {string | null | undefined} part
|
||
* File path part.
|
||
* @param {string} name
|
||
* Part name.
|
||
* @returns {undefined}
|
||
* Nothing.
|
||
*/
|
||
function assertPart(part, name) {
|
||
if (part && part.includes(minpath.sep)) {
|
||
throw new Error(
|
||
'`' + name + '` cannot be a path: did not expect `' + minpath.sep + '`'
|
||
)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Assert that `part` is not empty.
|
||
*
|
||
* @param {string | undefined} part
|
||
* Thing.
|
||
* @param {string} name
|
||
* Part name.
|
||
* @returns {asserts part is string}
|
||
* Nothing.
|
||
*/
|
||
function assertNonEmpty(part, name) {
|
||
if (!part) {
|
||
throw new Error('`' + name + '` cannot be empty')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Assert `path` exists.
|
||
*
|
||
* @param {string | undefined} path
|
||
* Path.
|
||
* @param {string} name
|
||
* Dependency name.
|
||
* @returns {asserts path is string}
|
||
* Nothing.
|
||
*/
|
||
function assertPath(path, name) {
|
||
if (!path) {
|
||
throw new Error('Setting `' + name + '` requires `path` to be set too')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Assert `value` is an `Uint8Array`.
|
||
*
|
||
* @param {unknown} value
|
||
* thing.
|
||
* @returns {value is Uint8Array}
|
||
* Whether `value` is an `Uint8Array`.
|
||
*/
|
||
function isUint8Array(value) {
|
||
return Boolean(
|
||
value &&
|
||
typeof value === 'object' &&
|
||
'byteLength' in value &&
|
||
'byteOffset' in value
|
||
)
|
||
}
|