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.
551 lines
13 KiB
JavaScript
551 lines
13 KiB
JavaScript
'use strict'
|
|
|
|
let Container = require('./container')
|
|
let Document = require('./document')
|
|
let MapGenerator = require('./map-generator')
|
|
let parse = require('./parse')
|
|
let Result = require('./result')
|
|
let Root = require('./root')
|
|
let stringify = require('./stringify')
|
|
let { isClean, my } = require('./symbols')
|
|
let warnOnce = require('./warn-once')
|
|
|
|
const TYPE_TO_CLASS_NAME = {
|
|
atrule: 'AtRule',
|
|
comment: 'Comment',
|
|
decl: 'Declaration',
|
|
document: 'Document',
|
|
root: 'Root',
|
|
rule: 'Rule'
|
|
}
|
|
|
|
const PLUGIN_PROPS = {
|
|
AtRule: true,
|
|
AtRuleExit: true,
|
|
Comment: true,
|
|
CommentExit: true,
|
|
Declaration: true,
|
|
DeclarationExit: true,
|
|
Document: true,
|
|
DocumentExit: true,
|
|
Once: true,
|
|
OnceExit: true,
|
|
postcssPlugin: true,
|
|
prepare: true,
|
|
Root: true,
|
|
RootExit: true,
|
|
Rule: true,
|
|
RuleExit: true
|
|
}
|
|
|
|
const NOT_VISITORS = {
|
|
Once: true,
|
|
postcssPlugin: true,
|
|
prepare: true
|
|
}
|
|
|
|
const CHILDREN = 0
|
|
|
|
function isPromise(obj) {
|
|
return typeof obj === 'object' && typeof obj.then === 'function'
|
|
}
|
|
|
|
function getEvents(node) {
|
|
let key = false
|
|
let type = TYPE_TO_CLASS_NAME[node.type]
|
|
if (node.type === 'decl') {
|
|
key = node.prop.toLowerCase()
|
|
} else if (node.type === 'atrule') {
|
|
key = node.name.toLowerCase()
|
|
}
|
|
|
|
if (key && node.append) {
|
|
return [
|
|
type,
|
|
type + '-' + key,
|
|
CHILDREN,
|
|
type + 'Exit',
|
|
type + 'Exit-' + key
|
|
]
|
|
} else if (key) {
|
|
return [type, type + '-' + key, type + 'Exit', type + 'Exit-' + key]
|
|
} else if (node.append) {
|
|
return [type, CHILDREN, type + 'Exit']
|
|
} else {
|
|
return [type, type + 'Exit']
|
|
}
|
|
}
|
|
|
|
function toStack(node) {
|
|
let events
|
|
if (node.type === 'document') {
|
|
events = ['Document', CHILDREN, 'DocumentExit']
|
|
} else if (node.type === 'root') {
|
|
events = ['Root', CHILDREN, 'RootExit']
|
|
} else {
|
|
events = getEvents(node)
|
|
}
|
|
|
|
return {
|
|
eventIndex: 0,
|
|
events,
|
|
iterator: 0,
|
|
node,
|
|
visitorIndex: 0,
|
|
visitors: []
|
|
}
|
|
}
|
|
|
|
function cleanMarks(node) {
|
|
node[isClean] = false
|
|
if (node.nodes) node.nodes.forEach(i => cleanMarks(i))
|
|
return node
|
|
}
|
|
|
|
let postcss = {}
|
|
|
|
class LazyResult {
|
|
get content() {
|
|
return this.stringify().content
|
|
}
|
|
|
|
get css() {
|
|
return this.stringify().css
|
|
}
|
|
|
|
get map() {
|
|
return this.stringify().map
|
|
}
|
|
|
|
get messages() {
|
|
return this.sync().messages
|
|
}
|
|
|
|
get opts() {
|
|
return this.result.opts
|
|
}
|
|
|
|
get processor() {
|
|
return this.result.processor
|
|
}
|
|
|
|
get root() {
|
|
return this.sync().root
|
|
}
|
|
|
|
get [Symbol.toStringTag]() {
|
|
return 'LazyResult'
|
|
}
|
|
|
|
constructor(processor, css, opts) {
|
|
this.stringified = false
|
|
this.processed = false
|
|
|
|
let root
|
|
if (
|
|
typeof css === 'object' &&
|
|
css !== null &&
|
|
(css.type === 'root' || css.type === 'document')
|
|
) {
|
|
root = cleanMarks(css)
|
|
} else if (css instanceof LazyResult || css instanceof Result) {
|
|
root = cleanMarks(css.root)
|
|
if (css.map) {
|
|
if (typeof opts.map === 'undefined') opts.map = {}
|
|
if (!opts.map.inline) opts.map.inline = false
|
|
opts.map.prev = css.map
|
|
}
|
|
} else {
|
|
let parser = parse
|
|
if (opts.syntax) parser = opts.syntax.parse
|
|
if (opts.parser) parser = opts.parser
|
|
if (parser.parse) parser = parser.parse
|
|
|
|
try {
|
|
root = parser(css, opts)
|
|
} catch (error) {
|
|
this.processed = true
|
|
this.error = error
|
|
}
|
|
|
|
if (root && !root[my]) {
|
|
/* c8 ignore next 2 */
|
|
Container.rebuild(root)
|
|
}
|
|
}
|
|
|
|
this.result = new Result(processor, root, opts)
|
|
this.helpers = { ...postcss, postcss, result: this.result }
|
|
this.plugins = this.processor.plugins.map(plugin => {
|
|
if (typeof plugin === 'object' && plugin.prepare) {
|
|
return { ...plugin, ...plugin.prepare(this.result) }
|
|
} else {
|
|
return plugin
|
|
}
|
|
})
|
|
}
|
|
|
|
async() {
|
|
if (this.error) return Promise.reject(this.error)
|
|
if (this.processed) return Promise.resolve(this.result)
|
|
if (!this.processing) {
|
|
this.processing = this.runAsync()
|
|
}
|
|
return this.processing
|
|
}
|
|
|
|
catch(onRejected) {
|
|
return this.async().catch(onRejected)
|
|
}
|
|
|
|
finally(onFinally) {
|
|
return this.async().then(onFinally, onFinally)
|
|
}
|
|
|
|
getAsyncError() {
|
|
throw new Error('Use process(css).then(cb) to work with async plugins')
|
|
}
|
|
|
|
handleError(error, node) {
|
|
let plugin = this.result.lastPlugin
|
|
try {
|
|
if (node) node.addToError(error)
|
|
this.error = error
|
|
if (error.name === 'CssSyntaxError' && !error.plugin) {
|
|
error.plugin = plugin.postcssPlugin
|
|
error.setMessage()
|
|
} else if (plugin.postcssVersion) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
let pluginName = plugin.postcssPlugin
|
|
let pluginVer = plugin.postcssVersion
|
|
let runtimeVer = this.result.processor.version
|
|
let a = pluginVer.split('.')
|
|
let b = runtimeVer.split('.')
|
|
|
|
if (a[0] !== b[0] || parseInt(a[1]) > parseInt(b[1])) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(
|
|
'Unknown error from PostCSS plugin. Your current PostCSS ' +
|
|
'version is ' +
|
|
runtimeVer +
|
|
', but ' +
|
|
pluginName +
|
|
' uses ' +
|
|
pluginVer +
|
|
'. Perhaps this is the source of the error below.'
|
|
)
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
/* c8 ignore next 3 */
|
|
// eslint-disable-next-line no-console
|
|
if (console && console.error) console.error(err)
|
|
}
|
|
return error
|
|
}
|
|
|
|
prepareVisitors() {
|
|
this.listeners = {}
|
|
let add = (plugin, type, cb) => {
|
|
if (!this.listeners[type]) this.listeners[type] = []
|
|
this.listeners[type].push([plugin, cb])
|
|
}
|
|
for (let plugin of this.plugins) {
|
|
if (typeof plugin === 'object') {
|
|
for (let event in plugin) {
|
|
if (!PLUGIN_PROPS[event] && /^[A-Z]/.test(event)) {
|
|
throw new Error(
|
|
`Unknown event ${event} in ${plugin.postcssPlugin}. ` +
|
|
`Try to update PostCSS (${this.processor.version} now).`
|
|
)
|
|
}
|
|
if (!NOT_VISITORS[event]) {
|
|
if (typeof plugin[event] === 'object') {
|
|
for (let filter in plugin[event]) {
|
|
if (filter === '*') {
|
|
add(plugin, event, plugin[event][filter])
|
|
} else {
|
|
add(
|
|
plugin,
|
|
event + '-' + filter.toLowerCase(),
|
|
plugin[event][filter]
|
|
)
|
|
}
|
|
}
|
|
} else if (typeof plugin[event] === 'function') {
|
|
add(plugin, event, plugin[event])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.hasListener = Object.keys(this.listeners).length > 0
|
|
}
|
|
|
|
async runAsync() {
|
|
this.plugin = 0
|
|
for (let i = 0; i < this.plugins.length; i++) {
|
|
let plugin = this.plugins[i]
|
|
let promise = this.runOnRoot(plugin)
|
|
if (isPromise(promise)) {
|
|
try {
|
|
await promise
|
|
} catch (error) {
|
|
throw this.handleError(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
this.prepareVisitors()
|
|
if (this.hasListener) {
|
|
let root = this.result.root
|
|
while (!root[isClean]) {
|
|
root[isClean] = true
|
|
let stack = [toStack(root)]
|
|
while (stack.length > 0) {
|
|
let promise = this.visitTick(stack)
|
|
if (isPromise(promise)) {
|
|
try {
|
|
await promise
|
|
} catch (e) {
|
|
let node = stack[stack.length - 1].node
|
|
throw this.handleError(e, node)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.listeners.OnceExit) {
|
|
for (let [plugin, visitor] of this.listeners.OnceExit) {
|
|
this.result.lastPlugin = plugin
|
|
try {
|
|
if (root.type === 'document') {
|
|
let roots = root.nodes.map(subRoot =>
|
|
visitor(subRoot, this.helpers)
|
|
)
|
|
|
|
await Promise.all(roots)
|
|
} else {
|
|
await visitor(root, this.helpers)
|
|
}
|
|
} catch (e) {
|
|
throw this.handleError(e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.processed = true
|
|
return this.stringify()
|
|
}
|
|
|
|
runOnRoot(plugin) {
|
|
this.result.lastPlugin = plugin
|
|
try {
|
|
if (typeof plugin === 'object' && plugin.Once) {
|
|
if (this.result.root.type === 'document') {
|
|
let roots = this.result.root.nodes.map(root =>
|
|
plugin.Once(root, this.helpers)
|
|
)
|
|
|
|
if (isPromise(roots[0])) {
|
|
return Promise.all(roots)
|
|
}
|
|
|
|
return roots
|
|
}
|
|
|
|
return plugin.Once(this.result.root, this.helpers)
|
|
} else if (typeof plugin === 'function') {
|
|
return plugin(this.result.root, this.result)
|
|
}
|
|
} catch (error) {
|
|
throw this.handleError(error)
|
|
}
|
|
}
|
|
|
|
stringify() {
|
|
if (this.error) throw this.error
|
|
if (this.stringified) return this.result
|
|
this.stringified = true
|
|
|
|
this.sync()
|
|
|
|
let opts = this.result.opts
|
|
let str = stringify
|
|
if (opts.syntax) str = opts.syntax.stringify
|
|
if (opts.stringifier) str = opts.stringifier
|
|
if (str.stringify) str = str.stringify
|
|
|
|
let map = new MapGenerator(str, this.result.root, this.result.opts)
|
|
let data = map.generate()
|
|
this.result.css = data[0]
|
|
this.result.map = data[1]
|
|
|
|
return this.result
|
|
}
|
|
|
|
sync() {
|
|
if (this.error) throw this.error
|
|
if (this.processed) return this.result
|
|
this.processed = true
|
|
|
|
if (this.processing) {
|
|
throw this.getAsyncError()
|
|
}
|
|
|
|
for (let plugin of this.plugins) {
|
|
let promise = this.runOnRoot(plugin)
|
|
if (isPromise(promise)) {
|
|
throw this.getAsyncError()
|
|
}
|
|
}
|
|
|
|
this.prepareVisitors()
|
|
if (this.hasListener) {
|
|
let root = this.result.root
|
|
while (!root[isClean]) {
|
|
root[isClean] = true
|
|
this.walkSync(root)
|
|
}
|
|
if (this.listeners.OnceExit) {
|
|
if (root.type === 'document') {
|
|
for (let subRoot of root.nodes) {
|
|
this.visitSync(this.listeners.OnceExit, subRoot)
|
|
}
|
|
} else {
|
|
this.visitSync(this.listeners.OnceExit, root)
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.result
|
|
}
|
|
|
|
then(onFulfilled, onRejected) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
if (!('from' in this.opts)) {
|
|
warnOnce(
|
|
'Without `from` option PostCSS could generate wrong source map ' +
|
|
'and will not find Browserslist config. Set it to CSS file path ' +
|
|
'or to `undefined` to prevent this warning.'
|
|
)
|
|
}
|
|
}
|
|
return this.async().then(onFulfilled, onRejected)
|
|
}
|
|
|
|
toString() {
|
|
return this.css
|
|
}
|
|
|
|
visitSync(visitors, node) {
|
|
for (let [plugin, visitor] of visitors) {
|
|
this.result.lastPlugin = plugin
|
|
let promise
|
|
try {
|
|
promise = visitor(node, this.helpers)
|
|
} catch (e) {
|
|
throw this.handleError(e, node.proxyOf)
|
|
}
|
|
if (node.type !== 'root' && node.type !== 'document' && !node.parent) {
|
|
return true
|
|
}
|
|
if (isPromise(promise)) {
|
|
throw this.getAsyncError()
|
|
}
|
|
}
|
|
}
|
|
|
|
visitTick(stack) {
|
|
let visit = stack[stack.length - 1]
|
|
let { node, visitors } = visit
|
|
|
|
if (node.type !== 'root' && node.type !== 'document' && !node.parent) {
|
|
stack.pop()
|
|
return
|
|
}
|
|
|
|
if (visitors.length > 0 && visit.visitorIndex < visitors.length) {
|
|
let [plugin, visitor] = visitors[visit.visitorIndex]
|
|
visit.visitorIndex += 1
|
|
if (visit.visitorIndex === visitors.length) {
|
|
visit.visitors = []
|
|
visit.visitorIndex = 0
|
|
}
|
|
this.result.lastPlugin = plugin
|
|
try {
|
|
return visitor(node.toProxy(), this.helpers)
|
|
} catch (e) {
|
|
throw this.handleError(e, node)
|
|
}
|
|
}
|
|
|
|
if (visit.iterator !== 0) {
|
|
let iterator = visit.iterator
|
|
let child
|
|
while ((child = node.nodes[node.indexes[iterator]])) {
|
|
node.indexes[iterator] += 1
|
|
if (!child[isClean]) {
|
|
child[isClean] = true
|
|
stack.push(toStack(child))
|
|
return
|
|
}
|
|
}
|
|
visit.iterator = 0
|
|
delete node.indexes[iterator]
|
|
}
|
|
|
|
let events = visit.events
|
|
while (visit.eventIndex < events.length) {
|
|
let event = events[visit.eventIndex]
|
|
visit.eventIndex += 1
|
|
if (event === CHILDREN) {
|
|
if (node.nodes && node.nodes.length) {
|
|
node[isClean] = true
|
|
visit.iterator = node.getIterator()
|
|
}
|
|
return
|
|
} else if (this.listeners[event]) {
|
|
visit.visitors = this.listeners[event]
|
|
return
|
|
}
|
|
}
|
|
stack.pop()
|
|
}
|
|
|
|
walkSync(node) {
|
|
node[isClean] = true
|
|
let events = getEvents(node)
|
|
for (let event of events) {
|
|
if (event === CHILDREN) {
|
|
if (node.nodes) {
|
|
node.each(child => {
|
|
if (!child[isClean]) this.walkSync(child)
|
|
})
|
|
}
|
|
} else {
|
|
let visitors = this.listeners[event]
|
|
if (visitors) {
|
|
if (this.visitSync(visitors, node.toProxy())) return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
warnings() {
|
|
return this.sync().warnings()
|
|
}
|
|
}
|
|
|
|
LazyResult.registerPostcss = dependant => {
|
|
postcss = dependant
|
|
}
|
|
|
|
module.exports = LazyResult
|
|
LazyResult.default = LazyResult
|
|
|
|
Root.registerLazyResult(LazyResult)
|
|
Document.registerLazyResult(LazyResult)
|