docs(release): Dokumentation ins Englische übersetzen und v1.11.1 Docs
- README, CHANGELOG, DISCLAIMER, SECURITY auf Englisch übersetzen - Projekt-Docs (architecture, patterns, widget-schema, style-guide) übersetzen - CODEOWNERS für Master-Branch-Schutz hinzufügen - release.yml auf Englisch übersetzen - STYLE_GUIDE von src/css/ nach docs/ verschieben
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
# Hellion Dashboard — Design & Theme System
|
||||
|
||||
> This document is intentionally written in English. Full German/English i18n support
|
||||
> is planned for v2.0 — until then, English keeps the docs accessible to anyone
|
||||
> who wants to contribute or fork the project.
|
||||
|
||||
---
|
||||
|
||||
## Design Pillars
|
||||
|
||||
| Pillar | Description |
|
||||
|---|---|
|
||||
| **Immersion** | The interface feels like a HUD floating over the scene, not a foreign object sitting on top of it |
|
||||
| **Visual Clarity** | Deliberate use of `blur` separates UI from background and reduces visual noise and cognitive load |
|
||||
| **Harmony** | Every theme pulls its colors from the dominant light sources in its background image |
|
||||
|
||||
---
|
||||
|
||||
## Background Images — WebP Only
|
||||
|
||||
**All background images must be in WebP format.** This is an intentional architectural
|
||||
decision to keep storage quota usage predictable and leave room for future features
|
||||
(widgets, image references, etc.) that also compete for the 10 MB `chrome.storage` limit.
|
||||
|
||||
JPG, PNG and other formats are not accepted, so convert before adding a theme.
|
||||
|
||||
### Recommended Settings
|
||||
|
||||
| Quality | When to use |
|
||||
|---|---|
|
||||
| 85 | Default, good balance of size and sharpness |
|
||||
| 80 | For images over 500 KB |
|
||||
| 90 | For images with fine details (stars, in-game UI text) |
|
||||
|
||||
### Conversion Tools
|
||||
|
||||
**Squoosh** (squoosh.app) — browser-based, no install, nothing gets uploaded to external servers.
|
||||
Drag in the image, pick WebP, set quality to 85, download. Done.
|
||||
|
||||
**cwebp** (command line):
|
||||
```bash
|
||||
cwebp -q 85 input.jpg -o output.webp
|
||||
```
|
||||
|
||||
### Current Theme Images
|
||||
|
||||
| File | Status |
|
||||
|---|---|
|
||||
| `bg-nebula.webp` | ✅ WebP |
|
||||
| `bg-crescent.webp` | ✅ WebP |
|
||||
| `bg-event-horizon.webp` | ✅ WebP |
|
||||
| `bg-merchantman.webp` | ✅ WebP |
|
||||
| `bg-julia-jin.webp` | ✅ WebP |
|
||||
| `bg-sc-sunset.webp` | ✅ WebP |
|
||||
| `bg-hellion-hud.webp` | ✅ WebP |
|
||||
| `bg-hellion-energy.webp` | ✅ WebP |
|
||||
| `bg-satisfactory.webp` | ✅ WebP |
|
||||
| `bg-avorion.webp` | ✅ WebP |
|
||||
| `bg-scPolaris.webp` | ✅ WebP |
|
||||
|
||||
---
|
||||
|
||||
## Anatomy of a Theme
|
||||
|
||||
Every theme lives in `main.css` as a `[data-theme="name"]` block. Copy this template
|
||||
to add a new one:
|
||||
|
||||
```css
|
||||
[data-theme="your-theme-name"] {
|
||||
/* 1. ACCENTS — The light source */
|
||||
--accent: #HEXCODE; /* Main color (neon/light) */
|
||||
--accent-dim: rgba(R, G, B, 0.12); /* Subtle background tint */
|
||||
--accent-glow: rgba(R, G, B, 0.08); /* Glow for logo & clock */
|
||||
--border-accent: rgba(R, G, B, 0.25); /* Focus ring */
|
||||
|
||||
/* 2. BASE — The foundation */
|
||||
--bg-primary: #HEXCODE; /* Darkest point in the image */
|
||||
--bg-board: rgba(R, G, B, 0.55); /* Glass effect on boards */
|
||||
--border: rgba(R, G, B, 0.12); /* Default border */
|
||||
|
||||
/* 3. TEXT — Contrast */
|
||||
--text-primary: #FFFFFF; /* Readable, slightly tinted */
|
||||
--text-secondary: #A0A0A0; /* Desaturated, less visual weight */
|
||||
--text-muted: #606060; /* Barely visible, for hints */
|
||||
|
||||
/* 4. OVERLAY — Vignette */
|
||||
--overlay-bg: radial-gradient(
|
||||
circle at center,
|
||||
transparent 0%,
|
||||
var(--bg-primary) 100%
|
||||
);
|
||||
|
||||
/* 5. COMPONENT COLORS */
|
||||
--header-bg: rgba(R, G, B, 0.94);
|
||||
--board-hover-border: rgba(R, G, B, 0.22);
|
||||
--toggle-on-bg: rgba(R, G, B, 0.20);
|
||||
--logo-shadow: rgba(R, G, B, 0.50);
|
||||
|
||||
/* 6. FONTS */
|
||||
--font-display: 'Rajdhani', sans-serif;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/* Theme-specific overrides */
|
||||
[data-theme="your-theme-name"] .logo { letter-spacing: 4px; }
|
||||
[data-theme="your-theme-name"] .clock { color: var(--accent); }
|
||||
[data-theme="your-theme-name"] .board-title { text-transform: uppercase; }
|
||||
[data-theme="your-theme-name"] .board { backdrop-filter: blur(8px); }
|
||||
[data-theme="your-theme-name"] .bm-item:hover { background: var(--accent-dim); }
|
||||
```
|
||||
|
||||
After adding the CSS block, register the theme in `src/js/themes.js` and add a preview entry in the theme picker.
|
||||
|
||||
---
|
||||
|
||||
## UI Patterns
|
||||
|
||||
### Frosted Glass
|
||||
|
||||
Hardware-accelerated blur for readability on complex backgrounds:
|
||||
|
||||
```css
|
||||
backdrop-filter: blur(8px);
|
||||
```
|
||||
|
||||
Creates depth and visual calm behind text and UI elements. Standard value is `8px`. Only increase it when the background image has a lot of fine detail that competes with the UI.
|
||||
|
||||
### Clock Color
|
||||
|
||||
All themes set `color: var(--accent)` on the clock element. This is a consistent
|
||||
detail across the entire theme system. Don't skip it for new themes.
|
||||
|
||||
```css
|
||||
[data-theme="your-theme"] .clock { color: var(--accent); }
|
||||
```
|
||||
|
||||
### Typography Hierarchy
|
||||
|
||||
| Font | Usage |
|
||||
|---|---|
|
||||
| **Rajdhani** | Display: clock, logo, titles. Anything that should feel like a system readout |
|
||||
| **Inter** | Body: bookmark titles, lists, interactive elements |
|
||||
| **Cinzel** | Fantasy: reserved for themes with a majestic or ancient aesthetic (Crescent, Julia & Jin) |
|
||||
|
||||
### Overlay Strategy
|
||||
|
||||
The overlay gradient determines what stays visible in the background image.
|
||||
|
||||
**Radial (default)** draws attention to the center and darkens edges:
|
||||
```css
|
||||
--overlay-bg: radial-gradient(circle at center, transparent 0%, var(--bg-primary) 100%);
|
||||
```
|
||||
|
||||
**Linear** darkens top and bottom and leaves the middle open. Use when the subject
|
||||
is horizontally centered and should stay visible (Satisfactory factory floor, SC Sunset horizon):
|
||||
```css
|
||||
--overlay-bg: linear-gradient(180deg, rgba(R,G,B,0.85) 0%, rgba(R,G,B,0.15) 50%, rgba(R,G,B,0.90) 100%);
|
||||
```
|
||||
|
||||
Choose based on where the most important part of the image is, not by habit.
|
||||
|
||||
---
|
||||
|
||||
## Focus & Accessibility
|
||||
|
||||
For backgrounds with a lot of detail (many small elements, high contrast, busy textures),
|
||||
increase board alpha and blur to reduce visual noise. This makes boards easier to scan,
|
||||
especially for users with ADHD or attention sensitivities.
|
||||
|
||||
```css
|
||||
--bg-board: rgba(R, G, B, 0.65); /* Up from default 0.55 */
|
||||
backdrop-filter: blur(12px); /* Up from default 8px */
|
||||
```
|
||||
|
||||
This was applied intentionally to the Satisfactory theme, because the factory floor screenshot
|
||||
has a lot going on and needed more visual separation between background and UI.
|
||||
|
||||
---
|
||||
|
||||
## All 11 Themes
|
||||
|
||||
| Theme | File | Accent | Mood | Overlay |
|
||||
|---|---|---|---|---|
|
||||
| Nebula | `bg-nebula.webp` | `#b359ff` Magenta | Chill, Cosmic | Radial |
|
||||
| Crescent | `bg-crescent.webp` | `#d4bd8a` Gold | Luxury, Night | Radial |
|
||||
| Event Horizon | `bg-event-horizon.webp` | `#9d5cff` Purple | Deep Space, Void | Radial |
|
||||
| Merchantman | `bg-merchantman.webp` | `#2eb8b8` Emerald | Industrial, Alien | Radial |
|
||||
| Julia & Jin | `bg-julia-jin.webp` | `#7db3ff` Aetherial Blue | FFXIV Night | Linear |
|
||||
| SC Sunset | `bg-sc-sunset.webp` | `#ff8c3d` Amber | Emotional, Horizon | Linear |
|
||||
| Hellion HUD | `bg-hellion-hud.webp` | `#32ff6a` Neon Green | Tactical, Admin | Radial |
|
||||
| Hellion Energy | `bg-hellion-energy.webp` | `#1eff8e` Acid Green | Overdrive, Power | Radial |
|
||||
| Satisfactory | `bg-satisfactory.webp` | `#00b4d8` Cyan | Industrial Desert | Linear |
|
||||
| Avorion | `bg-avorion.webp` | `#2ec4a0` Turquoise | Deep Void | Radial |
|
||||
| Hellion Stealth | `bg-scPolaris.webp` | `#5ec2ff` Tech Blue | Tactical Recon | Radial |
|
||||
|
||||
### Theme Quirks Worth Knowing
|
||||
|
||||
**Julia & Jin** uses `Cinzel` as display font and a linear gradient. The subjects in
|
||||
the screenshot are positioned left of center, so radial would soften them.
|
||||
|
||||
**Satisfactory** has increased board alpha (0.65) and stronger blur (12px), an intentional
|
||||
ADHD optimization for a visually busy background.
|
||||
|
||||
**Avorion** uses `letter-spacing: 6px` on the logo for maximum HUD feel.
|
||||
|
||||
**Hellion Stealth** is the only theme with `border-left: 2px solid var(--accent)` on
|
||||
`.bm-item:hover`. Every other theme uses background tinting only. This is intentional
|
||||
and gives Stealth its tactical scanner character. Don't apply it to other themes.
|
||||
|
||||
---
|
||||
|
||||
## Registering a Theme in themes.js
|
||||
|
||||
The `THEMES` object in `src/js/themes.js` is the single source of truth for which
|
||||
themes exist and which background image they use. CSS handles all the visual variables —
|
||||
`themes.js` only needs the image path.
|
||||
|
||||
```javascript
|
||||
const THEMES = {
|
||||
'nebula': { bg: 'assets/themes/bg-nebula.webp' },
|
||||
'crescent': { bg: 'assets/themes/bg-crescent.webp' },
|
||||
'event-horizon': { bg: 'assets/themes/bg-event-horizon.webp' },
|
||||
'merchantman': { bg: 'assets/themes/bg-merchantman.webp' },
|
||||
'julia-jin': { bg: 'assets/themes/bg-julia-jin.webp' },
|
||||
'sc-sunset': { bg: 'assets/themes/bg-sc-sunset.webp' },
|
||||
'hellion-hud': { bg: 'assets/themes/bg-hellion-hud.webp' },
|
||||
'hellion-energy': { bg: 'assets/themes/bg-hellion-energy.webp' },
|
||||
'satisfactory': { bg: 'assets/themes/bg-satisfactory.webp' },
|
||||
'avorion': { bg: 'assets/themes/bg-avorion.webp' },
|
||||
'hellion-stealth': { bg: 'assets/themes/bg-scPolaris.webp' }
|
||||
};
|
||||
```
|
||||
|
||||
To add a new theme, add one line. The key must exactly match the `data-theme`
|
||||
attribute in the CSS block. If they don't match, `applyTheme()` will silently
|
||||
do nothing and no one will know why.
|
||||
|
||||
```javascript
|
||||
// New theme: key must match [data-theme="your-theme-name"] in main.css
|
||||
'your-theme-name': { bg: 'assets/themes/bg-your-theme.webp' }
|
||||
```
|
||||
|
||||
### How applyTheme() works
|
||||
|
||||
```javascript
|
||||
function applyTheme(themeName, skipBgOverride) {
|
||||
const theme = THEMES[themeName];
|
||||
if (!theme) return;
|
||||
|
||||
// Sets data-theme on <html> — activates the matching CSS variable block
|
||||
document.documentElement.setAttribute('data-theme', themeName);
|
||||
|
||||
// Applies the background image unless a custom background is active
|
||||
if (!skipBgOverride) {
|
||||
document.getElementById('bgLayer').style.backgroundImage = `url('${theme.bg}')`;
|
||||
}
|
||||
|
||||
// Updates the active state in the theme picker UI
|
||||
document.querySelectorAll('.theme-card').forEach(card => {
|
||||
card.classList.toggle('active', card.dataset.value === themeName);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The `skipBgOverride` flag exists for one specific case: when a user has set a custom
|
||||
background image, switching themes should still update the CSS variables and the picker
|
||||
UI, but not wipe their custom image. Pass `true` to skip the background update.
|
||||
|
||||
---
|
||||
|
||||
## Adding a Theme Card to newtab.html
|
||||
|
||||
The theme picker modal lives in `newtab.html` as `#themeOverlay`. Every theme
|
||||
needs a card in the `.theme-grid` — without it the theme exists in CSS and JS
|
||||
but never shows up in the UI.
|
||||
|
||||
Copy this block and add it inside `.theme-grid`, after the last existing card:
|
||||
|
||||
```html
|
||||
<div class="theme-card" data-value="your-theme-name">
|
||||
<img class="theme-card-img" src="assets/themes/bg-your-theme.webp" alt="Your Theme" />
|
||||
<span class="theme-card-label">Your Theme</span>
|
||||
<span class="theme-card-check">✓</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
Three things that must match exactly:
|
||||
|
||||
- `data-value` must match the key in `THEMES` in `themes.js`
|
||||
- `data-value` must match the `[data-theme="..."]` attribute in `main.css`
|
||||
- `src` must point to the correct WebP file in `assets/themes/`
|
||||
|
||||
The label shown in the picker can be shorter than the full theme name — "HUD" and
|
||||
"Energy" are good examples of that. Keep it short enough to fit the card.
|
||||
|
||||
The `active` class is toggled by `applyTheme()` automatically, so don't add it
|
||||
manually unless you want that theme to be the default on first load (Nebula currently
|
||||
has it as fallback).
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Theme — Checklist
|
||||
|
||||
- [ ] Background image converted to WebP (quality 85)
|
||||
- [ ] Image added to `assets/themes/`
|
||||
- [ ] CSS block added to `src/css/main.css`
|
||||
- [ ] Theme registered in `src/js/themes.js` (one line, key + bg path)
|
||||
- [ ] Theme card added to `.theme-grid` in `newtab.html` (data-value, img src, label)
|
||||
- [ ] Theme added to theme table in `README.md`
|
||||
- [ ] Theme added to theme table in this document
|
||||
- [ ] Image credit added to Bild-Credits table in `README.md`
|
||||
- [ ] `CHANGELOG.md` entry added
|
||||
|
||||
---
|
||||
|
||||
Developed by **[Hellion Online Media — Florian Wathling](https://hellion-media.de)** — JonKazama-Hellion
|
||||
+35
-29
@@ -1,11 +1,17 @@
|
||||
# Hellion Dashboard — Architecture
|
||||
|
||||
> This document is intentionally written in English. Full German/English i18n support
|
||||
> is planned for v2.0 — until then, English keeps the docs accessible to anyone
|
||||
> who wants to contribute or fork the project.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Hellion Dashboard is a browser extension (NewTab replacement) built with **Vanilla JavaScript ES2020**, **CSS Custom Properties**, and **zero dependencies**. No build step, no framework, no bundler — files are loaded directly via `<script>` tags.
|
||||
|
||||
**Storage:** `chrome.storage.local` with `localStorage` fallback.
|
||||
**Manifest:** V3 for Chromium browsers, V3 for Firefox (separate manifest).
|
||||
**Manifest:** V3 across all supported browsers (separate files for Firefox and Opera GX).
|
||||
|
||||
---
|
||||
|
||||
@@ -14,59 +20,60 @@ Hellion Dashboard is a browser extension (NewTab replacement) built with **Vanil
|
||||
```
|
||||
HOM_NewTab_Project/
|
||||
├── newtab.html # Single HTML entry point
|
||||
├── manifest.json # Chrome/Edge/Brave/Vivaldi (MV3)
|
||||
├── manifest.json # Chrome, Edge, Brave, Vivaldi (MV3)
|
||||
├── manifest.firefox.json # Firefox (MV3)
|
||||
├── manifest.opera.json # Opera/Opera GX (MV3 + workarounds)
|
||||
├── manifest.opera.json # Opera, Opera GX (MV3 + workarounds)
|
||||
├── src/
|
||||
│ ├── css/
|
||||
│ │ └── main.css # All styles, themes, responsive breakpoints
|
||||
│ │ └── main.css # All styles, 11 themes, responsive breakpoints
|
||||
│ └── js/
|
||||
│ ├── dialog.js # Custom dialog system (alert, confirm)
|
||||
│ ├── storage.js # Storage abstraction layer
|
||||
│ ├── state.js # Global state, defaults, helpers
|
||||
│ ├── themes.js # Theme definitions & application
|
||||
│ ├── themes.js # Theme definitions & application (11 themes)
|
||||
│ ├── boards.js # Board/bookmark rendering & events
|
||||
│ ├── drag.js # Drag & drop (Pointer Events API)
|
||||
│ ├── settings.js # Settings panel, toggles, theme picker
|
||||
│ ├── search.js # Search bar (Google, DuckDuckGo, Bing)
|
||||
│ ├── onboarding.js # First-run onboarding flow
|
||||
│ ├── widgets.js # Widget manager (registry, drag, resize)
|
||||
│ ├── notes.js # Notes/checklists (multi-instance widgets)
|
||||
│ ├── calculator.js # Calculator widget (single-instance)
|
||||
│ ├── timer.js # Timer/countdown widget (single-instance)
|
||||
│ ├── image-ref.js # Image reference widget (multi-instance)
|
||||
│ ├── onboarding.js # First-run onboarding flow
|
||||
│ ├── data.js # JSON export/import (backup & restore)
|
||||
│ ├── app.js # Init, clock, global events (entry point)
|
||||
│ └── dialog.js # Custom dialog system (alert, confirm)
|
||||
│ └── app.js # Init, clock, global events (entry point)
|
||||
├── assets/
|
||||
│ ├── fonts/ # Local fonts (Rajdhani, Inter, Cinzel)
|
||||
│ ├── icons/ # Extension icons (16-512px)
|
||||
│ └── themes/ # Theme background images
|
||||
└── docs/ # Documentation (you are here)
|
||||
│ └── themes/ # 11 theme background images
|
||||
└── docs/ # You are here
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Responsibilities
|
||||
|
||||
Each module has exactly one responsibility. They communicate through global references (no import/export — this is a browser extension without a bundler).
|
||||
Each module has exactly one responsibility. Communication happens through global references — no import/export, because this is a browser extension without a bundler.
|
||||
|
||||
| Module | Responsibility |
|
||||
|---|---|
|
||||
| `storage.js` | **Only** place that touches `chrome.storage` / `localStorage`. All other modules go through `Store.get()` / `Store.set()`. |
|
||||
| `dialog.js` | `HellionDialog.alert()` and `HellionDialog.confirm()` — custom styled dialogs that replace native browser popups. Loaded first so every other module can use it. |
|
||||
| `storage.js` | The **only** place that touches `chrome.storage` / `localStorage`. Everything else goes through `Store.get()` / `Store.set()`. |
|
||||
| `state.js` | Global `boards` and `settings` arrays, default values, `uid()`, `escHtml()`, `getFaviconUrl()`. |
|
||||
| `themes.js` | Theme CSS variable application. 8 themes, each with its own `[data-theme]` block in CSS. |
|
||||
| `themes.js` | Applies theme CSS variables. 11 themes, each with its own `[data-theme]` block in `main.css`. |
|
||||
| `boards.js` | Renders boards and bookmarks. Event delegation on board containers. |
|
||||
| `drag.js` | Board and bookmark reordering via Pointer Events API. |
|
||||
| `settings.js` | Settings panel UI, toggle handlers, theme modal, background upload. |
|
||||
| `settings.js` | Settings panel UI, toggle handlers, appearance modal, background upload. |
|
||||
| `search.js` | Search bar with engine switching (Google, DuckDuckGo, Bing). |
|
||||
| `onboarding.js` | Multi-slide first-run flow including the gaming starter board opt-in. |
|
||||
| `widgets.js` | Widget manager — creates DOM, handles drag/resize/z-index, provides registry. See [widget-schema.md](widget-schema.md). |
|
||||
| `notes.js` | Notes and checklists as widgets. Multi-instance (max 5). Notebook sidebar. Also handles widget toolbar events. |
|
||||
| `calculator.js` | Calculator widget. Single-instance. Shunting-yard expression parser (no `eval()`). |
|
||||
| `timer.js` | Timer/countdown widget. Single-instance. Presets, Web Audio API alarm, tab-title blink. |
|
||||
| `image-ref.js` | Image reference widget. Multi-instance (max 3). Canvas API WebP conversion, sessionStorage for image data. |
|
||||
| `onboarding.js` | Multi-slide onboarding flow. Gaming starter board opt-in. |
|
||||
| `data.js` | JSON export/import with validation. Handles boards, notes, calculator history, timer presets. |
|
||||
| `calculator.js` | Calculator widget. Single-instance. Shunting-yard expression parser — no `eval()`. |
|
||||
| `timer.js` | Timer/countdown widget. Single-instance. Presets, Web Audio API alarm, tab-title blink on completion. |
|
||||
| `image-ref.js` | Image reference widget. Multi-instance (max 3). Canvas API WebP conversion, sessionStorage for image data — cleared on browser close. |
|
||||
| `data.js` | JSON export/import with validation. Covers boards, notes, calculator history and timer presets. |
|
||||
| `app.js` | Entry point. Calls `init()` on DOMContentLoaded. Clock, global event binding. |
|
||||
| `dialog.js` | `HellionDialog.alert()` and `HellionDialog.confirm()` — custom styled dialogs replacing native browser dialogs. |
|
||||
|
||||
---
|
||||
|
||||
@@ -83,7 +90,7 @@ DOMContentLoaded
|
||||
→ bindGlobalEvents() # Header buttons, modals
|
||||
→ bindSettingsEvents() # Settings toggles, theme picker
|
||||
→ initSearch() # Search bar
|
||||
→ migrateSticky() # Legacy sticky note migration
|
||||
→ migrateSticky() # Legacy sticky note migration (v1.5.x → v1.6+)
|
||||
→ Notes.init() # Notes + widget toolbar
|
||||
→ Calculator.init() # Calculator widget
|
||||
→ Timer.init() # Timer widget
|
||||
@@ -96,7 +103,7 @@ DOMContentLoaded
|
||||
|
||||
## Script Load Order
|
||||
|
||||
Scripts are loaded in `newtab.html` in dependency order:
|
||||
Scripts are loaded in `newtab.html` in dependency order. A module may only reference modules loaded before it — there is no bundler to handle this automatically.
|
||||
|
||||
```html
|
||||
<script src="src/js/dialog.js"></script>
|
||||
@@ -117,8 +124,6 @@ Scripts are loaded in `newtab.html` in dependency order:
|
||||
<script src="src/js/app.js"></script>
|
||||
```
|
||||
|
||||
**Rule:** A module may only reference modules loaded before it.
|
||||
|
||||
---
|
||||
|
||||
## Z-Index Hierarchy
|
||||
@@ -133,7 +138,7 @@ Scripts are loaded in `newtab.html` in dependency order:
|
||||
| Dialogs / Modals | 300 | `.hellion-dialog-overlay`, modals |
|
||||
| Onboarding | 400 | `#onboardingOverlay` |
|
||||
|
||||
Widgets use incrementing z-index (`WidgetManager._topZ++`) to stack above each other on click.
|
||||
Widgets use an incrementing z-index (`WidgetManager._topZ++`) so the last clicked widget always sits on top.
|
||||
|
||||
---
|
||||
|
||||
@@ -143,9 +148,9 @@ Widgets use incrementing z-index (`WidgetManager._topZ++`) to stack above each o
|
||||
|---|---|---|
|
||||
| `boards` | Array | Board objects with bookmarks |
|
||||
| `settings` | Object | User preferences (theme, toggles, etc.) |
|
||||
| `widgetStates` | Object | All widget data (see [widget-schema.md](widget-schema.md)) |
|
||||
| `onboardingDone` | Boolean | Whether onboarding has been completed |
|
||||
| `lastBackupReminder` | Number | Timestamp of last backup reminder |
|
||||
| `widgetStates` | Object | All widget data — see [widget-schema.md](widget-schema.md) |
|
||||
| `onboardingDone` | Boolean | Whether the first-run onboarding has been completed |
|
||||
| `lastBackupReminder` | Number | Timestamp of the last backup reminder |
|
||||
|
||||
---
|
||||
|
||||
@@ -160,4 +165,5 @@ Widgets use incrementing z-index (`WidgetManager._topZ++`) to stack above each o
|
||||
| Opera / GX | Chromium MV3 | `manifest.opera.json` |
|
||||
| Firefox | Gecko MV3 | `manifest.firefox.json` |
|
||||
|
||||
Changes affecting manifest fields must be synchronized across all three manifest files.
|
||||
Any change that touches manifest fields — version numbers, permissions, content scripts —
|
||||
needs to be applied to all three files. The CI quality check will catch it if they drift out of sync.
|
||||
|
||||
+43
-45
@@ -1,5 +1,11 @@
|
||||
# Hellion Dashboard — Code Patterns & Conventions
|
||||
|
||||
> This document is intentionally written in English. Full German/English i18n support
|
||||
> is planned for v2.0 — until then, English keeps the docs accessible to anyone
|
||||
> who wants to contribute or fork the project.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
- **Vanilla JS ES2020** — No frameworks, no TypeScript, no build step
|
||||
@@ -15,11 +21,11 @@
|
||||
|
||||
**File:** `src/js/storage.js`
|
||||
|
||||
All persistent data goes through the `Store` object. Never access `chrome.storage` or `localStorage` directly.
|
||||
All persistent data goes through the `Store` object. Never access `chrome.storage` or `localStorage` directly — `Store` handles the fallback between the two transparently and provides unified error handling when storage is full.
|
||||
|
||||
```javascript
|
||||
// Reading
|
||||
const boards = await Store.get('boards'); // Returns null if not found
|
||||
const boards = await Store.get('boards'); // Returns null if not found
|
||||
const settings = await Store.get('settings');
|
||||
|
||||
// Writing
|
||||
@@ -30,13 +36,11 @@ await Store.set('settings', settings);
|
||||
await Store.checkQuota();
|
||||
```
|
||||
|
||||
**Why?** The `Store` handles the chrome.storage / localStorage fallback transparently. It also provides unified error handling (shows a dialog when storage is full).
|
||||
|
||||
---
|
||||
|
||||
## Pattern: Event Delegation
|
||||
|
||||
Instead of attaching listeners to each element, attach one to the container and use `closest()` to find the target.
|
||||
One listener on the container, `closest()` to find the target. Much cleaner than attaching a listener to every single element, and it works automatically for dynamically added content.
|
||||
|
||||
```javascript
|
||||
// GOOD — one listener, handles all bookmarks
|
||||
@@ -53,13 +57,13 @@ bookmarks.forEach(bm => {
|
||||
});
|
||||
```
|
||||
|
||||
**Used in:** `boards.js` (board/bookmark events), `notes.js` (toolbar), `calculator.js` (button grid)
|
||||
Used in `boards.js` (board/bookmark events), `notes.js` (toolbar) and `calculator.js` (button grid).
|
||||
|
||||
---
|
||||
|
||||
## Pattern: createElement over innerHTML
|
||||
|
||||
Always build DOM with `document.createElement()`. This prevents XSS and is the project's #1 security rule.
|
||||
Always build DOM with `document.createElement()`. This is the project's #1 security rule — `innerHTML` with user-provided content is an XSS risk, full stop.
|
||||
|
||||
```javascript
|
||||
// GOOD
|
||||
@@ -76,7 +80,7 @@ container.innerHTML = `<a href="${url}">${title}</a>`;
|
||||
|
||||
## Pattern: Shared Storage Key
|
||||
|
||||
Multiple widget modules share the `widgetStates` key. Every module must read-before-write and preserve other modules' data.
|
||||
All widget modules share the `widgetStates` storage key. Every module that writes to it must read first and preserve what's already there — otherwise modules silently overwrite each other's data.
|
||||
|
||||
```javascript
|
||||
async save() {
|
||||
@@ -85,7 +89,7 @@ async save() {
|
||||
// Write your own data
|
||||
data.yourKey = { /* ... */ };
|
||||
|
||||
// DON'T overwrite — the key already contains other modules' data
|
||||
// Don't replace the whole object — other modules live here too
|
||||
await Store.set('widgetStates', data);
|
||||
}
|
||||
```
|
||||
@@ -96,13 +100,13 @@ See [widget-schema.md](widget-schema.md) for the full `widgetStates` structure.
|
||||
|
||||
## Pattern: Widget Lifecycle Hooks
|
||||
|
||||
Single-instance widgets (Calculator, Timer) need to know when they're closed, minimized, or reopened. They wrap `WidgetManager` methods in their `init()`:
|
||||
Single-instance widgets (Calculator, Timer) need to react when they're closed, minimized, or reopened. They do this by wrapping `WidgetManager` methods in their `init()`.
|
||||
|
||||
```javascript
|
||||
async init() {
|
||||
// Wrap close
|
||||
const prevClose = WidgetManager.close;
|
||||
const self = this;
|
||||
|
||||
WidgetManager.close = function(id) {
|
||||
prevClose.call(WidgetManager, id);
|
||||
if (id === self.WIDGET_ID) {
|
||||
@@ -110,7 +114,6 @@ async init() {
|
||||
}
|
||||
};
|
||||
|
||||
// Wrap minimize
|
||||
const prevMinimize = WidgetManager.minimize;
|
||||
WidgetManager.minimize = async function(id) {
|
||||
await prevMinimize.call(WidgetManager, id);
|
||||
@@ -122,13 +125,13 @@ async init() {
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** Multiple widgets chain these wraps. Calculator wraps first, Timer wraps Calculator's already-wrapped version, and so on. The chain must not break.
|
||||
Multiple widgets chain these wraps — Calculator wraps first, Timer wraps Calculator's already-wrapped version, and so on. Always call the previous method (`prevClose.call(...)`) or the chain breaks and other widgets stop responding.
|
||||
|
||||
---
|
||||
|
||||
## Pattern: Debounced Save
|
||||
|
||||
For frequent updates (typing in notes, moving widgets), use debounced saves to avoid excessive storage writes:
|
||||
For frequent updates like typing in notes or dragging widgets, debouncing avoids hammering storage with a write on every keystroke.
|
||||
|
||||
```javascript
|
||||
_saveTimer: null,
|
||||
@@ -138,20 +141,20 @@ _debouncedSave() {
|
||||
this._saveTimer = setTimeout(() => this.save(), 500);
|
||||
}
|
||||
|
||||
// Usage: call _debouncedSave() instead of save() for frequent events
|
||||
// Use _debouncedSave() instead of save() for frequent events
|
||||
textarea.addEventListener('input', () => {
|
||||
noteData.content = textarea.value;
|
||||
this._debouncedSave();
|
||||
});
|
||||
```
|
||||
|
||||
**Used in:** `notes.js` (text editing), `image-ref.js` (label editing)
|
||||
Used in `notes.js` (text editing) and `image-ref.js` (label editing).
|
||||
|
||||
---
|
||||
|
||||
## Pattern: Theme System
|
||||
|
||||
All themes use CSS Custom Properties defined in `[data-theme="name"]` blocks:
|
||||
All themes use CSS Custom Properties in `[data-theme="name"]` blocks in `main.css`. There are currently 11 themes.
|
||||
|
||||
```css
|
||||
[data-theme="nebula"] {
|
||||
@@ -164,55 +167,51 @@ All themes use CSS Custom Properties defined in `[data-theme="name"]` blocks:
|
||||
}
|
||||
```
|
||||
|
||||
**Never hardcode colors in JS.** Use CSS classes or variables:
|
||||
Never hardcode colors in JS. Let CSS handle it.
|
||||
|
||||
```javascript
|
||||
// GOOD — let CSS handle colors
|
||||
// GOOD
|
||||
element.classList.add('active');
|
||||
|
||||
// BAD — hardcoded color
|
||||
// BAD — breaks every theme that isn't Nebula
|
||||
element.style.color = '#7db3ff';
|
||||
```
|
||||
|
||||
8 themes are available: Nebula, Crescent, Event Horizon, Merchantman, Julia & Jin, SC Sunset, Hellion HUD, Hellion Energy.
|
||||
|
||||
---
|
||||
|
||||
## Pattern: Onboarding Slides
|
||||
|
||||
The onboarding system (`onboarding.js`) uses a data-driven slide array. Each slide is an object with rendering hints:
|
||||
The onboarding system in `onboarding.js` is data-driven. Each slide is a plain object — add a new slide by adding an object to the `slides` array, the `_render()` method handles the rest.
|
||||
|
||||
```javascript
|
||||
{
|
||||
hero: '🎮', // Large emoji/icon
|
||||
title: 'Slide Title', // Heading
|
||||
text: 'Description...', // Optional text paragraph
|
||||
title: 'Slide Title',
|
||||
text: 'Optional description',
|
||||
features: ['Item 1', ...], // Optional bullet list
|
||||
showThemes: true, // Optional theme grid
|
||||
interactive: 'gaming-board' // Optional custom buttons
|
||||
}
|
||||
```
|
||||
|
||||
The `_render()` method reads these properties and builds the DOM. To add a new slide, just add an object to the `slides` array.
|
||||
|
||||
---
|
||||
|
||||
## Pattern: Dialog System
|
||||
|
||||
Custom dialogs replace native `alert()` and `confirm()`:
|
||||
Custom dialogs replace native `alert()` and `confirm()` everywhere in the project.
|
||||
|
||||
```javascript
|
||||
// Alert (informational)
|
||||
// Informational
|
||||
await HellionDialog.alert('Message text', {
|
||||
type: 'info', // 'info', 'success', 'warning', 'danger'
|
||||
type: 'info', // 'info', 'success', 'warning', 'danger'
|
||||
title: 'Title'
|
||||
});
|
||||
|
||||
// Confirm (yes/no)
|
||||
// Yes/no
|
||||
const ok = await HellionDialog.confirm('Are you sure?', {
|
||||
type: 'danger',
|
||||
title: 'Delete',
|
||||
confirmText: 'Delete', // Custom button text
|
||||
confirmText: 'Delete',
|
||||
cancelText: 'Cancel'
|
||||
});
|
||||
if (ok) { /* user confirmed */ }
|
||||
@@ -222,7 +221,7 @@ if (ok) { /* user confirmed */ }
|
||||
|
||||
## Pattern: Pointer Events for Drag
|
||||
|
||||
Widget dragging and board reordering use the Pointer Events API (not mouse events):
|
||||
Widget dragging and board reordering use the Pointer Events API instead of mouse events. The reason: Pointer Events work with both mouse and touch, and `setPointerCapture` keeps the events flowing even if the cursor leaves the element mid-drag.
|
||||
|
||||
```javascript
|
||||
element.addEventListener('pointerdown', (e) => {
|
||||
@@ -243,13 +242,11 @@ element.addEventListener('pointerdown', (e) => {
|
||||
});
|
||||
```
|
||||
|
||||
**Why Pointer Events over Mouse Events?** They work with both mouse and touch, and `setPointerCapture` ensures events continue even if the cursor leaves the element.
|
||||
|
||||
---
|
||||
|
||||
## Pattern: Canvas API Image Processing
|
||||
|
||||
The image reference widget converts uploaded images to WebP for smaller size:
|
||||
The image reference widget converts uploaded images to WebP locally in the browser — no external service, no upload, nothing leaves the device.
|
||||
|
||||
```javascript
|
||||
_processFile(file) {
|
||||
@@ -264,7 +261,7 @@ _processFile(file) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const webpUrl = canvas.toDataURL('image/webp', 0.85);
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
URL.revokeObjectURL(objectUrl); // Always free the object URL
|
||||
resolve(webpUrl);
|
||||
};
|
||||
|
||||
@@ -278,7 +275,7 @@ _processFile(file) {
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** Always call `URL.revokeObjectURL()` to free memory.
|
||||
Always call `URL.revokeObjectURL()` after the image has loaded — skipping it leaks memory.
|
||||
|
||||
---
|
||||
|
||||
@@ -287,24 +284,25 @@ _processFile(file) {
|
||||
| Rule | Rationale |
|
||||
|---|---|
|
||||
| `createElement` only, never `innerHTML` | XSS prevention |
|
||||
| All storage through `Store` | Browser compatibility |
|
||||
| CSS variables, no hardcoded colors | Theme support |
|
||||
| Event delegation | Performance, dynamic content |
|
||||
| All storage through `Store` | Browser compatibility + unified error handling |
|
||||
| CSS variables, no hardcoded colors | Theme support across all 11 themes |
|
||||
| Event delegation | Performance, works with dynamic content |
|
||||
| `const`/`let`, never `var` | Block scoping |
|
||||
| No external dependencies | Extension simplicity |
|
||||
| No build step | Direct development |
|
||||
| JSDoc comments on public functions | Documentation |
|
||||
| No build step | Direct development, no toolchain to break |
|
||||
| JSDoc comments on public functions | Documentation for contributors |
|
||||
| URL validation before `href` | Security |
|
||||
| Error handling on storage operations | Graceful failure |
|
||||
| `URL.revokeObjectURL()` after Canvas ops | Memory management |
|
||||
|
||||
---
|
||||
|
||||
## Manifest Synchronization
|
||||
|
||||
Three manifest files must stay in sync:
|
||||
Three manifest files must always stay in sync:
|
||||
|
||||
- `manifest.json` — Chrome, Edge, Brave, Vivaldi
|
||||
- `manifest.firefox.json` — Firefox
|
||||
- `manifest.opera.json` — Opera, Opera GX
|
||||
|
||||
When changing version numbers, permissions, or content script entries, update all three files.
|
||||
Version numbers, permissions and content script entries need to be updated in all three. The CI quality check will catch drift, but it's cleaner not to let it get there in the first place.
|
||||
|
||||
+67
-70
@@ -1,8 +1,14 @@
|
||||
# Hellion Dashboard — Widget Schema
|
||||
|
||||
> This document is intentionally written in English. Full German/English i18n support
|
||||
> is planned for v2.0 — until then, English keeps the docs accessible to anyone
|
||||
> who wants to contribute or fork the project.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The widget system provides draggable, resizable floating panels managed by `WidgetManager` (`src/js/widgets.js`). Each widget type has its own module that handles content rendering and state management.
|
||||
The widget system provides draggable, resizable floating panels managed by `WidgetManager` (`src/js/widgets.js`). Each widget type has its own module that handles content rendering and state management — `WidgetManager` only knows about DOM and position, never about content.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,23 +27,23 @@ The widget system provides draggable, resizable floating panels managed by `Widg
|
||||
|
||||
### `create(type, config) → string`
|
||||
|
||||
Creates a widget and appends it to the DOM.
|
||||
Creates a widget and appends it to the DOM. Returns the widget ID.
|
||||
|
||||
```javascript
|
||||
const id = WidgetManager.create('note', {
|
||||
id: 'note_abc123', // Optional, auto-generated if omitted
|
||||
title: 'My Note', // Default: 'Note'
|
||||
x: 120, // Left position in px
|
||||
y: 80, // Top position in px
|
||||
width: 280, // Width in px (min: 200)
|
||||
height: 220, // Height in px (min: 150)
|
||||
open: true // Visible state (default: true)
|
||||
id: 'note_abc123', // Optional, auto-generated if omitted
|
||||
title: 'My Note', // Default: 'Note'
|
||||
x: 120, // Left position in px
|
||||
y: 80, // Top position in px
|
||||
width: 280, // Width in px (min: 200)
|
||||
height: 220, // Height in px (min: 150)
|
||||
open: true // Visible state (default: true)
|
||||
});
|
||||
```
|
||||
|
||||
### `getBody(id) → HTMLElement | null`
|
||||
|
||||
Returns the `.widget-body` element for content rendering.
|
||||
Returns the `.widget-body` element. This is where your module renders its content.
|
||||
|
||||
```javascript
|
||||
const body = WidgetManager.getBody('widget_calculator');
|
||||
@@ -46,7 +52,7 @@ if (body) Calculator.renderBody(body);
|
||||
|
||||
### `getState(id) → Object | null`
|
||||
|
||||
Returns the current widget state (position, size, open status).
|
||||
Returns the current widget state — position, size, open status.
|
||||
|
||||
```javascript
|
||||
const state = WidgetManager.getState('widget_timer');
|
||||
@@ -55,11 +61,11 @@ const state = WidgetManager.getState('widget_timer');
|
||||
|
||||
### `close(id)`
|
||||
|
||||
Permanently removes a widget from the DOM and registry.
|
||||
Permanently removes a widget from the DOM and registry. No undo.
|
||||
|
||||
### `minimize(id)`
|
||||
|
||||
Hides a widget with animation. Widget remains in registry with `open: false`.
|
||||
Hides a widget with animation. The widget stays in the registry with `open: false` so it can be restored.
|
||||
|
||||
### `openWidget(id)`
|
||||
|
||||
@@ -67,36 +73,36 @@ Restores a minimized widget with animation.
|
||||
|
||||
### `bringToFront(id)`
|
||||
|
||||
Increments z-index to bring widget above all others.
|
||||
Increments z-index so the widget sits above everything else. Called automatically on `pointerdown`.
|
||||
|
||||
### `save() → Array`
|
||||
|
||||
Returns an array of all `type: 'note'` widget states. Used by `Notes.save()` to merge with note content data.
|
||||
Returns an array of all `type: 'note'` widget states. Used by `Notes.save()` to merge position/size data with note content.
|
||||
|
||||
### `restore(renderCallback)`
|
||||
|
||||
Loads widget states from storage and recreates all note widgets. Only handles notes — single-instance widgets (calculator, timer) restore themselves in their own `init()`.
|
||||
Loads widget states from storage and recreates all note widgets. Single-instance widgets (Calculator, Timer) restore themselves in their own `init()` — `restore()` only handles notes.
|
||||
|
||||
---
|
||||
|
||||
## Shared Storage Key: `widgetStates`
|
||||
|
||||
All widget modules share a single storage key. Each module's `save()` method must preserve other modules' data.
|
||||
All widget modules share a single storage key. Every module's `save()` must read first and preserve whatever it doesn't own — otherwise modules silently wipe each other's data on every save.
|
||||
|
||||
```javascript
|
||||
// Structure of widgetStates
|
||||
// Full widgetStates structure
|
||||
{
|
||||
notes: [
|
||||
{
|
||||
id: 'note_abc123',
|
||||
title: 'My Note',
|
||||
content: 'Hello world',
|
||||
template: 'text', // 'text' or 'checklist'
|
||||
template: 'text', // 'text' or 'checklist'
|
||||
x: 120, y: 80,
|
||||
width: 280, height: 220,
|
||||
open: true,
|
||||
checklistItems: [], // For checklist template
|
||||
checkedItems: [] // Checked item IDs
|
||||
checklistItems: [], // Only used by checklist template
|
||||
checkedItems: [] // Checked item IDs
|
||||
}
|
||||
],
|
||||
calculator: {
|
||||
@@ -124,26 +130,27 @@ All widget modules share a single storage key. Each module's `save()` method mus
|
||||
x: 200, y: 120,
|
||||
width: 320, height: 280,
|
||||
open: true
|
||||
// Image data is NOT stored here — sessionStorage only
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Save Pattern — Preserving Other Modules' Data
|
||||
### The Save Pattern
|
||||
|
||||
Every module that saves to `widgetStates` must read existing data first and preserve keys it doesn't own:
|
||||
Every module that touches `widgetStates` must follow this pattern:
|
||||
|
||||
```javascript
|
||||
// Example from notes.js
|
||||
// From notes.js — same pattern applies to every widget module
|
||||
async save() {
|
||||
const existing = await Store.get(this.STORAGE_KEY);
|
||||
const saveData = { notes: mergedNotes };
|
||||
|
||||
// Preserve other modules
|
||||
// Preserve everything we don't own
|
||||
if (existing && existing.calculator) saveData.calculator = existing.calculator;
|
||||
if (existing && existing.timer) saveData.timer = existing.timer;
|
||||
if (existing && existing.imageRef) saveData.imageRef = existing.imageRef;
|
||||
if (existing && existing.timer) saveData.timer = existing.timer;
|
||||
if (existing && existing.imageRef) saveData.imageRef = existing.imageRef;
|
||||
|
||||
await Store.set(this.STORAGE_KEY, saveData);
|
||||
}
|
||||
@@ -153,20 +160,21 @@ async save() {
|
||||
|
||||
## Creating a New Widget Type
|
||||
|
||||
### Step 1: Choose Single or Multi-Instance
|
||||
### Step 1: Single or Multi-Instance?
|
||||
|
||||
- **Single-instance** (like Calculator, Timer): One widget with a fixed ID. `toggle()` opens/closes.
|
||||
- **Multi-instance** (like Notes, ImageRef): Multiple widgets with dynamic IDs. `create()` adds new ones.
|
||||
**Single-instance** (Calculator, Timer style): one widget, fixed ID, `toggle()` opens and closes it.
|
||||
**Multi-instance** (Notes, ImageRef style): multiple widgets, dynamic IDs, `create()` adds new ones.
|
||||
|
||||
### Step 2: Create the Module (`src/js/your-widget.js`)
|
||||
### Step 2: Create the Module
|
||||
|
||||
Here's a minimal single-instance widget template. Follow the same structure — the lifecycle hooks especially are easy to get wrong.
|
||||
|
||||
```javascript
|
||||
const YourWidget = {
|
||||
WIDGET_ID: 'widget_yourwidget', // Fixed ID for single-instance
|
||||
WIDGET_ID: 'widget_yourwidget',
|
||||
STORAGE_KEY: 'widgetStates',
|
||||
_isOpen: false,
|
||||
|
||||
// Load state from storage
|
||||
async load() {
|
||||
const data = await Store.get(this.STORAGE_KEY);
|
||||
if (data && data.yourWidget) {
|
||||
@@ -174,25 +182,23 @@ const YourWidget = {
|
||||
}
|
||||
},
|
||||
|
||||
// Save state, preserving other modules
|
||||
async save() {
|
||||
const data = await Store.get(this.STORAGE_KEY) || {};
|
||||
if (data.notes === undefined) data.notes = [];
|
||||
|
||||
const widgetState = WidgetManager.getState(this.WIDGET_ID);
|
||||
data.yourWidget = {
|
||||
x: widgetState ? widgetState.x : 400,
|
||||
y: widgetState ? widgetState.y : 120,
|
||||
width: widgetState ? widgetState.width : 280,
|
||||
x: widgetState ? widgetState.x : 400,
|
||||
y: widgetState ? widgetState.y : 120,
|
||||
width: widgetState ? widgetState.width : 280,
|
||||
height: widgetState ? widgetState.height : 300,
|
||||
open: this._isOpen,
|
||||
open: this._isOpen,
|
||||
// ... your custom data
|
||||
};
|
||||
|
||||
await Store.set(this.STORAGE_KEY, data);
|
||||
},
|
||||
|
||||
// Open widget
|
||||
async open() {
|
||||
if (this._isOpen) {
|
||||
WidgetManager.bringToFront(this.WIDGET_ID);
|
||||
@@ -203,13 +209,13 @@ const YourWidget = {
|
||||
const saved = (data && data.yourWidget) ? data.yourWidget : {};
|
||||
|
||||
WidgetManager.create('yourwidget', {
|
||||
id: this.WIDGET_ID,
|
||||
title: 'Your Widget',
|
||||
x: saved.x || 400,
|
||||
y: saved.y || 120,
|
||||
width: saved.width || 280,
|
||||
id: this.WIDGET_ID,
|
||||
title: 'Your Widget',
|
||||
x: saved.x || 400,
|
||||
y: saved.y || 120,
|
||||
width: saved.width || 280,
|
||||
height: saved.height || 300,
|
||||
open: true
|
||||
open: true
|
||||
});
|
||||
|
||||
const body = WidgetManager.getBody(this.WIDGET_ID);
|
||||
@@ -219,7 +225,6 @@ const YourWidget = {
|
||||
await this.save();
|
||||
},
|
||||
|
||||
// Toggle open/close
|
||||
async toggle() {
|
||||
if (this._isOpen) {
|
||||
const entry = WidgetManager._widgets.get(this.WIDGET_ID);
|
||||
@@ -237,24 +242,23 @@ const YourWidget = {
|
||||
}
|
||||
},
|
||||
|
||||
// Render widget content
|
||||
renderBody(bodyEl) {
|
||||
bodyEl.textContent = '';
|
||||
// Build your UI with createElement (never innerHTML!)
|
||||
// Build your UI with createElement — never innerHTML!
|
||||
},
|
||||
|
||||
// Initialize and hook into lifecycle
|
||||
async init() {
|
||||
await this.load();
|
||||
|
||||
// Restore if was open last time
|
||||
const data = await Store.get(this.STORAGE_KEY);
|
||||
if (data && data.yourWidget && data.yourWidget.open) {
|
||||
await this.open();
|
||||
}
|
||||
|
||||
// Hook into close event
|
||||
// Lifecycle hooks — always call the previous method first
|
||||
// or you'll break every widget that wrapped before yours
|
||||
const self = this;
|
||||
|
||||
const prevClose = WidgetManager.close;
|
||||
WidgetManager.close = function(id) {
|
||||
prevClose.call(WidgetManager, id);
|
||||
@@ -264,7 +268,6 @@ const YourWidget = {
|
||||
}
|
||||
};
|
||||
|
||||
// Hook into minimize event
|
||||
const prevMinimize = WidgetManager.minimize;
|
||||
WidgetManager.minimize = async function(id) {
|
||||
await prevMinimize.call(WidgetManager, id);
|
||||
@@ -274,7 +277,6 @@ const YourWidget = {
|
||||
}
|
||||
};
|
||||
|
||||
// Hook into open event
|
||||
const prevOpen = WidgetManager.openWidget;
|
||||
WidgetManager.openWidget = async function(id) {
|
||||
await prevOpen.call(WidgetManager, id);
|
||||
@@ -293,38 +295,33 @@ const YourWidget = {
|
||||
|
||||
### Step 3: Integration Checklist
|
||||
|
||||
1. **`newtab.html`** — Add `<script>` tag (after `widgets.js`, before `data.js`)
|
||||
2. **`newtab.html`** — Add toolbar button: `<button class="widget-toolbar-btn" data-action="your-action">`
|
||||
3. **`notes.js`** — Add toolbar handler in `initToolbar()`: `} else if (action === 'your-action') { YourWidget.toggle(); }`
|
||||
4. **`notes.js`** — Preserve your data in `save()`: `if (existing && existing.yourWidget) saveData.yourWidget = existing.yourWidget;`
|
||||
5. **`app.js`** — Add `await YourWidget.init();` to the init sequence
|
||||
6. **`src/css/main.css`** — Add widget-specific CSS styles
|
||||
7. **`data.js`** — Add export/import logic (if data should be included in backups)
|
||||
1. `newtab.html` — Add `<script>` tag after `widgets.js` and before `data.js`
|
||||
2. `newtab.html` — Add toolbar button: `<button class="widget-toolbar-btn" data-action="your-action">`
|
||||
3. `notes.js` — Add handler in `initToolbar()`: `else if (action === 'your-action') { YourWidget.toggle(); }`
|
||||
4. `notes.js` — Preserve your key in `save()`: `if (existing && existing.yourWidget) saveData.yourWidget = existing.yourWidget;`
|
||||
5. `app.js` — Add `await YourWidget.init();` to the init sequence
|
||||
6. `main.css` — Add widget-specific styles
|
||||
7. `data.js` — Add export/import logic if your data should survive a JSON backup
|
||||
|
||||
---
|
||||
|
||||
## Widget DOM Structure
|
||||
|
||||
Every widget created by `WidgetManager.create()` has this structure:
|
||||
Every widget created by `WidgetManager.create()` has this structure. Your module renders into `.widget-body` via `renderBody()` — never touch the header or resize handle.
|
||||
|
||||
```html
|
||||
<div class="widget" data-widget-id="widget_abc123"
|
||||
style="left: 120px; top: 80px; width: 280px; height: 220px;">
|
||||
<div class="widget-header">
|
||||
<span class="widget-title">Title</span>
|
||||
<div class="widget-header"> <!-- Drag handle -->
|
||||
<span class="widget-title">Title</span> <!-- Double-click to edit, max 20 chars -->
|
||||
<div class="widget-actions">
|
||||
<button class="widget-btn widget-minimize">─</button>
|
||||
<button class="widget-btn widget-close">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-body">
|
||||
<!-- Your content goes here (via renderBody) -->
|
||||
<!-- Your content goes here via renderBody() -->
|
||||
</div>
|
||||
<div class="widget-resize-handle"></div>
|
||||
<div class="widget-resize-handle"></div> <!-- Bottom-right, visible on hover -->
|
||||
</div>
|
||||
```
|
||||
|
||||
- **Header** is the drag handle (Pointer Events)
|
||||
- **Title** supports double-click to edit (contentEditable, max 20 chars)
|
||||
- **Body** is where your module renders content
|
||||
- **Resize handle** appears on hover (bottom-right corner)
|
||||
|
||||
Reference in New Issue
Block a user