Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ const base = process.env.GH_BASE || '/docs/'

// Construct vitepress config object...
import path from 'node:path'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitepress'
import playground from './lib/cds-playground/index.js'
import languages from './languages'
import { Menu } from './menu.js'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

const config = defineConfig({

title: 'capire',
Expand Down Expand Up @@ -77,7 +81,9 @@ const config = defineConfig({
['link', { rel: 'shortcut icon', href: base+'favicon.ico' }],
['link', { rel: 'apple-touch-icon', sizes: '180x180', href: base+'logos/cap.png' }],
// Inline script to restore impl-variant selection immediately (before first paint)
['script', { id: 'check-impl-variant' }, `{const p=new URLSearchParams(location.search),v=p.get('impl-variant')||localStorage.getItem('impl-variant');if(v)document.documentElement.classList.add(v)}`]
['script', { id: 'check-impl-variant' }, `{const p=new URLSearchParams(location.search),v=p.get('impl-variant')||localStorage.getItem('impl-variant');if(v)document.documentElement.classList.add(v)}`],
// Inline script to restore code group tab preferences (before Vue hydration)
['script', {}, readFileSync(path.resolve(__dirname, './lib/restoreCodeGroupPreferences.js'), 'utf-8')]
],

vite: {
Expand Down
193 changes: 193 additions & 0 deletions .vitepress/lib/restoreCodeGroupPreferences.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
;(() => {
// Code Group Tab Synchronization - Early Execution Script
// This script loads preferences and applies them before Vue hydration to prevent flicker
//
// Features:
// - Syncs tabs with exact or fuzzy matching ("/" delimiter)
// - "macOS/Linux" matches "macOS/Linux", "macOS", and "Linux"
// - "macOS" matches "macOS" and "macOS/Linux"
// - Stores preferences by independent dimensions (runtime vs OS)
// - runtime: Node.js ↔ Java
// - os: macOS ↔ Windows ↔ Linux (+ combinations)
// - Storage format: { "runtime": "Java", "os": "macOS" }
// - First entry in each dimension array is the default

// Define independent dimensions of tabs
// Tabs within a dimension are mutually exclusive
// Note: First entry in each dimension is the default (used when no preference is saved)
// Note: Combinations like "macOS/Linux" are handled automatically by fuzzy matching
const TAB_DIMENSIONS = {
'runtime': ['Node.js', 'Java'],
'os': ['macOS', 'Windows', 'Linux']
}

// Determine which dimension a tab belongs to (including fuzzy matches)
const getTabDimension = (tabLabel) => {
for (const [dimension, tabs] of Object.entries(TAB_DIMENSIONS)) {
for (const dimTab of tabs) {
if (tabsMatch(tabLabel, dimTab)) {
return dimension
}
}
}
return null // Unknown dimension
}

// Check if two tab labels match (exact or fuzzy match)
// Treats "/" as a delimiter for combined tabs
const tabsMatch = (tab1, tab2) => {
if (tab1 === tab2) return true

// Split by "/" to get components
const components1 = tab1.split('/').map(s => s.trim())
const components2 = tab2.split('/').map(s => s.trim())

// Check if any component from tab1 exists in components2 or vice versa
return components1.some(c1 => components2.includes(c1)) ||
components2.some(c2 => components1.includes(c2))
}

// Get active tabs from localStorage (dimension-based storage)
const getActiveTabsByDimension = () => {
try {
const stored = localStorage.getItem('code-group-active-tabs')
if (stored) {
const parsed = JSON.parse(stored)
// Handle both old format (array) and new format (object)
if (Array.isArray(parsed)) {
// Migrate from old single-value format
return {}
}
return typeof parsed === 'object' ? parsed : {}
}
} catch (e) {

Check warning on line 63 in .vitepress/lib/restoreCodeGroupPreferences.js

View workflow job for this annotation

GitHub Actions / build

'e' is defined but never used
// localStorage might not be available or JSON parse failed
}
return {}
}

// Clean up old localStorage entries from previous implementation
const cleanupOldEntries = () => {
try {
const keysToRemove = []
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key && (key.startsWith('code-group-preference:') || key.startsWith('code-group-tab:'))) {
keysToRemove.push(key)
}
}
keysToRemove.forEach(key => localStorage.removeItem(key))
} catch (e) {

Check warning on line 80 in .vitepress/lib/restoreCodeGroupPreferences.js

View workflow job for this annotation

GitHub Actions / build

'e' is defined but never used
// localStorage might not be available
}
}

// Clean up old entries on first run
cleanupOldEntries()

// Determine the best tab from a set based on preferences and defaults
const getBestTab = (tabs, activeTabs) => {
// Check if any tab matches an active preference (exact or fuzzy match)
for (const tab of tabs) {
// Find which dimension this tab belongs to
const dimension = getTabDimension(tab)
if (dimension && activeTabs[dimension]) {
const activeTab = activeTabs[dimension]
// Check if this tab matches the active preference
if (tab === activeTab || tabsMatch(tab, activeTab)) {
return tab
}
}
}

// Apply dimension defaults (first entry in each dimension)
for (const tab of tabs) {
const dimension = getTabDimension(tab)
if (dimension && TAB_DIMENSIONS[dimension]) {
const defaultTab = TAB_DIMENSIONS[dimension][0]
// Check if this tab matches the dimension default (exact or fuzzy)
if (tab === defaultTab || tabsMatch(tab, defaultTab)) {
return tab
}
}
}

// Fallback to first tab alphabetically if no match
return tabs.sort()[0]
}

// Load active tabs from storage
const activeTabs = getActiveTabsByDimension()

// Store in global variable for later use by Vue components
window.__CODE_GROUP_ACTIVE_TABS__ = activeTabs

// Apply preferences to a code group element
const applyToCodeGroup = (element) => {
const tabElements = element.querySelectorAll('.tabs label')
const tabs = Array.from(tabElements).map((label) =>
(label.textContent || '').trim()
).filter(Boolean)

if (tabs.length === 0) return

// Determine which tab should be selected
const selectedTab = getBestTab(tabs, activeTabs)
const selectedIndex = tabs.indexOf(selectedTab)

if (selectedIndex === -1) return

// Apply the selection immediately to prevent flicker
const inputs = element.querySelectorAll('.tabs input')
const blocks = element.querySelectorAll('div[class*="language-"], .vp-block')

inputs.forEach((input, index) => {
input.checked = (index === selectedIndex)
})

blocks.forEach((block, index) => {
if (index === selectedIndex) {
block.classList.add('active')
} else {
block.classList.remove('active')
}
})
}

const applyToAllCodeGroups = () => {
const codeGroups = document.querySelectorAll('.vp-code-group')
codeGroups.forEach(applyToCodeGroup)
}

// Apply immediately to any existing code groups (runs synchronously)
applyToAllCodeGroups()

// Watch for code groups being added dynamically (SPA navigation, HMR in dev mode)
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node instanceof HTMLElement) {
if (node.classList?.contains('vp-code-group')) {
applyToCodeGroup(node)
} else if (node.querySelector) {
const codeGroups = node.querySelectorAll('.vp-code-group')
codeGroups.forEach(applyToCodeGroup)
}
}
}
}
})

// Start observing as soon as script runs
if (document.documentElement) {
observer.observe(document.documentElement, {
childList: true,
subtree: true
})
}

// Apply again on DOMContentLoaded as safety net
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', applyToAllCodeGroups)
}
})()
Loading
Loading