jekyll-theme-centos 2.52.0.beta.55 → 2.52.0.beta.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/_config.yml +1 -1
- data/_data/base/backtotop_schema.yml +6 -0
- data/_data/base/datatable_schema.yml +3 -3
- data/_data/base/head_schema.yml +19 -0
- data/_data/base/heading_anchor.yml +1 -3
- data/_data/base/heading_anchor_schema.yml +1 -1
- data/_data/base/highlight_schema.yml +30 -0
- data/_data/base/ogp.yml +23 -0
- data/_data/base/ogp_schema.yml +14 -22
- data/_includes/base/backtotop.html.liquid +10 -2
- data/_includes/base/copyvalue.html.liquid +10 -1
- data/_includes/base/datatable.html.liquid +8 -3
- data/_includes/base/fontawesome.html.liquid +2 -2
- data/_includes/base/{partials/footer.html.liquid → footer.html.liquid} +2 -2
- data/_includes/base/{partials/head.html.liquid → head.html.liquid} +54 -14
- data/_includes/base/heading_anchor.html.liquid +8 -3
- data/_includes/base/highlight.html.liquid +14 -9
- data/_includes/base/{partials/navbar.html.liquid → navbar.html.liquid} +9 -6
- data/_includes/base/navindex.html.liquid +2 -2
- data/_includes/base/ogp.html.liquid +58 -56
- data/_includes/base/shortcut.html.liquid +2 -1
- data/_layouts/base/default.html +7 -10
- data/_sass/base/_customization.scss +27 -13
- data/assets/img/base/example-ogp-image.png +0 -0
- data/assets/img/base/example-ogp-image.svg +297 -0
- data/assets/img/base/page-layout-default.png +0 -0
- data/assets/img/base/page-layout-default.svg +4 -4
- data/assets/img/base/page-with-ogp.png +0 -0
- data/assets/img/base/page-with-ogp.svg +333 -300
- data/assets/img/centos-social-share.png +0 -0
- data/assets/js/base/backtotop.js +7 -10
- data/assets/js/base/copyvalue.js +96 -94
- data/assets/js/base/datatable.js +28 -42
- data/assets/js/base/heading-anchor.js +53 -53
- data/assets/js/base/highlight.js +9 -3
- data/assets/js/base/init-tooltips.js +10 -4
- metadata +15 -13
- data/_data/base/partials/head_schema.yml +0 -47
- data/_includes/base/partials/script.html.liquid +0 -47
- /data/_data/base/{partials/content.yml → content.yml} +0 -0
- /data/_data/base/{partials/content_schema.yml → content_schema.yml} +0 -0
- /data/_data/base/{partials/footer.yml → footer.yml} +0 -0
- /data/_data/base/{partials/footer_schema.yml → footer_schema.yml} +0 -0
- /data/_data/base/{partials/navbar.yml → navbar.yml} +0 -0
- /data/_data/base/{partials/navbar_schema.yml → navbar_schema.yml} +0 -0
- /data/_data/base/{partials/script_schema.yml → script_schema.yml} +0 -0
|
Binary file
|
data/assets/js/base/backtotop.js
CHANGED
|
@@ -3,21 +3,18 @@
|
|
|
3
3
|
// Applied via backtotop.html.liquid template with data attributes
|
|
4
4
|
|
|
5
5
|
(function () {
|
|
6
|
-
|
|
7
|
-
const scriptTag = document.getElementById('centos-backtop');
|
|
6
|
+
const scriptTag = document.getElementById('centos-backtop')
|
|
8
7
|
|
|
9
8
|
if (!scriptTag) {
|
|
10
|
-
console.warn('Back-to-Top: script tag with id "centos-backtop" not found')
|
|
11
|
-
return
|
|
9
|
+
console.warn('Back-to-Top: script tag with id "centos-backtop" not found')
|
|
10
|
+
return
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
// Read configuration from data attributes
|
|
15
13
|
const config = {
|
|
16
|
-
diameter: parseInt(scriptTag.dataset.diameter || 56),
|
|
14
|
+
diameter: parseInt(scriptTag.dataset.diameter || '56', 10),
|
|
17
15
|
backgroundColor: scriptTag.dataset.backgroundColor || '#A54C93',
|
|
18
16
|
textColor: scriptTag.dataset.textColor || '#ffffff'
|
|
19
|
-
}
|
|
17
|
+
}
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
})();
|
|
19
|
+
addBackToTop(config)
|
|
20
|
+
})()
|
data/assets/js/base/copyvalue.js
CHANGED
|
@@ -3,66 +3,67 @@
|
|
|
3
3
|
// Safely handles escaped and unescaped values, HTML entities, and special characters
|
|
4
4
|
// ARMORED IMPLEMENTATION: Handles any value passed via data-copy-value attribute
|
|
5
5
|
|
|
6
|
+
(function () {
|
|
6
7
|
class CopyButtonHandler {
|
|
7
|
-
constructor(button, config = {}) {
|
|
8
|
-
this.button = button
|
|
9
|
-
this.originalTitle = this.button.getAttribute('data-bs-title') || this.button.getAttribute('aria-label') || this.button.getAttribute('title')
|
|
10
|
-
this.originalSvgHTML = this.button.innerHTML
|
|
11
|
-
this.resetDelay = config.resetDelay || 2000
|
|
12
|
-
this.checkmarkPath = config.checkmarkPath || 'M434.8 70.1c14.3 10.4 17.5 30.4 7.1 44.7l-256 352c-5.5 7.6-14 12.3-23.4 13.1s-18.5-2.7-25.1-9.3l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l101.5 101.5 234-321.7c10.4-14.3 30.4-17.5 44.7-7.1z'
|
|
13
|
-
this.checkmarkViewbox = config.checkmarkViewbox || '0 0 448 512'
|
|
14
|
-
this.successMessage = config.successMessage || 'Copied!'
|
|
15
|
-
|
|
16
|
-
this.button.addEventListener('click', (e) => this.handleClick(e))
|
|
8
|
+
constructor (button, config = {}) {
|
|
9
|
+
this.button = button
|
|
10
|
+
this.originalTitle = this.button.getAttribute('data-bs-title') || this.button.getAttribute('aria-label') || this.button.getAttribute('title')
|
|
11
|
+
this.originalSvgHTML = this.button.innerHTML
|
|
12
|
+
this.resetDelay = config.resetDelay || 2000
|
|
13
|
+
this.checkmarkPath = config.checkmarkPath || 'M434.8 70.1c14.3 10.4 17.5 30.4 7.1 44.7l-256 352c-5.5 7.6-14 12.3-23.4 13.1s-18.5-2.7-25.1-9.3l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l101.5 101.5 234-321.7c10.4-14.3 30.4-17.5 44.7-7.1z'
|
|
14
|
+
this.checkmarkViewbox = config.checkmarkViewbox || '0 0 448 512'
|
|
15
|
+
this.successMessage = config.successMessage || 'Copied!'
|
|
16
|
+
|
|
17
|
+
this.button.addEventListener('click', (e) => this.handleClick(e))
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
handleClick(e) {
|
|
20
|
-
e.preventDefault()
|
|
20
|
+
handleClick (e) {
|
|
21
|
+
e.preventDefault()
|
|
21
22
|
|
|
22
23
|
// ARMOR: Retrieve the attribute value (may be escaped or unescaped)
|
|
23
|
-
let rawValue = this.button.getAttribute('data-copy-value')
|
|
24
|
+
let rawValue = this.button.getAttribute('data-copy-value')
|
|
24
25
|
|
|
25
26
|
// ARMOR: If attribute appears truncated or incomplete, try HTML extraction fallback
|
|
26
27
|
const appearsTruncated = !rawValue ||
|
|
27
28
|
rawValue.endsWith('=') ||
|
|
28
29
|
rawValue.match(/^[a-zA-Z-]+$/) ||
|
|
29
|
-
(rawValue.length < 5 && rawValue.includes(' '))
|
|
30
|
+
(rawValue.length < 5 && rawValue.includes(' '))
|
|
30
31
|
|
|
31
32
|
if (appearsTruncated) {
|
|
32
|
-
const extractedValue = this.extractDataCopyValueFromHTML()
|
|
33
|
+
const extractedValue = this.extractDataCopyValueFromHTML()
|
|
33
34
|
if (extractedValue && extractedValue.length > (rawValue?.length || 0)) {
|
|
34
|
-
rawValue = extractedValue
|
|
35
|
+
rawValue = extractedValue
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
// Defensive check: ensure we have a value to copy
|
|
39
40
|
if (!rawValue) {
|
|
40
|
-
console.warn('No copy value found in data-copy-value attribute', this.button)
|
|
41
|
-
return
|
|
41
|
+
console.warn('No copy value found in data-copy-value attribute', this.button)
|
|
42
|
+
return
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// ARMOR: Auto-detect and handle both escaped and unescaped values
|
|
45
|
-
const finalValue = this.normalizeAttributeValue(rawValue)
|
|
46
|
+
const finalValue = this.normalizeAttributeValue(rawValue)
|
|
46
47
|
|
|
47
|
-
const svg = this.button.querySelector('svg')
|
|
48
|
+
const svg = this.button.querySelector('svg')
|
|
48
49
|
|
|
49
50
|
if (!svg) {
|
|
50
|
-
console.error('Icon SVG not found in button', this.button)
|
|
51
|
-
return
|
|
51
|
+
console.error('Icon SVG not found in button', this.button)
|
|
52
|
+
return
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
// ARMOR: Use trim() to remove accidental whitespace
|
|
55
56
|
navigator.clipboard.writeText(finalValue.trim())
|
|
56
57
|
.then(() => this.showSuccess(svg))
|
|
57
58
|
.catch((err) => {
|
|
58
|
-
console.error('Failed to copy value:', finalValue, 'Error:', err)
|
|
59
|
-
})
|
|
59
|
+
console.error('Failed to copy value:', finalValue, 'Error:', err)
|
|
60
|
+
})
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
extractDataCopyValueFromHTML() {
|
|
63
|
+
extractDataCopyValueFromHTML () {
|
|
63
64
|
// ARMOR: Extract data-copy-value from raw HTML string
|
|
64
65
|
try {
|
|
65
|
-
const outerHTML = this.button.outerHTML
|
|
66
|
+
const outerHTML = this.button.outerHTML
|
|
66
67
|
|
|
67
68
|
const patterns = [
|
|
68
69
|
/data-copy-value="([^"]*)"/,
|
|
@@ -70,85 +71,85 @@ class CopyButtonHandler {
|
|
|
70
71
|
/data-copy-value="((?:[^"\\]|\\.*)*)"/,
|
|
71
72
|
/data-copy-value="([^"]*(?:"[^"]*)*?)"/,
|
|
72
73
|
/data-copy-value=["']?([^"\s>]+?)["'\s>]/,
|
|
73
|
-
/data-copy-value="([^"]*(?:[^"]*?)?)"
|
|
74
|
-
]
|
|
74
|
+
/data-copy-value="([^"]*(?:[^"]*?)?)"/
|
|
75
|
+
]
|
|
75
76
|
|
|
76
77
|
for (const pattern of patterns) {
|
|
77
|
-
const match = outerHTML.match(pattern)
|
|
78
|
+
const match = outerHTML.match(pattern)
|
|
78
79
|
if (match && match[1] && match[1].length > 0) {
|
|
79
80
|
if (!match[1].startsWith('data-') && !match[1].startsWith('class') && !match[1].startsWith('id')) {
|
|
80
|
-
return match[1]
|
|
81
|
+
return match[1]
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
const attrIndex = outerHTML.indexOf('data-copy-value')
|
|
86
|
+
const attrIndex = outerHTML.indexOf('data-copy-value')
|
|
86
87
|
if (attrIndex !== -1) {
|
|
87
|
-
const afterAttr = outerHTML.substring(attrIndex + 15).trim()
|
|
88
|
+
const afterAttr = outerHTML.substring(attrIndex + 15).trim()
|
|
88
89
|
|
|
89
90
|
if (afterAttr.startsWith('=')) {
|
|
90
|
-
const afterEquals = afterAttr.substring(1).trim()
|
|
91
|
-
const quoteChar = afterEquals[0]
|
|
91
|
+
const afterEquals = afterAttr.substring(1).trim()
|
|
92
|
+
const quoteChar = afterEquals[0]
|
|
92
93
|
|
|
93
94
|
if (quoteChar === '"' || quoteChar === "'") {
|
|
94
|
-
let endIndex = 1
|
|
95
|
-
let escaped = false
|
|
95
|
+
let endIndex = 1
|
|
96
|
+
let escaped = false
|
|
96
97
|
|
|
97
98
|
for (; endIndex < afterEquals.length; endIndex++) {
|
|
98
|
-
const char = afterEquals[endIndex]
|
|
99
|
+
const char = afterEquals[endIndex]
|
|
99
100
|
|
|
100
101
|
if (escaped) {
|
|
101
|
-
escaped = false
|
|
102
|
-
continue
|
|
102
|
+
escaped = false
|
|
103
|
+
continue
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
if (char === '\\') {
|
|
106
|
-
escaped = true
|
|
107
|
-
continue
|
|
107
|
+
escaped = true
|
|
108
|
+
continue
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
if (char === quoteChar) {
|
|
111
|
-
return afterEquals.substring(1, endIndex)
|
|
112
|
+
return afterEquals.substring(1, endIndex)
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
return null
|
|
119
|
+
return null
|
|
119
120
|
} catch (err) {
|
|
120
|
-
console.warn('Failed to extract data-copy-value from HTML:', err)
|
|
121
|
-
return null
|
|
121
|
+
console.warn('Failed to extract data-copy-value from HTML:', err)
|
|
122
|
+
return null
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
normalizeAttributeValue(text) {
|
|
126
|
+
normalizeAttributeValue (text) {
|
|
126
127
|
// ARMOR: Smart normalization for escaped and unescaped values
|
|
127
128
|
if (!text) {
|
|
128
|
-
return text
|
|
129
|
+
return text
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
const hasHTMLEntities = /&(?:quot|apos|amp|lt|gt|#34|#39|#47|#60|#62|#x22|#x27|#x2[Ff]|#x3[Cc]|#x3[Ee]);/.test(text)
|
|
132
|
+
const hasHTMLEntities = /&(?:quot|apos|amp|lt|gt|#34|#39|#47|#60|#62|#x22|#x27|#x2[Ff]|#x3[Cc]|#x3[Ee]);/.test(text)
|
|
132
133
|
|
|
133
134
|
if (hasHTMLEntities) {
|
|
134
|
-
return this.decodeHTMLEntities(text)
|
|
135
|
+
return this.decodeHTMLEntities(text)
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
const hasUnescapedQuotes = /"(?![>\s])|'(?![>\s])/.test(text.trim())
|
|
138
|
-
const hasUnescapedSpecialChars = /[<>]/.test(text)
|
|
138
|
+
const hasUnescapedQuotes = /"(?![>\s])|'(?![>\s])/.test(text.trim())
|
|
139
|
+
const hasUnescapedSpecialChars = /[<>]/.test(text)
|
|
139
140
|
|
|
140
141
|
if (hasUnescapedQuotes || hasUnescapedSpecialChars) {
|
|
141
|
-
return text
|
|
142
|
+
return text
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
return text
|
|
145
|
+
return text
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
decodeHTMLEntities(text) {
|
|
148
|
+
decodeHTMLEntities (text) {
|
|
148
149
|
// ARMOR: Comprehensively decode HTML entities
|
|
149
|
-
const textarea = document.createElement('textarea')
|
|
150
|
-
textarea.innerHTML = text
|
|
151
|
-
let decoded = textarea.value
|
|
150
|
+
const textarea = document.createElement('textarea')
|
|
151
|
+
textarea.innerHTML = text
|
|
152
|
+
let decoded = textarea.value
|
|
152
153
|
|
|
153
154
|
decoded = decoded
|
|
154
155
|
.replace(/"/gi, '"')
|
|
@@ -165,87 +166,88 @@ class CopyButtonHandler {
|
|
|
165
166
|
.replace(/>/gi, '>')
|
|
166
167
|
.replace(/>/gi, '>')
|
|
167
168
|
.replace(/[Ee];/g, '>')
|
|
168
|
-
.replace(/&/gi, '&')
|
|
169
|
+
.replace(/&/gi, '&')
|
|
169
170
|
|
|
170
|
-
return decoded
|
|
171
|
+
return decoded
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
showSuccess(svg) {
|
|
174
|
-
this.changeToCheckmark(svg)
|
|
175
|
-
this.updateTooltip(this.successMessage)
|
|
176
|
-
setTimeout(() => this.reset(svg), this.resetDelay)
|
|
174
|
+
showSuccess (svg) {
|
|
175
|
+
this.changeToCheckmark(svg)
|
|
176
|
+
this.updateTooltip(this.successMessage)
|
|
177
|
+
setTimeout(() => this.reset(svg), this.resetDelay)
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
changeToCheckmark(svg) {
|
|
180
|
-
const path = svg.querySelector('path')
|
|
180
|
+
changeToCheckmark (svg) {
|
|
181
|
+
const path = svg.querySelector('path')
|
|
181
182
|
if (path) {
|
|
182
|
-
svg.setAttribute('viewBox', this.checkmarkViewbox)
|
|
183
|
-
path.setAttribute('d', this.checkmarkPath)
|
|
183
|
+
svg.setAttribute('viewBox', this.checkmarkViewbox)
|
|
184
|
+
path.setAttribute('d', this.checkmarkPath)
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
updateTooltip(message) {
|
|
188
|
+
updateTooltip (message) {
|
|
188
189
|
if (!message || typeof message !== 'string') {
|
|
189
|
-
console.warn('Invalid tooltip message:', message)
|
|
190
|
-
return
|
|
190
|
+
console.warn('Invalid tooltip message:', message)
|
|
191
|
+
return
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
this.button.setAttribute('data-bs-title', message)
|
|
194
|
+
this.button.setAttribute('data-bs-title', message)
|
|
194
195
|
|
|
195
196
|
try {
|
|
196
|
-
const tooltip = bootstrap.Tooltip.getInstance(this.button)
|
|
197
|
+
const tooltip = bootstrap.Tooltip.getInstance(this.button)
|
|
197
198
|
if (tooltip) {
|
|
198
|
-
tooltip._config.title = message
|
|
199
|
-
tooltip.update()
|
|
200
|
-
tooltip.show()
|
|
199
|
+
tooltip._config.title = message
|
|
200
|
+
tooltip.update()
|
|
201
|
+
tooltip.show()
|
|
201
202
|
}
|
|
202
203
|
} catch (err) {
|
|
203
|
-
console.warn('Bootstrap Tooltip unavailable or error updating tooltip:', err)
|
|
204
|
+
console.warn('Bootstrap Tooltip unavailable or error updating tooltip:', err)
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
reset(svg) {
|
|
208
|
-
this.button.innerHTML = this.originalSvgHTML
|
|
209
|
-
this.updateTooltip(this.originalTitle)
|
|
208
|
+
reset (svg) {
|
|
209
|
+
this.button.innerHTML = this.originalSvgHTML
|
|
210
|
+
this.updateTooltip(this.originalTitle)
|
|
210
211
|
|
|
211
212
|
try {
|
|
212
|
-
const tooltip = bootstrap.Tooltip.getInstance(this.button)
|
|
213
|
+
const tooltip = bootstrap.Tooltip.getInstance(this.button)
|
|
213
214
|
if (tooltip) {
|
|
214
|
-
tooltip.hide()
|
|
215
|
+
tooltip.hide()
|
|
215
216
|
}
|
|
216
217
|
} catch (err) {
|
|
217
|
-
console.warn('Bootstrap Tooltip unavailable or error hiding tooltip:', err)
|
|
218
|
+
console.warn('Bootstrap Tooltip unavailable or error hiding tooltip:', err)
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
// Initialize all copy buttons when DOM is ready
|
|
223
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
224
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
224
225
|
// Get configuration from script's data attributes
|
|
225
|
-
const scriptElement = document.getElementById('centos-copyvalue')
|
|
226
|
-
const config = {}
|
|
226
|
+
const scriptElement = document.getElementById('centos-copyvalue')
|
|
227
|
+
const config = {}
|
|
227
228
|
|
|
228
229
|
if (scriptElement) {
|
|
229
230
|
if (scriptElement.dataset.resetDelay) {
|
|
230
|
-
config.resetDelay = parseInt(scriptElement.dataset.resetDelay, 10)
|
|
231
|
+
config.resetDelay = parseInt(scriptElement.dataset.resetDelay, 10)
|
|
231
232
|
}
|
|
232
233
|
if (scriptElement.dataset.checkmarkPath) {
|
|
233
|
-
config.checkmarkPath = scriptElement.dataset.checkmarkPath
|
|
234
|
+
config.checkmarkPath = scriptElement.dataset.checkmarkPath
|
|
234
235
|
}
|
|
235
236
|
if (scriptElement.dataset.checkmarkViewbox) {
|
|
236
|
-
config.checkmarkViewbox = scriptElement.dataset.checkmarkViewbox
|
|
237
|
+
config.checkmarkViewbox = scriptElement.dataset.checkmarkViewbox
|
|
237
238
|
}
|
|
238
239
|
if (scriptElement.dataset.successMessage) {
|
|
239
|
-
config.successMessage = scriptElement.dataset.successMessage
|
|
240
|
+
config.successMessage = scriptElement.dataset.successMessage
|
|
240
241
|
}
|
|
241
242
|
}
|
|
242
243
|
|
|
243
|
-
const copyButtons = document.querySelectorAll('.copy-btn')
|
|
244
|
+
const copyButtons = document.querySelectorAll('.copy-btn')
|
|
244
245
|
copyButtons.forEach(button => {
|
|
245
246
|
try {
|
|
246
|
-
new CopyButtonHandler(button, config)
|
|
247
|
+
new CopyButtonHandler(button, config)
|
|
247
248
|
} catch (err) {
|
|
248
|
-
console.error('Failed to initialize copy button:', button, 'Error:', err)
|
|
249
|
+
console.error('Failed to initialize copy button:', button, 'Error:', err)
|
|
249
250
|
}
|
|
250
|
-
})
|
|
251
|
-
})
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
})()
|
data/assets/js/base/datatable.js
CHANGED
|
@@ -2,54 +2,40 @@
|
|
|
2
2
|
// Reads configuration from data attributes on this script tag
|
|
3
3
|
// Applied via datatable.html.liquid template with data attributes
|
|
4
4
|
|
|
5
|
-
(function() {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
(function () {
|
|
6
|
+
if (typeof DataTable === 'undefined') {
|
|
7
|
+
console.warn('datatable: DataTable not available')
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const scriptTag = document.getElementById('centos-datatable')
|
|
8
12
|
|
|
9
13
|
if (!scriptTag) {
|
|
10
|
-
console.warn('DataTables: script tag with id "centos-datatable" not found')
|
|
11
|
-
return
|
|
14
|
+
console.warn('DataTables: script tag with id "centos-datatable" not found')
|
|
15
|
+
return
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const lengthMenuStr = scriptTag.dataset.lengthMenu || '[10,25,50,100]';
|
|
18
|
+
const pageLength = parseInt(scriptTag.dataset.pageLength || '10', 10)
|
|
19
|
+
const lengthMenuStr = scriptTag.dataset.lengthMenu || '[10,25,50,75]'
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
let lengthMenu = [10, 25, 50, 100];
|
|
21
|
+
let lengthMenu = [10, 25, 50, 100]
|
|
20
22
|
try {
|
|
21
|
-
lengthMenu = JSON.parse(lengthMenuStr)
|
|
23
|
+
lengthMenu = JSON.parse(lengthMenuStr)
|
|
22
24
|
} catch (e) {
|
|
23
|
-
console.warn('DataTables: Invalid lengthMenu format, using default:', e)
|
|
25
|
+
console.warn('DataTables: Invalid lengthMenu format, using default:', e)
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const enabledCondition = (rowCount > 10);
|
|
41
|
-
|
|
42
|
-
// Initialize DataTable with configuration
|
|
43
|
-
tableElement.DataTable({
|
|
44
|
-
responsive: true,
|
|
45
|
-
paging: enabledCondition,
|
|
46
|
-
info: enabledCondition,
|
|
47
|
-
order: [[0, 'asc']], // Order by first column
|
|
48
|
-
searching: enabledCondition,
|
|
49
|
-
pageLength: pageLength,
|
|
50
|
-
lengthMenu: lengthMenu
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
})();
|
|
28
|
+
document.querySelectorAll('table.dataTable').forEach(function (tableEl) {
|
|
29
|
+
const rowCount = tableEl.querySelectorAll('tbody tr').length
|
|
30
|
+
const enabledCondition = rowCount > 10
|
|
31
|
+
|
|
32
|
+
new DataTable(tableEl, {
|
|
33
|
+
paging: enabledCondition,
|
|
34
|
+
info: enabledCondition,
|
|
35
|
+
searching: enabledCondition,
|
|
36
|
+
order: [[0, 'asc']],
|
|
37
|
+
pageLength: pageLength,
|
|
38
|
+
lengthMenu: lengthMenu
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
})()
|
|
@@ -2,107 +2,107 @@
|
|
|
2
2
|
|
|
3
3
|
document.addEventListener('DOMContentLoaded', () => {
|
|
4
4
|
if (typeof bootstrap === 'undefined') {
|
|
5
|
-
console.warn('heading-anchor: Bootstrap not available')
|
|
6
|
-
return
|
|
5
|
+
console.warn('heading-anchor: Bootstrap not available')
|
|
6
|
+
return
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const DEFAULT_TEXT = 'Copy link'
|
|
10
|
-
const scriptEl = document.getElementById('centos-heading-anchor')
|
|
9
|
+
const DEFAULT_TEXT = 'Copy link'
|
|
10
|
+
const scriptEl = document.getElementById('centos-heading-anchor')
|
|
11
11
|
const RESET_DELAY = scriptEl?.dataset.resetDelay
|
|
12
|
-
? parseInt(scriptEl.dataset.resetDelay, 10) :
|
|
13
|
-
const SUCCESS_TEXT = scriptEl?.dataset.successMessage || 'Copied!'
|
|
12
|
+
? parseInt(scriptEl.dataset.resetDelay, 10) : 2000
|
|
13
|
+
const SUCCESS_TEXT = scriptEl?.dataset.successMessage || 'Copied!'
|
|
14
14
|
|
|
15
15
|
// One shared region: multiple regions would cause simultaneous AT announcements per anchor.
|
|
16
|
-
let liveRegion = document.getElementById('centos-heading-anchor-live')
|
|
16
|
+
let liveRegion = document.getElementById('centos-heading-anchor-live')
|
|
17
17
|
if (!liveRegion) {
|
|
18
|
-
liveRegion = document.createElement('div')
|
|
19
|
-
liveRegion.id = 'centos-heading-anchor-live'
|
|
20
|
-
liveRegion.setAttribute('aria-live', 'polite')
|
|
21
|
-
liveRegion.setAttribute('aria-atomic', 'true')
|
|
22
|
-
liveRegion.className = 'visually-hidden'
|
|
23
|
-
document.body.appendChild(liveRegion)
|
|
18
|
+
liveRegion = document.createElement('div')
|
|
19
|
+
liveRegion.id = 'centos-heading-anchor-live'
|
|
20
|
+
liveRegion.setAttribute('aria-live', 'polite')
|
|
21
|
+
liveRegion.setAttribute('aria-atomic', 'true')
|
|
22
|
+
liveRegion.className = 'visually-hidden'
|
|
23
|
+
document.body.appendChild(liveRegion)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
document.querySelectorAll('.heading-anchor[data-copy-anchor]').forEach(anchor => {
|
|
27
27
|
// FA SVG+JS replaces <i> with <svg> before this DOMContentLoaded fires.
|
|
28
28
|
// Cache innerHTML after FA has processed so it can be restored on reset.
|
|
29
|
-
const originalIconHTML = anchor.innerHTML
|
|
29
|
+
const originalIconHTML = anchor.innerHTML
|
|
30
30
|
// Guards blur/mouseenter handlers from collapsing the tooltip during the success window.
|
|
31
|
-
let isLocked = false
|
|
31
|
+
let isLocked = false
|
|
32
32
|
|
|
33
33
|
const tooltip = new bootstrap.Tooltip(anchor, {
|
|
34
34
|
title: DEFAULT_TEXT,
|
|
35
35
|
trigger: 'manual', // CSS sets pointer-events:none on hidden anchors, defeating Bootstrap's auto hover/focus triggers.
|
|
36
36
|
placement: 'top',
|
|
37
37
|
animation: false // Prevents Bootstrap's fade and the CSS opacity transition from desyncing.
|
|
38
|
-
})
|
|
38
|
+
})
|
|
39
39
|
|
|
40
40
|
const updateUI = (isSuccess) => {
|
|
41
|
-
tooltip.setContent({ '.tooltip-inner': isSuccess ? SUCCESS_TEXT : DEFAULT_TEXT })
|
|
41
|
+
tooltip.setContent({ '.tooltip-inner': isSuccess ? SUCCESS_TEXT : DEFAULT_TEXT })
|
|
42
42
|
if (isSuccess) {
|
|
43
43
|
// FA SVG+JS mode: <i> has been replaced by <svg>; swap the path to a checkmark.
|
|
44
|
-
const svg = anchor.querySelector('svg')
|
|
44
|
+
const svg = anchor.querySelector('svg')
|
|
45
45
|
if (svg) {
|
|
46
|
-
const path = svg.querySelector('path')
|
|
46
|
+
const path = svg.querySelector('path')
|
|
47
47
|
if (path) {
|
|
48
|
-
svg.setAttribute('viewBox', '0 0 448 512')
|
|
49
|
-
path.setAttribute('d', 'M434.8 70.1c14.3 10.4 17.5 30.4 7.1 44.7l-256 352c-5.5 7.6-14 12.3-23.4 13.1s-18.5-2.7-25.1-9.3l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l101.5 101.5 234-321.7c10.4-14.3 30.4-17.5 44.7-7.1z')
|
|
48
|
+
svg.setAttribute('viewBox', '0 0 448 512')
|
|
49
|
+
path.setAttribute('d', 'M434.8 70.1c14.3 10.4 17.5 30.4 7.1 44.7l-256 352c-5.5 7.6-14 12.3-23.4 13.1s-18.5-2.7-25.1-9.3l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l101.5 101.5 234-321.7c10.4-14.3 30.4-17.5 44.7-7.1z')
|
|
50
50
|
}
|
|
51
51
|
} else {
|
|
52
52
|
// FA webfont mode: <i> is still in the DOM; toggle classes normally.
|
|
53
|
-
const icon = anchor.querySelector('i')
|
|
54
|
-
if (icon) { icon.classList.remove('fa-
|
|
53
|
+
const icon = anchor.querySelector('i')
|
|
54
|
+
if (icon) { icon.classList.remove('fa-hashtag'); icon.classList.add('fa-check') }
|
|
55
55
|
}
|
|
56
56
|
} else {
|
|
57
|
-
anchor.innerHTML = originalIconHTML
|
|
57
|
+
anchor.innerHTML = originalIconHTML
|
|
58
58
|
}
|
|
59
|
-
anchor.classList.toggle('is-active', isSuccess)
|
|
59
|
+
anchor.classList.toggle('is-active', isSuccess)
|
|
60
60
|
// Empty string on reset prevents AT from re-announcing stale text on DOM inspection.
|
|
61
|
-
liveRegion.textContent = isSuccess ? SUCCESS_TEXT : ''
|
|
62
|
-
if (isSuccess) tooltip.show()
|
|
63
|
-
}
|
|
61
|
+
liveRegion.textContent = isSuccess ? SUCCESS_TEXT : ''
|
|
62
|
+
if (isSuccess) tooltip.show()
|
|
63
|
+
}
|
|
64
64
|
|
|
65
65
|
const handleInteraction = (show) => {
|
|
66
|
-
if (!isLocked) show ? tooltip.show() : tooltip.hide()
|
|
67
|
-
}
|
|
66
|
+
if (!isLocked) show ? tooltip.show() : tooltip.hide()
|
|
67
|
+
}
|
|
68
68
|
|
|
69
|
-
anchor.addEventListener('mouseenter', () => handleInteraction(true))
|
|
70
|
-
anchor.addEventListener('mouseleave', () => handleInteraction(false))
|
|
71
|
-
anchor.addEventListener('focus', () => handleInteraction(true))
|
|
72
|
-
anchor.addEventListener('blur', () => handleInteraction(false))
|
|
69
|
+
anchor.addEventListener('mouseenter', () => handleInteraction(true))
|
|
70
|
+
anchor.addEventListener('mouseleave', () => handleInteraction(false))
|
|
71
|
+
anchor.addEventListener('focus', () => handleInteraction(true))
|
|
72
|
+
anchor.addEventListener('blur', () => handleInteraction(false))
|
|
73
73
|
|
|
74
74
|
anchor.addEventListener('click', async (e) => {
|
|
75
|
-
e.preventDefault()
|
|
76
|
-
if (isLocked) return
|
|
75
|
+
e.preventDefault()
|
|
76
|
+
if (isLocked) return
|
|
77
77
|
|
|
78
78
|
if (!navigator.clipboard) {
|
|
79
|
-
console.warn('heading-anchor: Clipboard API unavailable')
|
|
80
|
-
return
|
|
79
|
+
console.warn('heading-anchor: Clipboard API unavailable')
|
|
80
|
+
return
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
const url = window.location.origin + window.location.pathname + anchor.dataset.copyAnchor
|
|
83
|
+
const url = window.location.origin + window.location.pathname + anchor.dataset.copyAnchor
|
|
84
84
|
|
|
85
85
|
try {
|
|
86
|
-
await navigator.clipboard.writeText(url)
|
|
87
|
-
isLocked = true
|
|
86
|
+
await navigator.clipboard.writeText(url)
|
|
87
|
+
isLocked = true
|
|
88
88
|
// isLocked must be true before blur() so the resulting blur event is suppressed
|
|
89
89
|
// and does not hide the tooltip before the success state has been rendered.
|
|
90
|
-
anchor.blur()
|
|
91
|
-
updateUI(true)
|
|
90
|
+
anchor.blur()
|
|
91
|
+
updateUI(true)
|
|
92
92
|
|
|
93
93
|
setTimeout(() => {
|
|
94
|
-
isLocked = false
|
|
95
|
-
updateUI(false)
|
|
94
|
+
isLocked = false
|
|
95
|
+
updateUI(false)
|
|
96
96
|
// mouseleave/blur events fired while isLocked was true were suppressed;
|
|
97
97
|
// hide now if the user already moved away during the success window.
|
|
98
|
-
if (!anchor.matches(':hover')) tooltip.hide()
|
|
99
|
-
}, RESET_DELAY)
|
|
98
|
+
if (!anchor.matches(':hover')) tooltip.hide()
|
|
99
|
+
}, RESET_DELAY)
|
|
100
100
|
|
|
101
101
|
} catch (err) {
|
|
102
|
-
console.warn('heading-anchor: clipboard write failed', err)
|
|
103
|
-
isLocked = false
|
|
104
|
-
anchor.innerHTML = originalIconHTML
|
|
102
|
+
console.warn('heading-anchor: clipboard write failed', err)
|
|
103
|
+
isLocked = false
|
|
104
|
+
anchor.innerHTML = originalIconHTML
|
|
105
105
|
}
|
|
106
|
-
})
|
|
107
|
-
})
|
|
108
|
-
})
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
})
|
data/assets/js/base/highlight.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Highlight.js Initialization
|
|
2
2
|
// Initializes syntax highlighting on all code blocks with 'hljs' class
|
|
3
3
|
// Enables copy-to-clipboard functionality via highlightjs-copy plugin
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
(function () {
|
|
5
|
+
if (typeof hljs === 'undefined' || typeof CopyButtonPlugin === 'undefined') {
|
|
6
|
+
console.warn('highlight: hljs or CopyButtonPlugin not available')
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
document.querySelectorAll('.content code').forEach(el => el.classList.add('hljs'))
|
|
10
|
+
hljs.addPlugin(new CopyButtonPlugin())
|
|
11
|
+
hljs.highlightAll()
|
|
12
|
+
})()
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Bootstrap Tooltip and Popover Initialization
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
(function () {
|
|
3
|
+
if (typeof bootstrap === 'undefined') {
|
|
4
|
+
console.warn('init-tooltips: Bootstrap not available')
|
|
5
|
+
return
|
|
6
|
+
}
|
|
7
|
+
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
|
8
|
+
;[...tooltipTriggerList].forEach((el) => new bootstrap.Tooltip(el))
|
|
4
9
|
|
|
5
|
-
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
|
|
6
|
-
|
|
10
|
+
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
|
|
11
|
+
;[...popoverTriggerList].forEach((el) => new bootstrap.Popover(el))
|
|
12
|
+
})()
|