PageSourceSearch

https://getgrav.org/user/plugins/codesh/js/codesh.js?6a31d9eb

js getgrav.org collected 2026-09-24 15:17:17 UTC 6,064 bytes, 183 lines download raw bytes

1/**
2 * Codesh - Code Syntax Highlighter
3 * Copy to clipboard and code group tab functionality
4 */
5(function() {
6    'use strict';
7
8    const STORAGE_KEY = 'codesh-tabs';
9
10    /**
11     * Get stored tab preferences
12     */
13    function getStoredTabs() {
14        try {
15            return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
16        } catch (e) {
17            return {};
18        }
19    }
20
21    /**
22     * Store tab preference
23     */
24    function storeTab(syncKey, tabTitle) {
25        try {
26            const stored = getStoredTabs();
27            stored[syncKey] = tabTitle;
28            localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
29        } catch (e) {
30            // Ignore storage errors
31        }
32    }
33
34    /**
35     * Copy code to clipboard
36     */
37    async function copyCode(button) {
38        // Check if we're in a code group or a regular block
39        const group = button.closest('.codesh-group');
40        const block = button.closest('.codesh-block');
41
42        let codeEl;
43        if (group) {
44            // For groups, get the active panel's code
45            const activePanel = group.querySelector('.codesh-group-panel.active');
46            codeEl = activePanel ? activePanel.querySelector('code') : null;
47        } else if (block) {
48            codeEl = block.querySelector('.codesh-code code');
49        }
50
51        if (!codeEl) return;
52
53        try {
54            // Clone and remove line numbers for clean copy
55            const clone = codeEl.cloneNode(true);
56            const lineNumbers = clone.querySelectorAll('.line-number');
57            lineNumbers.forEach(el => el.remove());
58
59            await navigator.clipboard.writeText(clone.textContent);
60
61            // Show success state
62            const textEl = button.querySelector('.codesh-copy-text');
63            const iconEl = button.querySelector('.codesh-copy-icon');
64
65            if (textEl) {
66                const originalText = textEl.textContent;
67                textEl.textContent = 'Copied!';
68                button.classList.add('copied');
69
70                // Swap icon to checkmark
71                if (iconEl) {
72                    iconEl.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>';
73                }
74
75                setTimeout(() => {
76                    textEl.textContent = originalText;
77                    button.classList.remove('copied');
78                    if (iconEl) {
79                        iconEl.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>';
80                    }
81                }, 2000);
82            }
83        } catch (err) {
84            console.error('Codesh: Failed to copy code:', err);
85        }
86    }
87
88    /**
89     * Switch to a tab in a code group
90     */
91    function switchTab(group, tabButton) {
92        const tabId = tabButton.dataset.tab;
93        const tabTitle = tabButton.textContent.trim();
94
95        // Update tab active states
96        group.querySelectorAll('.codesh-group-tab').forEach(tab => {
97            tab.classList.toggle('active', tab.dataset.tab === tabId);
98        });
99
100        // Update panel active states
101        group.querySelectorAll('.codesh-group-panel').forEach(panel => {
102            panel.classList.toggle('active', panel.dataset.panel === tabId);
103        });
104
105        // Sync with other groups if sync key is present
106        const syncKey = group.dataset.sync;
107        if (syncKey) {
108            storeTab(syncKey, tabTitle);
109            syncTabs(syncKey, tabTitle, group);
110        }
111    }
112
113    /**
114     * Sync tabs across groups with same sync key
115     */
116    function syncTabs(syncKey, tabTitle, excludeGroup) {
117        document.querySelectorAll(`.codesh-group[data-sync="${syncKey}"]`).forEach(group => {
118            if (group === excludeGroup) return;
119
120            // Find a tab with matching title
121            const matchingTab = Array.from(group.querySelectorAll('.codesh-group-tab'))
122                .find(tab => tab.textContent.trim() === tabTitle);
123
124            if (matchingTab) {
125                switchTab(group, matchingTab);
126            }
127        });
128    }
129
130    /**
131     * Restore stored tab preferences
132     */
133    function restoreStoredTabs() {
134        const stored = getStoredTabs();
135
136        Object.entries(stored).forEach(([syncKey, tabTitle]) => {
137            document.querySelectorAll(`.codesh-group[data-sync="${syncKey}"]`).forEach(group => {
138                const matchingTab = Array.from(group.querySelectorAll('.codesh-group-tab'))
139                    .find(tab => tab.textContent.trim() === tabTitle);
140
141                if (matchingTab && !matchingTab.classList.contains('active')) {
142                    switchTab(group, matchingTab);
143                }
144            });
145        });
146    }
147
148    /**
149     * Initialize all functionality. Click handling is delegated from the
150     * document so code blocks injected after load — live updates, htmx
151     * swaps, infinite scroll — work without rebinding.
152     */
153    function init() {
154        document.addEventListener('click', function(e) {
155            const copy = e.target.closest('.codesh-copy');
156            if (copy) {
157                e.preventDefault();
158                copyCode(copy);
159                return;
160            }
161            const tab = e.target.closest('.codesh-group-tab');
162            if (tab) {
163                const group = tab.closest('.codesh-group');
164                if (group) {
165                    e.preventDefault();
166                    switchTab(group, tab);
167                }
168            }
169        });
170
171        restoreStoredTabs();
172
173        // Newly swapped-in groups should honor the stored tab preferences.
174        document.addEventListener('htmx:afterSettle', restoreStoredTabs);
175    }
176
177    // Initialize when DOM is ready
178    if (document.readyState === 'loading') {
179        document.addEventListener('DOMContentLoaded', init);
180    } else {
181        init();
182    }
183})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.