arsxy-theme 1.0.0.pre.rc
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +166 -0
- data/_config.yml +131 -0
- data/_includes/footer.html +45 -0
- data/_includes/header.html +68 -0
- data/_includes/image.html +8 -0
- data/_includes/related-posts.html +83 -0
- data/_includes/search.html +32 -0
- data/_includes/social-sharing.html +86 -0
- data/_includes/toc.html +83 -0
- data/_layouts/default.html +76 -0
- data/_layouts/docs.html +90 -0
- data/_layouts/home.html +172 -0
- data/_layouts/post.html +114 -0
- data/_sass/_base.scss +264 -0
- data/_sass/_dark-mode.scss +749 -0
- data/_sass/_layout.scss +118 -0
- data/_sass/_responsive.scss +157 -0
- data/_sass/_syntax-highlighting.scss +147 -0
- data/_sass/_typography.scss +226 -0
- data/_sass/_utilities.scss +138 -0
- data/_sass/_variables.scss +63 -0
- data/_sass/components/_code.scss +245 -0
- data/_sass/components/_docs.scss +263 -0
- data/_sass/components/_footer.scss +170 -0
- data/_sass/components/_header.scss +313 -0
- data/_sass/components/_homepage.scss +597 -0
- data/_sass/components/_image.scss +22 -0
- data/_sass/components/_pagination.scss +146 -0
- data/_sass/components/_post.scss +1039 -0
- data/_sass/components/_related-posts.scss +70 -0
- data/_sass/components/_search.scss +276 -0
- data/_sass/components/_social-sharing.scss +95 -0
- data/_sass/components/_toc.scss +193 -0
- data/assets/css/main.scss +24 -0
- data/assets/images/favicon/android-chrome-192x192.png +0 -0
- data/assets/images/favicon/android-chrome-512x512.png +0 -0
- data/assets/images/favicon/apple-touch-icon.png +0 -0
- data/assets/images/favicon/favicon-16x16.png +0 -0
- data/assets/images/favicon/favicon-32x32.png +0 -0
- data/assets/images/favicon/favicon.ico +0 -0
- data/assets/images/favicon/site.webmanifest +19 -0
- data/assets/images/new-features-update.svg +58 -0
- data/assets/images/related-posts-example.svg +37 -0
- data/assets/images/seo-optimization.png +0 -0
- data/assets/images/social-sharing-sample.png +0 -0
- data/assets/images/ss-arsxy-theme.png +0 -0
- data/assets/images/table-of-content-thumbnail.png +0 -0
- data/assets/images/theme-demo-2.jpeg +0 -0
- data/assets/images/theme-demo.jpeg +0 -0
- data/assets/images/welcome-to-arsxy-theme.png +0 -0
- data/assets/js/main.js +369 -0
- data/assets/js/search-index.json +31 -0
- data/assets/js/search.js +207 -0
- metadata +226 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
<!--
|
2
|
+
This is an SVG placeholder for the new features update image.
|
3
|
+
It would be better to replace this with a real screenshot or custom image.
|
4
|
+
-->
|
5
|
+
<svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
|
6
|
+
<style>
|
7
|
+
.title { fill: #333; font-family: 'Arial', sans-serif; font-size: 60px; font-weight: bold; }
|
8
|
+
.subtitle { fill: #666; font-family: 'Arial', sans-serif; font-size: 36px; }
|
9
|
+
.feature { fill: #444; font-family: 'Arial', sans-serif; font-size: 32px; font-weight: bold; }
|
10
|
+
.icon { stroke: #0066cc; stroke-width: 3; fill: none; }
|
11
|
+
.bg-rect { fill: #f8f9fa; stroke: #ddd; stroke-width: 2; }
|
12
|
+
</style>
|
13
|
+
|
14
|
+
<rect width="100%" height="100%" fill="#ffffff"/>
|
15
|
+
|
16
|
+
<!-- Header background -->
|
17
|
+
<rect width="100%" height="150" fill="#0066cc"/>
|
18
|
+
|
19
|
+
<!-- Title -->
|
20
|
+
<text x="50" y="100" class="title" fill="#ffffff">Arsxy Theme - New Features</text>
|
21
|
+
|
22
|
+
<!-- Feature sections -->
|
23
|
+
<rect x="50" y="200" width="340" height="380" rx="10" class="bg-rect"/>
|
24
|
+
<rect x="430" y="200" width="340" height="380" rx="10" class="bg-rect"/>
|
25
|
+
<rect x="810" y="200" width="340" height="380" rx="10" class="bg-rect"/>
|
26
|
+
|
27
|
+
<!-- Feature titles -->
|
28
|
+
<text x="170" y="250" text-anchor="middle" class="feature">Related Posts</text>
|
29
|
+
<text x="600" y="250" text-anchor="middle" class="feature">Social Sharing</text>
|
30
|
+
<text x="980" y="250" text-anchor="middle" class="feature">Search</text>
|
31
|
+
|
32
|
+
<!-- Feature icons -->
|
33
|
+
<!-- Related posts icon -->
|
34
|
+
<g transform="translate(170, 320)">
|
35
|
+
<rect x="-60" y="-35" width="120" height="70" rx="5" class="icon"/>
|
36
|
+
<rect x="-60" y="50" width="120" height="70" rx="5" class="icon"/>
|
37
|
+
<rect x="-60" y="135" width="120" height="70" rx="5" class="icon"/>
|
38
|
+
<line x1="-40" y1="0" x2="40" y2="0" class="icon"/>
|
39
|
+
<line x1="-40" y1="85" x2="40" y2="85" class="icon"/>
|
40
|
+
<line x1="-40" y1="170" x2="40" y2="170" class="icon"/>
|
41
|
+
</g>
|
42
|
+
|
43
|
+
<!-- Social sharing icon -->
|
44
|
+
<g transform="translate(600, 320)">
|
45
|
+
<circle cx="0" cy="0" r="50" class="icon"/>
|
46
|
+
<path d="M -25,15 L 0,-15 L 25,15" class="icon"/>
|
47
|
+
<path d="M 0,-15 L 0,50" class="icon"/>
|
48
|
+
</g>
|
49
|
+
|
50
|
+
<!-- Search icon -->
|
51
|
+
<g transform="translate(980, 320)">
|
52
|
+
<circle cx="-15" cy="-15" r="40" class="icon"/>
|
53
|
+
<line x1="15" y1="15" x2="45" y2="45" stroke-width="6" class="icon"/>
|
54
|
+
</g>
|
55
|
+
|
56
|
+
<!-- Bottom tagline -->
|
57
|
+
<text x="600" y="600" text-anchor="middle" class="subtitle">Enhancing user experience and content discovery</text>
|
58
|
+
</svg>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<!--
|
2
|
+
This is an SVG placeholder showing related posts example.
|
3
|
+
In a production environment, replace with actual screenshots.
|
4
|
+
-->
|
5
|
+
<svg width="800" height="400" xmlns="http://www.w3.org/2000/svg">
|
6
|
+
<style>
|
7
|
+
.title { fill: #333; font-family: 'Arial', sans-serif; font-size: 24px; font-weight: bold; }
|
8
|
+
.post-title { fill: #0066cc; font-family: 'Arial', sans-serif; font-size: 14px; font-weight: bold; }
|
9
|
+
.date { fill: #666; font-family: 'Arial', sans-serif; font-size: 12px; }
|
10
|
+
.post-card { fill: #ffffff; stroke: #ddd; stroke-width: 1; }
|
11
|
+
.post-img { fill: #f0f0f0; stroke: #ddd; stroke-width: 1; }
|
12
|
+
.section-title { fill: #333; font-family: 'Arial', sans-serif; font-size: 28px; font-weight: bold; }
|
13
|
+
</style>
|
14
|
+
|
15
|
+
<rect width="100%" height="100%" fill="#f9f9f9"/>
|
16
|
+
|
17
|
+
<!-- Section Title -->
|
18
|
+
<text x="30" y="50" class="section-title">Related Articles</text>
|
19
|
+
|
20
|
+
<!-- Post 1 -->
|
21
|
+
<rect x="30" y="80" width="230" height="280" rx="5" class="post-card"/>
|
22
|
+
<rect x="45" y="100" width="200" height="120" rx="3" class="post-img"/>
|
23
|
+
<text x="45" y="250" class="post-title">Customizing Your Theme</text>
|
24
|
+
<text x="45" y="270" class="date">May 13, 2025</text>
|
25
|
+
|
26
|
+
<!-- Post 2 -->
|
27
|
+
<rect x="285" y="80" width="230" height="280" rx="5" class="post-card"/>
|
28
|
+
<rect x="300" y="100" width="200" height="120" rx="3" class="post-img"/>
|
29
|
+
<text x="300" y="250" class="post-title">Dark Mode Implementation</text>
|
30
|
+
<text x="300" y="270" class="date">May 14, 2025</text>
|
31
|
+
|
32
|
+
<!-- Post 3 -->
|
33
|
+
<rect x="540" y="80" width="230" height="280" rx="5" class="post-card"/>
|
34
|
+
<rect x="555" y="100" width="200" height="120" rx="3" class="post-img"/>
|
35
|
+
<text x="555" y="250" class="post-title">SEO Optimization Tips</text>
|
36
|
+
<text x="555" y="270" class="date">May 13, 2025</text>
|
37
|
+
</svg>
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/assets/js/main.js
ADDED
@@ -0,0 +1,369 @@
|
|
1
|
+
/**
|
2
|
+
* Main JavaScript file for the Arsxy Theme
|
3
|
+
*/
|
4
|
+
document.addEventListener('DOMContentLoaded', function() {
|
5
|
+
// Initialize dark mode
|
6
|
+
initDarkMode();
|
7
|
+
|
8
|
+
// Initialize table of contents
|
9
|
+
initTableOfContents();
|
10
|
+
|
11
|
+
// Initialize code copy buttons
|
12
|
+
initCodeCopy();
|
13
|
+
|
14
|
+
// Initialize mobile navigation
|
15
|
+
initMobileNav();
|
16
|
+
});
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Dark Mode Functionality
|
20
|
+
*/
|
21
|
+
function initDarkMode() {
|
22
|
+
const themeToggle = document.querySelector('.theme-toggle');
|
23
|
+
if (!themeToggle) return;
|
24
|
+
|
25
|
+
// Get configuration from data attributes
|
26
|
+
const html = document.documentElement;
|
27
|
+
const darkModeEnabled = html.getAttribute('data-dark-mode-enabled') === 'true';
|
28
|
+
|
29
|
+
// If dark mode is completely disabled, ensure light mode is always applied
|
30
|
+
if (!darkModeEnabled) {
|
31
|
+
html.classList.remove('dark-mode');
|
32
|
+
localStorage.setItem('theme', 'light');
|
33
|
+
showLightModeIcon(themeToggle);
|
34
|
+
} else {
|
35
|
+
// Add dark-active class to the toggle button if currently in dark mode
|
36
|
+
if (html.classList.contains('dark-mode')) {
|
37
|
+
themeToggle.classList.add('dark-active');
|
38
|
+
showDarkModeIcon(themeToggle);
|
39
|
+
} else {
|
40
|
+
themeToggle.classList.remove('dark-active');
|
41
|
+
showLightModeIcon(themeToggle);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
themeToggle.addEventListener('click', function() {
|
46
|
+
// If dark mode is completely disabled, don't allow toggling to dark mode
|
47
|
+
if (!darkModeEnabled) {
|
48
|
+
html.classList.remove('dark-mode');
|
49
|
+
localStorage.setItem('theme', 'light');
|
50
|
+
showLightModeIcon(themeToggle);
|
51
|
+
return;
|
52
|
+
}
|
53
|
+
|
54
|
+
if (html.classList.contains('dark-mode')) {
|
55
|
+
html.classList.remove('dark-mode');
|
56
|
+
themeToggle.classList.remove('dark-active');
|
57
|
+
localStorage.setItem('theme', 'light');
|
58
|
+
showLightModeIcon(themeToggle);
|
59
|
+
} else {
|
60
|
+
html.classList.add('dark-mode');
|
61
|
+
themeToggle.classList.add('dark-active');
|
62
|
+
localStorage.setItem('theme', 'dark');
|
63
|
+
showDarkModeIcon(themeToggle);
|
64
|
+
}
|
65
|
+
});
|
66
|
+
|
67
|
+
// Helper functions to toggle icons
|
68
|
+
function showDarkModeIcon(toggle) {
|
69
|
+
const sunIcon = toggle.querySelector('.sun');
|
70
|
+
const moonIcon = toggle.querySelector('.moon');
|
71
|
+
if (sunIcon) sunIcon.style.display = 'block';
|
72
|
+
if (moonIcon) moonIcon.style.display = 'none';
|
73
|
+
}
|
74
|
+
|
75
|
+
function showLightModeIcon(toggle) {
|
76
|
+
const sunIcon = toggle.querySelector('.sun');
|
77
|
+
const moonIcon = toggle.querySelector('.moon');
|
78
|
+
if (sunIcon) sunIcon.style.display = 'none';
|
79
|
+
if (moonIcon) moonIcon.style.display = 'block';
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
* Table of Contents Functionality
|
85
|
+
*/
|
86
|
+
function initTableOfContents() {
|
87
|
+
// Handle static TOC for mobile
|
88
|
+
const staticTocContainer = document.querySelector('.table-of-contents');
|
89
|
+
if (staticTocContainer) {
|
90
|
+
// Make TOC collapsible on mobile
|
91
|
+
if (window.innerWidth < 600) { // $on-palm value
|
92
|
+
const tocHeading = staticTocContainer.querySelector('h2');
|
93
|
+
const tocContent = document.createElement('div');
|
94
|
+
tocContent.className = 'toc-content';
|
95
|
+
|
96
|
+
// Move all elements after the heading into the content div
|
97
|
+
let nextSibling = tocHeading.nextElementSibling;
|
98
|
+
while (nextSibling) {
|
99
|
+
const currentSibling = nextSibling;
|
100
|
+
nextSibling = nextSibling.nextElementSibling;
|
101
|
+
tocContent.appendChild(currentSibling);
|
102
|
+
}
|
103
|
+
|
104
|
+
staticTocContainer.appendChild(tocContent);
|
105
|
+
|
106
|
+
// Add click event to toggle
|
107
|
+
tocHeading.addEventListener('click', function() {
|
108
|
+
tocHeading.classList.toggle('active');
|
109
|
+
tocContent.classList.toggle('expanded');
|
110
|
+
});
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
// Handle all TOC containers (floating TOC, docs sidebar, and static TOC)
|
115
|
+
const floatingToc = document.querySelector('.floating-toc');
|
116
|
+
const docsSidebar = document.querySelector('.docs-sidebar .docs-nav');
|
117
|
+
const tocContainers = [floatingToc, staticTocContainer, docsSidebar].filter(Boolean);
|
118
|
+
|
119
|
+
if (tocContainers.length) {
|
120
|
+
// Add scroll spy functionality for all content areas
|
121
|
+
const headings = document.querySelectorAll('.post-content h2, .post-content h3, .post-content h4, .docs-body h2, .docs-body h3, .docs-body h4, .docs-content h2, .docs-content h3, .docs-content h4');
|
122
|
+
|
123
|
+
// Get all possible TOC links across all containers
|
124
|
+
const tocLinks = document.querySelectorAll('.floating-toc .toc a, .table-of-contents .toc a, .docs-sidebar .docs-nav a');
|
125
|
+
|
126
|
+
if (headings.length && tocLinks.length) {
|
127
|
+
// Add debug logs
|
128
|
+
console.log(`TOC Initialization: Found ${headings.length} headings and ${tocLinks.length} TOC links`);
|
129
|
+
console.log('TOC Containers:', tocContainers);
|
130
|
+
|
131
|
+
// Set up Intersection Observer for headings
|
132
|
+
const headingsObserver = new IntersectionObserver((entries) => {
|
133
|
+
entries.forEach(entry => {
|
134
|
+
// Store entry information in a data attribute for later use
|
135
|
+
if (entry.isIntersecting) {
|
136
|
+
entry.target.dataset.visible = true;
|
137
|
+
} else {
|
138
|
+
delete entry.target.dataset.visible;
|
139
|
+
}
|
140
|
+
|
141
|
+
// Find the heading that's currently most visible
|
142
|
+
const visibleHeadings = Array.from(headings).filter(h => h.dataset.visible);
|
143
|
+
|
144
|
+
if (visibleHeadings.length > 0) {
|
145
|
+
// Sort visible headings by their position from the top
|
146
|
+
const sortedHeadings = visibleHeadings.sort((a, b) => {
|
147
|
+
return a.getBoundingClientRect().top - b.getBoundingClientRect().top;
|
148
|
+
});
|
149
|
+
|
150
|
+
// Use the first visible heading (closest to the top)
|
151
|
+
const activeHeading = sortedHeadings[0];
|
152
|
+
if (activeHeading && activeHeading.id) {
|
153
|
+
updateActiveTocLink(activeHeading.id);
|
154
|
+
}
|
155
|
+
}
|
156
|
+
});
|
157
|
+
}, {
|
158
|
+
rootMargin: '-100px 0px -80% 0px', // Adjust based on when you want to trigger the change
|
159
|
+
threshold: 0
|
160
|
+
});
|
161
|
+
|
162
|
+
// Observe all headings
|
163
|
+
headings.forEach(heading => {
|
164
|
+
if (heading.id) {
|
165
|
+
headingsObserver.observe(heading);
|
166
|
+
} else {
|
167
|
+
console.warn('Found heading without ID:', heading.textContent.trim());
|
168
|
+
}
|
169
|
+
});
|
170
|
+
|
171
|
+
// Add click event to smooth scroll to sections
|
172
|
+
tocLinks.forEach(link => {
|
173
|
+
link.addEventListener('click', function(e) {
|
174
|
+
const href = this.getAttribute('href');
|
175
|
+
|
176
|
+
// Only handle links that refer to page anchors
|
177
|
+
if (href && href.startsWith('#')) {
|
178
|
+
e.preventDefault();
|
179
|
+
|
180
|
+
const targetId = href.substring(1);
|
181
|
+
const targetElement = document.getElementById(targetId);
|
182
|
+
|
183
|
+
if (targetElement) {
|
184
|
+
// Calculate proper scroll position with offset
|
185
|
+
const headerOffset = 100; // Adjust this value based on your header height and desired spacing
|
186
|
+
const elementPosition = targetElement.getBoundingClientRect().top;
|
187
|
+
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
|
188
|
+
|
189
|
+
// Smooth scroll to target with proper offset
|
190
|
+
window.scrollTo({
|
191
|
+
top: offsetPosition - 50,
|
192
|
+
behavior: 'smooth'
|
193
|
+
});
|
194
|
+
|
195
|
+
// Update URL hash without jumping
|
196
|
+
history.pushState(null, null, `#${targetId}`);
|
197
|
+
|
198
|
+
// Update active TOC link
|
199
|
+
updateActiveTocLink(targetId);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
});
|
203
|
+
});
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
// Function to update active TOC link and auto-scroll the TOC
|
208
|
+
function updateActiveTocLink(id) {
|
209
|
+
// Process all TOC containers
|
210
|
+
tocContainers.forEach(container => {
|
211
|
+
if (!container) return;
|
212
|
+
|
213
|
+
let activeLink;
|
214
|
+
|
215
|
+
// Handle docs sidebar differently from other TOCs
|
216
|
+
if (container.classList.contains('docs-nav')) {
|
217
|
+
// For docs sidebar, we need to handle the specific structure
|
218
|
+
const allLinks = container.querySelectorAll('.docs-nav-list a');
|
219
|
+
|
220
|
+
// Remove active class from all links in this container
|
221
|
+
allLinks.forEach(link => {
|
222
|
+
link.classList.remove('active');
|
223
|
+
});
|
224
|
+
|
225
|
+
// Find the matching link in the docs sidebar
|
226
|
+
// First, try direct href match
|
227
|
+
activeLink = container.querySelector(`a[href="${window.location.pathname}#${id}"], a[href="#${id}"]`);
|
228
|
+
|
229
|
+
// If no direct match, try partial match based on section name
|
230
|
+
if (!activeLink) {
|
231
|
+
// Sometimes the ID might be a transformed version of the text
|
232
|
+
const idParts = id.split('-');
|
233
|
+
for (const link of allLinks) {
|
234
|
+
const linkText = link.textContent.trim().toLowerCase();
|
235
|
+
// Check if the link text contains parts of the ID
|
236
|
+
if (idParts.some(part => linkText.includes(part.toLowerCase()))) {
|
237
|
+
activeLink = link;
|
238
|
+
break;
|
239
|
+
}
|
240
|
+
}
|
241
|
+
}
|
242
|
+
} else {
|
243
|
+
// Standard TOC (floating or static)
|
244
|
+
const allLinks = container.querySelectorAll('.toc a');
|
245
|
+
|
246
|
+
// Remove active class from all TOC links in this container
|
247
|
+
allLinks.forEach(link => {
|
248
|
+
link.classList.remove('active');
|
249
|
+
});
|
250
|
+
|
251
|
+
// Find the active link by href
|
252
|
+
activeLink = container.querySelector(`.toc a[href="#${id}"]`);
|
253
|
+
}
|
254
|
+
|
255
|
+
if (activeLink) {
|
256
|
+
// Add active class to current link
|
257
|
+
activeLink.classList.add('active');
|
258
|
+
console.log('Activated link:', activeLink.textContent, 'in container:', container.className);
|
259
|
+
|
260
|
+
// Find the scrollable container for this TOC
|
261
|
+
let tocElement;
|
262
|
+
if (container.classList.contains('docs-nav')) {
|
263
|
+
tocElement = container.closest('.docs-sidebar');
|
264
|
+
} else {
|
265
|
+
tocElement = container.closest('.table-of-contents, .floating-toc');
|
266
|
+
}
|
267
|
+
|
268
|
+
// Auto-scroll the TOC to show the active link
|
269
|
+
if (tocElement && tocElement.scrollHeight > tocElement.clientHeight) {
|
270
|
+
console.log('Autoscrolling container:', tocElement.className);
|
271
|
+
|
272
|
+
// Calculate the position of the active link relative to the TOC container
|
273
|
+
const activeLinkRect = activeLink.getBoundingClientRect();
|
274
|
+
const tocRect = tocElement.getBoundingClientRect();
|
275
|
+
|
276
|
+
// Check if the active link is outside the visible area of the TOC
|
277
|
+
const isLinkAbove = activeLinkRect.top < tocRect.top + 40; // Add padding for better visibility
|
278
|
+
const isLinkBelow = activeLinkRect.bottom > tocRect.bottom - 40; // Add padding for better visibility
|
279
|
+
|
280
|
+
if (isLinkAbove || isLinkBelow) {
|
281
|
+
console.log('Link is outside visible area, scrolling TOC');
|
282
|
+
|
283
|
+
// Get the scroll position of the link relative to the TOC
|
284
|
+
// This is more reliable than offsetTop in some cases
|
285
|
+
const linkTop = activeLink.getBoundingClientRect().top - tocElement.getBoundingClientRect().top + tocElement.scrollTop;
|
286
|
+
|
287
|
+
// Calculate a position that centers the active link in the TOC as much as possible
|
288
|
+
const scrollPosition = linkTop - (tocElement.clientHeight / 2) + (activeLink.clientHeight / 2);
|
289
|
+
|
290
|
+
// Scroll the TOC smoothly to show the active link
|
291
|
+
tocElement.scrollTo({
|
292
|
+
top: Math.max(0, scrollPosition),
|
293
|
+
behavior: 'smooth'
|
294
|
+
});
|
295
|
+
}
|
296
|
+
}
|
297
|
+
}
|
298
|
+
});
|
299
|
+
}
|
300
|
+
}
|
301
|
+
|
302
|
+
/**
|
303
|
+
* Code Copy Button Functionality
|
304
|
+
*/
|
305
|
+
function initCodeCopy() {
|
306
|
+
const codeBlocks = document.querySelectorAll('pre');
|
307
|
+
|
308
|
+
codeBlocks.forEach(block => {
|
309
|
+
// Create copy button
|
310
|
+
const copyButton = document.createElement('button');
|
311
|
+
copyButton.className = 'copy-code-button';
|
312
|
+
copyButton.textContent = 'Copy';
|
313
|
+
|
314
|
+
// Add button to pre element's parent (usually div.highlight)
|
315
|
+
const container = block.parentNode;
|
316
|
+
container.style.position = 'relative';
|
317
|
+
container.insertBefore(copyButton, container.firstChild);
|
318
|
+
|
319
|
+
// Add click event
|
320
|
+
copyButton.addEventListener('click', function() {
|
321
|
+
const code = block.textContent;
|
322
|
+
|
323
|
+
navigator.clipboard.writeText(code).then(() => {
|
324
|
+
// Visual feedback
|
325
|
+
copyButton.textContent = 'Copied!';
|
326
|
+
setTimeout(() => {
|
327
|
+
copyButton.textContent = 'Copy';
|
328
|
+
}, 2000);
|
329
|
+
}).catch(err => {
|
330
|
+
console.error('Could not copy text: ', err);
|
331
|
+
copyButton.textContent = 'Failed';
|
332
|
+
setTimeout(() => {
|
333
|
+
copyButton.textContent = 'Copy';
|
334
|
+
}, 2000);
|
335
|
+
});
|
336
|
+
});
|
337
|
+
});
|
338
|
+
}
|
339
|
+
|
340
|
+
/**
|
341
|
+
* Mobile Navigation Functionality
|
342
|
+
*/
|
343
|
+
function initMobileNav() {
|
344
|
+
const navTrigger = document.querySelector('.nav-trigger');
|
345
|
+
if (!navTrigger) return;
|
346
|
+
|
347
|
+
// Close menu when clicking outside
|
348
|
+
document.addEventListener('click', function(event) {
|
349
|
+
const isNavOpen = navTrigger.checked;
|
350
|
+
const isClickInsideNav = event.target.closest('.site-nav');
|
351
|
+
|
352
|
+
if (isNavOpen && !isClickInsideNav) {
|
353
|
+
navTrigger.checked = false;
|
354
|
+
}
|
355
|
+
});
|
356
|
+
}
|
357
|
+
|
358
|
+
/**
|
359
|
+
* Utility: Debounce function for scroll events
|
360
|
+
*/
|
361
|
+
function debounce(func, wait) {
|
362
|
+
let timeout;
|
363
|
+
return function() {
|
364
|
+
const context = this;
|
365
|
+
const args = arguments;
|
366
|
+
clearTimeout(timeout);
|
367
|
+
timeout = setTimeout(() => func.apply(context, args), wait);
|
368
|
+
};
|
369
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
---
|
2
|
+
layout: null
|
3
|
+
---
|
4
|
+
[
|
5
|
+
{% for post in site.posts %}
|
6
|
+
{
|
7
|
+
"title": {{ post.title | jsonify }},
|
8
|
+
"url": {{ post.url | relative_url | jsonify }},
|
9
|
+
"date": {{ post.date | date: site.date_format | jsonify }},
|
10
|
+
"content": {{ post.content | strip_html | strip_newlines | jsonify }},
|
11
|
+
"excerpt": {{ post.excerpt | strip_html | strip_newlines | truncate: 160 | jsonify }},
|
12
|
+
{% if post.description %}
|
13
|
+
"description": {{ post.description | jsonify }},
|
14
|
+
{% endif %}
|
15
|
+
{% if post.categories %}
|
16
|
+
"categories": {{ post.categories | jsonify }},
|
17
|
+
{% endif %}
|
18
|
+
{% if post.tags %}
|
19
|
+
"tags": {{ post.tags | jsonify }},
|
20
|
+
{% endif %}
|
21
|
+
{% if post.author %}
|
22
|
+
"author": {{ post.author | jsonify }},
|
23
|
+
{% endif %}
|
24
|
+
{% if post.featured_image %}
|
25
|
+
"featured_image": {{ post.featured_image | relative_url | jsonify }}
|
26
|
+
{% else %}
|
27
|
+
"featured_image": ""
|
28
|
+
{% endif %}
|
29
|
+
}{% unless forloop.last %},{% endunless %}
|
30
|
+
{% endfor %}
|
31
|
+
]
|