Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6c0d5c468 | |||
| dbd209bc2b | |||
| 7900962c5a | |||
| 1bbdbdef1c | |||
| f07200cd8e | |||
| ab165d4f75 | |||
| 4a66015258 | |||
| d0f870ace1 | |||
| daea57a9df | |||
| f937f7c39c | |||
| 3ab8847f31 | |||
| 36335d3cc4 | |||
| 1b39ac863b | |||
| 522b177470 | |||
| f2d4e22b86 |
@@ -6,6 +6,27 @@ All notable changes per version. Format based on [Keep a Changelog](https://keep
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### v2.0.0 — 22.03.2026
|
||||||
|
|
||||||
|
#### New Features
|
||||||
|
|
||||||
|
- **Internationalization (i18n)** — Full DE/EN language support with runtime switching
|
||||||
|
- Language setting in Settings panel: German, English or Auto-detect (browser language)
|
||||||
|
- `i18n.js` module with ~220+ string keys, `t(key, vars?)` helper and `data-i18n` HTML attributes
|
||||||
|
- `_locales/de/` and `_locales/en/` for manifest-level i18n (`__MSG_extName__`, `__MSG_extDesc__`)
|
||||||
|
- `<html lang>` attribute updates dynamically when language changes
|
||||||
|
- All modules migrated: dialog, boards, onboarding, notes, calculator, timer, image-ref, data, bookmark-import, storage, settings, widgets, app
|
||||||
|
|
||||||
|
#### Technical
|
||||||
|
|
||||||
|
- New script load order: `storage → state → i18n → dialog → ...`
|
||||||
|
- `applyLanguage()` scans DOM for `data-i18n`, `data-i18n-placeholder`, `data-i18n-title`
|
||||||
|
- Onboarding slides use i18n keys instead of hardcoded text (rendered at display time)
|
||||||
|
- Clock day/month names via i18n keys instead of hardcoded arrays
|
||||||
|
- `resolveLang()` helper for DRY language resolution (auto → browser detect)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### v1.10.0 — 22.03.2026
|
### v1.10.0 — 22.03.2026
|
||||||
|
|
||||||
#### Themes
|
#### Themes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ⬡ Hellion Dashboard v1.9.0
|
# ⬡ Hellion Dashboard v2.0.0
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
@@ -10,7 +10,8 @@
|
|||||||
**No account. No subscription. No cloud. All data stays 100% local.**
|
**No account. No subscription. No cloud. All data stays 100% local.**
|
||||||
|
|
||||||
A personal bookmark dashboard as a browser extension.
|
A personal bookmark dashboard as a browser extension.
|
||||||
Boards, drag & drop, 11 themes, search bar, widget system with notes, calculator, timer and more. All in the browser, all offline.
|
Boards, drag & drop, 11 themes, search bar, widget system with notes, calculator, timer and more.
|
||||||
|
Full DE/EN language support with runtime switching. All in the browser, all offline.
|
||||||
No external data transmission, no trackers, no analytics, no ads.
|
No external data transmission, no trackers, no analytics, no ads.
|
||||||
|
|
||||||
Developed by **[Hellion Online Media — Florian Wathling](https://hellion-media.de)** — JonKazama-Hellion.
|
Developed by **[Hellion Online Media — Florian Wathling](https://hellion-media.de)** — JonKazama-Hellion.
|
||||||
@@ -89,6 +90,12 @@ What you see is what's saved. No magic.
|
|||||||
| Avorion | Own work, screenshot from Avorion, Hellion Initiative ship | Hellion Online Media |
|
| Avorion | Own work, screenshot from Avorion, Hellion Initiative ship | Hellion Online Media |
|
||||||
| Hellion Stealth | Screenshot from Star Citizen by Cloud Imperium Games | Fan Content |
|
| Hellion Stealth | Screenshot from Star Citizen by Cloud Imperium Games | Fan Content |
|
||||||
|
|
||||||
|
### Language Support (i18n)
|
||||||
|
|
||||||
|
- German and English with runtime switching via Settings
|
||||||
|
- Auto-detect from browser language, manual override available
|
||||||
|
- All UI elements, dialogs, onboarding and widget labels fully translated
|
||||||
|
|
||||||
### Onboarding & Dialogs
|
### Onboarding & Dialogs
|
||||||
|
|
||||||
- 7-step welcome flow on first launch with widget explanation and optional gaming starter board
|
- 7-step welcome flow on first launch with widget explanation and optional gaming starter board
|
||||||
@@ -103,7 +110,7 @@ What you see is what's saved. No magic.
|
|||||||
- Compact mode, shorten titles, search bar toggle, open links in new tab, descriptions, hide extra bookmarks
|
- Compact mode, shorten titles, search bar toggle, open links in new tab, descriptions, hide extra bookmarks
|
||||||
- JSON export & import (backup & restore)
|
- JSON export & import (backup & restore)
|
||||||
- Onboarding repeatable
|
- Onboarding repeatable
|
||||||
- All UI labels in German (English coming in v2.1)
|
- Language setting: German, English or auto-detect
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -223,10 +230,15 @@ hellion-newtab/
|
|||||||
├── SECURITY.md # Security policy and reporting
|
├── SECURITY.md # Security policy and reporting
|
||||||
├── DISCLAIMER.md # Disclaimer and legal
|
├── DISCLAIMER.md # Disclaimer and legal
|
||||||
│
|
│
|
||||||
|
├── _locales/
|
||||||
|
│ ├── de/messages.json # Manifest-level i18n (German)
|
||||||
|
│ └── en/messages.json # Manifest-level i18n (English)
|
||||||
|
│
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── js/
|
│ ├── js/
|
||||||
│ │ ├── storage.js # Storage abstraction + quota check
|
│ │ ├── storage.js # Storage abstraction + quota check
|
||||||
│ │ ├── state.js # Global state, defaults, helpers
|
│ │ ├── state.js # Global state, defaults, helpers
|
||||||
|
│ │ ├── i18n.js # Internationalization (DE/EN, ~220+ keys, t() helper)
|
||||||
│ │ ├── dialog.js # Custom dialog system (HellionDialog.alert/confirm)
|
│ │ ├── dialog.js # Custom dialog system (HellionDialog.alert/confirm)
|
||||||
│ │ ├── themes.js # Theme definitions & application (11 themes)
|
│ │ ├── themes.js # Theme definitions & application (11 themes)
|
||||||
│ │ ├── boards.js # Board/bookmark rendering, event delegation, modals
|
│ │ ├── boards.js # Board/bookmark rendering, event delegation, modals
|
||||||
@@ -272,7 +284,7 @@ hellion-newtab/
|
|||||||
|
|
||||||
- **Zero Dependencies** — No npm, no build, no framework. Runs directly
|
- **Zero Dependencies** — No npm, no build, no framework. Runs directly
|
||||||
- **Privacy First** — All data local, no server contact
|
- **Privacy First** — All data local, no server contact
|
||||||
- **Modular** — 15 JS files with clear responsibilities
|
- **Modular** — 16 JS files with clear responsibilities
|
||||||
- **Responsive** — Tablet (768px) and smartphone (480px) breakpoints
|
- **Responsive** — Tablet (768px) and smartphone (480px) breakpoints
|
||||||
- **Secure** — `createElement` instead of `innerHTML`, URL validation, storage error handling
|
- **Secure** — `createElement` instead of `innerHTML`, URL validation, storage error handling
|
||||||
- **Event Delegation** — One listener per board list instead of per bookmark (performance)
|
- **Event Delegation** — One listener per board list instead of per bookmark (performance)
|
||||||
@@ -305,8 +317,8 @@ hellion-newtab/
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create a release:
|
# Create a release:
|
||||||
git tag v1.10.0
|
git tag v2.0.0
|
||||||
git push origin v1.10.0
|
git push origin v2.0.0
|
||||||
# → GitHub Action automatically creates release with ZIP files
|
# → GitHub Action automatically creates release with ZIP files
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extName": { "message": "Hellion NewTab" },
|
||||||
|
"extDesc": { "message": "Persönliches Bookmark-Dashboard mit Boards, Widgets und 11 Themes. Komplett lokal, keine Cloud, kein Tracking." }
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extName": { "message": "Hellion NewTab" },
|
||||||
|
"extDesc": { "message": "Personal bookmark dashboard with boards, widgets, and 11 themes. Local-only, no cloud, no tracking." }
|
||||||
|
}
|
||||||
+14
-8
@@ -27,21 +27,23 @@ HOM_NewTab_Project/
|
|||||||
│ ├── css/
|
│ ├── css/
|
||||||
│ │ └── main.css # All styles, 11 themes, responsive breakpoints
|
│ │ └── main.css # All styles, 11 themes, responsive breakpoints
|
||||||
│ └── js/
|
│ └── js/
|
||||||
│ ├── dialog.js # Custom dialog system (alert, confirm)
|
|
||||||
│ ├── storage.js # Storage abstraction layer
|
│ ├── storage.js # Storage abstraction layer
|
||||||
│ ├── state.js # Global state, defaults, helpers
|
│ ├── state.js # Global state, defaults, helpers
|
||||||
|
│ ├── i18n.js # Internationalization (DE/EN, t() helper)
|
||||||
|
│ ├── dialog.js # Custom dialog system (alert, confirm)
|
||||||
│ ├── themes.js # Theme definitions & application (11 themes)
|
│ ├── themes.js # Theme definitions & application (11 themes)
|
||||||
│ ├── boards.js # Board/bookmark rendering & events
|
|
||||||
│ ├── drag.js # Drag & drop (Pointer Events API)
|
│ ├── drag.js # Drag & drop (Pointer Events API)
|
||||||
|
│ ├── boards.js # Board/bookmark rendering & events
|
||||||
│ ├── settings.js # Settings panel, toggles, theme picker
|
│ ├── settings.js # Settings panel, toggles, theme picker
|
||||||
│ ├── search.js # Search bar (Google, DuckDuckGo, Bing)
|
│ ├── search.js # Search bar (Google, DuckDuckGo, Bing)
|
||||||
│ ├── onboarding.js # First-run onboarding flow
|
|
||||||
│ ├── widgets.js # Widget manager (registry, drag, resize)
|
│ ├── widgets.js # Widget manager (registry, drag, resize)
|
||||||
│ ├── notes.js # Notes/checklists (multi-instance widgets)
|
│ ├── notes.js # Notes/checklists (multi-instance widgets)
|
||||||
│ ├── calculator.js # Calculator widget (single-instance)
|
│ ├── calculator.js # Calculator widget (single-instance)
|
||||||
│ ├── timer.js # Timer/countdown widget (single-instance)
|
│ ├── timer.js # Timer/countdown widget (single-instance)
|
||||||
│ ├── image-ref.js # Image reference widget (multi-instance)
|
│ ├── image-ref.js # Image reference widget (multi-instance)
|
||||||
|
│ ├── bookmark-import.js # Browser bookmark import (chrome.bookmarks API)
|
||||||
│ ├── data.js # JSON export/import (backup & restore)
|
│ ├── data.js # JSON export/import (backup & restore)
|
||||||
|
│ ├── onboarding.js # First-run onboarding flow
|
||||||
│ └── app.js # Init, clock, global events (entry point)
|
│ └── app.js # Init, clock, global events (entry point)
|
||||||
├── assets/
|
├── assets/
|
||||||
│ ├── fonts/ # Local fonts (Rajdhani, Inter, Cinzel)
|
│ ├── fonts/ # Local fonts (Rajdhani, Inter, Cinzel)
|
||||||
@@ -58,21 +60,23 @@ Each module has exactly one responsibility. Communication happens through global
|
|||||||
|
|
||||||
| Module | Responsibility |
|
| Module | Responsibility |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `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()`. |
|
| `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()`. |
|
| `state.js` | Global `boards` and `settings` arrays, default values, `uid()`, `escHtml()`, `getFaviconUrl()`. |
|
||||||
|
| `i18n.js` | Internationalization module. `STRINGS` object with ~220+ keys (DE/EN), `t(key, vars?)` helper, `applyLanguage()` DOM scanner, `setLanguage()`, `I18n.init()`. |
|
||||||
|
| `dialog.js` | `HellionDialog.alert()` and `HellionDialog.confirm()` — custom styled dialogs that replace native browser popups. |
|
||||||
| `themes.js` | Applies theme CSS variables. 11 themes, each with its own `[data-theme]` block in `main.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. |
|
| `boards.js` | Renders boards and bookmarks. Event delegation on board containers. |
|
||||||
| `drag.js` | Board and bookmark reordering via Pointer Events API. |
|
| `drag.js` | Board and bookmark reordering via Pointer Events API. |
|
||||||
| `settings.js` | Settings panel UI, toggle handlers, appearance modal, background upload. |
|
| `settings.js` | Settings panel UI, toggle handlers, appearance modal, background upload. |
|
||||||
| `search.js` | Search bar with engine switching (Google, DuckDuckGo, Bing). |
|
| `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). |
|
| `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. |
|
| `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()`. |
|
| `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. |
|
| `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. |
|
| `image-ref.js` | Image reference widget. Multi-instance (max 3). Canvas API WebP conversion, sessionStorage for image data — cleared on browser close. |
|
||||||
|
| `bookmark-import.js` | Direct browser bookmark import via `chrome.bookmarks.getTree()`. Folder selection modal with duplicate detection. |
|
||||||
| `data.js` | JSON export/import with validation. Covers boards, notes, calculator history and timer presets. |
|
| `data.js` | JSON export/import with validation. Covers boards, notes, calculator history and timer presets. |
|
||||||
|
| `onboarding.js` | Multi-slide first-run flow including the gaming starter board opt-in. |
|
||||||
| `app.js` | Entry point. Calls `init()` on DOMContentLoaded. Clock, global event binding. |
|
| `app.js` | Entry point. Calls `init()` on DOMContentLoaded. Clock, global event binding. |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -106,21 +110,23 @@ DOMContentLoaded
|
|||||||
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.
|
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
|
```html
|
||||||
<script src="src/js/dialog.js"></script>
|
|
||||||
<script src="src/js/storage.js"></script>
|
<script src="src/js/storage.js"></script>
|
||||||
<script src="src/js/state.js"></script>
|
<script src="src/js/state.js"></script>
|
||||||
|
<script src="src/js/i18n.js"></script>
|
||||||
|
<script src="src/js/dialog.js"></script>
|
||||||
<script src="src/js/themes.js"></script>
|
<script src="src/js/themes.js"></script>
|
||||||
<script src="src/js/boards.js"></script>
|
|
||||||
<script src="src/js/drag.js"></script>
|
<script src="src/js/drag.js"></script>
|
||||||
|
<script src="src/js/boards.js"></script>
|
||||||
<script src="src/js/settings.js"></script>
|
<script src="src/js/settings.js"></script>
|
||||||
<script src="src/js/search.js"></script>
|
<script src="src/js/search.js"></script>
|
||||||
<script src="src/js/onboarding.js"></script>
|
|
||||||
<script src="src/js/widgets.js"></script>
|
<script src="src/js/widgets.js"></script>
|
||||||
<script src="src/js/notes.js"></script>
|
<script src="src/js/notes.js"></script>
|
||||||
<script src="src/js/calculator.js"></script>
|
<script src="src/js/calculator.js"></script>
|
||||||
<script src="src/js/timer.js"></script>
|
<script src="src/js/timer.js"></script>
|
||||||
<script src="src/js/image-ref.js"></script>
|
<script src="src/js/image-ref.js"></script>
|
||||||
|
<script src="src/js/bookmark-import.js"></script>
|
||||||
<script src="src/js/data.js"></script>
|
<script src="src/js/data.js"></script>
|
||||||
|
<script src="src/js/onboarding.js"></script>
|
||||||
<script src="src/js/app.js"></script>
|
<script src="src/js/app.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Hellion NewTab",
|
"name": "__MSG_extName__",
|
||||||
"version": "1.11.1",
|
"default_locale": "en",
|
||||||
"description": "Personal bookmark dashboard — local, private, no account needed. By Hellion Online Media.",
|
"version": "2.0.0",
|
||||||
|
"description": "__MSG_extDesc__",
|
||||||
"author": "Hellion Online Media - Florian Wathling",
|
"author": "Hellion Online Media - Florian Wathling",
|
||||||
"homepage_url": "https://hellion-media.de",
|
"homepage_url": "https://hellion-media.de",
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Hellion NewTab",
|
"name": "__MSG_extName__",
|
||||||
"version": "1.11.1",
|
"default_locale": "en",
|
||||||
"description": "Personal bookmark dashboard — local, private, no account needed. By Hellion Online Media.",
|
"version": "2.0.0",
|
||||||
|
"description": "__MSG_extDesc__",
|
||||||
"author": "Hellion Online Media - Florian Wathling",
|
"author": "Hellion Online Media - Florian Wathling",
|
||||||
"homepage_url": "https://hellion-media.de",
|
"homepage_url": "https://hellion-media.de",
|
||||||
"chrome_url_overrides": {
|
"chrome_url_overrides": {
|
||||||
|
|||||||
+4
-3
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Hellion Dashboard (GX Native)",
|
"name": "__MSG_extName__",
|
||||||
"version": "1.11.1",
|
"default_locale": "en",
|
||||||
"description": "Ersetzt die Opera GX Startseite durch dein persönliches, leistungsoptimiertes Hellion Dashboard. Schnell, sauber und werbefrei.",
|
"version": "2.0.0",
|
||||||
|
"description": "__MSG_extDesc__",
|
||||||
"author": "Hellion Online Media - Florian Wathling",
|
"author": "Hellion Online Media - Florian Wathling",
|
||||||
"homepage_url": "https://hellion-media.de",
|
"homepage_url": "https://hellion-media.de",
|
||||||
|
|
||||||
|
|||||||
+105
-82
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
@@ -25,23 +25,23 @@
|
|||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<button class="btn-icon" id="btnImport" title="Bookmarks importieren (HTML)">
|
<button class="btn-icon" id="btnImport" title="Bookmarks importieren (HTML)">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||||||
Import
|
<span data-i18n="header.import">Import</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-icon" id="btnAddBoard" title="Neues Board hinzufügen">
|
<button class="btn-icon" id="btnAddBoard" title="Neues Board hinzufügen">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||||
Board
|
<span data-i18n="header.board">Board</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-icon" id="btnNote" title="Schnellnotiz">
|
<button class="btn-icon" id="btnNote" title="Schnellnotiz">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
||||||
Note
|
<span data-i18n="header.note">Note</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-icon" id="btnTheme" title="Darstellung & Theme">
|
<button class="btn-icon" id="btnTheme" title="Darstellung & Theme">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 100 20 4 4 0 01-1-7.9 1 1 0 011-.1h1a2 2 0 002-2V7a5 5 0 00-3-4.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="6" r="1.5"/><circle cx="17" cy="10" r="1.5"/><circle cx="9" cy="17" r="1.5"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 100 20 4 4 0 01-1-7.9 1 1 0 011-.1h1a2 2 0 002-2V7a5 5 0 00-3-4.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="6" r="1.5"/><circle cx="17" cy="10" r="1.5"/><circle cx="9" cy="17" r="1.5"/></svg>
|
||||||
Darstellung
|
<span data-i18n="header.theme">Darstellung</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-icon" id="btnSettings" title="Einstellungen">
|
<button class="btn-icon" id="btnSettings" title="Einstellungen">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
|
||||||
Settings
|
<span data-i18n="header.settings">Settings</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -49,10 +49,10 @@
|
|||||||
<!-- SEARCH BAR -->
|
<!-- SEARCH BAR -->
|
||||||
<div class="search-bar-wrapper" id="searchBarWrapper">
|
<div class="search-bar-wrapper" id="searchBarWrapper">
|
||||||
<div class="search-bar">
|
<div class="search-bar">
|
||||||
<button class="search-engine-toggle" id="searchEngineToggle" title="Suchmaschine wechseln">
|
<button class="search-engine-toggle" id="searchEngineToggle" data-i18n-title="settings.search_engine_toggle" title="Suchmaschine wechseln">
|
||||||
<span id="searchEngineIcon">G</span>
|
<span id="searchEngineIcon">G</span>
|
||||||
</button>
|
</button>
|
||||||
<input type="text" class="search-input" id="searchInput" placeholder="Search the web…" autocomplete="off" />
|
<input type="text" class="search-input" id="searchInput" data-i18n-placeholder="search.placeholder" placeholder="Search the web…" autocomplete="off" />
|
||||||
<button class="search-submit" id="searchSubmit">
|
<button class="search-submit" id="searchSubmit">
|
||||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||||
</button>
|
</button>
|
||||||
@@ -61,22 +61,22 @@
|
|||||||
|
|
||||||
<!-- WIDGET TOOLBAR -->
|
<!-- WIDGET TOOLBAR -->
|
||||||
<div class="widget-toolbar" id="widgetToolbar">
|
<div class="widget-toolbar" id="widgetToolbar">
|
||||||
<button class="widget-toolbar-btn" data-action="new-note" title="Note erstellen">
|
<button class="widget-toolbar-btn" data-action="new-note" data-i18n-title="toolbar.note" title="Note erstellen">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="widget-toolbar-btn" data-action="new-checklist" title="Checkliste erstellen">
|
<button class="widget-toolbar-btn" data-action="new-checklist" data-i18n-title="toolbar.checklist" title="Checkliste erstellen">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="widget-toolbar-btn" data-action="calculator" title="Taschenrechner">
|
<button class="widget-toolbar-btn" data-action="calculator" data-i18n-title="toolbar.calculator" title="Taschenrechner">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="2" width="16" height="20" rx="2"/><line x1="8" y1="6" x2="16" y2="6"/><circle cx="8" cy="10" r="0.5"/><circle cx="12" cy="10" r="0.5"/><circle cx="16" cy="10" r="0.5"/><circle cx="8" cy="14" r="0.5"/><circle cx="12" cy="14" r="0.5"/><circle cx="16" cy="14" r="0.5"/><circle cx="8" cy="18" r="0.5"/><circle cx="12" cy="18" r="0.5"/><circle cx="16" cy="18" r="0.5"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="2" width="16" height="20" rx="2"/><line x1="8" y1="6" x2="16" y2="6"/><circle cx="8" cy="10" r="0.5"/><circle cx="12" cy="10" r="0.5"/><circle cx="16" cy="10" r="0.5"/><circle cx="8" cy="14" r="0.5"/><circle cx="12" cy="14" r="0.5"/><circle cx="16" cy="14" r="0.5"/><circle cx="8" cy="18" r="0.5"/><circle cx="12" cy="18" r="0.5"/><circle cx="16" cy="18" r="0.5"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="widget-toolbar-btn" data-action="timer" title="Timer">
|
<button class="widget-toolbar-btn" data-action="timer" data-i18n-title="toolbar.timer" title="Timer">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="13" r="8"/><path d="M12 9v4l2 2"/><path d="M5 3l2 2"/><path d="M19 3l-2 2"/><line x1="12" y1="1" x2="12" y2="3"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="13" r="8"/><path d="M12 9v4l2 2"/><path d="M5 3l2 2"/><path d="M19 3l-2 2"/><line x1="12" y1="1" x2="12" y2="3"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="widget-toolbar-btn hidden" data-action="image-ref" title="Bild-Referenz">
|
<button class="widget-toolbar-btn hidden" data-action="image-ref" data-i18n-title="toolbar.imageref" title="Bild-Referenz">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="widget-toolbar-btn" data-action="notebook" title="Alle Notes">
|
<button class="widget-toolbar-btn" data-action="notebook" data-i18n-title="toolbar.notebook" title="Alle Notes">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
<div class="notebook-overlay" id="notebookOverlay"></div>
|
<div class="notebook-overlay" id="notebookOverlay"></div>
|
||||||
<aside class="notebook-panel" id="notebookPanel">
|
<aside class="notebook-panel" id="notebookPanel">
|
||||||
<div class="notebook-header">
|
<div class="notebook-header">
|
||||||
<span class="notebook-header-title">Notebook <span class="notebook-count" id="notebookCount">0 / 5</span></span>
|
<span class="notebook-header-title"><span data-i18n="notebook.title">Notebook</span> <span class="notebook-count" id="notebookCount">0 / 5</span></span>
|
||||||
<button class="btn-close" id="btnCloseNotebook">✕</button>
|
<button class="btn-close" id="btnCloseNotebook">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="notebook-slots" id="notebookSlots">
|
<div class="notebook-slots" id="notebookSlots">
|
||||||
@@ -105,32 +105,53 @@
|
|||||||
<div class="panel-overlay" id="settingsOverlay"></div>
|
<div class="panel-overlay" id="settingsOverlay"></div>
|
||||||
<aside class="settings-panel" id="settingsPanel">
|
<aside class="settings-panel" id="settingsPanel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<span>Einstellungen</span>
|
<span data-i18n="settings.title">Einstellungen</span>
|
||||||
<button class="btn-close" id="btnCloseSettings">✕</button>
|
<button class="btn-close" id="btnCloseSettings">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
|
||||||
|
<!-- SPRACHE -->
|
||||||
|
<section class="settings-section" data-section="language">
|
||||||
|
<button class="settings-section-title" type="button">
|
||||||
|
<span class="section-chevron">▸</span>
|
||||||
|
<span data-i18n="settings.section.display">DARSTELLUNG</span>
|
||||||
|
</button>
|
||||||
|
<div class="section-content">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label" data-i18n="settings.language">Sprache</span>
|
||||||
|
<span class="setting-desc" data-i18n="settings.language.desc">Anzeigesprache wählen</span>
|
||||||
|
</div>
|
||||||
|
<select class="select-input" id="settingLanguage">
|
||||||
|
<option value="auto" data-i18n="settings.language.auto">Automatisch</option>
|
||||||
|
<option value="de">Deutsch</option>
|
||||||
|
<option value="en">English</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- WIDGETS -->
|
<!-- WIDGETS -->
|
||||||
<section class="settings-section" data-section="widgets">
|
<section class="settings-section" data-section="widgets">
|
||||||
<button class="settings-section-title" type="button">
|
<button class="settings-section-title" type="button">
|
||||||
<span class="section-chevron">▸</span>
|
<span class="section-chevron">▸</span>
|
||||||
WIDGETS
|
<span data-i18n="settings.section.widgets">WIDGETS</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Toolbar-Position</span>
|
<span class="setting-label" data-i18n="settings.toolbar_pos">Toolbar-Position</span>
|
||||||
<span class="setting-desc">Widget-Toolbar links oder rechts anzeigen</span>
|
<span class="setting-desc" data-i18n="settings.toolbar_pos.desc">Widget-Toolbar links oder rechts anzeigen</span>
|
||||||
</div>
|
</div>
|
||||||
<select class="select-input" id="settingToolbarPos">
|
<select class="select-input" id="settingToolbarPos">
|
||||||
<option value="right" selected>Rechts</option>
|
<option value="right" selected data-i18n="settings.toolbar_pos.right">Rechts</option>
|
||||||
<option value="left">Links</option>
|
<option value="left" data-i18n="settings.toolbar_pos.left">Links</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Bild-Referenz Widgets</span>
|
<span class="setting-label" data-i18n="settings.image_ref">Bild-Referenz Widgets</span>
|
||||||
<span class="setting-desc">Bilder als Referenz anzeigen (nur aktuelle Session)</span>
|
<span class="setting-desc" data-i18n="settings.image_ref.desc">Bilder als Referenz anzeigen (nur aktuelle Session)</span>
|
||||||
</div>
|
</div>
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input type="checkbox" id="settingImageRef">
|
<input type="checkbox" id="settingImageRef">
|
||||||
@@ -144,35 +165,35 @@
|
|||||||
<section class="settings-section" data-section="data">
|
<section class="settings-section" data-section="data">
|
||||||
<button class="settings-section-title" type="button">
|
<button class="settings-section-title" type="button">
|
||||||
<span class="section-chevron">▸</span>
|
<span class="section-chevron">▸</span>
|
||||||
DATEN & HILFE
|
<span data-i18n="settings.section.data">DATEN & HILFE</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Backup exportieren</span>
|
<span class="setting-label" data-i18n="settings.export">Backup exportieren</span>
|
||||||
<span class="setting-desc">Alle Boards, Notes und Einstellungen als JSON sichern</span>
|
<span class="setting-desc" data-i18n="settings.export.desc">Alle Boards, Notes und Einstellungen als JSON sichern</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-small" id="btnExportJSON">Export</button>
|
<button class="btn-small" id="btnExportJSON" data-i18n="settings.export.btn">Export</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Backup importieren</span>
|
<span class="setting-label" data-i18n="settings.import">Backup importieren</span>
|
||||||
<span class="setting-desc">JSON-Backup wiederherstellen</span>
|
<span class="setting-desc" data-i18n="settings.import.desc">JSON-Backup wiederherstellen</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-small" id="btnImportJSON">Import</button>
|
<button class="btn-small" id="btnImportJSON" data-i18n="header.import">Import</button>
|
||||||
<input type="file" id="jsonImportInput" accept=".json" class="hidden" />
|
<input type="file" id="jsonImportInput" accept=".json" class="hidden" />
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row" id="browserImportRow">
|
<div class="setting-row" id="browserImportRow">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Browser-Lesezeichen</span>
|
<span class="setting-label" data-i18n="settings.browser_import">Browser-Lesezeichen</span>
|
||||||
<span class="setting-desc">Lesezeichen direkt aus dem Browser importieren</span>
|
<span class="setting-desc" data-i18n="settings.browser_import.desc">Lesezeichen direkt aus dem Browser importieren</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-small" id="btnBrowserImport">Import</button>
|
<button class="btn-small" id="btnBrowserImport" data-i18n="header.import">Import</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Onboarding wiederholen</span>
|
<span class="setting-label" data-i18n="settings.onboarding">Onboarding wiederholen</span>
|
||||||
<span class="setting-desc">Willkommens-Tour erneut anzeigen</span>
|
<span class="setting-desc" data-i18n="settings.onboarding.desc">Willkommens-Tour erneut anzeigen</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-small" id="btnRestartOnboarding">Start</button>
|
<button class="btn-small" id="btnRestartOnboarding">Start</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -183,13 +204,13 @@
|
|||||||
<section class="settings-section" data-section="danger">
|
<section class="settings-section" data-section="danger">
|
||||||
<button class="settings-section-title danger" type="button">
|
<button class="settings-section-title danger" type="button">
|
||||||
<span class="section-chevron">▸</span>
|
<span class="section-chevron">▸</span>
|
||||||
DANGER ZONE
|
<span data-i18n="settings.section.danger">DANGER ZONE</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Alles zurücksetzen</span>
|
<span class="setting-label" data-i18n="settings.reset">Alles zurücksetzen</span>
|
||||||
<span class="setting-desc">Löscht alle Boards, Notes und Einstellungen</span>
|
<span class="setting-desc" data-i18n="settings.reset.desc">Löscht alle Boards, Notes und Einstellungen</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-danger" id="btnResetAll">Reset</button>
|
<button class="btn-danger" id="btnResetAll">Reset</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -201,13 +222,13 @@
|
|||||||
<!-- ABOUT — fixiert am unteren Rand -->
|
<!-- ABOUT — fixiert am unteren Rand -->
|
||||||
<div class="panel-footer">
|
<div class="panel-footer">
|
||||||
<div class="about-block">
|
<div class="about-block">
|
||||||
<div class="about-logo">⬡ HELLION NEWTAB</div>
|
<div class="about-logo" data-i18n="about.title">⬡ HELLION NEWTAB</div>
|
||||||
<div class="about-version">Version 1.11.1 · by Hellion Online Media</div>
|
<div class="about-version">Version 2.0.0 · by Hellion Online Media</div>
|
||||||
|
|
||||||
<div class="about-links">
|
<div class="about-links">
|
||||||
<a href="https://hellion-media.de/impressum" target="_blank" class="about-link">
|
<a href="https://hellion-media.de/impressum" target="_blank" class="about-link">
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||||
Impressum
|
<span data-i18n="about.impressum">Impressum</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hellion-media.de" target="_blank" class="about-link">
|
<a href="https://hellion-media.de" target="_blank" class="about-link">
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
|
||||||
@@ -218,26 +239,26 @@
|
|||||||
<div class="about-divider"></div>
|
<div class="about-divider"></div>
|
||||||
|
|
||||||
<div class="about-info-row">
|
<div class="about-info-row">
|
||||||
<span class="about-info-label">Entwickler</span>
|
<span class="about-info-label" data-i18n="about.developer">Entwickler</span>
|
||||||
<span class="about-info-value">Florian Wathling</span>
|
<span class="about-info-value">Florian Wathling</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="about-info-row">
|
<div class="about-info-row">
|
||||||
<span class="about-info-label">Unternehmen</span>
|
<span class="about-info-label" data-i18n="about.company">Unternehmen</span>
|
||||||
<span class="about-info-value">Hellion Online Media</span>
|
<span class="about-info-value">Hellion Online Media</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="about-info-row">
|
<div class="about-info-row">
|
||||||
<span class="about-info-label">Lizenz</span>
|
<span class="about-info-label" data-i18n="about.license">Lizenz</span>
|
||||||
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" class="about-info-value about-link-subtle">CC BY-NC-SA 4.0</a>
|
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" class="about-info-value about-link-subtle">CC BY-NC-SA 4.0</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="about-info-row">
|
<div class="about-info-row">
|
||||||
<span class="about-info-label">Datenspeicherung</span>
|
<span class="about-info-label" data-i18n="about.storage">Datenspeicherung</span>
|
||||||
<span class="about-info-value">100% lokal · Kein Server · Kein Account</span>
|
<span class="about-info-value" data-i18n="about.storage.value">100% lokal · Kein Server · Kein Account</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="about-divider"></div>
|
<div class="about-divider"></div>
|
||||||
|
|
||||||
<div class="about-bugreport">
|
<div class="about-bugreport">
|
||||||
<span class="about-info-label about-info-label-block">Bug Report / Feedback</span>
|
<span class="about-info-label about-info-label-block" data-i18n="about.bugreport">Bug Report / Feedback</span>
|
||||||
<a href="mailto:kontakt@hellion-media.de?subject=Hellion NewTab – Bug Report" class="about-link-mail">
|
<a href="mailto:kontakt@hellion-media.de?subject=Hellion NewTab – Bug Report" class="about-link-mail">
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
||||||
kontakt@hellion-media.de
|
kontakt@hellion-media.de
|
||||||
@@ -245,7 +266,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="about-bugreport">
|
<div class="about-bugreport">
|
||||||
<span class="about-info-label about-info-label-block">Support</span>
|
<span class="about-info-label about-info-label-block" data-i18n="about.support">Support</span>
|
||||||
<a href="https://ko-fi.com/hellionmedia" target="_blank" class="about-link">
|
<a href="https://ko-fi.com/hellionmedia" target="_blank" class="about-link">
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
|
||||||
Ko-fi — hellionmedia
|
Ko-fi — hellionmedia
|
||||||
@@ -253,7 +274,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="about-browsers">
|
<div class="about-browsers">
|
||||||
<span class="about-info-label about-info-label-block">Kompatible Browser</span>
|
<span class="about-info-label about-info-label-block" data-i18n="about.browsers">Kompatible Browser</span>
|
||||||
<div class="about-browser-tags">
|
<div class="about-browser-tags">
|
||||||
<span class="browser-tag">Chrome</span>
|
<span class="browser-tag">Chrome</span>
|
||||||
<span class="browser-tag">Edge</span>
|
<span class="browser-tag">Edge</span>
|
||||||
@@ -272,7 +293,7 @@
|
|||||||
<div class="modal-overlay" id="themeOverlay">
|
<div class="modal-overlay" id="themeOverlay">
|
||||||
<div class="theme-modal" id="themeModal">
|
<div class="theme-modal" id="themeModal">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<span>Darstellung</span>
|
<span data-i18n="modal.theme_header">Darstellung</span>
|
||||||
<button class="btn-close" id="btnCloseTheme">✕</button>
|
<button class="btn-close" id="btnCloseTheme">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-grid">
|
<div class="theme-grid">
|
||||||
@@ -333,75 +354,75 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-modal-section">
|
<div class="theme-modal-section">
|
||||||
<h3 class="settings-section-title">HINTERGRUND</h3>
|
<h3 class="settings-section-title" data-i18n="settings.section.bg">HINTERGRUND</h3>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Bild-URL</span>
|
<span class="setting-label" data-i18n="settings.bg_url">Bild-URL</span>
|
||||||
<span class="setting-desc">Eigenes Hintergrundbild per URL</span>
|
<span class="setting-desc" data-i18n="settings.bg_url.desc">Eigenes Hintergrundbild per URL</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-small" id="btnChangeBg">Ändern</button>
|
<button class="btn-small" id="btnChangeBg" data-i18n="settings.bg_change">Ändern</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row hidden" id="bgInputRow">
|
<div class="setting-row hidden" id="bgInputRow">
|
||||||
<input type="text" class="text-input full-width" id="bgUrlInput" placeholder="https://... oder leer für Standard" />
|
<input type="text" class="text-input full-width" id="bgUrlInput" placeholder="https://... oder leer für Standard" />
|
||||||
<button class="btn-small" id="btnApplyBg">Übernehmen</button>
|
<button class="btn-small" id="btnApplyBg" data-i18n="settings.bg_apply">Übernehmen</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Datei hochladen</span>
|
<span class="setting-label" data-i18n="settings.bg_upload">Datei hochladen</span>
|
||||||
<span class="setting-desc">Lokales Bild als Hintergrund verwenden</span>
|
<span class="setting-desc" data-i18n="settings.bg_upload.desc">Lokales Bild als Hintergrund verwenden</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-small" id="btnBgFile">Upload</button>
|
<button class="btn-small" id="btnBgFile">Upload</button>
|
||||||
<input type="file" id="bgFileInput" accept="image/*" class="hidden" />
|
<input type="file" id="bgFileInput" accept="image/*" class="hidden" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-modal-section">
|
<div class="theme-modal-section">
|
||||||
<h3 class="settings-section-title">DARSTELLUNG</h3>
|
<h3 class="settings-section-title" data-i18n="settings.section.display">DARSTELLUNG</h3>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Kompaktmodus</span>
|
<span class="setting-label" data-i18n="settings.compact">Kompaktmodus</span>
|
||||||
<span class="setting-desc">Weniger Abstand für mehr Bookmarks</span>
|
<span class="setting-desc" data-i18n="settings.compact.desc">Weniger Abstand für mehr Bookmarks</span>
|
||||||
</div>
|
</div>
|
||||||
<label class="toggle"><input type="checkbox" id="settingCompact" /><span class="slider"></span></label>
|
<label class="toggle"><input type="checkbox" id="settingCompact" /><span class="slider"></span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Lange Titel kürzen</span>
|
<span class="setting-label" data-i18n="settings.shorten">Lange Titel kürzen</span>
|
||||||
<span class="setting-desc">Titel auf eine Zeile mit „…" kürzen</span>
|
<span class="setting-desc" data-i18n="settings.shorten.desc">Titel auf eine Zeile mit „…" kürzen</span>
|
||||||
</div>
|
</div>
|
||||||
<label class="toggle"><input type="checkbox" id="settingShorten" /><span class="slider"></span></label>
|
<label class="toggle"><input type="checkbox" id="settingShorten" /><span class="slider"></span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Suchleiste anzeigen</span>
|
<span class="setting-label" data-i18n="settings.search">Suchleiste anzeigen</span>
|
||||||
<span class="setting-desc">Suchleiste unter dem Header ein/aus</span>
|
<span class="setting-desc" data-i18n="settings.search.desc">Suchleiste unter dem Header ein/aus</span>
|
||||||
</div>
|
</div>
|
||||||
<label class="toggle"><input type="checkbox" id="settingShowSearch" checked /><span class="slider"></span></label>
|
<label class="toggle"><input type="checkbox" id="settingShowSearch" checked /><span class="slider"></span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Links in neuem Tab</span>
|
<span class="setting-label" data-i18n="settings.newtab">Links in neuem Tab</span>
|
||||||
<span class="setting-desc">Bookmarks in neuem Browser-Tab öffnen</span>
|
<span class="setting-desc" data-i18n="settings.newtab.desc">Bookmarks in neuem Browser-Tab öffnen</span>
|
||||||
</div>
|
</div>
|
||||||
<label class="toggle"><input type="checkbox" id="settingNewTab" checked /><span class="slider"></span></label>
|
<label class="toggle"><input type="checkbox" id="settingNewTab" checked /><span class="slider"></span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Beschreibungen anzeigen</span>
|
<span class="setting-label" data-i18n="settings.showdesc">Beschreibungen anzeigen</span>
|
||||||
<span class="setting-desc">Gespeicherte Beschreibung unter Bookmarks</span>
|
<span class="setting-desc" data-i18n="settings.showdesc.desc">Gespeicherte Beschreibung unter Bookmarks</span>
|
||||||
</div>
|
</div>
|
||||||
<label class="toggle"><input type="checkbox" id="settingShowDesc" /><span class="slider"></span></label>
|
<label class="toggle"><input type="checkbox" id="settingShowDesc" /><span class="slider"></span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Bookmarks ausblenden</span>
|
<span class="setting-label" data-i18n="settings.hideextra">Bookmarks ausblenden</span>
|
||||||
<span class="setting-desc">Überzählige Bookmarks in langen Boards verstecken</span>
|
<span class="setting-desc" data-i18n="settings.hideextra.desc">Überzählige Bookmarks in langen Boards verstecken</span>
|
||||||
</div>
|
</div>
|
||||||
<label class="toggle"><input type="checkbox" id="settingHideExtra" /><span class="slider"></span></label>
|
<label class="toggle"><input type="checkbox" id="settingHideExtra" /><span class="slider"></span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row" id="visibleCountRow">
|
<div class="setting-row" id="visibleCountRow">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Sichtbare Bookmarks</span>
|
<span class="setting-label" data-i18n="settings.visible_count">Sichtbare Bookmarks</span>
|
||||||
<span class="setting-desc">Anzahl vor dem Ausblenden</span>
|
<span class="setting-desc" data-i18n="settings.visible_count.desc">Anzahl vor dem Ausblenden</span>
|
||||||
</div>
|
</div>
|
||||||
<select class="select-input" id="settingVisibleCount">
|
<select class="select-input" id="settingVisibleCount">
|
||||||
<option value="5">5</option>
|
<option value="5">5</option>
|
||||||
@@ -417,14 +438,14 @@
|
|||||||
<div class="modal-overlay" id="addBoardOverlay">
|
<div class="modal-overlay" id="addBoardOverlay">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<span>New Board</span>
|
<span data-i18n="modal.new_board">New Board</span>
|
||||||
<button class="btn-close" id="btnCancelBoard">✕</button>
|
<button class="btn-close" id="btnCancelBoard">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="text" class="text-input full-width" id="newBoardName" placeholder="Board name..." maxlength="40" />
|
<input type="text" class="text-input full-width" id="newBoardName" data-i18n-placeholder="modal.board_name" placeholder="Board name..." maxlength="40" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn-primary" id="btnConfirmBoard">Create</button>
|
<button class="btn-primary" id="btnConfirmBoard" data-i18n="modal.create">Create</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -433,16 +454,16 @@
|
|||||||
<div class="modal-overlay" id="addBookmarkOverlay">
|
<div class="modal-overlay" id="addBookmarkOverlay">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<span>New Bookmark</span>
|
<span data-i18n="modal.new_bookmark">New Bookmark</span>
|
||||||
<button class="btn-close" id="btnCancelBookmark">✕</button>
|
<button class="btn-close" id="btnCancelBookmark">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="text" class="text-input full-width" id="newBmTitle" placeholder="Title..." maxlength="60" />
|
<input type="text" class="text-input full-width" id="newBmTitle" data-i18n-placeholder="modal.bm_title" placeholder="Title..." maxlength="60" />
|
||||||
<input type="url" class="text-input full-width modal-input-spaced" id="newBmUrl" placeholder="https://..." />
|
<input type="url" class="text-input full-width modal-input-spaced" id="newBmUrl" placeholder="https://..." />
|
||||||
<input type="text" class="text-input full-width modal-input-spaced" id="newBmDesc" placeholder="Description (optional)" />
|
<input type="text" class="text-input full-width modal-input-spaced" id="newBmDesc" data-i18n-placeholder="modal.bm_desc" placeholder="Description (optional)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn-primary" id="btnConfirmBookmark">Add</button>
|
<button class="btn-primary" id="btnConfirmBookmark" data-i18n="modal.bm_add">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -451,14 +472,14 @@
|
|||||||
<div class="modal-overlay" id="renameOverlay">
|
<div class="modal-overlay" id="renameOverlay">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<span>Rename</span>
|
<span data-i18n="modal.rename">Rename</span>
|
||||||
<button class="btn-close" id="btnCancelRename">✕</button>
|
<button class="btn-close" id="btnCancelRename">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="text" class="text-input full-width" id="renameInput" placeholder="New name..." maxlength="60" />
|
<input type="text" class="text-input full-width" id="renameInput" data-i18n-placeholder="modal.rename_placeholder" placeholder="New name..." maxlength="60" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn-primary" id="btnConfirmRename">Rename</button>
|
<button class="btn-primary" id="btnConfirmRename" data-i18n="modal.rename_confirm">Rename</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -472,6 +493,8 @@
|
|||||||
<script src="src/js/storage.js"></script>
|
<script src="src/js/storage.js"></script>
|
||||||
<!-- State & Hilfsfunktionen -->
|
<!-- State & Hilfsfunktionen -->
|
||||||
<script src="src/js/state.js"></script>
|
<script src="src/js/state.js"></script>
|
||||||
|
<!-- i18n (nach state.js, vor allen Modulen die t() nutzen könnten) -->
|
||||||
|
<script src="src/js/i18n.js"></script>
|
||||||
<!-- Dialog-System (vor Features, wird überall gebraucht) -->
|
<!-- Dialog-System (vor Features, wird überall gebraucht) -->
|
||||||
<script src="src/js/dialog.js"></script>
|
<script src="src/js/dialog.js"></script>
|
||||||
<!-- Theme-System -->
|
<!-- Theme-System -->
|
||||||
|
|||||||
+11
-10
@@ -10,6 +10,7 @@ async function init() {
|
|||||||
boards = savedBoards ?? getDefaultBoards();
|
boards = savedBoards ?? getDefaultBoards();
|
||||||
if (savedSettings) Object.assign(settings, savedSettings);
|
if (savedSettings) Object.assign(settings, savedSettings);
|
||||||
|
|
||||||
|
I18n.init();
|
||||||
applySettings();
|
applySettings();
|
||||||
renderBoards();
|
renderBoards();
|
||||||
startClock();
|
startClock();
|
||||||
@@ -94,8 +95,8 @@ async function checkBackupReminder() {
|
|||||||
if (boards.length === 0) return;
|
if (boards.length === 0) return;
|
||||||
|
|
||||||
const doBackup = await HellionDialog.confirm(
|
const doBackup = await HellionDialog.confirm(
|
||||||
'Du hast seit über einer Woche kein Backup gemacht. Beim Löschen der Browserdaten gehen deine Boards verloren. Jetzt sichern?',
|
t('app.backup_reminder'),
|
||||||
{ type: 'warning', title: 'Backup-Erinnerung', confirmText: 'Jetzt sichern', cancelText: 'Später' }
|
{ type: 'warning', title: t('app.backup_reminder.title'), confirmText: t('app.backup_now'), cancelText: t('app.backup_later') }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (doBackup) {
|
if (doBackup) {
|
||||||
@@ -104,7 +105,7 @@ async function checkBackupReminder() {
|
|||||||
const notesData = (widgetData && Array.isArray(widgetData.notes)) ? widgetData.notes : [];
|
const notesData = (widgetData && Array.isArray(widgetData.notes)) ? widgetData.notes : [];
|
||||||
const calcHistory = (widgetData && widgetData.calculator) ? widgetData.calculator.history || [] : [];
|
const calcHistory = (widgetData && widgetData.calculator) ? widgetData.calculator.history || [] : [];
|
||||||
const timerPresets = (widgetData && widgetData.timer) ? widgetData.timer.presets || [] : [];
|
const timerPresets = (widgetData && widgetData.timer) ? widgetData.timer.presets || [] : [];
|
||||||
const data = { version: '1.11.1', exported: new Date().toISOString(), boards, settings, notes: notesData, calculator: calcHistory, timerPresets };
|
const data = { version: '2.0.0', exported: new Date().toISOString(), boards, settings, notes: notesData, calculator: calcHistory, timerPresets };
|
||||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -120,15 +121,15 @@ async function checkBackupReminder() {
|
|||||||
|
|
||||||
// ---- CLOCK & DATE ----
|
// ---- CLOCK & DATE ----
|
||||||
function startClock() {
|
function startClock() {
|
||||||
const DAYS = ['So','Mo','Di','Mi','Do','Fr','Sa'];
|
const DAY_KEYS = ['clock.days.sun','clock.days.mon','clock.days.tue','clock.days.wed','clock.days.thu','clock.days.fri','clock.days.sat'];
|
||||||
const MONTHS = ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'];
|
const MONTH_KEYS = ['clock.months.jan','clock.months.feb','clock.months.mar','clock.months.apr','clock.months.may','clock.months.jun','clock.months.jul','clock.months.aug','clock.months.sep','clock.months.oct','clock.months.nov','clock.months.dec'];
|
||||||
|
|
||||||
function tick() {
|
function tick() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
document.getElementById('clock').textContent =
|
document.getElementById('clock').textContent =
|
||||||
`${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`;
|
`${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`;
|
||||||
document.getElementById('date').textContent =
|
document.getElementById('date').textContent =
|
||||||
`${DAYS[now.getDay()]}, ${String(now.getDate()).padStart(2,'0')}. ${MONTHS[now.getMonth()]}`;
|
`${t(DAY_KEYS[now.getDay()])}, ${String(now.getDate()).padStart(2,'0')}. ${t(MONTH_KEYS[now.getMonth()])}`;
|
||||||
}
|
}
|
||||||
tick();
|
tick();
|
||||||
setInterval(tick, 1000);
|
setInterval(tick, 1000);
|
||||||
@@ -148,7 +149,7 @@ function bindGlobalEvents() {
|
|||||||
if (!file) return;
|
if (!file) return;
|
||||||
const imported = parseBookmarkHtml(await file.text());
|
const imported = parseBookmarkHtml(await file.text());
|
||||||
if (imported.length === 0) {
|
if (imported.length === 0) {
|
||||||
await HellionDialog.alert('Keine Bookmarks in dieser Datei gefunden.', { type: 'warning', title: 'Import' });
|
await HellionDialog.alert(t('app.no_bookmarks'), { type: 'warning', title: t('app.import_title') });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boards = [...boards, ...imported];
|
boards = [...boards, ...imported];
|
||||||
@@ -156,8 +157,8 @@ function bindGlobalEvents() {
|
|||||||
renderBoards();
|
renderBoards();
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
`${imported.length} Board(s) mit ${imported.reduce((s,b) => s + b.bookmarks.length, 0)} Bookmarks importiert.`,
|
t('app.html_import_success', { count: imported.length, total: imported.reduce((s,b) => s + b.bookmarks.length, 0) }),
|
||||||
{ type: 'success', title: 'Import erfolgreich' }
|
{ type: 'success', title: t('app.import_success_title') }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -189,7 +190,7 @@ function bindGlobalEvents() {
|
|||||||
const url = document.getElementById('newBmUrl').value.trim();
|
const url = document.getElementById('newBmUrl').value.trim();
|
||||||
const desc = document.getElementById('newBmDesc').value.trim();
|
const desc = document.getElementById('newBmDesc').value.trim();
|
||||||
if (!title || !url) return;
|
if (!title || !url) return;
|
||||||
try { new URL(url); } catch { await HellionDialog.alert('Ungültige URL. Bitte mit https:// beginnen.', { type: 'warning', title: 'URL ungültig' }); return; }
|
try { new URL(url); } catch { await HellionDialog.alert(t('app.invalid_url'), { type: 'warning', title: t('app.invalid_url.title') }); return; }
|
||||||
const board = boards.find(b => b.id === pendingBookmarkBoardId);
|
const board = boards.find(b => b.id === pendingBookmarkBoardId);
|
||||||
if (!board) return;
|
if (!board) return;
|
||||||
board.bookmarks.push({ id: uid(), title, url, desc });
|
board.bookmarks.push({ id: uid(), title, url, desc });
|
||||||
|
|||||||
+16
-16
@@ -57,14 +57,14 @@ function renderBoards() {
|
|||||||
|
|
||||||
const boardStrong = document.createElement('strong');
|
const boardStrong = document.createElement('strong');
|
||||||
boardStrong.className = 'accent-text';
|
boardStrong.className = 'accent-text';
|
||||||
boardStrong.textContent = '+ Board';
|
boardStrong.textContent = t('boards.add_board');
|
||||||
|
|
||||||
const importStrong = document.createElement('strong');
|
const importStrong = document.createElement('strong');
|
||||||
importStrong.className = 'accent-text';
|
importStrong.className = 'accent-text';
|
||||||
importStrong.textContent = 'Import';
|
importStrong.textContent = t('boards.import');
|
||||||
|
|
||||||
empty.append(
|
empty.append(
|
||||||
'No boards yet. Click ', boardStrong, ' to create one, or use ', importStrong, ' to load your browser bookmarks.'
|
t('boards.empty_state_pre'), boardStrong, t('boards.empty_state_mid'), importStrong, t('boards.empty_state_post')
|
||||||
);
|
);
|
||||||
wrapper.appendChild(empty);
|
wrapper.appendChild(empty);
|
||||||
return;
|
return;
|
||||||
@@ -85,7 +85,7 @@ function createBoardEl(board) {
|
|||||||
|
|
||||||
const dragHandle = document.createElement('span');
|
const dragHandle = document.createElement('span');
|
||||||
dragHandle.className = 'board-drag-handle';
|
dragHandle.className = 'board-drag-handle';
|
||||||
dragHandle.title = 'Board verschieben';
|
dragHandle.title = t('boards.drag_title');
|
||||||
dragHandle.appendChild(createDragHandleSvg());
|
dragHandle.appendChild(createDragHandleSvg());
|
||||||
|
|
||||||
const titleSpanHeader = document.createElement('span');
|
const titleSpanHeader = document.createElement('span');
|
||||||
@@ -98,17 +98,17 @@ function createBoardEl(board) {
|
|||||||
|
|
||||||
const btnBlur = document.createElement('button');
|
const btnBlur = document.createElement('button');
|
||||||
btnBlur.className = 'board-action-btn btn-blur-board';
|
btnBlur.className = 'board-action-btn btn-blur-board';
|
||||||
btnBlur.title = board.blurred ? 'Unblur' : 'Blur (privat)';
|
btnBlur.title = board.blurred ? t('boards.unblur') : t('boards.blur');
|
||||||
btnBlur.textContent = '\uD83D\uDD12';
|
btnBlur.textContent = '\uD83D\uDD12';
|
||||||
|
|
||||||
const btnRename = document.createElement('button');
|
const btnRename = document.createElement('button');
|
||||||
btnRename.className = 'board-action-btn btn-rename-board';
|
btnRename.className = 'board-action-btn btn-rename-board';
|
||||||
btnRename.title = 'Umbenennen';
|
btnRename.title = t('boards.rename');
|
||||||
btnRename.textContent = '\u270E';
|
btnRename.textContent = '\u270E';
|
||||||
|
|
||||||
const btnDelete = document.createElement('button');
|
const btnDelete = document.createElement('button');
|
||||||
btnDelete.className = 'board-action-btn btn-delete-board';
|
btnDelete.className = 'board-action-btn btn-delete-board';
|
||||||
btnDelete.title = 'Löschen';
|
btnDelete.title = t('boards.delete');
|
||||||
btnDelete.textContent = '\u2715';
|
btnDelete.textContent = '\u2715';
|
||||||
|
|
||||||
actions.append(btnBlur, btnRename, btnDelete);
|
actions.append(btnBlur, btnRename, btnDelete);
|
||||||
@@ -123,14 +123,14 @@ function createBoardEl(board) {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
board.blurred = !board.blurred;
|
board.blurred = !board.blurred;
|
||||||
div.classList.toggle('blurred', board.blurred);
|
div.classList.toggle('blurred', board.blurred);
|
||||||
btnBlur.title = board.blurred ? 'Unblur' : 'Blur (privat)';
|
btnBlur.title = board.blurred ? t('boards.unblur') : t('boards.blur');
|
||||||
await saveBoards();
|
await saveBoards();
|
||||||
});
|
});
|
||||||
|
|
||||||
blurOverlay.addEventListener('click', async () => {
|
blurOverlay.addEventListener('click', async () => {
|
||||||
board.blurred = false;
|
board.blurred = false;
|
||||||
div.classList.remove('blurred');
|
div.classList.remove('blurred');
|
||||||
btnBlur.title = 'Blur (privat)';
|
btnBlur.title = t('boards.blur');
|
||||||
await saveBoards();
|
await saveBoards();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,8 +147,8 @@ function createBoardEl(board) {
|
|||||||
btnDelete.addEventListener('click', async e => {
|
btnDelete.addEventListener('click', async e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const ok = await HellionDialog.confirm(
|
const ok = await HellionDialog.confirm(
|
||||||
`Board "${board.title}" wirklich löschen?`,
|
t('boards.delete_confirm', { title: board.title }),
|
||||||
{ type: 'danger', title: 'Board löschen', confirmText: 'Löschen' }
|
{ type: 'danger', title: t('boards.delete_confirm.title'), confirmText: t('boards.delete') }
|
||||||
);
|
);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
boards = boards.filter(b => b.id !== board.id);
|
boards = boards.filter(b => b.id !== board.id);
|
||||||
@@ -180,16 +180,16 @@ function createBoardEl(board) {
|
|||||||
let hiddenEls = [];
|
let hiddenEls = [];
|
||||||
const showMoreBtn = document.createElement('button');
|
const showMoreBtn = document.createElement('button');
|
||||||
showMoreBtn.className = 'show-more-btn';
|
showMoreBtn.className = 'show-more-btn';
|
||||||
showMoreBtn.textContent = `Show ${hidden.length} more…`;
|
showMoreBtn.textContent = t('boards.show_more', { count: hidden.length });
|
||||||
showMoreBtn.addEventListener('click', () => {
|
showMoreBtn.addEventListener('click', () => {
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
hidden.forEach(bm => { const el = createBmEl(bm); hiddenEls.push(el); list.appendChild(el); });
|
hidden.forEach(bm => { const el = createBmEl(bm); hiddenEls.push(el); list.appendChild(el); });
|
||||||
showMoreBtn.textContent = 'Show less';
|
showMoreBtn.textContent = t('boards.show_less');
|
||||||
expanded = true;
|
expanded = true;
|
||||||
} else {
|
} else {
|
||||||
hiddenEls.forEach(el => el.remove());
|
hiddenEls.forEach(el => el.remove());
|
||||||
hiddenEls = [];
|
hiddenEls = [];
|
||||||
showMoreBtn.textContent = `Show ${hidden.length} more…`;
|
showMoreBtn.textContent = t('boards.show_more', { count: hidden.length });
|
||||||
expanded = false;
|
expanded = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -200,7 +200,7 @@ function createBoardEl(board) {
|
|||||||
const addBtn = document.createElement('button');
|
const addBtn = document.createElement('button');
|
||||||
addBtn.className = 'add-bm-btn';
|
addBtn.className = 'add-bm-btn';
|
||||||
addBtn.appendChild(createPlusSvg());
|
addBtn.appendChild(createPlusSvg());
|
||||||
addBtn.append(' Add link');
|
addBtn.append(t('boards.add_link'));
|
||||||
addBtn.addEventListener('click', () => openAddBookmarkModal(board.id));
|
addBtn.addEventListener('click', () => openAddBookmarkModal(board.id));
|
||||||
div.appendChild(addBtn);
|
div.appendChild(addBtn);
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ function createBmEl(bm) {
|
|||||||
|
|
||||||
const deleteBtn = document.createElement('button');
|
const deleteBtn = document.createElement('button');
|
||||||
deleteBtn.className = 'bm-delete';
|
deleteBtn.className = 'bm-delete';
|
||||||
deleteBtn.title = 'Entfernen';
|
deleteBtn.title = t('boards.remove_bookmark');
|
||||||
deleteBtn.textContent = '✕';
|
deleteBtn.textContent = '✕';
|
||||||
|
|
||||||
li.appendChild(favicon);
|
li.appendChild(favicon);
|
||||||
|
|||||||
+19
-19
@@ -42,8 +42,8 @@ const BrowserBookmarkImport = {
|
|||||||
tree = await api.getTree();
|
tree = await api.getTree();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Zugriff auf Browser-Lesezeichen nicht möglich. Stelle sicher, dass die Extension die nötigen Berechtigungen hat.',
|
t('bm_import.no_access'),
|
||||||
{ type: 'warning', title: 'Lesezeichen-Import' }
|
{ type: 'warning', title: t('bm_import.title') }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,8 @@ const BrowserBookmarkImport = {
|
|||||||
const folders = this._extractFolders(tree[0]);
|
const folders = this._extractFolders(tree[0]);
|
||||||
if (folders.length === 0) {
|
if (folders.length === 0) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Keine Lesezeichen-Ordner gefunden.',
|
t('bm_import.no_folders'),
|
||||||
{ type: 'warning', title: 'Lesezeichen-Import' }
|
{ type: 'warning', title: t('bm_import.title') }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ const BrowserBookmarkImport = {
|
|||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
id: child.id,
|
id: child.id,
|
||||||
title: child.title || 'Unbenannt',
|
title: child.title || t('bm_import.unnamed'),
|
||||||
depth: depth,
|
depth: depth,
|
||||||
bookmarkCount: bookmarkCount,
|
bookmarkCount: bookmarkCount,
|
||||||
subfolderCount: subfolderCount,
|
subfolderCount: subfolderCount,
|
||||||
@@ -114,7 +114,7 @@ const BrowserBookmarkImport = {
|
|||||||
header.className = 'bm-import-header';
|
header.className = 'bm-import-header';
|
||||||
|
|
||||||
const title = document.createElement('span');
|
const title = document.createElement('span');
|
||||||
title.textContent = 'Browser-Lesezeichen importieren';
|
title.textContent = t('bm_import.modal_title');
|
||||||
header.appendChild(title);
|
header.appendChild(title);
|
||||||
|
|
||||||
const closeBtn = document.createElement('button');
|
const closeBtn = document.createElement('button');
|
||||||
@@ -128,7 +128,7 @@ const BrowserBookmarkImport = {
|
|||||||
// Info
|
// Info
|
||||||
const info = document.createElement('div');
|
const info = document.createElement('div');
|
||||||
info.className = 'bm-import-info';
|
info.className = 'bm-import-info';
|
||||||
info.textContent = 'Wähle die Ordner aus, die als Boards importiert werden sollen. Jeder Ordner wird ein eigenes Board.';
|
info.textContent = t('bm_import.info');
|
||||||
modal.appendChild(info);
|
modal.appendChild(info);
|
||||||
|
|
||||||
// Ordner-Liste
|
// Ordner-Liste
|
||||||
@@ -155,13 +155,13 @@ const BrowserBookmarkImport = {
|
|||||||
meta.className = 'bm-import-folder-meta';
|
meta.className = 'bm-import-folder-meta';
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (folder.bookmarkCount > 0) {
|
if (folder.bookmarkCount > 0) {
|
||||||
parts.push(folder.bookmarkCount + ' Link' + (folder.bookmarkCount !== 1 ? 's' : ''));
|
parts.push(t('bm_import.link_count', { count: folder.bookmarkCount }));
|
||||||
}
|
}
|
||||||
if (folder.subfolderCount > 0) {
|
if (folder.subfolderCount > 0) {
|
||||||
parts.push(folder.subfolderCount + ' Ordner');
|
parts.push(t('bm_import.folder_count', { count: folder.subfolderCount }));
|
||||||
}
|
}
|
||||||
if (parts.length === 0) {
|
if (parts.length === 0) {
|
||||||
parts.push('leer');
|
parts.push(t('bm_import.empty'));
|
||||||
}
|
}
|
||||||
meta.textContent = parts.join(', ');
|
meta.textContent = parts.join(', ');
|
||||||
row.appendChild(meta);
|
row.appendChild(meta);
|
||||||
@@ -177,18 +177,18 @@ const BrowserBookmarkImport = {
|
|||||||
|
|
||||||
const selectAll = document.createElement('button');
|
const selectAll = document.createElement('button');
|
||||||
selectAll.className = 'btn-secondary';
|
selectAll.className = 'btn-secondary';
|
||||||
selectAll.textContent = 'Alle auswählen';
|
selectAll.textContent = t('bm_import.select_all');
|
||||||
selectAll.addEventListener('click', () => {
|
selectAll.addEventListener('click', () => {
|
||||||
const boxes = list.querySelectorAll('.bm-import-checkbox');
|
const boxes = list.querySelectorAll('.bm-import-checkbox');
|
||||||
const allChecked = Array.from(boxes).every(function(cb) { return cb.checked; });
|
const allChecked = Array.from(boxes).every(function(cb) { return cb.checked; });
|
||||||
boxes.forEach(function(cb) { cb.checked = !allChecked; });
|
boxes.forEach(function(cb) { cb.checked = !allChecked; });
|
||||||
selectAll.textContent = allChecked ? 'Alle auswählen' : 'Alle abwählen';
|
selectAll.textContent = allChecked ? t('bm_import.select_all') : t('bm_import.deselect_all');
|
||||||
});
|
});
|
||||||
footer.appendChild(selectAll);
|
footer.appendChild(selectAll);
|
||||||
|
|
||||||
const importBtn = document.createElement('button');
|
const importBtn = document.createElement('button');
|
||||||
importBtn.className = 'btn-primary';
|
importBtn.className = 'btn-primary';
|
||||||
importBtn.textContent = 'Importieren';
|
importBtn.textContent = t('bm_import.import_btn');
|
||||||
importBtn.addEventListener('click', () => this._importSelected(folders));
|
importBtn.addEventListener('click', () => this._importSelected(folders));
|
||||||
footer.appendChild(importBtn);
|
footer.appendChild(importBtn);
|
||||||
|
|
||||||
@@ -216,8 +216,8 @@ const BrowserBookmarkImport = {
|
|||||||
const checkboxes = document.querySelectorAll('.bm-import-checkbox:checked');
|
const checkboxes = document.querySelectorAll('.bm-import-checkbox:checked');
|
||||||
if (checkboxes.length === 0) {
|
if (checkboxes.length === 0) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Bitte wähle mindestens einen Ordner aus.',
|
t('bm_import.no_selection'),
|
||||||
{ type: 'warning', title: 'Lesezeichen-Import' }
|
{ type: 'warning', title: t('bm_import.title') }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -289,15 +289,15 @@ const BrowserBookmarkImport = {
|
|||||||
|
|
||||||
// Ergebnis-Dialog
|
// Ergebnis-Dialog
|
||||||
const lines = [];
|
const lines = [];
|
||||||
lines.push(boardsCreated + ' Board' + (boardsCreated !== 1 ? 's' : '') + ' erstellt');
|
lines.push(t('bm_import.boards_created', { count: boardsCreated }));
|
||||||
lines.push(totalImported + ' Lesezeichen importiert');
|
lines.push(t('bm_import.bookmarks_imported', { count: totalImported }));
|
||||||
if (totalSkipped > 0) {
|
if (totalSkipped > 0) {
|
||||||
lines.push(totalSkipped + ' Duplikat' + (totalSkipped !== 1 ? 'e' : '') + ' übersprungen');
|
lines.push(t('bm_import.duplicates_skipped', { count: totalSkipped }));
|
||||||
}
|
}
|
||||||
|
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
lines.join('\n'),
|
lines.join('\n'),
|
||||||
{ type: 'success', title: 'Import abgeschlossen' }
|
{ type: 'success', title: t('bm_import.success_title') }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const Calculator = {
|
|||||||
|
|
||||||
const widgetId = WidgetManager.create('calculator', {
|
const widgetId = WidgetManager.create('calculator', {
|
||||||
id: this.WIDGET_ID,
|
id: this.WIDGET_ID,
|
||||||
title: 'Taschenrechner',
|
title: t('calculator.title'),
|
||||||
x: saved.x || 400,
|
x: saved.x || 400,
|
||||||
y: saved.y || 120,
|
y: saved.y || 120,
|
||||||
width: saved.width || 280,
|
width: saved.width || 280,
|
||||||
@@ -214,7 +214,7 @@ const Calculator = {
|
|||||||
|
|
||||||
const title = document.createElement('div');
|
const title = document.createElement('div');
|
||||||
title.className = 'calc-history-title';
|
title.className = 'calc-history-title';
|
||||||
title.textContent = 'History';
|
title.textContent = t('calculator.history');
|
||||||
container.appendChild(title);
|
container.appendChild(title);
|
||||||
|
|
||||||
this._renderHistoryItems(container);
|
this._renderHistoryItems(container);
|
||||||
@@ -345,7 +345,7 @@ const Calculator = {
|
|||||||
|
|
||||||
const result = this._evaluate(this._currentExpr);
|
const result = this._evaluate(this._currentExpr);
|
||||||
if (result === null) {
|
if (result === null) {
|
||||||
this._lastResult = 'Fehler';
|
this._lastResult = t('calculator.error');
|
||||||
this._updateDisplay();
|
this._updateDisplay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -13,7 +13,7 @@ function initDataButtons() {
|
|||||||
btnExport.addEventListener('click', async () => {
|
btnExport.addEventListener('click', async () => {
|
||||||
const widgetData = await Store.get('widgetStates');
|
const widgetData = await Store.get('widgetStates');
|
||||||
const data = {
|
const data = {
|
||||||
version: '1.11.1',
|
version: '2.0.0',
|
||||||
exported: new Date().toISOString(),
|
exported: new Date().toISOString(),
|
||||||
boards,
|
boards,
|
||||||
settings,
|
settings,
|
||||||
@@ -37,7 +37,7 @@ function initDataButtons() {
|
|||||||
if (!file) return;
|
if (!file) return;
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(await file.text());
|
const data = JSON.parse(await file.text());
|
||||||
if (!Array.isArray(data.boards)) throw new Error('Ungültiges Format');
|
if (!Array.isArray(data.boards)) throw new Error(t('data.invalid_format'));
|
||||||
const validBoards = data.boards.filter(b => {
|
const validBoards = data.boards.filter(b => {
|
||||||
if (!b || typeof b.title !== 'string' || !Array.isArray(b.bookmarks)) return false;
|
if (!b || typeof b.title !== 'string' || !Array.isArray(b.bookmarks)) return false;
|
||||||
b.id = b.id || uid();
|
b.id = b.id || uid();
|
||||||
@@ -50,10 +50,10 @@ function initDataButtons() {
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
if (validBoards.length === 0) throw new Error('Keine gültigen Boards gefunden');
|
if (validBoards.length === 0) throw new Error(t('data.no_boards'));
|
||||||
const ok = await HellionDialog.confirm(
|
const ok = await HellionDialog.confirm(
|
||||||
`${validBoards.length} Boards importieren? Bestehende Daten bleiben erhalten.`,
|
t('data.import_confirm', { count: validBoards.length }),
|
||||||
{ type: 'info', title: 'JSON Import' }
|
{ type: 'info', title: t('data.import_confirm.title') }
|
||||||
);
|
);
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
boards = [...boards, ...validBoards];
|
boards = [...boards, ...validBoards];
|
||||||
@@ -112,15 +112,15 @@ function initDataButtons() {
|
|||||||
// Gemeinsam speichern
|
// Gemeinsam speichern
|
||||||
await Store.set('widgetStates', existingWidgets);
|
await Store.set('widgetStates', existingWidgets);
|
||||||
|
|
||||||
const noteMsg = notesImported > 0 ? ` + ${notesImported} Note(s)` : '';
|
const noteMsg = notesImported > 0 ? t('data.notes_suffix', { count: notesImported }) : '';
|
||||||
const calcMsg = calcImported ? ' + Calculator-History' : '';
|
const calcMsg = calcImported ? t('data.calc_suffix') : '';
|
||||||
const timerMsg = timerImported ? ' + Timer-Presets' : '';
|
const timerMsg = timerImported ? t('data.timer_suffix') : '';
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
`${validBoards.length} Board(s)${noteMsg}${calcMsg}${timerMsg} erfolgreich importiert.`,
|
t('data.import_success', { boards: validBoards.length, notes: noteMsg, calc: calcMsg, timer: timerMsg }),
|
||||||
{ type: 'success', title: 'Import erfolgreich' }
|
{ type: 'success', title: t('data.import_success.title') }
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await HellionDialog.alert('Fehler beim Import: ' + err.message, { type: 'danger', title: 'Import fehlgeschlagen' });
|
await HellionDialog.alert(t('data.import_error', { error: err.message }), { type: 'danger', title: t('data.import_error.title') });
|
||||||
}
|
}
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
});
|
});
|
||||||
|
|||||||
+5
-5
@@ -126,8 +126,8 @@ const HellionDialog = {
|
|||||||
const opts = options || {};
|
const opts = options || {};
|
||||||
return this._show({
|
return this._show({
|
||||||
message,
|
message,
|
||||||
title: opts.title || 'Hinweis',
|
title: opts.title || t('dialog.default_title'),
|
||||||
confirmText: opts.confirmText || 'OK',
|
confirmText: opts.confirmText || t('dialog.ok'),
|
||||||
cancelText: '',
|
cancelText: '',
|
||||||
type: opts.type || 'info',
|
type: opts.type || 'info',
|
||||||
isConfirm: false
|
isConfirm: false
|
||||||
@@ -144,9 +144,9 @@ const HellionDialog = {
|
|||||||
const opts = options || {};
|
const opts = options || {};
|
||||||
return this._show({
|
return this._show({
|
||||||
message,
|
message,
|
||||||
title: opts.title || 'Bestätigung',
|
title: opts.title || t('dialog.confirm_title'),
|
||||||
confirmText: opts.confirmText || 'OK',
|
confirmText: opts.confirmText || t('dialog.ok'),
|
||||||
cancelText: opts.cancelText || 'Abbrechen',
|
cancelText: opts.cancelText || t('dialog.cancel'),
|
||||||
type: opts.type || 'info',
|
type: opts.type || 'info',
|
||||||
isConfirm: true
|
isConfirm: true
|
||||||
});
|
});
|
||||||
|
|||||||
+666
@@ -0,0 +1,666 @@
|
|||||||
|
/* =============================================
|
||||||
|
HELLION NEWTAB — i18n.js
|
||||||
|
Internationalisierung: DE/EN Sprachumschaltung
|
||||||
|
============================================= */
|
||||||
|
|
||||||
|
const STRINGS = {
|
||||||
|
de: {
|
||||||
|
// Dialog-System
|
||||||
|
'dialog.default_title': 'Hinweis',
|
||||||
|
'dialog.ok': 'OK',
|
||||||
|
'dialog.confirm_title': 'Bestätigung',
|
||||||
|
'dialog.cancel': 'Abbrechen',
|
||||||
|
|
||||||
|
// Boards
|
||||||
|
'boards.empty_state_pre': 'Noch keine Boards. Klicke auf ',
|
||||||
|
'boards.add_board': '+ Board',
|
||||||
|
'boards.empty_state_mid': ' um eins zu erstellen, oder nutze ',
|
||||||
|
'boards.import': 'Import',
|
||||||
|
'boards.empty_state_post': ' um deine Browser-Lesezeichen zu laden.',
|
||||||
|
'boards.drag_title': 'Board verschieben',
|
||||||
|
'boards.blur': 'Blur (privat)',
|
||||||
|
'boards.unblur': 'Unblur',
|
||||||
|
'boards.rename': 'Umbenennen',
|
||||||
|
'boards.delete': 'Löschen',
|
||||||
|
'boards.delete_confirm': 'Board „{title}" wirklich löschen?',
|
||||||
|
'boards.delete_confirm.title': 'Board löschen',
|
||||||
|
'boards.show_more': '{count} weitere anzeigen…',
|
||||||
|
'boards.show_less': 'Weniger anzeigen',
|
||||||
|
'boards.add_link': ' Link hinzufügen',
|
||||||
|
'boards.remove_bookmark': 'Entfernen',
|
||||||
|
|
||||||
|
// Onboarding
|
||||||
|
'onboarding.skip': 'Überspringen',
|
||||||
|
'onboarding.back': 'Zurück',
|
||||||
|
'onboarding.next': 'Weiter',
|
||||||
|
'onboarding.start': 'Los geht\'s!',
|
||||||
|
'onboarding.yes': 'Ja, gerne',
|
||||||
|
'onboarding.no': 'Nein danke',
|
||||||
|
'onboarding.s1.title': 'Willkommen bei Hellion Dashboard',
|
||||||
|
'onboarding.s1.text': 'Dein neuer Browser-Startbildschirm. Minimalistisch, schnell und vollständig lokal — keine Cloud, kein Account, keine Datensammlung.',
|
||||||
|
'onboarding.s2.title': 'Boards & Bookmarks',
|
||||||
|
'onboarding.s2.f1': 'Erstelle Boards mit dem „+ Board" Button oben',
|
||||||
|
'onboarding.s2.f2': 'Importiere Browser-Lesezeichen über den „Import" Button im Header',
|
||||||
|
'onboarding.s2.f3': 'Drag & Drop zum Umsortieren von Boards und Links',
|
||||||
|
'onboarding.s2.f4': 'Blur-Modus für private Boards (🔒 Icon)',
|
||||||
|
'onboarding.s3.title': '11 handgefertigte Themes',
|
||||||
|
'onboarding.s3.text': 'Klicke auf den „Theme" Button im Header um dein Theme zu wählen. Jedes hat seinen eigenen Stil und Farbpalette.',
|
||||||
|
'onboarding.s4.title': 'Widget-Toolbar',
|
||||||
|
'onboarding.s4.f1': 'Die schwebenden Buttons rechts öffnen Widgets',
|
||||||
|
'onboarding.s4.f2': 'Notes und Checklisten für schnelle Notizen',
|
||||||
|
'onboarding.s4.f3': 'Taschenrechner mit History',
|
||||||
|
'onboarding.s4.f4': 'Timer/Countdown mit speicherbaren Presets',
|
||||||
|
'onboarding.s4.f5': 'Bild-Referenz Widgets (aktivierbar in Settings)',
|
||||||
|
'onboarding.s4.f6': 'Notebook-Sidebar zeigt alle Notes auf einen Blick',
|
||||||
|
'onboarding.s5.title': 'Backups nicht vergessen!',
|
||||||
|
'onboarding.s5.text': 'Deine Daten sind lokal im Browser gespeichert. Wenn du Browserdaten löschst, gehen sie verloren! Sichere regelmäßig über Settings → Data → Export. Wir erinnern dich alle 7 Tage daran.',
|
||||||
|
'onboarding.s6.title': 'Gaming Starter Board',
|
||||||
|
'onboarding.s6.text': 'Spielst du Games wie Satisfactory, Factorio oder Star Citizen? Ich kann ein Board mit nützlichen Community-Links anlegen.',
|
||||||
|
'onboarding.s7.title': 'Bereit!',
|
||||||
|
'onboarding.s7.text': 'Erstelle dein erstes Board mit „+ Board" oder importiere deine Browser-Lesezeichen über den Import-Button im Header. Viel Spaß!',
|
||||||
|
|
||||||
|
// Notes
|
||||||
|
'notes.limit_message': 'Maximale Anzahl erreicht! Du kannst maximal {max} Notes gleichzeitig haben. Lösche eine bestehende Note um eine neue zu erstellen.',
|
||||||
|
'notes.limit_title': 'Limit erreicht',
|
||||||
|
'notes.checklist_title': 'Checkliste',
|
||||||
|
'notes.default_title': 'Note',
|
||||||
|
'notes.placeholder': 'Notiz schreiben...',
|
||||||
|
'notes.checklist_placeholder': 'Neues Item...',
|
||||||
|
'notes.delete_confirm': 'Note endgültig löschen? Das kann nicht rückgängig gemacht werden.',
|
||||||
|
'notes.delete_title': 'Note löschen',
|
||||||
|
'notes.delete_button': 'Löschen',
|
||||||
|
'notes.checklist_progress': '{done}/{total} erledigt',
|
||||||
|
'notes.empty_preview': 'Leer',
|
||||||
|
'notes.export': 'Export',
|
||||||
|
'notes.export_footer': 'Exportiert aus Hellion Dashboard',
|
||||||
|
'notes.create': '+ Note erstellen',
|
||||||
|
'notes.text_type': '✎ Freitext',
|
||||||
|
'notes.checklist_type': '☑ Checkliste',
|
||||||
|
|
||||||
|
// Calculator
|
||||||
|
'calculator.title': 'Taschenrechner',
|
||||||
|
'calculator.history': 'History',
|
||||||
|
'calculator.error': 'Fehler',
|
||||||
|
|
||||||
|
// Timer
|
||||||
|
'timer.title': 'Timer',
|
||||||
|
'timer.start': 'Start',
|
||||||
|
'timer.pause': 'Pause',
|
||||||
|
'timer.reset': 'Reset',
|
||||||
|
'timer.restart': 'Neustart',
|
||||||
|
'timer.presets': 'Presets',
|
||||||
|
'timer.save_preset': 'Preset speichern',
|
||||||
|
'timer.preset_name_placeholder': 'Name...',
|
||||||
|
'timer.ok': 'OK',
|
||||||
|
'timer.limit_title': 'Limit erreicht',
|
||||||
|
'timer.limit_message': 'Maximale Anzahl erreicht! Du kannst maximal {max} Presets speichern.',
|
||||||
|
'timer.no_time_title': 'Keine Zeit',
|
||||||
|
'timer.no_time_message': 'Gib zuerst eine Zeit ein, bevor du ein Preset speicherst.',
|
||||||
|
'timer.mute': 'Ton ausschalten',
|
||||||
|
'timer.unmute': 'Ton einschalten',
|
||||||
|
'timer.finished_title': '[!] Timer abgelaufen',
|
||||||
|
'timer.default_page_title': 'Hellion Dashboard',
|
||||||
|
|
||||||
|
// Bild-Referenz
|
||||||
|
'imageref.title': 'Bild-Referenz',
|
||||||
|
'imageref.dropzone': 'Klicken oder Bild hierher ziehen',
|
||||||
|
'imageref.replace': 'Bild ersetzen',
|
||||||
|
'imageref.label_placeholder': 'Beschriftung (optional)',
|
||||||
|
'imageref.storage_error': 'Bild konnte nicht gespeichert werden. Der Browser-Speicher ist voll.',
|
||||||
|
'imageref.storage_error.title': 'Speicherfehler',
|
||||||
|
'imageref.limit': 'Maximal {max} Bild-Widgets gleichzeitig. Schliesse eines um ein neues zu oeffnen.',
|
||||||
|
'imageref.limit.title': 'Limit erreicht',
|
||||||
|
'imageref.load_error': 'Bild konnte nicht geladen werden: {error}',
|
||||||
|
'imageref.load_error.title': 'Bildfehler',
|
||||||
|
'imageref.invalid_file': 'Bitte eine Bilddatei verwenden (PNG, JPG, WebP, etc.).',
|
||||||
|
'imageref.invalid_file.title': 'Kein Bild',
|
||||||
|
|
||||||
|
// Widget-Manager
|
||||||
|
'widget.minimize': 'Minimieren',
|
||||||
|
'widget.close': 'Schließen',
|
||||||
|
|
||||||
|
// Daten (Export/Import)
|
||||||
|
'data.invalid_format': 'Ungültiges Format',
|
||||||
|
'data.no_boards': 'Keine gültigen Boards gefunden',
|
||||||
|
'data.import_confirm': '{count} Boards importieren? Bestehende Daten bleiben erhalten.',
|
||||||
|
'data.import_confirm.title': 'JSON Import',
|
||||||
|
'data.import_success': '{boards} Board(s){notes}{calc}{timer} erfolgreich importiert.',
|
||||||
|
'data.import_success.title': 'Import erfolgreich',
|
||||||
|
'data.import_error': 'Fehler beim Import: {error}',
|
||||||
|
'data.import_error.title': 'Import fehlgeschlagen',
|
||||||
|
'data.notes_suffix': ' + {count} Note(s)',
|
||||||
|
'data.calc_suffix': ' + Calculator-History',
|
||||||
|
'data.timer_suffix': ' + Timer-Presets',
|
||||||
|
|
||||||
|
// Browser-Lesezeichen Import
|
||||||
|
'bm_import.no_access': 'Zugriff auf Browser-Lesezeichen nicht möglich. Stelle sicher, dass die Extension die nötigen Berechtigungen hat.',
|
||||||
|
'bm_import.title': 'Lesezeichen-Import',
|
||||||
|
'bm_import.no_folders': 'Keine Lesezeichen-Ordner gefunden.',
|
||||||
|
'bm_import.modal_title': 'Browser-Lesezeichen importieren',
|
||||||
|
'bm_import.info': 'Wähle die Ordner aus, die als Boards importiert werden sollen. Jeder Ordner wird ein eigenes Board.',
|
||||||
|
'bm_import.unnamed': 'Unbenannt',
|
||||||
|
'bm_import.link_count': '{count} Link(s)',
|
||||||
|
'bm_import.folder_count': '{count} Ordner',
|
||||||
|
'bm_import.empty': 'leer',
|
||||||
|
'bm_import.select_all': 'Alle auswählen',
|
||||||
|
'bm_import.deselect_all': 'Alle abwählen',
|
||||||
|
'bm_import.import_btn': 'Importieren',
|
||||||
|
'bm_import.no_selection': 'Bitte wähle mindestens einen Ordner aus.',
|
||||||
|
'bm_import.boards_created': '{count} Board(s) erstellt',
|
||||||
|
'bm_import.bookmarks_imported': '{count} Lesezeichen importiert',
|
||||||
|
'bm_import.duplicates_skipped': '{count} Duplikat(e) übersprungen',
|
||||||
|
'bm_import.success_title': 'Import abgeschlossen',
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
'storage.quota_full': 'Speicher voll! Bitte lösche alte Boards oder das Hintergrundbild, um Platz zu schaffen.',
|
||||||
|
'storage.quota_full.title': 'Speicher voll',
|
||||||
|
|
||||||
|
// App
|
||||||
|
'app.backup_reminder': 'Du hast seit über einer Woche kein Backup gemacht. Beim Löschen der Browserdaten gehen deine Boards verloren. Jetzt sichern?',
|
||||||
|
'app.backup_reminder.title': 'Backup-Erinnerung',
|
||||||
|
'app.backup_now': 'Jetzt sichern',
|
||||||
|
'app.backup_later': 'Später',
|
||||||
|
'app.no_bookmarks': 'Keine Bookmarks in dieser Datei gefunden.',
|
||||||
|
'app.import_title': 'Import',
|
||||||
|
'app.html_import_success': '{count} Board(s) mit {total} Bookmarks importiert.',
|
||||||
|
'app.import_success_title': 'Import erfolgreich',
|
||||||
|
'app.invalid_url': 'Ungültige URL. Bitte mit https:// beginnen.',
|
||||||
|
'app.invalid_url.title': 'URL ungültig',
|
||||||
|
|
||||||
|
// Uhr
|
||||||
|
'clock.days.sun': 'So',
|
||||||
|
'clock.days.mon': 'Mo',
|
||||||
|
'clock.days.tue': 'Di',
|
||||||
|
'clock.days.wed': 'Mi',
|
||||||
|
'clock.days.thu': 'Do',
|
||||||
|
'clock.days.fri': 'Fr',
|
||||||
|
'clock.days.sat': 'Sa',
|
||||||
|
'clock.months.jan': 'Jan',
|
||||||
|
'clock.months.feb': 'Feb',
|
||||||
|
'clock.months.mar': 'Mär',
|
||||||
|
'clock.months.apr': 'Apr',
|
||||||
|
'clock.months.may': 'Mai',
|
||||||
|
'clock.months.jun': 'Jun',
|
||||||
|
'clock.months.jul': 'Jul',
|
||||||
|
'clock.months.aug': 'Aug',
|
||||||
|
'clock.months.sep': 'Sep',
|
||||||
|
'clock.months.oct': 'Okt',
|
||||||
|
'clock.months.nov': 'Nov',
|
||||||
|
'clock.months.dec': 'Dez',
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
'settings.file_read_error': 'Fehler beim Lesen der Datei. Bitte eine andere Datei wählen.',
|
||||||
|
'settings.file_read_error.title': 'Dateifehler',
|
||||||
|
'settings.reset_confirm': 'Wirklich alle Boards und Einstellungen löschen? Das kann nicht rückgängig gemacht werden.',
|
||||||
|
'settings.reset_confirm.title': 'Alles zurücksetzen',
|
||||||
|
'settings.reset_confirm.button': 'Alles löschen',
|
||||||
|
|
||||||
|
// Header
|
||||||
|
'header.import': 'Import',
|
||||||
|
'header.board': 'Board',
|
||||||
|
'header.note': 'Note',
|
||||||
|
'header.theme': 'Darstellung',
|
||||||
|
'header.settings': 'Einstellungen',
|
||||||
|
|
||||||
|
// Settings-Panel Überschrift
|
||||||
|
'settings.title': 'Einstellungen',
|
||||||
|
|
||||||
|
// Settings-Panel Sektionen
|
||||||
|
'settings.section.widgets': 'WIDGETS',
|
||||||
|
'settings.section.data': 'DATEN & HILFE',
|
||||||
|
'settings.section.danger': 'DANGER ZONE',
|
||||||
|
'settings.section.bg': 'HINTERGRUND',
|
||||||
|
'settings.section.display': 'DARSTELLUNG',
|
||||||
|
|
||||||
|
// Settings-Zeilen
|
||||||
|
'settings.language': 'Sprache',
|
||||||
|
'settings.language.desc': 'Anzeigesprache wählen',
|
||||||
|
'settings.language.auto': 'Automatisch',
|
||||||
|
'settings.toolbar_pos': 'Toolbar-Position',
|
||||||
|
'settings.toolbar_pos.desc': 'Widget-Toolbar links oder rechts anzeigen',
|
||||||
|
'settings.toolbar_pos.right': 'Rechts',
|
||||||
|
'settings.toolbar_pos.left': 'Links',
|
||||||
|
'settings.image_ref': 'Bild-Referenz Widgets',
|
||||||
|
'settings.image_ref.desc': 'Bilder als Referenz anzeigen (nur aktuelle Session)',
|
||||||
|
'settings.export': 'Backup exportieren',
|
||||||
|
'settings.export.desc': 'Alle Boards, Notes und Einstellungen als JSON sichern',
|
||||||
|
'settings.export.btn': 'Export',
|
||||||
|
'settings.import': 'Backup importieren',
|
||||||
|
'settings.import.desc': 'JSON-Backup wiederherstellen',
|
||||||
|
'settings.browser_import': 'Browser-Lesezeichen',
|
||||||
|
'settings.browser_import.desc': 'Lesezeichen direkt aus dem Browser importieren',
|
||||||
|
'settings.onboarding': 'Onboarding wiederholen',
|
||||||
|
'settings.onboarding.desc': 'Willkommens-Tour erneut anzeigen',
|
||||||
|
'settings.reset': 'Alles zurücksetzen',
|
||||||
|
'settings.reset.desc': 'Löscht alle Boards, Notes und Einstellungen',
|
||||||
|
'settings.compact': 'Kompaktmodus',
|
||||||
|
'settings.compact.desc': 'Weniger Abstand für mehr Bookmarks',
|
||||||
|
'settings.shorten': 'Lange Titel kürzen',
|
||||||
|
'settings.shorten.desc': 'Titel auf eine Zeile mit „…" kürzen',
|
||||||
|
'settings.search': 'Suchleiste anzeigen',
|
||||||
|
'settings.search.desc': 'Suchleiste unter dem Header ein/aus',
|
||||||
|
'settings.newtab': 'Links in neuem Tab',
|
||||||
|
'settings.newtab.desc': 'Bookmarks in neuem Browser-Tab öffnen',
|
||||||
|
'settings.showdesc': 'Beschreibungen anzeigen',
|
||||||
|
'settings.showdesc.desc': 'Gespeicherte Beschreibung unter Bookmarks',
|
||||||
|
'settings.hideextra': 'Bookmarks ausblenden',
|
||||||
|
'settings.hideextra.desc': 'Überzählige Bookmarks in langen Boards verstecken',
|
||||||
|
'settings.visible_count': 'Sichtbare Bookmarks',
|
||||||
|
'settings.visible_count.desc': 'Anzahl vor dem Ausblenden',
|
||||||
|
'settings.bg_url': 'Bild-URL',
|
||||||
|
'settings.bg_url.desc': 'Eigenes Hintergrundbild per URL',
|
||||||
|
'settings.bg_change': 'Ändern',
|
||||||
|
'settings.bg_apply': 'Übernehmen',
|
||||||
|
'settings.bg_upload': 'Datei hochladen',
|
||||||
|
'settings.bg_upload.desc': 'Lokales Bild als Hintergrund verwenden',
|
||||||
|
'settings.search_engine_toggle': 'Suchmaschine wechseln',
|
||||||
|
|
||||||
|
// Modals
|
||||||
|
'modal.new_board': 'Neues Board',
|
||||||
|
'modal.board_name': 'Board-Name...',
|
||||||
|
'modal.create': 'Erstellen',
|
||||||
|
'modal.new_bookmark': 'Neues Lesezeichen',
|
||||||
|
'modal.bm_title': 'Titel...',
|
||||||
|
'modal.bm_desc': 'Beschreibung (optional)',
|
||||||
|
'modal.bm_add': 'Hinzufügen',
|
||||||
|
'modal.rename': 'Umbenennen',
|
||||||
|
'modal.rename_placeholder': 'Neuer Name...',
|
||||||
|
'modal.rename_confirm': 'Umbenennen',
|
||||||
|
'modal.theme_header': 'Darstellung',
|
||||||
|
|
||||||
|
// About
|
||||||
|
'about.title': '⬡ HELLION NEWTAB',
|
||||||
|
'about.impressum': 'Impressum',
|
||||||
|
'about.developer': 'Entwickler',
|
||||||
|
'about.company': 'Unternehmen',
|
||||||
|
'about.license': 'Lizenz',
|
||||||
|
'about.storage': 'Datenspeicherung',
|
||||||
|
'about.storage.value': '100% lokal · Kein Server · Kein Account',
|
||||||
|
'about.bugreport': 'Bug Report / Feedback',
|
||||||
|
'about.support': 'Support',
|
||||||
|
'about.browsers': 'Kompatible Browser',
|
||||||
|
|
||||||
|
// Notebook
|
||||||
|
'notebook.title': 'Notebook',
|
||||||
|
|
||||||
|
// Suche
|
||||||
|
'search.placeholder': 'Im Web suchen…',
|
||||||
|
|
||||||
|
// Widget-Toolbar Tooltips
|
||||||
|
'toolbar.note': 'Note erstellen',
|
||||||
|
'toolbar.checklist': 'Checkliste erstellen',
|
||||||
|
'toolbar.calculator': 'Taschenrechner',
|
||||||
|
'toolbar.timer': 'Timer',
|
||||||
|
'toolbar.imageref': 'Bild-Referenz',
|
||||||
|
'toolbar.notebook': 'Alle Notes'
|
||||||
|
},
|
||||||
|
|
||||||
|
en: {
|
||||||
|
// Dialog system
|
||||||
|
'dialog.default_title': 'Notice',
|
||||||
|
'dialog.ok': 'OK',
|
||||||
|
'dialog.confirm_title': 'Confirmation',
|
||||||
|
'dialog.cancel': 'Cancel',
|
||||||
|
|
||||||
|
// Boards
|
||||||
|
'boards.empty_state_pre': 'No boards yet. Click ',
|
||||||
|
'boards.add_board': '+ Board',
|
||||||
|
'boards.empty_state_mid': ' to create one, or use ',
|
||||||
|
'boards.import': 'Import',
|
||||||
|
'boards.empty_state_post': ' to load your browser bookmarks.',
|
||||||
|
'boards.drag_title': 'Move board',
|
||||||
|
'boards.blur': 'Blur (private)',
|
||||||
|
'boards.unblur': 'Unblur',
|
||||||
|
'boards.rename': 'Rename',
|
||||||
|
'boards.delete': 'Delete',
|
||||||
|
'boards.delete_confirm': 'Really delete board "{title}"?',
|
||||||
|
'boards.delete_confirm.title': 'Delete board',
|
||||||
|
'boards.show_more': 'Show {count} more…',
|
||||||
|
'boards.show_less': 'Show less',
|
||||||
|
'boards.add_link': ' Add link',
|
||||||
|
'boards.remove_bookmark': 'Remove',
|
||||||
|
|
||||||
|
// Onboarding
|
||||||
|
'onboarding.skip': 'Skip',
|
||||||
|
'onboarding.back': 'Back',
|
||||||
|
'onboarding.next': 'Next',
|
||||||
|
'onboarding.start': 'Let\'s go!',
|
||||||
|
'onboarding.yes': 'Yes please',
|
||||||
|
'onboarding.no': 'No thanks',
|
||||||
|
'onboarding.s1.title': 'Welcome to Hellion Dashboard',
|
||||||
|
'onboarding.s1.text': 'Your new browser start screen. Minimalist, fast and fully local — no cloud, no account, no data collection.',
|
||||||
|
'onboarding.s2.title': 'Boards & Bookmarks',
|
||||||
|
'onboarding.s2.f1': 'Create boards with the "+ Board" button at the top',
|
||||||
|
'onboarding.s2.f2': 'Import browser bookmarks via the "Import" button in the header',
|
||||||
|
'onboarding.s2.f3': 'Drag & drop to reorder boards and links',
|
||||||
|
'onboarding.s2.f4': 'Blur mode for private boards (🔒 icon)',
|
||||||
|
'onboarding.s3.title': '11 handcrafted themes',
|
||||||
|
'onboarding.s3.text': 'Click the "Theme" button in the header to choose your theme. Each has its own style and color palette.',
|
||||||
|
'onboarding.s4.title': 'Widget Toolbar',
|
||||||
|
'onboarding.s4.f1': 'The floating buttons on the right open widgets',
|
||||||
|
'onboarding.s4.f2': 'Notes and checklists for quick notes',
|
||||||
|
'onboarding.s4.f3': 'Calculator with history',
|
||||||
|
'onboarding.s4.f4': 'Timer/countdown with saveable presets',
|
||||||
|
'onboarding.s4.f5': 'Image reference widgets (enable in Settings)',
|
||||||
|
'onboarding.s4.f6': 'Notebook sidebar shows all notes at a glance',
|
||||||
|
'onboarding.s5.title': 'Don\'t forget backups!',
|
||||||
|
'onboarding.s5.text': 'Your data is stored locally in the browser. If you clear browser data, it\'s gone! Back up regularly via Settings → Data → Export. We\'ll remind you every 7 days.',
|
||||||
|
'onboarding.s6.title': 'Gaming Starter Board',
|
||||||
|
'onboarding.s6.text': 'Do you play games like Satisfactory, Factorio or Star Citizen? I can create a board with useful community links.',
|
||||||
|
'onboarding.s7.title': 'Ready!',
|
||||||
|
'onboarding.s7.text': 'Create your first board with "+ Board" or import your browser bookmarks via the Import button in the header. Have fun!',
|
||||||
|
|
||||||
|
// Notes
|
||||||
|
'notes.limit_message': 'Maximum reached! You can have at most {max} notes at the same time. Delete an existing note to create a new one.',
|
||||||
|
'notes.limit_title': 'Limit reached',
|
||||||
|
'notes.checklist_title': 'Checklist',
|
||||||
|
'notes.default_title': 'Note',
|
||||||
|
'notes.placeholder': 'Write a note...',
|
||||||
|
'notes.checklist_placeholder': 'New item...',
|
||||||
|
'notes.delete_confirm': 'Permanently delete note? This cannot be undone.',
|
||||||
|
'notes.delete_title': 'Delete note',
|
||||||
|
'notes.delete_button': 'Delete',
|
||||||
|
'notes.checklist_progress': '{done}/{total} done',
|
||||||
|
'notes.empty_preview': 'Empty',
|
||||||
|
'notes.export': 'Export',
|
||||||
|
'notes.export_footer': 'Exported from Hellion Dashboard',
|
||||||
|
'notes.create': '+ Create note',
|
||||||
|
'notes.text_type': '✎ Free text',
|
||||||
|
'notes.checklist_type': '☑ Checklist',
|
||||||
|
|
||||||
|
// Calculator
|
||||||
|
'calculator.title': 'Calculator',
|
||||||
|
'calculator.history': 'History',
|
||||||
|
'calculator.error': 'Error',
|
||||||
|
|
||||||
|
// Timer
|
||||||
|
'timer.title': 'Timer',
|
||||||
|
'timer.start': 'Start',
|
||||||
|
'timer.pause': 'Pause',
|
||||||
|
'timer.reset': 'Reset',
|
||||||
|
'timer.restart': 'Restart',
|
||||||
|
'timer.presets': 'Presets',
|
||||||
|
'timer.save_preset': 'Save preset',
|
||||||
|
'timer.preset_name_placeholder': 'Name...',
|
||||||
|
'timer.ok': 'OK',
|
||||||
|
'timer.limit_title': 'Limit reached',
|
||||||
|
'timer.limit_message': 'Maximum reached! You can save at most {max} presets.',
|
||||||
|
'timer.no_time_title': 'No time',
|
||||||
|
'timer.no_time_message': 'Enter a time before saving a preset.',
|
||||||
|
'timer.mute': 'Mute sound',
|
||||||
|
'timer.unmute': 'Unmute sound',
|
||||||
|
'timer.finished_title': '[!] Timer finished',
|
||||||
|
'timer.default_page_title': 'Hellion Dashboard',
|
||||||
|
|
||||||
|
// Image reference
|
||||||
|
'imageref.title': 'Image Reference',
|
||||||
|
'imageref.dropzone': 'Click or drag image here',
|
||||||
|
'imageref.replace': 'Replace image',
|
||||||
|
'imageref.label_placeholder': 'Caption (optional)',
|
||||||
|
'imageref.storage_error': 'Image could not be saved. Browser storage is full.',
|
||||||
|
'imageref.storage_error.title': 'Storage error',
|
||||||
|
'imageref.limit': 'Maximum {max} image widgets at a time. Close one to open a new one.',
|
||||||
|
'imageref.limit.title': 'Limit reached',
|
||||||
|
'imageref.load_error': 'Image could not be loaded: {error}',
|
||||||
|
'imageref.load_error.title': 'Image error',
|
||||||
|
'imageref.invalid_file': 'Please use an image file (PNG, JPG, WebP, etc.).',
|
||||||
|
'imageref.invalid_file.title': 'Not an image',
|
||||||
|
|
||||||
|
// Widget manager
|
||||||
|
'widget.minimize': 'Minimize',
|
||||||
|
'widget.close': 'Close',
|
||||||
|
|
||||||
|
// Data (export/import)
|
||||||
|
'data.invalid_format': 'Invalid format',
|
||||||
|
'data.no_boards': 'No valid boards found',
|
||||||
|
'data.import_confirm': 'Import {count} boards? Existing data will be preserved.',
|
||||||
|
'data.import_confirm.title': 'JSON Import',
|
||||||
|
'data.import_success': '{boards} board(s){notes}{calc}{timer} successfully imported.',
|
||||||
|
'data.import_success.title': 'Import successful',
|
||||||
|
'data.import_error': 'Import error: {error}',
|
||||||
|
'data.import_error.title': 'Import failed',
|
||||||
|
'data.notes_suffix': ' + {count} note(s)',
|
||||||
|
'data.calc_suffix': ' + Calculator history',
|
||||||
|
'data.timer_suffix': ' + Timer presets',
|
||||||
|
|
||||||
|
// Browser bookmark import
|
||||||
|
'bm_import.no_access': 'Cannot access browser bookmarks. Make sure the extension has the required permissions.',
|
||||||
|
'bm_import.title': 'Bookmark import',
|
||||||
|
'bm_import.no_folders': 'No bookmark folders found.',
|
||||||
|
'bm_import.modal_title': 'Import browser bookmarks',
|
||||||
|
'bm_import.info': 'Select the folders to import as boards. Each folder becomes its own board.',
|
||||||
|
'bm_import.unnamed': 'Unnamed',
|
||||||
|
'bm_import.link_count': '{count} link(s)',
|
||||||
|
'bm_import.folder_count': '{count} folder(s)',
|
||||||
|
'bm_import.empty': 'empty',
|
||||||
|
'bm_import.select_all': 'Select all',
|
||||||
|
'bm_import.deselect_all': 'Deselect all',
|
||||||
|
'bm_import.import_btn': 'Import',
|
||||||
|
'bm_import.no_selection': 'Please select at least one folder.',
|
||||||
|
'bm_import.boards_created': '{count} board(s) created',
|
||||||
|
'bm_import.bookmarks_imported': '{count} bookmarks imported',
|
||||||
|
'bm_import.duplicates_skipped': '{count} duplicate(s) skipped',
|
||||||
|
'bm_import.success_title': 'Import complete',
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
'storage.quota_full': 'Storage full! Please delete old boards or the background image to free up space.',
|
||||||
|
'storage.quota_full.title': 'Storage full',
|
||||||
|
|
||||||
|
// App
|
||||||
|
'app.backup_reminder': 'You haven\'t made a backup in over a week. If you clear browser data, your boards will be lost. Back up now?',
|
||||||
|
'app.backup_reminder.title': 'Backup reminder',
|
||||||
|
'app.backup_now': 'Back up now',
|
||||||
|
'app.backup_later': 'Later',
|
||||||
|
'app.no_bookmarks': 'No bookmarks found in this file.',
|
||||||
|
'app.import_title': 'Import',
|
||||||
|
'app.html_import_success': '{count} board(s) with {total} bookmarks imported.',
|
||||||
|
'app.import_success_title': 'Import successful',
|
||||||
|
'app.invalid_url': 'Invalid URL. Please start with https://.',
|
||||||
|
'app.invalid_url.title': 'Invalid URL',
|
||||||
|
|
||||||
|
// Clock
|
||||||
|
'clock.days.sun': 'Sun',
|
||||||
|
'clock.days.mon': 'Mon',
|
||||||
|
'clock.days.tue': 'Tue',
|
||||||
|
'clock.days.wed': 'Wed',
|
||||||
|
'clock.days.thu': 'Thu',
|
||||||
|
'clock.days.fri': 'Fri',
|
||||||
|
'clock.days.sat': 'Sat',
|
||||||
|
'clock.months.jan': 'Jan',
|
||||||
|
'clock.months.feb': 'Feb',
|
||||||
|
'clock.months.mar': 'Mar',
|
||||||
|
'clock.months.apr': 'Apr',
|
||||||
|
'clock.months.may': 'May',
|
||||||
|
'clock.months.jun': 'Jun',
|
||||||
|
'clock.months.jul': 'Jul',
|
||||||
|
'clock.months.aug': 'Aug',
|
||||||
|
'clock.months.sep': 'Sep',
|
||||||
|
'clock.months.oct': 'Oct',
|
||||||
|
'clock.months.nov': 'Nov',
|
||||||
|
'clock.months.dec': 'Dec',
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
'settings.file_read_error': 'Error reading file. Please choose a different file.',
|
||||||
|
'settings.file_read_error.title': 'File error',
|
||||||
|
'settings.reset_confirm': 'Really delete all boards and settings? This cannot be undone.',
|
||||||
|
'settings.reset_confirm.title': 'Reset everything',
|
||||||
|
'settings.reset_confirm.button': 'Delete all',
|
||||||
|
|
||||||
|
// Header
|
||||||
|
'header.import': 'Import',
|
||||||
|
'header.board': 'Board',
|
||||||
|
'header.note': 'Note',
|
||||||
|
'header.theme': 'Theme',
|
||||||
|
'header.settings': 'Settings',
|
||||||
|
|
||||||
|
// Settings panel heading
|
||||||
|
'settings.title': 'Settings',
|
||||||
|
|
||||||
|
// Settings panel sections
|
||||||
|
'settings.section.widgets': 'WIDGETS',
|
||||||
|
'settings.section.data': 'DATA & HELP',
|
||||||
|
'settings.section.danger': 'DANGER ZONE',
|
||||||
|
'settings.section.bg': 'BACKGROUND',
|
||||||
|
'settings.section.display': 'DISPLAY',
|
||||||
|
|
||||||
|
// Settings rows
|
||||||
|
'settings.language': 'Language',
|
||||||
|
'settings.language.desc': 'Choose display language',
|
||||||
|
'settings.language.auto': 'Automatic',
|
||||||
|
'settings.toolbar_pos': 'Toolbar position',
|
||||||
|
'settings.toolbar_pos.desc': 'Show widget toolbar on the left or right',
|
||||||
|
'settings.toolbar_pos.right': 'Right',
|
||||||
|
'settings.toolbar_pos.left': 'Left',
|
||||||
|
'settings.image_ref': 'Image reference widgets',
|
||||||
|
'settings.image_ref.desc': 'Show images as reference (current session only)',
|
||||||
|
'settings.export': 'Export backup',
|
||||||
|
'settings.export.desc': 'Save all boards, notes and settings as JSON',
|
||||||
|
'settings.export.btn': 'Export',
|
||||||
|
'settings.import': 'Import backup',
|
||||||
|
'settings.import.desc': 'Restore a JSON backup',
|
||||||
|
'settings.browser_import': 'Browser bookmarks',
|
||||||
|
'settings.browser_import.desc': 'Import bookmarks directly from the browser',
|
||||||
|
'settings.onboarding': 'Replay onboarding',
|
||||||
|
'settings.onboarding.desc': 'Show the welcome tour again',
|
||||||
|
'settings.reset': 'Reset everything',
|
||||||
|
'settings.reset.desc': 'Deletes all boards, notes and settings',
|
||||||
|
'settings.compact': 'Compact mode',
|
||||||
|
'settings.compact.desc': 'Less spacing for more bookmarks',
|
||||||
|
'settings.shorten': 'Shorten long titles',
|
||||||
|
'settings.shorten.desc': 'Truncate titles to one line with "…"',
|
||||||
|
'settings.search': 'Show search bar',
|
||||||
|
'settings.search.desc': 'Toggle search bar below the header',
|
||||||
|
'settings.newtab': 'Links in new tab',
|
||||||
|
'settings.newtab.desc': 'Open bookmarks in a new browser tab',
|
||||||
|
'settings.showdesc': 'Show descriptions',
|
||||||
|
'settings.showdesc.desc': 'Show saved description below bookmarks',
|
||||||
|
'settings.hideextra': 'Hide bookmarks',
|
||||||
|
'settings.hideextra.desc': 'Hide excess bookmarks in long boards',
|
||||||
|
'settings.visible_count': 'Visible bookmarks',
|
||||||
|
'settings.visible_count.desc': 'Number before hiding',
|
||||||
|
'settings.bg_url': 'Image URL',
|
||||||
|
'settings.bg_url.desc': 'Custom background image via URL',
|
||||||
|
'settings.bg_change': 'Change',
|
||||||
|
'settings.bg_apply': 'Apply',
|
||||||
|
'settings.bg_upload': 'Upload file',
|
||||||
|
'settings.bg_upload.desc': 'Use a local image as background',
|
||||||
|
'settings.search_engine_toggle': 'Switch search engine',
|
||||||
|
|
||||||
|
// Modals
|
||||||
|
'modal.new_board': 'New Board',
|
||||||
|
'modal.board_name': 'Board name...',
|
||||||
|
'modal.create': 'Create',
|
||||||
|
'modal.new_bookmark': 'New Bookmark',
|
||||||
|
'modal.bm_title': 'Title...',
|
||||||
|
'modal.bm_desc': 'Description (optional)',
|
||||||
|
'modal.bm_add': 'Add',
|
||||||
|
'modal.rename': 'Rename',
|
||||||
|
'modal.rename_placeholder': 'New name...',
|
||||||
|
'modal.rename_confirm': 'Rename',
|
||||||
|
'modal.theme_header': 'Theme',
|
||||||
|
|
||||||
|
// About
|
||||||
|
'about.title': '⬡ HELLION NEWTAB',
|
||||||
|
'about.impressum': 'Legal Notice',
|
||||||
|
'about.developer': 'Developer',
|
||||||
|
'about.company': 'Company',
|
||||||
|
'about.license': 'License',
|
||||||
|
'about.storage': 'Data storage',
|
||||||
|
'about.storage.value': '100% local · No server · No account',
|
||||||
|
'about.bugreport': 'Bug Report / Feedback',
|
||||||
|
'about.support': 'Support',
|
||||||
|
'about.browsers': 'Compatible browsers',
|
||||||
|
|
||||||
|
// Notebook
|
||||||
|
'notebook.title': 'Notebook',
|
||||||
|
|
||||||
|
// Search
|
||||||
|
'search.placeholder': 'Search the web…',
|
||||||
|
|
||||||
|
// Widget toolbar tooltips
|
||||||
|
'toolbar.note': 'Create note',
|
||||||
|
'toolbar.checklist': 'Create checklist',
|
||||||
|
'toolbar.calculator': 'Calculator',
|
||||||
|
'toolbar.timer': 'Timer',
|
||||||
|
'toolbar.imageref': 'Image reference',
|
||||||
|
'toolbar.notebook': 'All notes'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {string} Aktuell aktive Sprache */
|
||||||
|
let currentLang = 'de';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Übersetzungsstring abrufen mit optionalen Platzhaltern
|
||||||
|
* @param {string} key - Schlüssel im STRINGS-Objekt
|
||||||
|
* @param {Object} [vars] - Platzhalter-Werte (z.B. { max: 5 })
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function t(key, vars) {
|
||||||
|
let str = (STRINGS[currentLang] && STRINGS[currentLang][key])
|
||||||
|
|| (STRINGS['en'] && STRINGS['en'][key])
|
||||||
|
|| key;
|
||||||
|
if (vars) {
|
||||||
|
for (const [k, v] of Object.entries(vars)) {
|
||||||
|
str = str.replaceAll('{' + k + '}', v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle data-i18n Elemente im Dokument mit aktueller Sprache befüllen
|
||||||
|
*/
|
||||||
|
function applyLanguage() {
|
||||||
|
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||||
|
el.textContent = t(el.dataset.i18n);
|
||||||
|
});
|
||||||
|
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
|
||||||
|
el.placeholder = t(el.dataset.i18nPlaceholder);
|
||||||
|
});
|
||||||
|
document.querySelectorAll('[data-i18n-title]').forEach(el => {
|
||||||
|
el.title = t(el.dataset.i18nTitle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 'auto' auflösen zu konkretem Sprachcode
|
||||||
|
* @param {string} lang - 'de', 'en' oder 'auto'
|
||||||
|
* @returns {string} 'de' oder 'en'
|
||||||
|
*/
|
||||||
|
function resolveLang(lang) {
|
||||||
|
return (lang === 'auto')
|
||||||
|
? (navigator.language.startsWith('de') ? 'de' : 'en')
|
||||||
|
: lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sprache setzen, speichern und DOM aktualisieren
|
||||||
|
* @param {string} lang - 'de', 'en' oder 'auto'
|
||||||
|
*/
|
||||||
|
function setLanguage(lang) {
|
||||||
|
currentLang = resolveLang(lang);
|
||||||
|
document.documentElement.lang = currentLang;
|
||||||
|
applyLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* i18n-Modul — öffentliche API
|
||||||
|
*/
|
||||||
|
const I18n = {
|
||||||
|
/** Aktuell aktive Sprache (nach Auto-Auflösung) */
|
||||||
|
get currentLang() { return currentLang; },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialisierung: Sprache aus Settings lesen, auflösen, DOM anwenden
|
||||||
|
* Muss NACH dem Laden des settings-Objekts aufgerufen werden
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
const lang = (typeof settings !== 'undefined' && settings.language)
|
||||||
|
? settings.language
|
||||||
|
: 'auto';
|
||||||
|
currentLang = resolveLang(lang);
|
||||||
|
document.documentElement.lang = currentLang;
|
||||||
|
applyLanguage();
|
||||||
|
}
|
||||||
|
};
|
||||||
+22
-22
@@ -114,8 +114,8 @@ const ImageRef = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('ImageRef: sessionStorage Write fehlgeschlagen', e);
|
console.warn('ImageRef: sessionStorage Write fehlgeschlagen', e);
|
||||||
HellionDialog.alert(
|
HellionDialog.alert(
|
||||||
'Bild konnte nicht gespeichert werden. Der Browser-Speicher ist voll.',
|
t('imageref.storage_error'),
|
||||||
{ type: 'danger', title: 'Speicherfehler' }
|
{ type: 'danger', title: t('imageref.storage_error.title') }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -144,8 +144,8 @@ const ImageRef = {
|
|||||||
|
|
||||||
if (this._images.length >= this.MAX_IMAGES) {
|
if (this._images.length >= this.MAX_IMAGES) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Maximal ' + this.MAX_IMAGES + ' Bild-Widgets gleichzeitig. Schliesse eines um ein neues zu oeffnen.',
|
t('imageref.limit', { max: this.MAX_IMAGES }),
|
||||||
{ type: 'warning', title: 'Limit erreicht' }
|
{ type: 'warning', title: t('imageref.limit.title') }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -172,8 +172,8 @@ const ImageRef = {
|
|||||||
dataUrl = await this._processFile(file);
|
dataUrl = await this._processFile(file);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Bild konnte nicht geladen werden: ' + err.message,
|
t('imageref.load_error', { error: err.message }),
|
||||||
{ type: 'danger', title: 'Bildfehler' }
|
{ type: 'danger', title: t('imageref.load_error.title') }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ const ImageRef = {
|
|||||||
_createWidget(imageData, dataUrl) {
|
_createWidget(imageData, dataUrl) {
|
||||||
WidgetManager.create('image', {
|
WidgetManager.create('image', {
|
||||||
id: imageData.id,
|
id: imageData.id,
|
||||||
title: imageData.label || 'Bild-Referenz',
|
title: imageData.label || t('imageref.title'),
|
||||||
x: imageData.x,
|
x: imageData.x,
|
||||||
y: imageData.y,
|
y: imageData.y,
|
||||||
width: imageData.width,
|
width: imageData.width,
|
||||||
@@ -249,14 +249,14 @@ const ImageRef = {
|
|||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.className = 'imgref-img';
|
img.className = 'imgref-img';
|
||||||
img.src = dataUrl;
|
img.src = dataUrl;
|
||||||
img.alt = imageData.label || 'Bild-Referenz';
|
img.alt = imageData.label || t('imageref.title');
|
||||||
wrapper.appendChild(img);
|
wrapper.appendChild(img);
|
||||||
|
|
||||||
// Bild ersetzen Button
|
// Bild ersetzen Button
|
||||||
const replaceBtn = document.createElement('button');
|
const replaceBtn = document.createElement('button');
|
||||||
replaceBtn.className = 'imgref-replace-btn';
|
replaceBtn.className = 'imgref-replace-btn';
|
||||||
replaceBtn.type = 'button';
|
replaceBtn.type = 'button';
|
||||||
replaceBtn.textContent = 'Bild ersetzen';
|
replaceBtn.textContent = t('imageref.replace');
|
||||||
replaceBtn.addEventListener('click', async () => {
|
replaceBtn.addEventListener('click', async () => {
|
||||||
const file = await this._pickFile();
|
const file = await this._pickFile();
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
@@ -266,8 +266,8 @@ const ImageRef = {
|
|||||||
this.renderBody(imageData, bodyEl, newDataUrl);
|
this.renderBody(imageData, bodyEl, newDataUrl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Bild konnte nicht geladen werden: ' + err.message,
|
t('imageref.load_error', { error: err.message }),
|
||||||
{ type: 'danger', title: 'Bildfehler' }
|
{ type: 'danger', title: t('imageref.load_error.title') }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -283,7 +283,7 @@ const ImageRef = {
|
|||||||
const label = document.createElement('input');
|
const label = document.createElement('input');
|
||||||
label.className = 'imgref-label';
|
label.className = 'imgref-label';
|
||||||
label.type = 'text';
|
label.type = 'text';
|
||||||
label.placeholder = 'Beschriftung (optional)';
|
label.placeholder = t('imageref.label_placeholder');
|
||||||
label.maxLength = 100;
|
label.maxLength = 100;
|
||||||
label.value = imageData.label || '';
|
label.value = imageData.label || '';
|
||||||
|
|
||||||
@@ -295,8 +295,8 @@ const ImageRef = {
|
|||||||
const entry = WidgetManager._widgets.get(imageData.id);
|
const entry = WidgetManager._widgets.get(imageData.id);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
const titleEl = entry.el.querySelector('.widget-title-text');
|
const titleEl = entry.el.querySelector('.widget-title-text');
|
||||||
if (titleEl) titleEl.textContent = text || 'Bild-Referenz';
|
if (titleEl) titleEl.textContent = text || t('imageref.title');
|
||||||
entry.state.title = text || 'Bild-Referenz';
|
entry.state.title = text || t('imageref.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
this._debouncedSave();
|
this._debouncedSave();
|
||||||
@@ -321,7 +321,7 @@ const ImageRef = {
|
|||||||
icon.textContent = '\uD83D\uDDBC\uFE0F';
|
icon.textContent = '\uD83D\uDDBC\uFE0F';
|
||||||
|
|
||||||
const text = document.createElement('span');
|
const text = document.createElement('span');
|
||||||
text.textContent = 'Klicken oder Bild hierher ziehen';
|
text.textContent = t('imageref.dropzone');
|
||||||
|
|
||||||
dropzone.append(icon, text);
|
dropzone.append(icon, text);
|
||||||
|
|
||||||
@@ -336,8 +336,8 @@ const ImageRef = {
|
|||||||
await this.save();
|
await this.save();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Bild konnte nicht geladen werden: ' + err.message,
|
t('imageref.load_error', { error: err.message }),
|
||||||
{ type: 'danger', title: 'Bildfehler' }
|
{ type: 'danger', title: t('imageref.load_error.title') }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -363,8 +363,8 @@ const ImageRef = {
|
|||||||
const file = e.dataTransfer.files[0];
|
const file = e.dataTransfer.files[0];
|
||||||
if (!file || !file.type.startsWith('image/')) {
|
if (!file || !file.type.startsWith('image/')) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Bitte eine Bilddatei verwenden (PNG, JPG, WebP, etc.).',
|
t('imageref.invalid_file'),
|
||||||
{ type: 'warning', title: 'Kein Bild' }
|
{ type: 'warning', title: t('imageref.invalid_file.title') }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -376,8 +376,8 @@ const ImageRef = {
|
|||||||
await this.save();
|
await this.save();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Bild konnte nicht geladen werden: ' + err.message,
|
t('imageref.load_error', { error: err.message }),
|
||||||
{ type: 'danger', title: 'Bildfehler' }
|
{ type: 'danger', title: t('imageref.load_error.title') }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -433,7 +433,7 @@ const ImageRef = {
|
|||||||
|
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
URL.revokeObjectURL(objectUrl);
|
URL.revokeObjectURL(objectUrl);
|
||||||
reject(new Error('Bild konnte nicht geladen werden'));
|
reject(new Error(t('imageref.load_error', { error: 'unknown' })));
|
||||||
};
|
};
|
||||||
|
|
||||||
img.src = objectUrl;
|
img.src = objectUrl;
|
||||||
|
|||||||
+16
-16
@@ -75,15 +75,15 @@ const Notes = {
|
|||||||
async create(template) {
|
async create(template) {
|
||||||
if (this._notes.length >= this.MAX_NOTES) {
|
if (this._notes.length >= this.MAX_NOTES) {
|
||||||
await HellionDialog.alert(
|
await HellionDialog.alert(
|
||||||
'Maximale Anzahl erreicht! Du kannst maximal ' + this.MAX_NOTES + ' Notes gleichzeitig haben. Lösche eine bestehende Note um eine neue zu erstellen.',
|
t('notes.limit_message', { max: this.MAX_NOTES }),
|
||||||
{ type: 'warning', title: 'Limit erreicht' }
|
{ type: 'warning', title: t('notes.limit_title') }
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteData = {
|
const noteData = {
|
||||||
id: 'note_' + uid(),
|
id: 'note_' + uid(),
|
||||||
title: template === 'checklist' ? 'Checkliste' : 'Note',
|
title: template === 'checklist' ? t('notes.checklist_title') : t('notes.default_title'),
|
||||||
content: '',
|
content: '',
|
||||||
template: template,
|
template: template,
|
||||||
x: 120 + (this._notes.length * 30),
|
x: 120 + (this._notes.length * 30),
|
||||||
@@ -138,7 +138,7 @@ const Notes = {
|
|||||||
_renderTextBody(noteData, bodyEl) {
|
_renderTextBody(noteData, bodyEl) {
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
textarea.className = 'widget-textarea';
|
textarea.className = 'widget-textarea';
|
||||||
textarea.placeholder = 'Notiz schreiben...';
|
textarea.placeholder = t('notes.placeholder');
|
||||||
textarea.spellcheck = false;
|
textarea.spellcheck = false;
|
||||||
textarea.value = noteData.content || '';
|
textarea.value = noteData.content || '';
|
||||||
textarea.maxLength = this.MAX_CHARS;
|
textarea.maxLength = this.MAX_CHARS;
|
||||||
@@ -204,7 +204,7 @@ const Notes = {
|
|||||||
const addInput = document.createElement('input');
|
const addInput = document.createElement('input');
|
||||||
addInput.className = 'checklist-add-input';
|
addInput.className = 'checklist-add-input';
|
||||||
addInput.type = 'text';
|
addInput.type = 'text';
|
||||||
addInput.placeholder = 'Neues Item...';
|
addInput.placeholder = t('notes.checklist_placeholder');
|
||||||
addInput.maxLength = 100;
|
addInput.maxLength = 100;
|
||||||
|
|
||||||
addInput.addEventListener('keydown', async (e) => {
|
addInput.addEventListener('keydown', async (e) => {
|
||||||
@@ -276,11 +276,11 @@ const Notes = {
|
|||||||
// Auto-Titel: "X/Y erledigt" falls kein manueller Titel
|
// Auto-Titel: "X/Y erledigt" falls kein manueller Titel
|
||||||
const widgetEntry = WidgetManager._widgets.get(noteData.id);
|
const widgetEntry = WidgetManager._widgets.get(noteData.id);
|
||||||
if (widgetEntry) {
|
if (widgetEntry) {
|
||||||
const defaultTitle = done + '/' + total + ' erledigt';
|
const defaultTitle = t('notes.checklist_progress', { done: done, total: total });
|
||||||
const titleEl = widgetEntry.el.querySelector('.widget-title');
|
const titleEl = widgetEntry.el.querySelector('.widget-title');
|
||||||
if (titleEl && titleEl.contentEditable !== 'true') {
|
if (titleEl && titleEl.contentEditable !== 'true') {
|
||||||
// Nur wenn Titel noch Standard ist
|
// Nur wenn Titel noch Standard ist
|
||||||
if (noteData.title === 'Checkliste' || /^\d+\/\d+ erledigt$/.test(noteData.title)) {
|
if (noteData.title === t('notes.checklist_title') || /^\d+\/\d+\s/.test(noteData.title)) {
|
||||||
noteData.title = defaultTitle;
|
noteData.title = defaultTitle;
|
||||||
titleEl.textContent = defaultTitle;
|
titleEl.textContent = defaultTitle;
|
||||||
widgetEntry.state.title = defaultTitle;
|
widgetEntry.state.title = defaultTitle;
|
||||||
@@ -307,8 +307,8 @@ const Notes = {
|
|||||||
if (idx === -1) return;
|
if (idx === -1) return;
|
||||||
|
|
||||||
const ok = await HellionDialog.confirm(
|
const ok = await HellionDialog.confirm(
|
||||||
'Note endgültig löschen? Das kann nicht rückgängig gemacht werden.',
|
t('notes.delete_confirm'),
|
||||||
{ type: 'danger', title: 'Note löschen', confirmText: 'Löschen' }
|
{ type: 'danger', title: t('notes.delete_title'), confirmText: t('notes.delete_button') }
|
||||||
);
|
);
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
||||||
@@ -330,7 +330,7 @@ const Notes = {
|
|||||||
} else {
|
} else {
|
||||||
md += noteData.content || '';
|
md += noteData.content || '';
|
||||||
}
|
}
|
||||||
md += '\n\n---\n*Exportiert aus Hellion Dashboard*\n';
|
md += '\n\n---\n*' + t('notes.export_footer') + '*\n';
|
||||||
|
|
||||||
const blob = new Blob([md], { type: 'text/markdown' });
|
const blob = new Blob([md], { type: 'text/markdown' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
@@ -419,9 +419,9 @@ const Notes = {
|
|||||||
if (note.template === 'checklist') {
|
if (note.template === 'checklist') {
|
||||||
const total = note.checklistItems ? note.checklistItems.length : 0;
|
const total = note.checklistItems ? note.checklistItems.length : 0;
|
||||||
const done = note.checklistItems ? note.checklistItems.filter(i => i.checked).length : 0;
|
const done = note.checklistItems ? note.checklistItems.filter(i => i.checked).length : 0;
|
||||||
preview.textContent = done + '/' + total + ' erledigt';
|
preview.textContent = t('notes.checklist_progress', { done: done, total: total });
|
||||||
} else {
|
} else {
|
||||||
preview.textContent = (note.content || '').slice(0, 50) || 'Leer';
|
preview.textContent = (note.content || '').slice(0, 50) || t('notes.empty_preview');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
@@ -430,7 +430,7 @@ const Notes = {
|
|||||||
|
|
||||||
const btnExport = document.createElement('button');
|
const btnExport = document.createElement('button');
|
||||||
btnExport.className = 'notebook-slot-btn';
|
btnExport.className = 'notebook-slot-btn';
|
||||||
btnExport.textContent = 'Export';
|
btnExport.textContent = t('notes.export');
|
||||||
btnExport.addEventListener('click', (e) => {
|
btnExport.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.exportNote(note);
|
this.exportNote(note);
|
||||||
@@ -470,7 +470,7 @@ const Notes = {
|
|||||||
slot.className = 'notebook-slot-empty';
|
slot.className = 'notebook-slot-empty';
|
||||||
|
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
label.textContent = '+ Note erstellen';
|
label.textContent = t('notes.create');
|
||||||
slot.appendChild(label);
|
slot.appendChild(label);
|
||||||
|
|
||||||
// Klick zeigt Typ-Auswahl
|
// Klick zeigt Typ-Auswahl
|
||||||
@@ -485,7 +485,7 @@ const Notes = {
|
|||||||
|
|
||||||
const btnText = document.createElement('button');
|
const btnText = document.createElement('button');
|
||||||
btnText.className = 'notebook-type-btn';
|
btnText.className = 'notebook-type-btn';
|
||||||
btnText.textContent = '\u270E Freitext';
|
btnText.textContent = t('notes.text_type');
|
||||||
btnText.addEventListener('click', async (e) => {
|
btnText.addEventListener('click', async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
await this.create('text');
|
await this.create('text');
|
||||||
@@ -494,7 +494,7 @@ const Notes = {
|
|||||||
|
|
||||||
const btnCheck = document.createElement('button');
|
const btnCheck = document.createElement('button');
|
||||||
btnCheck.className = 'notebook-type-btn';
|
btnCheck.className = 'notebook-type-btn';
|
||||||
btnCheck.textContent = '\u2611 Checkliste';
|
btnCheck.textContent = t('notes.checklist_type');
|
||||||
btnCheck.addEventListener('click', async (e) => {
|
btnCheck.addEventListener('click', async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
await this.create('checklist');
|
await this.create('checklist');
|
||||||
|
|||||||
+26
-38
@@ -9,52 +9,40 @@ const Onboarding = {
|
|||||||
slides: [
|
slides: [
|
||||||
{
|
{
|
||||||
hero: '\u2B21',
|
hero: '\u2B21',
|
||||||
title: 'Willkommen bei Hellion Dashboard',
|
titleKey: 'onboarding.s1.title',
|
||||||
text: 'Dein neuer Browser-Startbildschirm. Minimalistisch, schnell und vollst\u00E4ndig lokal \u2014 keine Cloud, kein Account, keine Datensammlung.'
|
textKey: 'onboarding.s1.text'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hero: '\uD83D\uDCCB',
|
hero: '\uD83D\uDCCB',
|
||||||
title: 'Boards & Bookmarks',
|
titleKey: 'onboarding.s2.title',
|
||||||
features: [
|
featureKeys: ['onboarding.s2.f1', 'onboarding.s2.f2', 'onboarding.s2.f3', 'onboarding.s2.f4']
|
||||||
'Erstelle Boards mit dem \u201E+ Board\u201C Button oben',
|
|
||||||
'Importiere Browser-Lesezeichen \u00FCber den \u201EImport\u201C Button im Header',
|
|
||||||
'Drag & Drop zum Umsortieren von Boards und Links',
|
|
||||||
'Blur-Modus f\u00FCr private Boards (\uD83D\uDD12 Icon)'
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hero: '\uD83C\uDFA8',
|
hero: '\uD83C\uDFA8',
|
||||||
title: '11 handgefertigte Themes',
|
titleKey: 'onboarding.s3.title',
|
||||||
text: 'Klicke auf den \u201ETheme\u201C Button im Header um dein Theme zu w\u00E4hlen. Jedes hat seinen eigenen Stil und Farbpalette.',
|
textKey: 'onboarding.s3.text',
|
||||||
showThemes: true
|
showThemes: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hero: '\uD83E\uDDF0',
|
hero: '\uD83E\uDDF0',
|
||||||
title: 'Widget-Toolbar',
|
titleKey: 'onboarding.s4.title',
|
||||||
features: [
|
featureKeys: ['onboarding.s4.f1', 'onboarding.s4.f2', 'onboarding.s4.f3', 'onboarding.s4.f4', 'onboarding.s4.f5', 'onboarding.s4.f6']
|
||||||
'Die schwebenden Buttons rechts \u00F6ffnen Widgets',
|
|
||||||
'Notes und Checklisten f\u00FCr schnelle Notizen',
|
|
||||||
'Taschenrechner mit History',
|
|
||||||
'Timer/Countdown mit speicherbaren Presets',
|
|
||||||
'Bild-Referenz Widgets (aktivierbar in Settings)',
|
|
||||||
'Notebook-Sidebar zeigt alle Notes auf einen Blick'
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hero: '\uD83D\uDEE1\uFE0F',
|
hero: '\uD83D\uDEE1\uFE0F',
|
||||||
title: 'Backups nicht vergessen!',
|
titleKey: 'onboarding.s5.title',
|
||||||
text: 'Deine Daten sind lokal im Browser gespeichert. Wenn du Browserdaten l\u00F6schst, gehen sie verloren! Sichere regelm\u00E4\u00DFig \u00FCber Settings \u2192 Data \u2192 Export. Wir erinnern dich alle 7 Tage daran.'
|
textKey: 'onboarding.s5.text'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hero: '\uD83C\uDFAE',
|
hero: '\uD83C\uDFAE',
|
||||||
title: 'Gaming Starter Board',
|
titleKey: 'onboarding.s6.title',
|
||||||
text: 'Spielst du Games wie Satisfactory, Factorio oder Star Citizen? Ich kann ein Board mit n\u00FCtzlichen Community-Links anlegen.',
|
textKey: 'onboarding.s6.text',
|
||||||
interactive: 'gaming-board'
|
interactive: 'gaming-board'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hero: '\uD83D\uDE80',
|
hero: '\uD83D\uDE80',
|
||||||
title: 'Bereit!',
|
titleKey: 'onboarding.s7.title',
|
||||||
text: 'Erstelle dein erstes Board mit \u201E+ Board\u201C oder importiere deine Browser-Lesezeichen \u00FCber den Import-Button im Header. Viel Spa\u00DF!'
|
textKey: 'onboarding.s7.text'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -87,7 +75,7 @@ const Onboarding = {
|
|||||||
if (!isLast) {
|
if (!isLast) {
|
||||||
const skip = document.createElement('button');
|
const skip = document.createElement('button');
|
||||||
skip.className = 'onboarding-skip';
|
skip.className = 'onboarding-skip';
|
||||||
skip.textContent = '\u00DCberspringen';
|
skip.textContent = t('onboarding.skip');
|
||||||
skip.addEventListener('click', () => this._finish());
|
skip.addEventListener('click', () => this._finish());
|
||||||
modal.appendChild(skip);
|
modal.appendChild(skip);
|
||||||
}
|
}
|
||||||
@@ -103,22 +91,22 @@ const Onboarding = {
|
|||||||
|
|
||||||
const title = document.createElement('div');
|
const title = document.createElement('div');
|
||||||
title.className = 'onboarding-title';
|
title.className = 'onboarding-title';
|
||||||
title.textContent = slide.title;
|
title.textContent = t(slide.titleKey);
|
||||||
slideEl.appendChild(title);
|
slideEl.appendChild(title);
|
||||||
|
|
||||||
if (slide.text) {
|
if (slide.textKey) {
|
||||||
const text = document.createElement('div');
|
const text = document.createElement('div');
|
||||||
text.className = 'onboarding-text';
|
text.className = 'onboarding-text';
|
||||||
text.textContent = slide.text;
|
text.textContent = t(slide.textKey);
|
||||||
slideEl.appendChild(text);
|
slideEl.appendChild(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slide.features) {
|
if (slide.featureKeys) {
|
||||||
const list = document.createElement('ul');
|
const list = document.createElement('ul');
|
||||||
list.className = 'onboarding-feature-list';
|
list.className = 'onboarding-feature-list';
|
||||||
slide.features.forEach(f => {
|
slide.featureKeys.forEach(key => {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.textContent = f;
|
li.textContent = t(key);
|
||||||
list.appendChild(li);
|
list.appendChild(li);
|
||||||
});
|
});
|
||||||
slideEl.appendChild(list);
|
slideEl.appendChild(list);
|
||||||
@@ -160,7 +148,7 @@ const Onboarding = {
|
|||||||
if (this.currentSlide > 0) {
|
if (this.currentSlide > 0) {
|
||||||
const backBtn = document.createElement('button');
|
const backBtn = document.createElement('button');
|
||||||
backBtn.className = 'btn-secondary';
|
backBtn.className = 'btn-secondary';
|
||||||
backBtn.textContent = 'Zur\u00FCck';
|
backBtn.textContent = t('onboarding.back');
|
||||||
backBtn.addEventListener('click', () => {
|
backBtn.addEventListener('click', () => {
|
||||||
this.currentSlide--;
|
this.currentSlide--;
|
||||||
this._render();
|
this._render();
|
||||||
@@ -172,7 +160,7 @@ const Onboarding = {
|
|||||||
// Interaktive Slide: Zwei Buttons statt "Weiter"
|
// Interaktive Slide: Zwei Buttons statt "Weiter"
|
||||||
const noBtn = document.createElement('button');
|
const noBtn = document.createElement('button');
|
||||||
noBtn.className = 'btn-secondary';
|
noBtn.className = 'btn-secondary';
|
||||||
noBtn.textContent = 'Nein danke';
|
noBtn.textContent = t('onboarding.no');
|
||||||
noBtn.addEventListener('click', () => {
|
noBtn.addEventListener('click', () => {
|
||||||
this.currentSlide++;
|
this.currentSlide++;
|
||||||
this._render();
|
this._render();
|
||||||
@@ -180,7 +168,7 @@ const Onboarding = {
|
|||||||
|
|
||||||
const yesBtn = document.createElement('button');
|
const yesBtn = document.createElement('button');
|
||||||
yesBtn.className = 'btn-primary';
|
yesBtn.className = 'btn-primary';
|
||||||
yesBtn.textContent = 'Ja, gerne';
|
yesBtn.textContent = t('onboarding.yes');
|
||||||
yesBtn.addEventListener('click', async () => {
|
yesBtn.addEventListener('click', async () => {
|
||||||
await this._createGamingBoard();
|
await this._createGamingBoard();
|
||||||
this.currentSlide++;
|
this.currentSlide++;
|
||||||
@@ -191,13 +179,13 @@ const Onboarding = {
|
|||||||
} else if (isLast) {
|
} else if (isLast) {
|
||||||
const startBtn = document.createElement('button');
|
const startBtn = document.createElement('button');
|
||||||
startBtn.className = 'btn-primary';
|
startBtn.className = 'btn-primary';
|
||||||
startBtn.textContent = 'Los geht\u2019s!';
|
startBtn.textContent = t('onboarding.start');
|
||||||
startBtn.addEventListener('click', () => this._finish());
|
startBtn.addEventListener('click', () => this._finish());
|
||||||
nav.appendChild(startBtn);
|
nav.appendChild(startBtn);
|
||||||
} else {
|
} else {
|
||||||
const nextBtn = document.createElement('button');
|
const nextBtn = document.createElement('button');
|
||||||
nextBtn.className = 'btn-primary';
|
nextBtn.className = 'btn-primary';
|
||||||
nextBtn.textContent = 'Weiter';
|
nextBtn.textContent = t('onboarding.next');
|
||||||
nextBtn.addEventListener('click', () => {
|
nextBtn.addEventListener('click', () => {
|
||||||
this.currentSlide++;
|
this.currentSlide++;
|
||||||
this._render();
|
this._render();
|
||||||
|
|||||||
+20
-4
@@ -83,6 +83,10 @@ function applySettings() {
|
|||||||
const toolbarPosEl = document.getElementById('settingToolbarPos');
|
const toolbarPosEl = document.getElementById('settingToolbarPos');
|
||||||
if (toolbarPosEl) toolbarPosEl.value = settings.toolbarPos || 'right';
|
if (toolbarPosEl) toolbarPosEl.value = settings.toolbarPos || 'right';
|
||||||
|
|
||||||
|
// Sprache (Dropdown-Wert setzen — I18n.init() übernimmt die eigentliche Anwendung)
|
||||||
|
const langEl = document.getElementById('settingLanguage');
|
||||||
|
if (langEl) langEl.value = settings.language || 'auto';
|
||||||
|
|
||||||
applyTheme(settings.theme || 'nebula', !!settings.bgUrl);
|
applyTheme(settings.theme || 'nebula', !!settings.bgUrl);
|
||||||
|
|
||||||
if (settings.bgUrl) {
|
if (settings.bgUrl) {
|
||||||
@@ -184,11 +188,22 @@ function bindSettingsEvents() {
|
|||||||
await saveSettings();
|
await saveSettings();
|
||||||
};
|
};
|
||||||
reader.onerror = () => {
|
reader.onerror = () => {
|
||||||
HellionDialog.alert('Fehler beim Lesen der Datei. Bitte eine andere Datei wählen.', { type: 'danger', title: 'Dateifehler' });
|
HellionDialog.alert(t('settings.file_read_error'), { type: 'danger', title: t('settings.file_read_error.title') });
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sprach-Einstellung
|
||||||
|
const languageEl = document.getElementById('settingLanguage');
|
||||||
|
if (languageEl) {
|
||||||
|
languageEl.value = settings.language || 'auto';
|
||||||
|
languageEl.addEventListener('change', async (e) => {
|
||||||
|
settings.language = e.target.value;
|
||||||
|
setLanguage(e.target.value);
|
||||||
|
await saveSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Toolbar-Position Setting
|
// Toolbar-Position Setting
|
||||||
const toolbarPosEl = document.getElementById('settingToolbarPos');
|
const toolbarPosEl = document.getElementById('settingToolbarPos');
|
||||||
if (toolbarPosEl) {
|
if (toolbarPosEl) {
|
||||||
@@ -209,17 +224,18 @@ function bindSettingsEvents() {
|
|||||||
// Reset All
|
// Reset All
|
||||||
document.getElementById('btnResetAll').addEventListener('click', async () => {
|
document.getElementById('btnResetAll').addEventListener('click', async () => {
|
||||||
const ok = await HellionDialog.confirm(
|
const ok = await HellionDialog.confirm(
|
||||||
'Wirklich alle Boards und Einstellungen löschen? Das kann nicht rückgängig gemacht werden.',
|
t('settings.reset_confirm'),
|
||||||
{ type: 'danger', title: 'Alles zurücksetzen', confirmText: 'Alles löschen' }
|
{ type: 'danger', title: t('settings.reset_confirm.title'), confirmText: t('settings.reset_confirm.button') }
|
||||||
);
|
);
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
boards = [];
|
boards = [];
|
||||||
settings = { compact: false, shortenTitles: false, newTab: true, showDesc: false,
|
settings = { compact: false, shortenTitles: false, newTab: true, showDesc: false,
|
||||||
hideExtra: false, visibleCount: 10, bgUrl: '', theme: 'nebula',
|
hideExtra: false, visibleCount: 10, bgUrl: '', theme: 'nebula',
|
||||||
showSearch: true, searchEngine: 'google', toolbarPos: 'right',
|
showSearch: true, searchEngine: 'google', toolbarPos: 'right',
|
||||||
imageRefEnabled: false };
|
imageRefEnabled: false, language: 'auto' };
|
||||||
await saveBoards();
|
await saveBoards();
|
||||||
await saveSettings();
|
await saveSettings();
|
||||||
|
setLanguage('auto');
|
||||||
applySettings();
|
applySettings();
|
||||||
renderBoards();
|
renderBoards();
|
||||||
closeSettings();
|
closeSettings();
|
||||||
|
|||||||
+2
-1
@@ -17,7 +17,8 @@ let settings = {
|
|||||||
showSearch: true,
|
showSearch: true,
|
||||||
searchEngine: 'google',
|
searchEngine: 'google',
|
||||||
toolbarPos: 'right',
|
toolbarPos: 'right',
|
||||||
imageRefEnabled: false
|
imageRefEnabled: false,
|
||||||
|
language: 'auto'
|
||||||
};
|
};
|
||||||
|
|
||||||
function uid() {
|
function uid() {
|
||||||
|
|||||||
+2
-2
@@ -23,7 +23,7 @@ const Store = {
|
|||||||
chrome.storage.local.set({ [key]: value }, () => {
|
chrome.storage.local.set({ [key]: value }, () => {
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
console.error('Storage-Fehler:', chrome.runtime.lastError.message);
|
console.error('Storage-Fehler:', chrome.runtime.lastError.message);
|
||||||
HellionDialog.alert('Speicher voll! Bitte lösche alte Boards oder das Hintergrundbild, um Platz zu schaffen.', { type: 'danger', title: 'Speicher voll' });
|
HellionDialog.alert(t('storage.quota_full'), { type: 'danger', title: t('storage.quota_full.title') });
|
||||||
reject(new Error(chrome.runtime.lastError.message));
|
reject(new Error(chrome.runtime.lastError.message));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ const Store = {
|
|||||||
resolve();
|
resolve();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Storage-Fehler:', e.message);
|
console.error('Storage-Fehler:', e.message);
|
||||||
HellionDialog.alert('Speicher voll! Bitte lösche alte Boards oder das Hintergrundbild, um Platz zu schaffen.', { type: 'danger', title: 'Speicher voll' });
|
HellionDialog.alert(t('storage.quota_full'), { type: 'danger', title: t('storage.quota_full.title') });
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-17
@@ -82,7 +82,7 @@ const Timer = {
|
|||||||
|
|
||||||
WidgetManager.create('timer', {
|
WidgetManager.create('timer', {
|
||||||
id: this.WIDGET_ID,
|
id: this.WIDGET_ID,
|
||||||
title: 'Timer',
|
title: t('timer.title'),
|
||||||
x: saved.x || 600,
|
x: saved.x || 600,
|
||||||
y: saved.y || 80,
|
y: saved.y || 80,
|
||||||
width: saved.width || 260,
|
width: saved.width || 260,
|
||||||
@@ -190,7 +190,7 @@ const Timer = {
|
|||||||
const btnStart = document.createElement('button');
|
const btnStart = document.createElement('button');
|
||||||
btnStart.className = 'timer-ctrl-btn primary';
|
btnStart.className = 'timer-ctrl-btn primary';
|
||||||
btnStart.type = 'button';
|
btnStart.type = 'button';
|
||||||
btnStart.textContent = 'Start';
|
btnStart.textContent = t('timer.start');
|
||||||
btnStart.addEventListener('click', () => {
|
btnStart.addEventListener('click', () => {
|
||||||
if (!this._running && this._remaining === 0) {
|
if (!this._running && this._remaining === 0) {
|
||||||
this._applyInput();
|
this._applyInput();
|
||||||
@@ -202,7 +202,7 @@ const Timer = {
|
|||||||
const btnPause = document.createElement('button');
|
const btnPause = document.createElement('button');
|
||||||
btnPause.className = 'timer-ctrl-btn';
|
btnPause.className = 'timer-ctrl-btn';
|
||||||
btnPause.type = 'button';
|
btnPause.type = 'button';
|
||||||
btnPause.textContent = 'Pause';
|
btnPause.textContent = t('timer.pause');
|
||||||
btnPause.disabled = true;
|
btnPause.disabled = true;
|
||||||
btnPause.addEventListener('click', () => this._pause());
|
btnPause.addEventListener('click', () => this._pause());
|
||||||
this._btnPause = btnPause;
|
this._btnPause = btnPause;
|
||||||
@@ -210,7 +210,7 @@ const Timer = {
|
|||||||
const btnReset = document.createElement('button');
|
const btnReset = document.createElement('button');
|
||||||
btnReset.className = 'timer-ctrl-btn danger';
|
btnReset.className = 'timer-ctrl-btn danger';
|
||||||
btnReset.type = 'button';
|
btnReset.type = 'button';
|
||||||
btnReset.textContent = 'Reset';
|
btnReset.textContent = t('timer.reset');
|
||||||
btnReset.addEventListener('click', () => this._reset());
|
btnReset.addEventListener('click', () => this._reset());
|
||||||
this._btnReset = btnReset;
|
this._btnReset = btnReset;
|
||||||
|
|
||||||
@@ -253,13 +253,13 @@ const Timer = {
|
|||||||
|
|
||||||
const title = document.createElement('span');
|
const title = document.createElement('span');
|
||||||
title.className = 'timer-presets-title';
|
title.className = 'timer-presets-title';
|
||||||
title.textContent = 'Presets';
|
title.textContent = t('timer.presets');
|
||||||
|
|
||||||
const addBtn = document.createElement('button');
|
const addBtn = document.createElement('button');
|
||||||
addBtn.className = 'timer-preset-add';
|
addBtn.className = 'timer-preset-add';
|
||||||
addBtn.type = 'button';
|
addBtn.type = 'button';
|
||||||
addBtn.textContent = '+';
|
addBtn.textContent = '+';
|
||||||
addBtn.title = 'Preset speichern';
|
addBtn.title = t('timer.save_preset');
|
||||||
addBtn.addEventListener('click', () => this._showAddPreset(container));
|
addBtn.addEventListener('click', () => this._showAddPreset(container));
|
||||||
|
|
||||||
header.append(title, addBtn);
|
header.append(title, addBtn);
|
||||||
@@ -322,8 +322,8 @@ const Timer = {
|
|||||||
|
|
||||||
if (this._presets.length >= this.MAX_PRESETS) {
|
if (this._presets.length >= this.MAX_PRESETS) {
|
||||||
HellionDialog.alert(
|
HellionDialog.alert(
|
||||||
'Maximale Anzahl erreicht! Du kannst maximal ' + this.MAX_PRESETS + ' Presets speichern.',
|
t('timer.limit_message', { max: this.MAX_PRESETS }),
|
||||||
{ type: 'warning', title: 'Limit erreicht' }
|
{ type: 'warning', title: t('timer.limit_title') }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -334,8 +334,8 @@ const Timer = {
|
|||||||
const parsed = this._parseTimeInput(this._inputEl.value);
|
const parsed = this._parseTimeInput(this._inputEl.value);
|
||||||
if (parsed === 0) {
|
if (parsed === 0) {
|
||||||
HellionDialog.alert(
|
HellionDialog.alert(
|
||||||
'Gib zuerst eine Zeit ein, bevor du ein Preset speicherst.',
|
t('timer.no_time_message'),
|
||||||
{ type: 'info', title: 'Keine Zeit' }
|
{ type: 'info', title: t('timer.no_time_title') }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -347,13 +347,13 @@ const Timer = {
|
|||||||
const nameInput = document.createElement('input');
|
const nameInput = document.createElement('input');
|
||||||
nameInput.className = 'timer-add-input';
|
nameInput.className = 'timer-add-input';
|
||||||
nameInput.type = 'text';
|
nameInput.type = 'text';
|
||||||
nameInput.placeholder = 'Name...';
|
nameInput.placeholder = t('timer.preset_name_placeholder');
|
||||||
nameInput.maxLength = 20;
|
nameInput.maxLength = 20;
|
||||||
|
|
||||||
const confirmBtn = document.createElement('button');
|
const confirmBtn = document.createElement('button');
|
||||||
confirmBtn.className = 'timer-add-confirm';
|
confirmBtn.className = 'timer-add-confirm';
|
||||||
confirmBtn.type = 'button';
|
confirmBtn.type = 'button';
|
||||||
confirmBtn.textContent = 'OK';
|
confirmBtn.textContent = t('timer.ok');
|
||||||
|
|
||||||
const doAdd = async () => {
|
const doAdd = async () => {
|
||||||
const name = nameInput.value.trim();
|
const name = nameInput.value.trim();
|
||||||
@@ -508,9 +508,9 @@ const Timer = {
|
|||||||
_startTitleBlink() {
|
_startTitleBlink() {
|
||||||
this._originalTitle = document.title;
|
this._originalTitle = document.title;
|
||||||
this._blinkIntervalId = setInterval(() => {
|
this._blinkIntervalId = setInterval(() => {
|
||||||
document.title = document.title === '[!] Timer abgelaufen'
|
document.title = document.title === t('timer.finished_title')
|
||||||
? this._originalTitle
|
? this._originalTitle
|
||||||
: '[!] Timer abgelaufen';
|
: t('timer.finished_title');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -521,7 +521,7 @@ const Timer = {
|
|||||||
if (this._blinkIntervalId) {
|
if (this._blinkIntervalId) {
|
||||||
clearInterval(this._blinkIntervalId);
|
clearInterval(this._blinkIntervalId);
|
||||||
this._blinkIntervalId = null;
|
this._blinkIntervalId = null;
|
||||||
document.title = this._originalTitle || 'Hellion Dashboard';
|
document.title = this._originalTitle || t('timer.default_page_title');
|
||||||
}
|
}
|
||||||
this._finished = false;
|
this._finished = false;
|
||||||
this._updateDisplay();
|
this._updateDisplay();
|
||||||
@@ -534,7 +534,7 @@ const Timer = {
|
|||||||
_updateMuteBtn() {
|
_updateMuteBtn() {
|
||||||
if (!this._muteBtn) return;
|
if (!this._muteBtn) return;
|
||||||
this._muteBtn.textContent = this._muted ? '\uD83D\uDD07' : '\uD83D\uDD0A';
|
this._muteBtn.textContent = this._muted ? '\uD83D\uDD07' : '\uD83D\uDD0A';
|
||||||
this._muteBtn.title = this._muted ? 'Ton einschalten' : 'Ton ausschalten';
|
this._muteBtn.title = this._muted ? t('timer.unmute') : t('timer.mute');
|
||||||
this._muteBtn.classList.toggle('muted', this._muted);
|
this._muteBtn.classList.toggle('muted', this._muted);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -555,7 +555,7 @@ const Timer = {
|
|||||||
_updateControls() {
|
_updateControls() {
|
||||||
if (this._btnStart) {
|
if (this._btnStart) {
|
||||||
this._btnStart.disabled = this._running;
|
this._btnStart.disabled = this._running;
|
||||||
this._btnStart.textContent = this._finished ? 'Neustart' : 'Start';
|
this._btnStart.textContent = this._finished ? t('timer.restart') : t('timer.start');
|
||||||
}
|
}
|
||||||
if (this._btnPause) {
|
if (this._btnPause) {
|
||||||
this._btnPause.disabled = !this._running;
|
this._btnPause.disabled = !this._running;
|
||||||
|
|||||||
+4
-4
@@ -20,7 +20,7 @@ const WidgetManager = {
|
|||||||
const state = {
|
const state = {
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
title: config.title || 'Note',
|
title: config.title || t('notes.default_title'),
|
||||||
x: config.x || 120,
|
x: config.x || 120,
|
||||||
y: config.y || 80,
|
y: config.y || 80,
|
||||||
width: config.width || 280,
|
width: config.width || 280,
|
||||||
@@ -75,7 +75,7 @@ const WidgetManager = {
|
|||||||
title.addEventListener('blur', async () => {
|
title.addEventListener('blur', async () => {
|
||||||
title.contentEditable = 'false';
|
title.contentEditable = 'false';
|
||||||
const newTitle = title.textContent.trim().slice(0, 20);
|
const newTitle = title.textContent.trim().slice(0, 20);
|
||||||
title.textContent = newTitle || 'Note';
|
title.textContent = newTitle || t('notes.default_title');
|
||||||
const entry = this._widgets.get(state.id);
|
const entry = this._widgets.get(state.id);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
entry.state.title = title.textContent;
|
entry.state.title = title.textContent;
|
||||||
@@ -94,13 +94,13 @@ const WidgetManager = {
|
|||||||
|
|
||||||
const btnMin = document.createElement('button');
|
const btnMin = document.createElement('button');
|
||||||
btnMin.className = 'widget-btn widget-minimize';
|
btnMin.className = 'widget-btn widget-minimize';
|
||||||
btnMin.title = 'Minimieren';
|
btnMin.title = t('widget.minimize');
|
||||||
btnMin.textContent = '\u2500';
|
btnMin.textContent = '\u2500';
|
||||||
btnMin.addEventListener('click', () => this.minimize(state.id));
|
btnMin.addEventListener('click', () => this.minimize(state.id));
|
||||||
|
|
||||||
const btnClose = document.createElement('button');
|
const btnClose = document.createElement('button');
|
||||||
btnClose.className = 'widget-btn widget-close';
|
btnClose.className = 'widget-btn widget-close';
|
||||||
btnClose.title = 'Schließen';
|
btnClose.title = t('widget.close');
|
||||||
btnClose.textContent = '\u2715';
|
btnClose.textContent = '\u2715';
|
||||||
btnClose.addEventListener('click', () => this.close(state.id));
|
btnClose.addEventListener('click', () => this.close(state.id));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user