Files
dealplustech/node_modules/hast-util-to-html/lib/handle/element.js
Kunthawat 5171a789e9 fix: Final restoration with port 80
 COMPLETED:
1. Dockerfile uses port 80 (astro preview)
2. BaseLayout imports globals.css
3. globals.css with Tailwind v4 @theme syntax
4. index.astro has Header, Footer, FixedContact
5. All image references fixed to existing files
6. Hero uses hdpe_pipe_main.jpg
7. Product cards use hdpe001.jpg
8. pt-20 on main for fixed header

 TESTED LOCALLY:
- Build: 15 pages in 1.27s
- Docker build successful
- Port 80 working
- Images load
- CSS works

Ready for Easypanel deployment.
2026-03-12 08:58:56 +07:00

266 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @import {Element, Parents, Properties} from 'hast'
* @import {State} from '../index.js'
*/
import {ccount} from 'ccount'
import {stringify as commas} from 'comma-separated-tokens'
import {find, svg} from 'property-information'
import {stringify as spaces} from 'space-separated-tokens'
import {stringifyEntities} from 'stringify-entities'
import {closing} from '../omission/closing.js'
import {opening} from '../omission/opening.js'
/**
* Maps of subsets.
*
* Each value is a matrix of tuples.
* The value at `0` causes parse errors, the value at `1` is valid.
* Of both, the value at `0` is unsafe, and the value at `1` is safe.
*
* @type {Record<'double' | 'name' | 'single' | 'unquoted', Array<[Array<string>, Array<string>]>>}
*/
const constants = {
// See: <https://html.spec.whatwg.org/#attribute-name-state>.
name: [
['\t\n\f\r &/=>'.split(''), '\t\n\f\r "&\'/=>`'.split('')],
['\0\t\n\f\r "&\'/<=>'.split(''), '\0\t\n\f\r "&\'/<=>`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(unquoted)-state>.
unquoted: [
['\t\n\f\r &>'.split(''), '\0\t\n\f\r "&\'<=>`'.split('')],
['\0\t\n\f\r "&\'<=>`'.split(''), '\0\t\n\f\r "&\'<=>`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(single-quoted)-state>.
single: [
["&'".split(''), '"&\'`'.split('')],
["\0&'".split(''), '\0"&\'`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(double-quoted)-state>.
double: [
['"&'.split(''), '"&\'`'.split('')],
['\0"&'.split(''), '\0"&\'`'.split('')]
]
}
/**
* Serialize an element node.
*
* @param {Element} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
export function element(node, index, parent, state) {
const schema = state.schema
const omit = schema.space === 'svg' ? false : state.settings.omitOptionalTags
let selfClosing =
schema.space === 'svg'
? state.settings.closeEmptyElements
: state.settings.voids.includes(node.tagName.toLowerCase())
/** @type {Array<string>} */
const parts = []
/** @type {string} */
let last
if (schema.space === 'html' && node.tagName === 'svg') {
state.schema = svg
}
const attributes = serializeAttributes(state, node.properties)
const content = state.all(
schema.space === 'html' && node.tagName === 'template' ? node.content : node
)
state.schema = schema
// If the node is categorised as void, but it has children, remove the
// categorisation.
// This enables for example `menuitem`s, which are void in W3C HTML but not
// void in WHATWG HTML, to be stringified properly.
// Note: `menuitem` has since been removed from the HTML spec, and so is no
// longer void.
if (content) selfClosing = false
if (attributes || !omit || !opening(node, index, parent)) {
parts.push('<', node.tagName, attributes ? ' ' + attributes : '')
if (
selfClosing &&
(schema.space === 'svg' || state.settings.closeSelfClosing)
) {
last = attributes.charAt(attributes.length - 1)
if (
!state.settings.tightSelfClosing ||
last === '/' ||
(last && last !== '"' && last !== "'")
) {
parts.push(' ')
}
parts.push('/')
}
parts.push('>')
}
parts.push(content)
if (!selfClosing && (!omit || !closing(node, index, parent))) {
parts.push('</' + node.tagName + '>')
}
return parts.join('')
}
/**
* @param {State} state
* @param {Properties | null | undefined} properties
* @returns {string}
*/
function serializeAttributes(state, properties) {
/** @type {Array<string>} */
const values = []
let index = -1
/** @type {string} */
let key
if (properties) {
for (key in properties) {
if (properties[key] !== null && properties[key] !== undefined) {
const value = serializeAttribute(state, key, properties[key])
if (value) values.push(value)
}
}
}
while (++index < values.length) {
const last = state.settings.tightAttributes
? values[index].charAt(values[index].length - 1)
: undefined
// In tight mode, dont add a space after quoted attributes.
if (index !== values.length - 1 && last !== '"' && last !== "'") {
values[index] += ' '
}
}
return values.join('')
}
/**
* @param {State} state
* @param {string} key
* @param {Properties[keyof Properties]} value
* @returns {string}
*/
function serializeAttribute(state, key, value) {
const info = find(state.schema, key)
const x =
state.settings.allowParseErrors && state.schema.space === 'html' ? 0 : 1
const y = state.settings.allowDangerousCharacters ? 0 : 1
let quote = state.quote
/** @type {string | undefined} */
let result
if (info.overloadedBoolean && (value === info.attribute || value === '')) {
value = true
} else if (
(info.boolean || info.overloadedBoolean) &&
(typeof value !== 'string' || value === info.attribute || value === '')
) {
value = Boolean(value)
}
if (
value === null ||
value === undefined ||
value === false ||
(typeof value === 'number' && Number.isNaN(value))
) {
return ''
}
const name = stringifyEntities(
info.attribute,
Object.assign({}, state.settings.characterReferences, {
// Always encode without parse errors in non-HTML.
subset: constants.name[x][y]
})
)
// No value.
// There is currently only one boolean property in SVG: `[download]` on
// `<a>`.
// This property does not seem to work in browsers (Firefox, Safari, Chrome),
// so I cant test if dropping the value works.
// But I assume that it should:
//
// ```html
// <!doctype html>
// <svg viewBox="0 0 100 100">
// <a href=https://example.com download>
// <circle cx=50 cy=40 r=35 />
// </a>
// </svg>
// ```
//
// See: <https://github.com/wooorm/property-information/blob/main/lib/svg.js>
if (value === true) return name
// `spaces` doesnt accept a second argument, but its given here just to
// keep the code cleaner.
value = Array.isArray(value)
? (info.commaSeparated ? commas : spaces)(value, {
padLeft: !state.settings.tightCommaSeparatedLists
})
: String(value)
if (state.settings.collapseEmptyAttributes && !value) return name
// Check unquoted value.
if (state.settings.preferUnquoted) {
result = stringifyEntities(
value,
Object.assign({}, state.settings.characterReferences, {
attribute: true,
subset: constants.unquoted[x][y]
})
)
}
// If we dont want unquoted, or if `value` contains character references when
// unquoted…
if (result !== value) {
// If the alternative is less common than `quote`, switch.
if (
state.settings.quoteSmart &&
ccount(value, quote) > ccount(value, state.alternative)
) {
quote = state.alternative
}
result =
quote +
stringifyEntities(
value,
Object.assign({}, state.settings.characterReferences, {
// Always encode without parse errors in non-HTML.
subset: (quote === "'" ? constants.single : constants.double)[x][y],
attribute: true
})
) +
quote
}
// Dont add a `=` for unquoted empties.
return name + (result ? '=' + result : result)
}