-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcommon.js
More file actions
166 lines (157 loc) · 4.87 KB
/
common.js
File metadata and controls
166 lines (157 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* Generate the node object.
*
* Contains additional logic to help break any unintended side effects of the top down parsing of bbob.
* @param {string} tag name of the tag
* @param {Object<string, boolean|string|string[]>} attrs attributes of the tag
* @param {any} content contents of the tag. `[]` will create an empty tag. `null` will create a self closing tag
*
* @example
* ```
* toNode("div", { class: "class" }, "content")
* ```
* becomes
* ```
* {
* tag: "div",
* attrs: { class: "class" },
* content: "content",
* gen: true,
* }
*/
const toNode = (tag, attrs, content = []) => ({
tag,
attrs,
content,
gen: true,
});
/**
* Preprocess attributes of a node to either return the default single attribute
* or return a keyed attribute list
* @param {import('@bbob/types').TagNode} node bbcode node to process
* @param {string} [raw] raw string. Only include if the single attribute is allowed to have spaces
* @returns processed attributes
*/
const preprocessAttr = (node, raw) => {
const keys = Object.keys(node.attrs).join(" ");
const vals = Object.values(node.attrs).join(" ");
if (keys !== vals) {
// [tag key=val]
return node.attrs;
}
if (!raw || !node.start) {
return {
_default: vals,
};
}
// [tag=attr]
// node.start.from = 0
// node.start.to = 10
const nodeRaw = raw.substring(node.start.from, node.start.to);
if (!nodeRaw.includes("=")) {
// [tag] or [tag attr]
return node.attrs;
}
const openTagParts = nodeRaw.split("=");
if (openTagParts.length !== 2) {
return node.attrs;
}
let val = openTagParts[1].slice(0, -1).trim(); // `attr` or `"attr"`
if (val.startsWith('"') && val.endsWith('"')) {
val = val.slice(1, -1);
}
return {
_default: val,
};
};
/**
* Attempts to return tag into its original form with proper attributes
* @returns string of tag start
*/
const toOriginalStartTag = (node, raw) => {
if (node.start) {
return raw.substring(node.start.from, node.start.to);
}
if (!node.attrs) {
return `[${node.tag}]`;
}
const attrs = preprocessAttr(node, raw);
if (attrs._default) {
return `[${node.tag}=${attrs._default}]`;
} else {
return node.toTagStart();
}
};
/**
* Attempts to return tag into its original form
* @returns string of tag end
*/
const toOriginalEndTag = (node, raw) => {
if (node.end) {
return raw.substring(node.end.from, node.end.to);
}
return node.toTagEnd();
};
/**
* Given a string, find the first position of a regex match
* @param {string} string to test against
* @param {RegExp} regex to test with
* @param {number} startpos starting position. Defaults to 0
* @returns index of the first match of the regex in the string
*/
const regexIndexOf = (string, regex, startpos) => {
const indexOf = string.substring(startpos || 0).search(regex);
return indexOf >= 0 ? indexOf + (startpos || 0) : indexOf;
};
const MD_NEWLINE_INJECT = "<!-- bbcode injected newlines -->\n\n";
const MD_NEWLINE_PRE_INJECT = "\n\n<!-- bbcode pre injected newlines -->";
const MD_NEWLINE_INJECT_COMMENT = "<!-- bbcode injected newlines -->";
const URL_REGEX =
/(http|ftp|https|upload):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])/;
const MD_URL_REGEX =
/\!?\[.*\]\((http|ftp|https|upload):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])\)/;
const URL_REGEX_SINGLE_LINE = new RegExp(`^${URL_REGEX.source}|${MD_URL_REGEX.source}$`);
const ESCAPABLES_REGEX =
/((\n|^)(?<fence>```+|~~~+)(?<fenceInfo>.*\n))|(?<bbcode>\[(?<bbcodeTag>i?code|plain)(=.*)?\])|(?<backtick>(?<tickStart>`{1,2})(.*)(?<tickEnd>\k<tickStart>))/im;
const MD_TABLE_REGEX = /^(\|[^\n]+\|\r?\n)((?:\| ?:?[-]+:? ?)+\|)(\n(?:\|[^\n]+\|\r?\n?)*)?$/m;
const MD_BROKEN_ORDERED_LIST = "</ol>\n<br><ol>";
const MD_BROKEN_UNORDERED_LIST = "</ul>\n<br><ul>";
const MD_BROKEN_BLOCKQUOTE = "</blockquote>\n<blockquote>";
/**
* Generates a random GUID.
*
* Mini Racer doesn't have the crypto module, so we can't use the built-in `crypto.randomUUID` function.
* @returns {string} a GUID
*/
function generateGUID() {
let d = new Date().getTime();
if (window.performance && typeof window.performance.now === "function") {
d += performance.now(); //use high-precision timer if available
}
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
// eslint-disable-next-line no-bitwise
const r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
// eslint-disable-next-line no-bitwise
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
});
}
export {
toNode,
toOriginalStartTag,
toOriginalEndTag,
generateGUID,
preprocessAttr,
regexIndexOf,
MD_NEWLINE_INJECT,
MD_NEWLINE_INJECT_COMMENT,
MD_NEWLINE_PRE_INJECT,
URL_REGEX,
MD_URL_REGEX,
MD_TABLE_REGEX,
URL_REGEX_SINGLE_LINE,
ESCAPABLES_REGEX,
MD_BROKEN_ORDERED_LIST,
MD_BROKEN_UNORDERED_LIST,
MD_BROKEN_BLOCKQUOTE,
};