remove addClass, add h

author
Marijn Besseling <njirambem@gmail.com> · 2026-07-07 15:02 UTC
commit
093ed2adcbdd8855dad0f5cd158a79d5e7ff1420
parent
34a563285b
tree
browse at this commit

10 files changed +300 -174

.run/Publish Blog to custom server.run.xml +8 -2

@@ -1,6 +1,12 @@
1 1 <component name="ProjectRunConfigurationManager">
2 2 <configuration default="false" name="Publish Blog to custom server" type="DotNetPublishAndUpload" factoryName="Publish to custom server">
3 - <riderPublish configuration="Release" platform="Any CPU" ready_to_run="true" runtime="linux-x64" server_id="f1ec4348-1f2d-4563-86c5-2d9f5df54746" serverName="Bes.is" target_framework="net10.0" uuid_high="6481978729836661993" uuid_low="-7312154749475505895" />
4 - <method v="2" />
3 + <riderPublish configuration="Release" platform="Any CPU" ready_to_run="true" server_id="f1ec4348-1f2d-4563-86c5-2d9f5df54746" serverName="Bes.is" target_framework="net10.0" uuid_high="6481978729836661993" uuid_low="-7312154749475505895">
4 + <runtimes>
5 + <item value="linux-x64" />
6 + </runtimes>
7 + </riderPublish>
8 + <method v="2">
9 + <option name="ToolBeforeRunTask" enabled="true" actionId="Tool_External Tools_restore linux" />
10 + </method>
5 11 </configuration>
6 12 </component>
No newline at end of file

Blog/Components/Pages/Calc.razor.js +6 -9

@@ -1,4 +1,4 @@
1 -import { getById, div, span, h, writeError } from "/common.module.js"
1 +import { getById, h, writeError } from "/common.module.js"
2 2
3 3 export function onLoad() {
4 4 console.log('Loaded');
@@ -110,15 +110,14 @@ function parse(word, stack) {
110 110 }
111 111
112 112 function writeLog(stack, words) {
113 - let log_left = span(`[${stack.join(', ')}]`);
114 - let log_right = span(words.join(' '));
113 + let log_left = h("span", `[${stack.join(', ')}]`);
114 + let log_right = h("span", words.join(' '));
115 115
116 116 if (words.length === 0) {
117 117 log_left.textContent += " <=="
118 118 }
119 119
120 - let log_row = div(log_left, log_right);
121 - log_row.className = "flex-spread";
120 + let log_row = h("div", {class: "flex-spread"}, [log_left, log_right]);
122 121
123 122 log.appendChild(log_row);
124 123 }
@@ -126,14 +125,12 @@ function writeLog(stack, words) {
126 125 function resetLog() {
127 126 if (!log.hasChildNodes()) return;
128 127
129 - let summary = h("summary");
130 - summary.textContent = log.firstChild.lastChild.textContent;
128 + let summary = h("summary", log.firstChild.lastChild.textContent);
131 129
132 130 let old_log = log.cloneNode(true);
133 131 old_log.id = '';
134 132
135 - let details = h("details", summary, old_log);
136 - details.className = "history"
133 + let details = h("details", {class: "history"}, [summary, old_log]);
137 134
138 135 log.insertAdjacentElement("afterend", details);
139 136 log.replaceChildren(); //remove children, clear log

Blog/Components/Pages/Concat.razor.js +7 -10

@@ -1,4 +1,4 @@
1 -import { getById, div, span, h, writeError } from "/common.module.js";
1 +import { getById, h, writeError } from "/common.module.js";
2 2 import lzString from "/lz-string.module.js";
3 3
4 4 let definitions = {};
@@ -229,16 +229,15 @@ function parseString(word, stack, rest) {
229 229
230 230 function writeLog(stack, words) {
231 231 return new Promise((resolve, _reject) => {
232 - let log_left = span(`[${stack.join(", ")}]`);
233 - let log_right = span(words.join(" "));
232 + let log_left = h("span", `[${stack.join(", ")}]`);
233 + let log_right = h("span", words.join(" "));
234 234
235 235 if (words.length === 0) {
236 236 log_left.textContent += " <==";
237 237 }
238 238
239 - let log_row = div(log_left, log_right);
240 - log_row.className = "flex-spread";
241 -
239 + let log_row = h("div", {class: "flex-spread"}, [log_left, log_right]);
240 +
242 241 log.appendChild(log_row);
243 242 resolve();
244 243 });
@@ -247,14 +246,12 @@ function writeLog(stack, words) {
247 246 function resetLog() {
248 247 if (!log.hasChildNodes()) return;
249 248
250 - let summary = h("summary");
251 - summary.textContent = log.firstChild.lastChild.textContent;
249 + let summary = h("summary", log.firstChild.lastChild.textContent);
252 250
253 251 let old_log = log.cloneNode(true);
254 252 old_log.id = "";
255 253
256 - let details = h("details", summary, old_log);
257 - details.className = "history";
254 + let details = h("details", {class: "history"}, [summary, old_log]);
258 255
259 256 log.insertAdjacentElement("afterend", details);
260 257 log.replaceChildren(); //remove children, clear log

Blog/Components/Pages/Generator.razor.js +3 -3

@@ -1,9 +1,9 @@
1 -import { addClass /*writeInfo*/, div, getById, span } from "/common.module.js";
1 +import { getById, h } from "/common.module.js";
2 2
3 3 function writeInfo(label, msg) {
4 4 log.appendChild(
5 - div(
6 - addClass(div(addClass(span(label), "noselect"), span(msg)), "info"),
5 + h("div",
6 + [h("div", {class: "info"}, [h("span", {class: "noselect"}, label), h("span", msg)])]
7 7 ),
8 8 );
9 9 }

Blog/Components/Pages/Letterflixd.razor.js +4 -4

@@ -1,4 +1,4 @@
1 -import { getById, resetLog, writeError, writeInfo, writeDebug, a, span, div, addClass } from "/common.module.js"
1 +import { getById, resetLog, writeError, writeInfo, writeDebug, h } from "/common.module.js"
2 2
3 3 let netflix_file_input = undefined;
4 4
@@ -92,8 +92,8 @@ function convertText(data) {
92 92 let letterboxd_import = new Blob([letterboxd_output], { type: 'text/csv' });
93 93
94 94 const filename = "netflix-letterboxd-import.csv"
95 - let download_button = a(window.URL.createObjectURL(letterboxd_import), filename)
96 - let download_text = span("Download import file ==> ")
95 + let download_button = h("a", {href: window.URL.createObjectURL(letterboxd_import)}, filename)
96 + let download_text = h("span", {class: "info"}, "Download import file ==> ")
97 97 download_button.download = filename;
98 - log.appendChild(div(addClass(download_text, "info"), download_button));
98 + log.appendChild(h("div", {class:"download_button"}, [download_text]));
99 99 }
No newline at end of file

Blog/Components/Pages/Query.razor.js +13 -12

@@ -1,4 +1,4 @@
1 -import {getById, writeError, writeDebug, h, span, addClass, div} from '/common.module.js'
1 +import {getById, writeError, writeDebug, h, t} from '/common.module.js'
2 2 import {DACTAL} from "/dactal.js"
3 3
4 4 const enterEvent = new KeyboardEvent('keydown', {
@@ -42,7 +42,7 @@ async function runQuery(query) {
42 42 results.replaceChildren(renderValue(result.slice(0, 100)))
43 43
44 44 if (result.length > 100) {
45 - results.appendChild(div(span(`Not showing ${result.length - 100} more results.`)))
45 + results.appendChild(h("div", [h("span", `Not showing ${result.length - 100} more results.`)]))
46 46 }
47 47 } catch (e) {
48 48 writeError(e.message)
@@ -53,21 +53,22 @@ async function runQuery(query) {
53 53 function renderValue(obj) {
54 54 if (Array.isArray(obj)) {
55 55 return h("ol",
56 - ...obj.map((item) => h("li", renderValue(item)))
56 + obj.map((item) => h("li", [renderValue(item)]))
57 57 )
58 58 } else if (typeof obj === "object") {
59 59 return h("ul",
60 - ...Object.entries(obj).map(([name, item]) =>
61 - addClass(h("li",
62 - addClass(span(name), "name"),
63 - span(" "),
64 - renderValue(item)),
65 - "dash")))
60 + Object.entries(obj).map(([name, item]) =>
61 + h("li", {class: "dash"},
62 + [
63 + h("span", {class: "name"}, name),
64 + t(" "),
65 + renderValue(item)
66 + ])))
66 67 } else if (typeof obj === "boolean") {
67 - return addClass(span(obj), obj ? "json-true" : "json-false")
68 + return h("span", {class: obj ? "json-true" : "json-false"})
68 69 } else if (typeof obj === "number") {
69 - return addClass(span(obj), "json-number")
70 + return h("span", {class: "json-number"}, obj.toString())
70 71 } else if (typeof obj === "string") {
71 - return span(obj)
72 + return h("span", obj)
72 73 }
73 74 }
No newline at end of file

Blog/Components/Pages/Storage.razor.js +46 -44

@@ -1,4 +1,4 @@
1 -import {getById, writeError, addClass, writeDebug, writeInfo, h, span} from "/common.module.js";
1 +import {getById, writeError, writeDebug, writeInfo, h } from "/common.module.js";
2 2
3 3 /** @member {IDBDatabase} db */
4 4 let db;
@@ -72,33 +72,34 @@ function showStores() {
72 72 let storeNames = db.objectStoreNames;
73 73
74 74 for (const storeName of storeNames) {
75 - const deleteButton = h("button", "Delete");
76 -
77 - const openButton = h("button", "Open");
78 - openButton.addEventListener('click', () => {
79 - if (openButton.textContent === "Open") {
80 - openButton.closeCallback = openStore(storeName, deleteButton);
81 - openButton.textContent = "Close";
82 - } else {
83 - openButton.closeCallback();
84 - openButton.textContent = "Open";
85 - openButton.closeCallback = null;
75 + const deleteButton = h("button", {
76 + onClick: async () => {
77 + if (await askForConfirmation(`Are you sure you want to delete store '${storeName}'?`)) {
78 + InitDB((event) => {
79 + writeDebug(`Deleting store: ${storeName}, version: ${event.newVersion}`);
80 + event.target.result.deleteObjectStore(storeName);
81 + writeDebug("Deleted store: " + storeName);
82 + });
83 +
84 + if (openButton.closeCallback) openButton.closeCallback();
85 + }
86 86 }
87 - })
88 -
89 - deleteButton.addEventListener('click', async () => {
90 - if (await askForConfirmation(`Are you sure you want to delete store '${storeName}'?`)) {
91 - InitDB((event) => {
92 - writeDebug(`Deleting store: ${storeName}, version: ${event.newVersion}`);
93 - event.target.result.deleteObjectStore(storeName);
94 - writeDebug("Deleted store: " + storeName);
95 - });
96 -
97 - if (openButton.closeCallback) openButton.closeCallback();
87 + }, "Delete");
88 +
89 + const openButton = h("button", {
90 + onClick: () => {
91 + if (openButton.textContent === "Open") {
92 + openButton.closeCallback = openStore(storeName, deleteButton);
93 + openButton.textContent = "Close";
94 + } else {
95 + openButton.closeCallback();
96 + openButton.textContent = "Open";
97 + openButton.closeCallback = null;
98 + }
98 99 }
99 - })
100 + }, "Open");
100 101
101 - const storeElement = span(storeName);
102 + const storeElement = h("span", storeName);
102 103 storeList.appendChild(storeElement);
103 104 storeList.appendChild(openButton);
104 105 storeList.appendChild(deleteButton);
@@ -109,13 +110,13 @@ function openStore(storeName) {
109 110 const openedStores = getById("openedStores");
110 111
111 112 const addButton = h("button", "Add");
112 - const storeElement = addClass(h("fieldset", h("legend", storeName + " ", addButton)), "docs");
113 + const storeElement = h("fieldset", {class: "docs"}, [h("legend", [storeName + " ", addButton])]);
113 114 const closeStore = () => {
114 115 openedStores.removeChild(storeElement);
115 116 };
116 117
117 118 openedStores.appendChild(storeElement);
118 - const elementList = addClass(h("div"), "grid-stores");
119 + const elementList = h("div", {class:"grid-stores"});
119 120 storeElement.appendChild(elementList);
120 121
121 122 function showItems() {
@@ -132,32 +133,33 @@ function openStore(storeName) {
132 133 }
133 134 const value = cursor.value;
134 135 const key = cursor.key;
135 - const editItemButton = h("button", "Edit");
136 -
137 - const deleteItemButton = h("button", "Delete");
138 - deleteItemButton.addEventListener('click', () => {
139 - const transaction = db.transaction(storeName, 'readwrite');
140 - transaction.oncomplete = () => {
141 - showItems();
136 + const editItemButton = h("button", {
137 + onClick: async () => {
138 + const value = await getInput("Edit value", "new value");
139 +
140 + if (value) {
141 + const transaction = db.transaction(storeName, 'readwrite');
142 + transaction.oncomplete = () => {
143 + showItems();
144 + }
145 + const store = transaction.objectStore(storeName);
146 + store.put(value, key);
147 + }
142 148 }
143 - const store = transaction.objectStore(storeName);
144 - store.delete(key);
145 - })
146 -
147 - editItemButton.addEventListener('click', async () => {
148 - const value = await getInput("Edit value", "new value");
149 + }, "Edit");
149 150
150 - if (value) {
151 + const deleteItemButton = h("button", {
152 + onClick: () => {
151 153 const transaction = db.transaction(storeName, 'readwrite');
152 154 transaction.oncomplete = () => {
153 155 showItems();
154 156 }
155 157 const store = transaction.objectStore(storeName);
156 - store.put(value, key);
158 + store.delete(key);
157 159 }
158 - })
160 + }, "Delete");
159 161
160 - newChildren.push(span(value));
162 + newChildren.push(h("span", value));
161 163 newChildren.push(editItemButton);
162 164 newChildren.push(deleteItemButton);
163 165

Blog/wwwroot/Blog.lib.module.js +1 -1

@@ -32,7 +32,7 @@ function unregisterPageScriptElement(src) {
32 32
33 33 async function initializePageScriptModule(src, pageScriptInfo) {
34 34 if (src.startsWith("./")) {
35 - src = new URL(src.substr(2), document.baseURI).toString();
35 + src = new URL(src.substring(2), document.baseURI).toString();
36 36 }
37 37
38 38 const module = await import(src);

Blog/wwwroot/app.css +1 -0

@@ -145,6 +145,7 @@ label {
145 145 display: block;
146 146 }
147 147
148 +.textarea,
148 149 textarea {
149 150 width: 100%;
150 151 display: block;

Blog/wwwroot/common.module.js +211 -89

@@ -1,45 +1,221 @@
1 -function getById(id) {
2 - let element = document.getElementById(id);
3 - if (element) {
4 - return element;
5 - } else {
6 - throw `Element with id '${id}' not found.`;
7 - }
1 +/**
2 + * Create a text node with the contents
3 + * @param {string} text The text content
4 + * @returns The text node
5 + */
6 +export function t(text) {
7 + return document.createTextNode(text);
8 8 }
9 9
10 10 /**
11 - * @param {string} href
12 - * @returns {HTMLAnchorElement}
11 + * @typedef {"stop"|"prevent"|"self"} EventModifier
13 12 */
14 -function a(href, pageName) {
15 - const aElem = document.createElement("a");
16 - aElem.href = href;
17 - aElem.text = pageName;
18 - return aElem;
19 -}
20 13
21 -function div(...children) {
22 - const divElem = document.createElement("div");
23 - divElem.replaceChildren(...children);
24 - return divElem;
14 +/**
15 + *
16 + * @param {CallableFunction} fn
17 + * @param {EventModifier|EventModifier[]} modifiers
18 + * @returns {CallableFunction}
19 + */
20 +export function withModifiers(fn, modifiers) {
21 + if (!Array.isArray(modifiers)) {
22 + modifiers = [modifiers];
23 + }
24 + return function (/** @type {Event} */ event , /** @type {any} */ ...args) {
25 + for (const modifier of modifiers) {
26 + if (modifier === "stop") {
27 + event.stopPropagation();
28 + } else if (modifier === "prevent") {
29 + event.preventDefault();
30 + } else if (modifier === "self") {
31 + if (event.target !== event.currentTarget) {
32 + return;
33 + }
34 + }
35 + }
36 + return fn(event, ...args);
37 + }
25 38 }
26 39
27 -function span(text) {
28 - const spanElem = document.createElement("span");
29 - spanElem.textContent = text;
30 - return spanElem;
40 +/**
41 + * @typedef {{ style?: Record<string, string>|Record<string, string>[], class?: Record<string, unknown> } & Record<string, string>} HtmlAttrs
42 + */
43 +
44 +/**
45 + * Assign attributes to the node
46 + * @param {HTMLElement} node
47 + * @param {HtmlAttrs} attrs
48 + */
49 +function setAttrs(node, attrs) {
50 + for (const key in attrs) {
51 + if (!Object.hasOwn(attrs, key)) continue;
52 + if (key === "style" && typeof attrs[key] === "object") {
53 + let styleAttrs = attrs[key];
54 + if (!Array.isArray(styleAttrs)) {
55 + styleAttrs = [styleAttrs];
56 + }
57 + for (const styles of styleAttrs) {
58 + for (const styleProp in styles) {
59 + if (!Object.hasOwn(styles, styleProp)) continue;
60 +
61 + const val = styles[styleProp];
62 +
63 + node.style.setProperty(styleProp, val);
64 + }
65 + }
66 + } else if (key === "class" && typeof attrs[key] === "object" && !Array.isArray(attrs[key])) {
67 + for (const className in attrs[key]) {
68 + if (!Object.hasOwn(attrs[key], className)) continue;
69 +
70 + const element = attrs[key][className];
71 +
72 + if (element) {
73 + node.classList.add(className);
74 + }
75 + }
76 + } else if (typeof attrs[key] === "string") {
77 + node.setAttribute(key, attrs[key]);
78 + } else if (typeof attrs[key] === "function" && /^on[A-Z]/.test(key)) {
79 + const eventSpec = key.substring(2);
80 + // Loop through PascalCase substrings
81 + const re = /[A-Z][a-z]+/g;
82 + const results = [...eventSpec.matchAll(re)];
83 + if (results.length === 0) {
84 + throw new Error(`Invalid event listener: on${eventSpec}`);
85 + }
86 + let eventName = results[0][0].toLowerCase();
87 + let options = /** @type {AddEventListenerOptions} */ ({});
88 + for (const regexResult of results.slice(1)) {
89 + let modifier = regexResult[0].toLowerCase();
90 + if (modifier === "capture") {
91 + options.capture = true;
92 + } else if (modifier === "once") {
93 + options.once = true;
94 + } else if (modifier === "passive") {
95 + options.passive = true;
96 + } else {
97 + throw new Error(`Unknown modifier: ${modifier}`);
98 + }
99 + }
100 + node.addEventListener(eventName, attrs[key], options);
101 + } else {
102 + throw new TypeError(`Unsupported attribute type ${typeof attrs[key]}`);
103 + }
104 + }
31 105 }
32 106
33 -function h(elementTag, ...children) {
34 - const elem = document.createElement(elementTag);
35 - elem.replaceChildren(...children);
36 - return elem;
107 +/**
108 + * Add the children to the node
109 + * @param {HTMLElement} node
110 + * @param {Array<HTMLElement|Text|string>} children
111 + */
112 +function setChildren(node, children) {
113 + for (const c of children) {
114 + if (typeof c === "string") {
115 + node.appendChild(t(c));
116 + } else {
117 + node.appendChild(c);
118 + }
119 + }
37 120 }
38 121
39 -/** @param {HTMLElement} element */
40 -function addClass(element, newClass) {
41 - element.classList.add(newClass);
42 - return element;
122 +/**
123 + * Build a HTMLElement
124 + * @overload
125 + * @param {keyof HTMLElementTagNameMap} tag
126 + * @returns {HTMLElement}
127 + */
128 +/**
129 + * @overload
130 + * @param {keyof HTMLElementTagNameMap} tag
131 + * @param {string} text
132 + * @returns {HTMLElement}
133 + */
134 +/**
135 + * @overload
136 + * @param {keyof HTMLElementTagNameMap} tag
137 + * @param {Array<HTMLElement|Text|string>} children
138 + * @returns {HTMLElement}
139 + */
140 +/**
141 + * @overload
142 + * @param {keyof HTMLElementTagNameMap} tag
143 + * @param {HtmlAttrs} attrs
144 + * @returns {HTMLElement}
145 + */
146 +/**
147 + * @overload
148 + * @param {keyof HTMLElementTagNameMap} tag
149 + * @param {HtmlAttrs} attrs
150 + * @param {string} text
151 + * @returns {HTMLElement}
152 + */
153 +/**
154 + * @overload
155 + * @param {keyof HTMLElementTagNameMap} tag
156 + * @param {HtmlAttrs} attrs
157 + * @param {Array<HTMLElement|Text|string>} children
158 + * @returns {HTMLElement}
159 + */
160 +/**
161 + * @param {keyof HTMLElementTagNameMap} tag
162 + * @param {...(HtmlAttrs|Array<HTMLElement|Text|string>|string)} args
163 + * @returns {HTMLElement}
164 + */
165 +export function h(tag, ...args) {
166 + let node = document.createElement(tag);
167 +
168 + if (args.length === 0) {
169 + return node;
170 + } else if (args.length === 1) {
171 + const attrs = args[0];
172 + if (typeof attrs === "object" && Array.isArray(attrs)) {
173 + // Children
174 + setChildren(node, attrs);
175 + return node;
176 + } else if (typeof attrs === "object") {
177 + // Attributes
178 + setAttrs(node, attrs);
179 + return node;
180 + } else if (typeof attrs === "string") {
181 + // Text node child
182 + const textNode = document.createTextNode(attrs);
183 + node.appendChild(textNode);
184 + return node;
185 + } else {
186 + throw new TypeError(`Invalid second argument type: ${typeof attrs}`);
187 + }
188 + } else if (args.length === 2) {
189 + // Second arg must be attributes, third arg must be children
190 + const attrs = args[0];
191 + const children = args[1];
192 +
193 + if (typeof attrs !== "object" || Array.isArray(attrs)) {
194 + throw new TypeError("Second argument must be an object");
195 + }
196 +
197 + setAttrs(node, attrs);
198 +
199 + if (typeof children === "string") {
200 + node.appendChild(t(children));
201 + } else if (typeof children === "object" && Array.isArray(children)) {
202 + setChildren(node, children);
203 + } else {
204 + throw new TypeError("Third argument must be an array or string");
205 + }
206 +
207 + return node;
208 + } else {
209 + throw new Error("Too many arguments");
210 + }
211 +}
212 +function getById(id) {
213 + let element = document.getElementById(id);
214 + if (element) {
215 + return element;
216 + } else {
217 + throw `Element with id '${id}' not found.`;
218 + }
43 219 }
44 220
45 221 function writeError(error) {
@@ -56,9 +232,11 @@ function writeDebug(msg) {
56 232
57 233 function writeLog(msg, className) {
58 234 if (typeof msg === "string" || msg instanceof String) {
59 - log.appendChild(div(addClass(span(msg), className)));
235 + // log.appendChild(div(addClass(span(msg), className)));
236 + log.appendChild(h("div", {class: className}, [h("span", msg)]));
60 237 } else {
61 - log.appendChild(div(addClass(msg, className)));
238 + log.appendChild(h("div", {class: className}, msg));
239 + // log.appendChild(div(addClass(msg, className)));
62 240 }
63 241 }
64 242
@@ -80,64 +258,8 @@ function debounce(callback, wait) {
80 258 };
81 259 }
82 260
83 -const stylesheet = new CSSStyleSheet();
84 -stylesheet.replaceSync(`
85 - a {
86 - color: var(--color);
87 - }
88 - nav > ul {
89 - list-style-type: none;
90 - padding-inline-start: 0;
91 - }
92 -
93 - nav > ul > li {
94 - display: inline-block;
95 - }
96 -
97 - nav > ul > li > a::after {
98 - display: inline-block;
99 - color: var(--color);
100 - content: ">";
101 - padding: 0 1em;
102 - }
103 -`);
104 -
105 -customElements.define("mb-nav",
106 - class MBNav extends HTMLElement {
107 - constructor() {
108 - super();
109 - }
110 -
111 - get pagename() {
112 - return this.getAttribute("pagename");
113 - }
114 -
115 - set pagename(value) {
116 - this.setAttribute("pagename", value);
117 - }
118 -
119 - connectedCallback() {
120 - const shadow = this.attachShadow({ mode: "open" });
121 - shadow.adoptedStyleSheets = [stylesheet];
122 -
123 - shadow.appendChild(
124 - h("nav",
125 - h("ul",
126 - h("li", a("/", "MB.bes.is")),
127 - h("li", span(this.pagename)),
128 - )
129 - )
130 - )
131 - }
132 - });
133 -
134 261 export {
135 262 getById,
136 - a,
137 - div,
138 - span,
139 - h,
140 - addClass,
141 263 writeError,
142 264 writeInfo,
143 265 writeDebug,