### Copy Environment Example Source: https://github.com/joelshepherd/tabliss/blob/main/README.md Copies the example environment file to be used for local configuration. This is necessary before entering API keys for external services. ```bash cp .env.example .env ``` -------------------------------- ### Install Dependencies Source: https://github.com/joelshepherd/tabliss/blob/main/README.md Run this command to install project dependencies before executing other scripts. ```bash npm install ``` -------------------------------- ### Local Development Server Source: https://github.com/joelshepherd/tabliss/blob/main/README.md Starts a local development server for making changes. Replace [:target] with 'firefox' or 'chrome' if needed. ```bash npm run dev[:target] ``` -------------------------------- ### Initialize and Use Tabliss Database Source: https://context7.com/joelshepherd/tabliss/llms.txt Demonstrates initializing a database with a defined state interface and performing basic operations like getting, putting, deleting, listening, prefix querying, and atomic transactions. ```typescript import * as DB from './lib/db/db'; // Initialize a database with default values interface AppState { background: { id: string; key: string }; 'widget/time': { id: string; position: string } | null; 'data/settings': unknown; focus: boolean; locale: string; } const db = DB.init({ background: { id: 'default', key: 'background/unsplash' }, focus: false, locale: 'en', }); // Get a value from the database const background = DB.get(db, 'background'); // Returns: { id: 'default', key: 'background/unsplash' } // Put a value into the database (notifies all listeners) DB.put(db, 'focus', true); // Delete a key from the database DB.del(db, 'widget/time'); // Listen to all database changes const unsubscribe = DB.listen(db, ([key, value]) => { console.log(`Key "${key}" changed to:`, value); }); // Query all keys with a prefix for (const [key, value] of DB.prefix(db, 'widget/')) { console.log(`Widget: ${key}`, value); } // Iterates: ['widget/time', {...}], ['widget/greeting', {...}] // Atomic transactions (all or nothing) DB.atomic(db, (snapshot) => { DB.put(snapshot, 'focus', true); DB.put(snapshot, 'locale', 'fr'); // If any operation fails, no changes are committed }); // Clean up listener when done unsubscribe(); ``` -------------------------------- ### Tabliss Build and Development Commands Source: https://context7.com/joelshepherd/tabliss/llms.txt Manage Tabliss project dependencies, run development servers with hot reload for different targets (web, Firefox, Chrome), and create production builds. Includes commands for testing, translations, and environment setup. ```bash # Install dependencies npm install # Development servers (with hot reload) npm run dev # Web development server (default) npm run dev:web # Web development server npm run dev:firefox # Firefox extension development npm run dev:chromium # Chrome extension development # Production builds npm run build # Web production build (default) npm run build:web # Web production build npm run build:firefox # Firefox extension build npm run build:chromium # Chrome extension build # Run tests npm test # Manage translation files npm run translations # Environment setup (for external APIs) cp .env.example .env # Edit .env with your API keys: # UNSPLASH_API_KEY=your_key_here # GIPHY_API_KEY=your_key_here ``` -------------------------------- ### Define Plugin API and Components Source: https://context7.com/joelshepherd/tabliss/llms.txt Defines the Plugin API interface for persistent data, cache, and loading states, along with an example implementation of a weather widget. ```typescript import { API, Config, Cache, Data, Loader } from './plugins/types'; import { ComponentType } from 'react'; // Plugin API interface received by all plugin components interface API { // Persistent data storage (synced, max 100KB total) data?: D; setData: (data: D) => void; // Temporary cache storage (local only) cache?: C; setCache: (cache: C) => void; // Loading indicator control loader: { push: () => void; // Show loading indicator pop: () => void; // Hide loading indicator }; } // Plugin configuration object const weatherPlugin: Config = { key: 'widget/weather', name: 'Weather', description: 'Display current weather and forecast', dashboardComponent: WeatherWidget, // Main display component settingsComponent: WeatherSettings, // Optional settings panel supportsBackdrop: true, // Optional backdrop support }; // Example widget using the API interface WeatherData { latitude: number; longitude: number; units: 'metric' | 'us'; } interface WeatherCache { timestamp: number; temperature: number; } const WeatherWidget: ComponentType> = ({ data, setData, cache, setCache, loader, }) => { React.useEffect(() => { if (!data?.latitude) return; loader.push(); // Show loading fetch(`https://api.weather.com?lat=${data.latitude}`) .then(res => res.json()) .then(result => { setCache({ timestamp: Date.now(), temperature: result.temp }); loader.pop(); // Hide loading }); }, [data?.latitude]); return (
{cache ? `${cache.temperature}°` : 'Loading...'}
); }; ``` -------------------------------- ### Production Build Source: https://github.com/joelshepherd/tabliss/blob/main/README.md Creates a production-ready build of the application. Replace [:target] with 'firefox' or 'chrome' if needed. ```bash npm run build[:target] ``` -------------------------------- ### Initialize Storage Providers Source: https://context7.com/joelshepherd/tabliss/llms.txt Configures database persistence using either IndexedDB for web builds or browser extension storage for Firefox/Chrome, including error handling via streams. ```typescript import * as DB from './lib/db/db'; import * as Storage from './lib/db/storage'; // Initialize database const db = DB.init({ user: 'guest', theme: 'dark' }); // IndexedDB storage (for web builds) const webStorage = await Storage.indexeddb(db, 'tabliss/config'); // - Opens IndexedDB database named 'tabliss/config' // - Loads existing data into db on init // - Automatically persists all future changes // - Returns error stream for handling storage errors // Extension storage (for Firefox/Chrome builds) const extensionStorage = await Storage.extension( db, 'tabliss/config', 'sync' // 'sync' | 'local' | 'managed' ); // - Uses browser.storage API // - 'sync' storage syncs across user's devices // - 'local' storage stays on local machine // - Keys are prefixed: 'tabliss/config/user', 'tabliss/config/theme' // Handle storage errors via returned stream import * as Stream from './lib/db/stream'; Stream.listen(webStorage, (error) => { console.error('Storage error:', error.message); // error.cause contains the original error }); ``` -------------------------------- ### Configure and Use Search Engines Source: https://context7.com/joelshepherd/tabliss/llms.txt Access the list of supported search engines and implement search or suggestion URL generation. ```typescript import { engines } from './plugins/widgets/search/engines'; // Available search engines const searchEngines = [ { key: 'google', name: 'Google', search_url: 'https://www.google.com/search?q={searchTerms}' }, { key: 'duckduckgo', name: 'DuckDuckGo', search_url: 'https://duckduckgo.com/?q={searchTerms}' }, { key: 'bing', name: 'Bing', search_url: 'https://www.bing.com/search?q={searchTerms}' }, { key: 'ecosia', name: 'Ecosia', search_url: 'https://www.ecosia.org/search?q={searchTerms}' }, { key: 'brave', name: 'Brave', search_url: 'https://search.brave.com/search?q={searchTerms}' }, { key: 'startpage', name: 'Startpage', search_url: 'https://www.startpage.com/do/search?q={searchTerms}' }, { key: 'qwant', name: 'Qwant', search_url: 'https://www.qwant.com/?q={searchTerms}' }, { key: 'phind', name: 'Phind', search_url: 'https://phind.com/search?q={searchTerms}' }, // ... and more ]; // Perform a search function performSearch(engine: typeof engines[0], query: string) { const url = engine.search_url.replace('{searchTerms}', encodeURIComponent(query)); window.location.href = url; } // Get search suggestions (if supported) function getSuggestions(engine: typeof engines[0], query: string, callback: string) { if (!engine.suggest_url) return null; return engine.suggest_url .replace('{searchTerms}', encodeURIComponent(query)) .replace('{callback}', callback); } ``` -------------------------------- ### Manage Translation Files Source: https://github.com/joelshepherd/tabliss/blob/main/README.md Executes a script to manage translation files for the project. ```bash npm run translations ``` -------------------------------- ### Implement useApi Hook Source: https://context7.com/joelshepherd/tabliss/llms.txt Demonstrates using the useApi hook within a plugin component to access persistent data, temporary cache, and update state. ```typescript import { useApi } from './hooks/useApi'; // Inside a plugin component function MyWidget({ id }: { id: string }) { const api = useApi(id); // Access persistent data (synced across devices) const settings = api.data as { name: string } | undefined; // Access temporary cache (local only) const cached = api.cache as { lastFetch: number } | undefined; // Update persistent data const handleNameChange = (name: string) => { api.setData({ name }); }; // Update cache const handleFetch = async () => { api.loader.push(); // Show loading indicator const result = await fetchData(); api.setCache({ lastFetch: Date.now(), ...result }); api.loader.pop(); // Hide loading indicator }; return (

Hello, {settings?.name ?? 'Guest'}

handleNameChange(e.target.value)} />
); } ``` -------------------------------- ### Retrieve Unsplash Background Images Source: https://context7.com/joelshepherd/tabliss/llms.txt Fetch images by collection, topic, or search query, and optimize URLs for screen resolution. ```typescript import { fetchImages, buildLink, calculateWidth } from './plugins/backgrounds/unsplash/api'; // Fetch images by collection const collectionImages = await fetchImages({ by: 'collections', collections: '1053828', // Tabliss official collection topics: '', featured: false, search: '', }); // Fetch images by topic const topicImages = await fetchImages({ by: 'topics', collections: '', topics: 'nature,wallpapers', featured: false, search: '', }); // Fetch images by search query const searchImages = await fetchImages({ by: 'search', collections: '', topics: '', featured: true, // Only featured/curated images search: 'mountains sunset', }); // Returns array of images: // [ // { // src: 'https://images.unsplash.com/photo-xxx?ixid=...', // credit: { // imageLink: 'https://unsplash.com/photos/xxx', // location: 'Swiss Alps', // userName: 'John Doe', // userLink: 'https://unsplash.com/@johndoe' // } // }, // // ... up to 10 images // ] // Build optimized image URL for screen size const optimizedUrl = buildLink('https://images.unsplash.com/photo-xxx'); // Adds quality and width parameters for optimal loading // Calculate optimal image width const width = calculateWidth(window.innerWidth, window.devicePixelRatio); // Snaps to nearest 240px for better CDN caching // Min: 1920, Max: 3840 ``` -------------------------------- ### Manage UI State with UiContext Source: https://context7.com/joelshepherd/tabliss/llms.txt Utilize the UiContext to access and manage global UI states such as settings visibility, error display, and loading indicators for asynchronous operations. Use pushLoader and popLoader to control the pending count. ```typescript import React from 'react'; import { UiContext } from './contexts/ui'; function MyComponent() { const { settings, // boolean - settings panel visible errors, // boolean - error panel visible pending, // number - count of pending operations toggleSettings, // () => void - toggle settings panel toggleErrors, // () => void - toggle error panel pushLoader, // () => void - increment pending count popLoader, // () => void - decrement pending count } = React.useContext(UiContext); const handleAsyncOperation = async () => { pushLoader(); // Shows loading indicator try { await fetchSomeData(); } finally { popLoader(); // Hides loading indicator } }; return (
{pending > 0 &&
}
); } ``` -------------------------------- ### React Hooks for Tabliss Database Integration Source: https://context7.com/joelshepherd/tabliss/llms.txt Provides React hooks for reactive database access, automatically updating components when data changes. Includes hooks for single key subscription, key subscription with a setter, and custom selector functions. ```typescript import React from 'react'; import * as DB from './lib/db/db'; import { useValue, useKey, useSelector } from './lib/db/react'; // Create a database instance const db = DB.init({ theme: 'dark', count: 0, 'widget/time': { id: 'w1', position: 'center' }, }); // useValue: Subscribe to a single key (read-only) function ThemeDisplay() { const theme = useValue(db, 'theme'); // Automatically re-renders when 'theme' changes return
Current theme: {theme}
; } // useKey: Subscribe to a key with setter (read-write) function Counter() { const [count, setCount] = useKey(db, 'count'); // Returns tuple: [value, setter function] return (

Count: {count}

); } // useSelector: Subscribe with a custom selector function function WidgetCount() { const widgetCount = useSelector( db, React.useCallback(() => { let count = 0; for (const _ of DB.prefix(db, 'widget/')) count++; return count; }, []) ); // Re-runs selector on any database change return

Total widgets: {widgetCount}

; } ``` -------------------------------- ### Fetch Weather Forecasts and Location Data Source: https://context7.com/joelshepherd/tabliss/llms.txt Use these functions to retrieve current location coordinates, geocode place names, and fetch weather forecasts with specific units. ```typescript import { getForecast, requestLocation, geocodeLocation } from './plugins/widgets/weather/api'; // Get user's current location via browser geolocation const coordinates = await requestLocation(); // Returns: { latitude: 51.5074, longitude: -0.1278 } // Or search for a location by name const searchResult = await geocodeLocation('London'); // Returns: { latitude: 51.5085, longitude: -0.1257 } // Fetch weather forecast const config = { latitude: 51.5074, longitude: -0.1278, units: 'metric' as const, // 'metric' (Celsius) | 'us' (Fahrenheit) }; const loader = { push: () => console.log('Loading...'), pop: () => console.log('Done'), }; const forecast = await getForecast(config, loader); // Returns: // { // timestamp: 1699200000000, // conditions: [ // { // timestamp: 1699200000000, // temperature: 12, // apparentTemperature: 10, // humidity: 75, // weatherCode: 3 // }, // // ... hourly forecasts // ] // } ``` -------------------------------- ### Manage Dashboard Widgets and Backgrounds Source: https://context7.com/joelshepherd/tabliss/llms.txt Functions for modifying dashboard settings, including background selection, widget manipulation, and store persistence. ```typescript import { setBackground, addWidget, removeWidget, reorderWidget, setWidgetDisplay, toggleFocus, importStore, exportStore, resetStore, } from './db/action'; // Change the background plugin setBackground('background/unsplash'); // Or use other backgrounds: setBackground('background/colour'); setBackground('background/gradient'); setBackground('background/giphy'); setBackground('background/image'); // Add a new widget to the dashboard addWidget('widget/time'); addWidget('widget/greeting'); addWidget('widget/weather'); addWidget('widget/quote'); addWidget('widget/todo'); addWidget('widget/search'); addWidget('widget/links'); // Remove a widget by its ID removeWidget('widget-abc123'); // Reorder widgets (move from index 2 to index 0) reorderWidget(2, 0); // Configure widget display properties setWidgetDisplay('widget-abc123', { position: 'topRight', // Widget position on screen colour: '#ffffff', // Text color fontFamily: 'Roboto', // Font family fontSize: 24, // Font size in pixels fontWeight: 400, // Font weight }); // Available positions: type WidgetPosition = | 'topLeft' | 'topCentre' | 'topRight' | 'middleLeft' | 'middleCentre' | 'middleRight' | 'bottomLeft' | 'bottomCentre' | 'bottomRight'; // Toggle focus mode (hides widgets temporarily) toggleFocus(); // Export settings to JSON file const settingsJson = exportStore(); // Returns: '{"background":{...},"widget/...":{...},"version":3}' // Import settings from JSON importStore(JSON.parse(settingsJson)); // Supports version 2 and 3 config formats // Reset all settings to defaults resetStore(); ``` -------------------------------- ### Use RotatingCache Hook for Rotating Items Source: https://context7.com/joelshepherd/tabliss/llms.txt useRotatingCache cycles through a collection of items, such as images or quotes, with configurable rotation intervals. It requires a fetch function, a cache object, a rotation timeout, and dependencies. Use for dynamic content like background images. ```typescript import { useCachedEffect, useRotatingCache, RotatingCache } from './hooks/useCache'; import { useTime } from './hooks/useTime'; // useRotatingCache: Rotate through a collection of items interface Image { url: string; author: string; } function BackgroundImage() { const { cache, setCache } = useApi('background-id'); const image = useRotatingCache( // Fetch function - returns array of items async () => { const res = await fetch('/api/images?count=10'); return res.json(); }, // Cache object from API { cache: cache as RotatingCache, setCache }, // Rotation timeout in milliseconds (0 = rotate on every page load) 60 * 60 * 1000, // 1 hour // Dependencies - refetch when these change ['nature', 'landscape'] ); if (!image) return
Loading...
; return (
Photo by {image.author}
); } ``` -------------------------------- ### Use Time Hook for Clock and Greetings Source: https://context7.com/joelshepherd/tabliss/llms.txt The useTime hook provides the current time, updating every second. It can return absolute time or time adjusted for the user's timezone. Use this for displaying clocks or time-based greetings. ```typescript import { useTime } from './hooks/useTime'; function ClockWidget() { // Get zoned time (respects user's timezone setting) const time = useTime('zoned'); // Updates every second automatically // Get absolute time (ignores timezone setting) const absoluteTime = useTime('absolute'); // Format the time const hours = time.getHours(); const minutes = time.getMinutes().toString().padStart(2, '0'); const seconds = time.getSeconds().toString().padStart(2, '0'); return (
{hours}:{minutes}:{seconds}
); } // Time-based greeting example function Greeting() { const hour = useTime().getHours(); const greeting = hour < 12 ? 'Good morning' : hour < 17 ? 'Good afternoon' : 'Good evening'; return

{greeting}

; } ``` -------------------------------- ### Fetch Quotes from Multiple Sources Source: https://context7.com/joelshepherd/tabliss/llms.txt Use the getQuote function to fetch various types of quotes. Specify the desired category as a string argument. The loader object can be used to manage loading states. ```typescript import { getQuote } from './plugins/widgets/quote/api'; // Create a loader for the API const loader = { push: () => console.log('Fetching quote...'), pop: () => console.log('Quote loaded'), }; // Get quote of the day (various categories available) const dailyQuote = await getQuote(loader, 'inspire'); // Categories: 'inspire', 'management', 'sports', 'life', 'funny', 'love', 'art' // Get a developer excuse const excuse = await getQuote(loader, 'developerexcuses'); // Returns: { quote: "It works on my machine.", timestamp: 1699200000000 } // Get bible verse of the day const verse = await getQuote(loader, 'bible'); // Returns: { author: "John 3:16", quote: "For God so loved...", timestamp: ... } // Response format: // { // quote: "The only way to do great work is to love what you do.", // author: "Steve Jobs", // May be undefined for some sources // timestamp: 1699200000000 // } ``` -------------------------------- ### Use Toggle Hook for Boolean State Management Source: https://context7.com/joelshepherd/tabliss/llms.txt The useToggle hook simplifies managing boolean states. It returns the current state and a function to toggle it. Ideal for UI elements like accordions, modals, or settings panels. ```typescript import { useToggle } from './hooks/useToggle'; import { useDebounce } from './hooks/useDebounce'; // useToggle: Simple boolean toggle state function SettingsPanel() { const [isOpen, toggle] = useToggle(false); // toggle() flips the boolean state return (
{isOpen && (

Settings

{/* Settings content */}
)}
); } ``` -------------------------------- ### Implement Persistent State with useSavedReducer Source: https://context7.com/joelshepherd/tabliss/llms.txt Use this hook to automatically persist state changes to storage via a provided save function. It is ideal for complex state structures like todo lists. ```typescript import { useSavedReducer } from './hooks/useSavedReducer'; // Define action types type TodoAction = | { type: 'ADD_TODO'; data: { id: string; contents: string; completed: boolean } } | { type: 'REMOVE_TODO'; data: { id: string } } | { type: 'TOGGLE_TODO'; data: { id: string } } | { type: 'UPDATE_TODO'; data: { id: string; contents: string } }; type Todo = { id: string; contents: string; completed: boolean }; // Reducer function function todoReducer(state: Todo[], action: TodoAction): Todo[] { switch (action.type) { case 'ADD_TODO': return state.concat(action.data); case 'REMOVE_TODO': return state.filter(todo => todo.id !== action.data.id); case 'TOGGLE_TODO': return state.map(todo => todo.id === action.data.id ? { ...todo, completed: !todo.completed } : todo ); case 'UPDATE_TODO': if (action.data.contents === '') { return state.filter(todo => todo.id !== action.data.id); } return state.map(todo => todo.id === action.data.id ? { ...todo, contents: action.data.contents } : todo ); default: throw new Error('Unknown action'); } } // Usage in component function TodoWidget({ data, setData }: API) { const dispatch = useSavedReducer( todoReducer, data ?? [], // Initial state from persistent storage setData // Save function - called after every state change ); const addTodo = (contents: string) => { dispatch({ type: 'ADD_TODO', data: { id: crypto.randomUUID(), contents, completed: false } }); }; const toggleTodo = (id: string) => { dispatch({ type: 'TOGGLE_TODO', data: { id } }); }; return (
    {(data ?? []).map(todo => (
  • toggleTodo(todo.id)}> {todo.completed ? '✓' : '○'} {todo.contents}
  • ))}
); } ``` -------------------------------- ### Use CachedEffect Hook for Data Fetching with Expiration Source: https://context7.com/joelshepherd/tabliss/llms.txt useCachedEffect fetches data and automatically refetches it when the expiration time passes or dependencies change. Configure expiration by setting a future timestamp. Useful for data that needs to be refreshed periodically, like quotes. ```typescript import { useCachedEffect, useRotatingCache, RotatingCache } from './hooks/useCache'; import { useTime } from './hooks/useTime'; // useCachedEffect: Effect that reruns on expiration or deps change function QuoteOfTheDay({ category }: { category: string }) { const [quote, setQuote] = React.useState(); const [expires, setExpires] = React.useState(0); useCachedEffect( () => { fetch(`/api/quote?category=${category}`) .then(res => res.json()) .then(data => { setQuote(data.quote); // Expire at midnight const tomorrow = new Date(); tomorrow.setHours(24, 0, 0, 0); setExpires(tomorrow.getTime()); }); }, expires, // Refetch when this time passes [category] // Also refetch when category changes ); return
{quote}
; } ``` -------------------------------- ### Use Debounce Hook for Input Value Debouncing Source: https://context7.com/joelshepherd/tabliss/llms.txt The useDebounce hook delays the update of a value until a specified time has passed without changes. This is useful for optimizing operations that depend on rapidly changing inputs, such as search queries. ```typescript import { useToggle } from './hooks/useToggle'; import { useDebounce } from './hooks/useDebounce'; // useDebounce: Debounce rapidly changing values function SearchWidget() { const [query, setQuery] = React.useState(''); const debouncedQuery = useDebounce(query, 300); // 300ms delay React.useEffect(() => { if (debouncedQuery) { // Only fires 300ms after user stops typing fetch(`/api/search?q=${debouncedQuery}`) .then(res => res.json()) .then(setResults); } }, [debouncedQuery]); return ( setQuery(e.target.value)} placeholder="Search..." /> ); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.