Skip to content

Commit b3471b0

Browse files
committed
Added <css|dom>.js
1 parent 3507ed9 commit b3471b0

6 files changed

Lines changed: 159 additions & 0 deletions

File tree

assets/js/lib/css.js/LICENSE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# 🏛️ MIT License
2+
3+
**Copyright © [Adam Lui](https://github.com/adamlui)**
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

assets/js/lib/css.js/css.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright © 2026 Adam Lui (https://github.com/adamlui) under the MIT license
2+
// Source: https://github.com/adamlui/ai-web-extensions/blob/main/assets/js/lib/css.js/css.js
3+
4+
window.css = {
5+
6+
addRisingParticles(targetNode, { lightScheme = 'gray', darkScheme = 'white' } = {}) {
7+
// * Requires https://assets.aiwebextensions.com/styles/rising-particles/dist/<lightScheme>.min.css
8+
9+
if (targetNode.querySelector('[id*=particles]')) return
10+
const particlesDivsWrapper = document.createElement('div')
11+
particlesDivsWrapper.style.cssText = (
12+
'position: absolute ; top: 0 ; left: 0 ;' // hug targetNode's top-left corner
13+
+ 'height: 100% ; width: 100% ; border-radius: 15px ; overflow: clip ;' // bound innards exactly by targetNode
14+
+ 'z-index: -1' ) // allow interactive elems to be clicked
15+
;['sm', 'med', 'lg'].forEach(particleSize => {
16+
const particlesDiv = document.createElement('div')
17+
particlesDiv.id = config?.bgAnimationsDisabled ? `particles-${particleSize}-off`
18+
: `${( env?.ui?.scheme || env?.ui?.app?.scheme ) == 'dark' ? darkScheme
19+
: lightScheme }-particles-${particleSize}`
20+
particlesDivsWrapper.append(particlesDiv)
21+
})
22+
targetNode.prepend(particlesDivsWrapper)
23+
},
24+
25+
selectors: {
26+
extract(obj, type = 'all') {
27+
if (!obj || typeof obj != 'object')
28+
throw new TypeError('First parameter must be an object')
29+
30+
const validTypes = ['all', 'css', 'xpath']
31+
if (!validTypes.includes(type))
32+
throw new TypeError(`Type must be one of: ${validTypes.join(', ')}`)
33+
34+
const selectors = Object.values(obj).flatMap(val => {
35+
if (val && typeof val == 'object') return this.extract(val, type) // recurse into nested objs
36+
return typeof val == 'string' ? [val] : [] // only include strings
37+
})
38+
39+
return type == 'css' ? selectors.filter(sel => !sel?.startsWith('//'))
40+
: type == 'xpath' ? selectors.filter(sel => sel?.startsWith('//'))
41+
: selectors
42+
},
43+
44+
fromClasses(classList) {
45+
return classList.toString()
46+
.replace(/([:[\]\\])/g, '\\$1') // escape special chars :[]\
47+
.replace(/^| /g, '.') // prefix w/ dot, convert spaces to dots
48+
}
49+
}
50+
};

assets/js/lib/css.js/dist/css.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/js/lib/dom.js/LICENSE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# 🏛️ MIT License
2+
3+
**Copyright © 2023–2026 [Adam Lui](https://github.com/adamlui)**
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

assets/js/lib/dom.js/dist/dom.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/js/lib/dom.js/dom.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright © 2025–2026 Adam Lui (https://github.com/adamlui) under the MIT license
2+
// Source: https://github.com/adamlui/ai-web-extensions/blob/main/assets/js/lib/dom.js/dom.js
3+
4+
window.dom = {
5+
6+
create: {
7+
anchor(linkHref, displayContent, attrs = {}) {
8+
const anchor = document.createElement('a'),
9+
defaultAttrs = { href: linkHref, target: '_blank', rel: 'noopener' },
10+
finalAttrs = { ...defaultAttrs, ...attrs }
11+
Object.entries(finalAttrs).forEach(([attr, value]) => anchor.setAttribute(attr, value))
12+
if (displayContent) anchor.append(displayContent)
13+
return anchor
14+
},
15+
16+
elem(elemType, attrs = {}) {
17+
const elem = document.createElement(elemType)
18+
for (const attr in attrs) {
19+
if (attr in elem) elem[attr] = attrs[attr]
20+
else elem.setAttribute(attr, attrs[attr])
21+
}
22+
return elem
23+
},
24+
25+
style(content, attrs = {}) {
26+
const style = document.createElement('style')
27+
style.setAttribute('type', 'text/css') // support older browsers
28+
for (const attr in attrs) style.setAttribute(attr, attrs[attr])
29+
if (content) style.textContent = content
30+
return style
31+
},
32+
33+
svgElem(type, attrs = {}) {
34+
const elem = document.createElementNS('http://www.w3.org/2000/svg', type)
35+
for (const attr in attrs) elem.setAttributeNS(null, attr, attrs[attr])
36+
return elem
37+
}
38+
},
39+
40+
get: {
41+
42+
computedSize(elems, { dimension } = {}) { // total width/height of elems (including margins)
43+
// * Returns { width: totalWidth, height: totalHeight } if no dimension passed
44+
// * Returns float if { dimension: 'width' | 'height' } passed
45+
46+
// Validate args
47+
elems = elems instanceof NodeList ? [...elems] : [].concat(elems)
48+
elems.forEach(elem => { if (!(elem instanceof Node))
49+
throw new Error(`Invalid elem: Element "${JSON.stringify(elem)}" is not a valid DOM node`) })
50+
const validDimensions = ['width', 'height'], dimensionsToCompute = [].concat(dimension || validDimensions)
51+
dimensionsToCompute.forEach(dimension => { if (!validDimensions.includes(dimension))
52+
throw new Error('Invalid dimension: Use \'width\' or \'height\'') })
53+
54+
// Compute dimensions
55+
const computedDimensions = { width: 0, height: 0 }
56+
elems.forEach(elem => {
57+
const elemStyle = getComputedStyle(elem) ; if (elemStyle.display == 'none') return
58+
Object.keys(computedDimensions).forEach(dimension => {
59+
if (dimensionsToCompute.includes(dimension))
60+
computedDimensions[dimension] += elem.getBoundingClientRect()[dimension]
61+
+ parseFloat(elemStyle[`margin${dimension == 'width' ? 'Left' : 'Top'}`])
62+
+ parseFloat(elemStyle[`margin${dimension == 'width' ? 'Right' : 'Bottom'}`])
63+
})
64+
})
65+
66+
// Return computed dimensions
67+
return dimensionsToCompute.length > 1 ? computedDimensions // obj w/ width/height
68+
: computedDimensions[dimensionsToCompute[0]] // single total val
69+
},
70+
71+
computedHeight(elems) { return this.computedSize(elems, { dimension: 'height' }) }, // including margins
72+
computedWidth(elems) { return this.computedSize(elems, { dimension: 'width' }) }, // including margins
73+
74+
loadedElem(selector, { timeout = null } = {}) {
75+
const raceEntries = [
76+
new Promise(resolve => { // when elem loads
77+
const elem = document.querySelector(selector)
78+
if (elem) resolve(elem)
79+
else new MutationObserver((_, obs) => {
80+
const elem = document.querySelector(selector)
81+
if (elem) { obs.disconnect() ; resolve(elem) }
82+
}).observe(document.documentElement, { childList: true, subtree: true })
83+
})
84+
]
85+
if (timeout) raceEntries.push(new Promise(resolve => setTimeout(() => resolve(null), timeout)))
86+
return Promise.race(raceEntries)
87+
}
88+
}
89+
};

0 commit comments

Comments
 (0)