Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions apps/builder/app/builder/features/settings-panel/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
type ComponentProps,
} from "react";
import equal from "fast-deep-equal";
import { ariaAttributes, attributesByTag } from "@webstudio-is/html-data";
import {
ariaAttributes,
attributesByTag,
elementsByTag,
} from "@webstudio-is/html-data";
import {
reactPropsToStandardAttributes,
showAttribute,
Expand Down Expand Up @@ -465,13 +469,16 @@ export const $selectedInstancePropsMetas = computed(
const propsMetas = new Map<Prop["name"], PropMeta>();
// add html attributes only when instance has tag
if (tag) {
for (const attribute of [...ariaAttributes].reverse()) {
propsMetas.set(attribute.name, attributeToMeta(attribute));
}
if (attributesByTag["*"]) {
for (const attribute of [...attributesByTag["*"]].reverse()) {
if (elementsByTag[tag].categories.includes("html-element")) {
for (const attribute of [...ariaAttributes].reverse()) {
propsMetas.set(attribute.name, attributeToMeta(attribute));
}
// include global attributes only for html elements
if (attributesByTag["*"]) {
for (const attribute of [...attributesByTag["*"]].reverse()) {
propsMetas.set(attribute.name, attributeToMeta(attribute));
}
}
}
if (attributesByTag[tag]) {
for (const attribute of [...attributesByTag[tag]].reverse()) {
Expand Down
94 changes: 83 additions & 11 deletions packages/html-data/bin/attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import {
} from "@webstudio-is/sdk";
import { generateWebstudioComponent } from "@webstudio-is/react-sdk";
import {
findByClasses,
findByTags,
getAttr,
getTextContent,
loadHtmlIndices,
loadSvgSinglePage,
parseHtml,
} from "./crawler";
import { possibleStandardNames } from "./possible-standard-names";
import { ignoredTags } from "./overrides";

const validHtmlAttributes = new Set<string>();

Expand All @@ -28,14 +31,7 @@ type Attribute = {
options?: string[];
};

const overrides: Record<
string,
false | Record<string, false | Partial<Attribute>>
> = {
template: false,
link: false,
script: false,
style: false,
const overrides: Record<string, Record<string, false | Partial<Attribute>>> = {
"*": {
// react has own opinions about it
style: false,
Expand Down Expand Up @@ -215,16 +211,15 @@ for (const row of rows) {
if (/custom elements/i.test(tag)) {
continue;
}
const tagOverride = overrides[tag];
if (tagOverride === false) {
if (ignoredTags.includes(tag)) {
continue;
}
if (!attributesByTag[tag]) {
attributesByTag[tag] = [];
}
const attributes = attributesByTag[tag];
if (!attributes.some((item) => item.name === attribute)) {
const override = tagOverride?.[attribute];
const override = overrides[tag]?.[attribute];
if (override !== false) {
attributes.push({
name: attribute,
Expand All @@ -238,6 +233,83 @@ for (const row of rows) {
}
}

{
const svg = await loadSvgSinglePage();
const document = parseHtml(svg);
const attributeOptions = new Map<string, string[]>();
// find all property definition and extract there keywords
for (const propdef of findByClasses(document, "propdef")) {
let options: undefined | string[];
for (const row of findByTags(propdef, "tr")) {
const [nameNode, valueNode] = row.childNodes;
const name = getTextContent(nameNode);
const list = getTextContent(valueNode)
.trim()
.split(/\s+\|\s+/);
if (
name.toLowerCase().includes("value") &&
list.every((item) => item.match(/^[a-zA-Z-]+$/))
) {
options = list;
}
}
for (const propNameNode of findByClasses(propdef, "propdef-title")) {
const propName = getTextContent(propNameNode).slice(1, -1);
if (options) {
attributeOptions.set(propName, options);
}
}
}

for (const summary of findByClasses(document, "element-summary")) {
const [tag] = findByClasses(summary, "element-summary-name").map((item) =>
getTextContent(item).slice(1, -1)
);
// ignore existing
if (attributesByTag[tag] || ignoredTags.includes(tag)) {
continue;
}
const attributes = new Set<string>();
const [dl] = findByTags(summary, "dl");
for (let index = 0; index < dl.childNodes.length; index += 1) {
const child = dl.childNodes[index];
if (getTextContent(child).toLowerCase().includes("attributes")) {
const dd = dl.childNodes[index + 1];
for (const attrNameNode of findByClasses(dd, "attr-name")) {
const attrName = getTextContent(attrNameNode).slice(1, -1);
// skip events
if (attrName.startsWith("on") || attrName === "style") {
continue;
}
validHtmlAttributes.add(attrName);
attributes.add(attrName);
}
}
}
attributesByTag[tag] = Array.from(attributes)
.sort()
.map((name) => {
let options = attributeOptions.get(name);
if (name === "externalResourcesRequired") {
options = ["true", "false"];
}
if (name === "accumulate") {
options = ["none", "sum"];
}
if (name === "additive") {
options = ["replace", "sum"];
}
if (name === "preserveAlpha") {
options = ["true", "false"];
}
if (options) {
return { name, description: "", type: "select", options };
}
return { name, description: "", type: "string" };
});
}
}

// sort tags and attributes
const tags = Object.keys(attributesByTag).sort();
for (const tag of tags) {
Expand Down
3 changes: 2 additions & 1 deletion packages/html-data/bin/crawler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export const findByClasses = (
if (
"tagName" in node &&
node.attrs.some(
(item) => item.name === "class" && item.value === className
(item) =>
item.name === "class" && item.value.split(/\s+/).includes(className)
)
) {
result.push(node);
Expand Down
24 changes: 14 additions & 10 deletions packages/html-data/bin/elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
loadSvgSinglePage,
parseHtml,
} from "./crawler";
import { ignoredTags } from "./overrides";

// Crawl WHATWG HTML.

Expand Down Expand Up @@ -59,6 +60,9 @@ const elementsByTag: Record<string, Element> = {};
categories.unshift("html-element");
let children = parseList(getTextContent(row.childNodes[4]));
for (const tag of elements) {
if (ignoredTags.includes(tag)) {
continue;
}
// textarea does not have value attribute and text content is used as initial value
// introduce fake value attribute to manage initial state similar to input
if (tag === "textarea") {
Expand Down Expand Up @@ -86,9 +90,12 @@ const elementsByTag: Record<string, Element> = {};
const document = parseHtml(svg);
const summaries = findByClasses(document, "element-summary");
for (const summary of summaries) {
const [name] = findByClasses(summary, "element-summary-name").map((item) =>
const [tag] = findByClasses(summary, "element-summary-name").map((item) =>
getTextContent(item).slice(1, -1)
);
if (ignoredTags.includes(tag)) {
continue;
}
const children: string[] = [];
const [dl] = findByTags(summary, "dl");
for (let index = 0; index < dl.childNodes.length; index += 1) {
Expand All @@ -100,13 +107,13 @@ const elementsByTag: Record<string, Element> = {};
}
}
}
if (elementsByTag[name]) {
console.info(`${name} element from SVG specification is skipped`);
if (elementsByTag[tag]) {
console.info(`${tag} element from SVG specification is skipped`);
continue;
}
const categories = name === "svg" ? ["flow", "phrasing"] : ["none"];
categories.unshift("svg-element");
elementsByTag[name] = {
const categories = tag === "svg" ? ["flow", "phrasing"] : ["none"];
categories.unshift(tag === "svg" ? "html-element" : "svg-element");
elementsByTag[tag] = {
description: "",
categories,
children,
Expand All @@ -127,10 +134,7 @@ await mkdir(dirname(contentModelFile), { recursive: true });
await writeFile(contentModelFile, contentModel);

const tags: string[] = [];
for (const [tag, element] of Object.entries(elementsByTag)) {
if (element.categories.includes("metadata")) {
continue;
}
for (const tag of Object.keys(elementsByTag)) {
tags.push(tag);
}
const getTagScore = (tag: string) => {
Expand Down
28 changes: 28 additions & 0 deletions packages/html-data/bin/overrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const ignoredTags: string[] = [
"base",
"template",
"meta",
"noscript",
"link",
"script",
"style",
"title",
"glyph",
"glyphRef",
"altGlyph",
"altGlyphDef",
"altGlyphItem",
"animateColor",
"color-profile",
"missing-glyph",
"vkern",
"hkern",
"cursor",
"tref",
"font",
"font-face",
"font-face-format",
"font-face-name",
"font-face-src",
"font-face-uri",
];
Loading