Feat v0.6.12 css isolation (#47)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #47.
This commit is contained in:
31
CHANGELOG.md
31
CHANGELOG.md
@@ -2,6 +2,37 @@
|
||||
|
||||
All notable changes to Armature are documented here.
|
||||
|
||||
## v0.6.12 — Extension CSS Isolation
|
||||
|
||||
Prefix enforcement prevents extension CSS from leaking into the kernel or
|
||||
sibling extensions. All 12 in-tree packages migrated to `.ext-{slug}-*`
|
||||
naming convention.
|
||||
|
||||
### Added
|
||||
|
||||
- **`data-ext` attribute** on extension mount container — enables scoped
|
||||
selectors like `[data-ext="chat"] .ext-chat-app`.
|
||||
- **CSS linter** (`scripts/lint-package-css.sh`) — validates that the first
|
||||
class selector in every extension CSS rule starts with `.ext-{slug}`.
|
||||
Exempts `:root`, `@keyframes`, `@font-face`, `@media`, kernel `.sw-*`
|
||||
classes, and CodeMirror `.cm-*` classes.
|
||||
- **Kernel CSS contract** (`docs/EXTENSION-CSS.md`) — documents stable
|
||||
public classes and CSS variables that extensions may reference. Everything
|
||||
else is internal kernel CSS.
|
||||
|
||||
### Changed
|
||||
|
||||
- **12 packages migrated** — all class selectors renamed to `.ext-{slug}-*`:
|
||||
chat, dashboard, editor, git-board, hello-dashboard, icd-test-runner,
|
||||
notes, schedules, sdk-test-runner, tasks, team-activity-log, workflow-demo.
|
||||
CSS and JS files updated in lockstep.
|
||||
- **`icd-test-runner`** — ID selectors (`#extension-mount`) converted to
|
||||
class-based selectors with proper prefix.
|
||||
- **`editor` cross-references** — compound selectors referencing notes
|
||||
classes updated to new `.ext-notes-*` names.
|
||||
- **`chat` kernel overrides** — `.sw-dialog:has(...)` override scoped under
|
||||
`[data-ext="chat"]` instead of global.
|
||||
|
||||
## v0.6.11 — CSS Deduplication
|
||||
|
||||
One class per concept. The old `primitives.css` button, toast, popup-menu,
|
||||
|
||||
@@ -63,16 +63,17 @@ Shipped. Old primitive system retired. One class per concept. See CHANGELOG.md.
|
||||
| Mark deprecated classes | Any remaining old classes get a `/* DEPRECATED v0.6.11 — use .sw-btn--* */` comment and a 1-version grace period for package authors. |
|
||||
| Package CSS audit | Scan all `packages/*/css/main.css` for references to deprecated kernel classes. Fix in-tree packages. |
|
||||
|
||||
### v0.6.12 — Extension CSS Isolation
|
||||
### v0.6.12 — Extension CSS Isolation ✅
|
||||
|
||||
Prevent extension CSS from leaking into the kernel or sibling extensions.
|
||||
Shipped. Prefix enforcement via linter. All 12 in-tree packages migrated.
|
||||
See CHANGELOG.md.
|
||||
|
||||
| Step | Description |
|
||||
|------|-------------|
|
||||
| Scoping strategy | Two options: (A) `@scope (.extension-mount[data-ext="slug"])` — CSS `@scope` is supported in Chrome 118+, Firefox 128+, Safari 17.4+. (B) Prefix enforcement — package CSS linter rejects selectors that don't start with `.ext-{slug}` or `[data-ext="{slug}"]`. **Recommendation**: (B) prefix enforcement. `@scope` support is still patchy; prefix enforcement works everywhere and is trivially lintable. |
|
||||
| Linter | `scripts/lint-package-css.sh` — shell script using `grep`/`awk`. Runs in CI. Accepts `.ext-{slug}` or `[data-ext="{slug}"]` prefixed selectors only. Exempts `:root`, `@keyframes`, `@font-face`, and `@media` wrappers. |
|
||||
| Kernel CSS contract | Document which kernel classes are stable public API for extensions to reference (`.sw-btn--*`, `.sw-input`, `.sw-field`, `.sw-dialog`, `.sw-toast`, `.sw-menu`, `.sw-tabs`, CSS variables). Everything else is internal. Publish in `docs/EXTENSION-CSS.md`. |
|
||||
| Migrate in-tree packages | Add `data-ext` attribute to extension mount container. Prefix all in-tree package CSS. Verify no visual regressions. |
|
||||
| Scoping strategy | Prefix enforcement (option B). `@scope` support still patchy; prefix works everywhere, trivially lintable. |
|
||||
| Linter | `scripts/lint-package-css.sh` — validates first class selector in every rule starts with `.ext-{slug}`. Exempts `:root`, `@keyframes`, `@font-face`, `@media`, `.sw-*` kernel classes, `.cm-*` CodeMirror classes. |
|
||||
| Kernel CSS contract | `docs/EXTENSION-CSS.md` — stable public classes (`.sw-btn--*`, `.sw-input`, `.sw-field`, `.sw-dialog`, `.sw-toast`, `.sw-menu`, `.sw-tabs`, etc.) and all CSS variables. Everything else internal. |
|
||||
| Migrate in-tree packages | `data-ext="{{.Surface}}"` on extension mount. All 12 packages prefixed to `.ext-{slug}-*`. CSS + JS updated in lockstep. |
|
||||
|
||||
### v0.6.13 — Responsive & Spacing
|
||||
|
||||
@@ -120,7 +121,7 @@ v0.6.10 Viewport Foundation ✅ SHIPPED
|
||||
↓
|
||||
v0.6.11 CSS Deduplication ✅ SHIPPED
|
||||
↓
|
||||
v0.6.12 Extension CSS Isolation ← Scoping requires the kernel CSS to be stable first
|
||||
v0.6.12 Extension CSS Isolation ✅ SHIPPED
|
||||
↓
|
||||
v0.6.13 Responsive & Spacing ← Spacing tokens need stable class names to attach to
|
||||
↓
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Armature — Roadmap
|
||||
|
||||
## Current: v0.6.11 — CSS Deduplication
|
||||
## Current: v0.6.12 — Extension CSS Isolation
|
||||
|
||||
Self-hosted extensible platform. Auth, identity, packages, Starlark sandbox,
|
||||
storage, realtime, and ops are kernel primitives. Everything else is an extension.
|
||||
|
||||
149
docs/EXTENSION-CSS.md
Normal file
149
docs/EXTENSION-CSS.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Extension CSS Contract
|
||||
|
||||
> **Version**: v0.6.12 — Extension CSS Isolation
|
||||
|
||||
This document defines the CSS contract between the Armature kernel and extension
|
||||
packages. Extensions **must** follow these rules; the kernel guarantees the listed
|
||||
classes and variables are stable public API.
|
||||
|
||||
---
|
||||
|
||||
## Naming Rule
|
||||
|
||||
All class selectors in extension CSS (`packages/{slug}/css/main.css`) must start
|
||||
with `.ext-{slug}-`. The `{slug}` is the package directory name.
|
||||
|
||||
```css
|
||||
/* Good */
|
||||
.ext-my-app-sidebar { ... }
|
||||
.ext-my-app-card { ... }
|
||||
|
||||
/* Bad — will be rejected by the linter */
|
||||
.sidebar { ... }
|
||||
.my-sidebar { ... }
|
||||
```
|
||||
|
||||
**Compound selectors**: Descendant classes scoped under your `.ext-{slug}` root
|
||||
are allowed to reference kernel classes or state modifiers:
|
||||
|
||||
```css
|
||||
/* Allowed — kernel class scoped under extension namespace */
|
||||
.ext-my-app .sw-btn { margin-top: 8px; }
|
||||
|
||||
/* Allowed — state modifier on an extension element */
|
||||
.ext-my-app-item.active { ... }
|
||||
```
|
||||
|
||||
Run `bash scripts/lint-package-css.sh` to validate. The linter checks that the
|
||||
**first** class selector in every rule starts with `.ext-{slug}`.
|
||||
|
||||
---
|
||||
|
||||
## Stable Kernel Classes
|
||||
|
||||
Extensions may reference these classes in compound selectors. They are part of
|
||||
the public API and will not change without a major version bump.
|
||||
|
||||
### Components (from `sw-primitives.css`)
|
||||
|
||||
| Class pattern | Component |
|
||||
|---------------|-----------|
|
||||
| `.sw-btn`, `.sw-btn--{variant}`, `.sw-btn--{size}` | Buttons |
|
||||
| `.sw-input` | Text inputs |
|
||||
| `.sw-field`, `.sw-field__label`, `.sw-field__hint` | Form fields |
|
||||
| `.sw-dialog`, `.sw-dialog__header`, `.sw-dialog__body`, `.sw-dialog__footer` | Dialogs |
|
||||
| `.sw-toast`, `.sw-toast-container` | Toast notifications |
|
||||
| `.sw-menu`, `.sw-menu-item` | Context menus |
|
||||
| `.sw-tabs`, `.sw-tab-btn` | Tab strips |
|
||||
| `.sw-dropdown` | Custom dropdowns |
|
||||
| `.sw-spinner` | Loading spinners |
|
||||
| `.sw-avatar` | User avatars |
|
||||
| `.sw-drawer` | Slide-out drawers |
|
||||
| `.sw-banner` | Banner bars |
|
||||
| `.sw-tooltip` | Tooltips |
|
||||
|
||||
### Extension Mount
|
||||
|
||||
The extension surface container has a `data-ext` attribute set to the package
|
||||
slug. Use this for scoping if needed:
|
||||
|
||||
```css
|
||||
[data-ext="my-app"] .ext-my-app-sidebar { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stable CSS Variables
|
||||
|
||||
All variables from `variables.css` are public API. Extensions should use these
|
||||
instead of hardcoded colors to respect the user's theme.
|
||||
|
||||
### Colors
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `--bg` | Page background |
|
||||
| `--bg-secondary` | Secondary/darker background |
|
||||
| `--bg-elevated` | Elevated surface background |
|
||||
| `--bg-raised` | Raised card background |
|
||||
| `--bg-surface` | Surface-level background |
|
||||
| `--bg-hover` | Hover state background |
|
||||
| `--bg-active` | Active/pressed state background |
|
||||
| `--bg-code` | Code block background |
|
||||
| `--text` | Primary text color |
|
||||
| `--text-2` | Secondary text color |
|
||||
| `--text-3` | Tertiary/muted text color |
|
||||
| `--text-on-color` | Text on colored backgrounds |
|
||||
| `--accent` | Primary accent color |
|
||||
| `--accent-dim` | Dimmed accent for backgrounds |
|
||||
| `--accent-hover` | Accent hover state |
|
||||
| `--accent-light` | Light accent variant |
|
||||
| `--border` | Default border color |
|
||||
| `--border-light` | Light border variant |
|
||||
| `--border-elevated` | Border for elevated surfaces |
|
||||
| `--danger` | Error/destructive color |
|
||||
| `--danger-dim` | Dimmed danger background |
|
||||
| `--danger-light` | Light danger variant |
|
||||
| `--success` | Success/positive color |
|
||||
| `--success-dim` | Dimmed success background |
|
||||
| `--success-light` | Light success variant |
|
||||
| `--warning` | Warning/caution color |
|
||||
| `--warning-dim` | Dimmed warning background |
|
||||
| `--warning-light` | Light warning variant |
|
||||
| `--purple` | Purple accent |
|
||||
| `--purple-dim` | Dimmed purple background |
|
||||
|
||||
### Layout & Typography
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `--font` | Primary font family |
|
||||
| `--mono` | Monospace font family |
|
||||
| `--radius` | Default border-radius (8px) |
|
||||
| `--radius-lg` | Large border-radius (12px) |
|
||||
| `--transition` | Default transition timing |
|
||||
| `--shadow-lg` | Large elevation shadow |
|
||||
| `--overlay` | Modal overlay color |
|
||||
| `--glass` | Glassmorphism backdrop |
|
||||
| `--input-bg` | Form input background |
|
||||
| `--sidebar-w` | Sidebar width |
|
||||
|
||||
---
|
||||
|
||||
## What Is Internal
|
||||
|
||||
Everything not listed above is **internal kernel CSS** and may change between
|
||||
minor versions. Extensions must not depend on:
|
||||
|
||||
- Kernel layout classes (`.admin-*`, `.surface-*`, `.sidebar`, etc.)
|
||||
- Kernel CSS file load order
|
||||
- Specific HTML structure of the shell or topbar
|
||||
- Undocumented CSS variables
|
||||
|
||||
---
|
||||
|
||||
## Enforcement
|
||||
|
||||
The linter script `scripts/lint-package-css.sh` runs against all
|
||||
`packages/*/css/main.css` files. It exits non-zero if any rule's first class
|
||||
selector does not start with `.ext-{slug}`.
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
/* ── Layout ─────────────────────────────── */
|
||||
|
||||
.chat-app {
|
||||
.ext-chat-app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -18,20 +18,20 @@
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.chat-loading {
|
||||
.ext-chat-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat-body {
|
||||
.ext-chat-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-main {
|
||||
.ext-chat-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
/* ── Topbar extras ──────────────────────── */
|
||||
|
||||
.chat-topbar__thread-title {
|
||||
.ext-chat-topbar__thread-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-right: 8px;
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
/* ── Sidebar ────────────────────────────── */
|
||||
|
||||
.chat-sidebar {
|
||||
.ext-chat-sidebar {
|
||||
width: 280px;
|
||||
min-width: 280px;
|
||||
border-right: 1px solid var(--border);
|
||||
@@ -58,7 +58,7 @@
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.chat-sidebar__header {
|
||||
.ext-chat-sidebar__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -66,46 +66,46 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.chat-sidebar__title {
|
||||
.ext-chat-sidebar__title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chat-sidebar__list {
|
||||
.ext-chat-sidebar__list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.chat-sidebar__empty {
|
||||
.ext-chat-sidebar__empty {
|
||||
padding: 24px 16px;
|
||||
text-align: center;
|
||||
color: var(--text-3);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.chat-sidebar__item {
|
||||
.ext-chat-sidebar__item {
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.chat-sidebar__item:hover {
|
||||
.ext-chat-sidebar__item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.chat-sidebar__item--active {
|
||||
.ext-chat-sidebar__item--active {
|
||||
background: var(--accent-dim);
|
||||
}
|
||||
|
||||
.chat-sidebar__item-top {
|
||||
.ext-chat-sidebar__item-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.chat-sidebar__item-title {
|
||||
.ext-chat-sidebar__item-title {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
@@ -115,19 +115,19 @@
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.chat-sidebar__item-time {
|
||||
.ext-chat-sidebar__item-time {
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-sidebar__item-bottom {
|
||||
.ext-chat-sidebar__item-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.chat-sidebar__item-preview {
|
||||
.ext-chat-sidebar__item-preview {
|
||||
font-size: 12px;
|
||||
color: var(--text-2);
|
||||
white-space: nowrap;
|
||||
@@ -136,7 +136,7 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chat-sidebar__badge {
|
||||
.ext-chat-sidebar__badge {
|
||||
background: var(--accent);
|
||||
color: var(--text-on-color);
|
||||
font-size: 11px;
|
||||
@@ -153,13 +153,13 @@
|
||||
|
||||
/* ── Sidebar Search ────────────────────── */
|
||||
|
||||
.chat-sidebar__search {
|
||||
.ext-chat-sidebar__search {
|
||||
position: relative;
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.chat-sidebar__search-input {
|
||||
.ext-chat-sidebar__search-input {
|
||||
width: 100%;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
@@ -171,12 +171,12 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-sidebar__search-input:focus {
|
||||
.ext-chat-sidebar__search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.chat-sidebar__search-clear {
|
||||
.ext-chat-sidebar__search-clear {
|
||||
position: absolute;
|
||||
right: 22px;
|
||||
top: 50%;
|
||||
@@ -190,16 +190,16 @@
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.chat-sidebar__search-clear:hover {
|
||||
.ext-chat-sidebar__search-clear:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.chat-sidebar__search-results {
|
||||
.ext-chat-sidebar__search-results {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.chat-sidebar__search-section {
|
||||
.ext-chat-sidebar__search-section {
|
||||
padding: 8px 16px 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
@@ -208,13 +208,13 @@
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.chat-sidebar__search-loading {
|
||||
.ext-chat-sidebar__search-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.chat-sidebar__item--search-msg .chat-sidebar__item-preview {
|
||||
.ext-chat-sidebar__item--search-msg .ext-chat-sidebar__item-preview {
|
||||
font-size: 13px;
|
||||
white-space: normal;
|
||||
display: -webkit-box;
|
||||
@@ -225,20 +225,20 @@
|
||||
|
||||
/* ── Message Thread ─────────────────────── */
|
||||
|
||||
.chat-thread {
|
||||
.ext-chat-thread {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-thread--empty {
|
||||
.ext-chat-thread--empty {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.chat-thread__messages {
|
||||
.ext-chat-thread__messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
@@ -247,19 +247,19 @@
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.chat-thread__loading {
|
||||
.ext-chat-thread__loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.chat-thread__loading-more {
|
||||
.ext-chat-thread__loading-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.chat-thread__load-more {
|
||||
.ext-chat-thread__load-more {
|
||||
align-self: center;
|
||||
background: none;
|
||||
border: 1px solid var(--border);
|
||||
@@ -271,11 +271,11 @@
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.chat-thread__load-more:hover {
|
||||
.ext-chat-thread__load-more:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.chat-thread__typing {
|
||||
.ext-chat-thread__typing {
|
||||
padding: 4px 16px 8px;
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
@@ -284,7 +284,7 @@
|
||||
|
||||
/* ── Message Bubble ─────────────────────── */
|
||||
|
||||
.chat-msg {
|
||||
.ext-chat-msg {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
@@ -292,44 +292,44 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-msg--own {
|
||||
.ext-chat-msg--own {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.chat-msg--system {
|
||||
.ext-chat-msg--system {
|
||||
justify-content: center;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.chat-msg--system span {
|
||||
.ext-chat-msg--system span {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.chat-msg--deleted {
|
||||
.ext-chat-msg--deleted {
|
||||
justify-content: center;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.chat-msg--deleted em {
|
||||
.ext-chat-msg--deleted em {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.chat-msg__body {
|
||||
.ext-chat-msg__body {
|
||||
max-width: 65%;
|
||||
background: var(--bg-raised);
|
||||
border-radius: 12px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.chat-msg--own .chat-msg__body {
|
||||
.ext-chat-msg--own .ext-chat-msg__body {
|
||||
background: var(--accent);
|
||||
color: var(--text-on-color);
|
||||
}
|
||||
|
||||
.chat-msg__name {
|
||||
.ext-chat-msg__name {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-2);
|
||||
@@ -337,42 +337,42 @@
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.chat-msg__content {
|
||||
.ext-chat-msg__content {
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.chat-msg__meta {
|
||||
.ext-chat-msg__meta {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.chat-msg__time {
|
||||
.ext-chat-msg__time {
|
||||
font-size: 10px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.chat-msg--own .chat-msg__time {
|
||||
.ext-chat-msg--own .ext-chat-msg__time {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.chat-msg__edited {
|
||||
.ext-chat-msg__edited {
|
||||
font-size: 10px;
|
||||
color: var(--text-3);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.chat-msg--own .chat-msg__edited {
|
||||
.ext-chat-msg--own .ext-chat-msg__edited {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
/* ── Message Actions ────────────────────── */
|
||||
|
||||
.chat-msg__actions {
|
||||
.ext-chat-msg__actions {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
position: absolute;
|
||||
@@ -385,12 +385,12 @@
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.chat-msg--own .chat-msg__actions {
|
||||
.ext-chat-msg--own .ext-chat-msg__actions {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.chat-msg__action {
|
||||
.ext-chat-msg__action {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px 6px;
|
||||
@@ -401,24 +401,24 @@
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.chat-msg__action:hover {
|
||||
.ext-chat-msg__action:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.chat-msg__action--danger:hover {
|
||||
.ext-chat-msg__action--danger:hover {
|
||||
background: var(--danger-bg);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* ── Message Edit ───────────────────────── */
|
||||
|
||||
.chat-msg__edit {
|
||||
.ext-chat-msg__edit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.chat-msg__edit-input {
|
||||
.ext-chat-msg__edit-input {
|
||||
width: 100%;
|
||||
min-width: 200px;
|
||||
border: 1px solid var(--border);
|
||||
@@ -431,7 +431,7 @@
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.chat-msg__edit-actions {
|
||||
.ext-chat-msg__edit-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
justify-content: flex-end;
|
||||
@@ -439,7 +439,7 @@
|
||||
|
||||
/* ── Compose Bar ────────────────────────── */
|
||||
|
||||
.chat-compose {
|
||||
.ext-chat-compose {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
@@ -448,7 +448,7 @@
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.chat-compose__input {
|
||||
.ext-chat-compose__input {
|
||||
flex: 1;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
@@ -462,7 +462,7 @@
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.chat-compose__input:focus {
|
||||
.ext-chat-compose__input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px var(--accent-dim);
|
||||
@@ -470,7 +470,7 @@
|
||||
|
||||
/* ── Participant Sidebar ────────────────── */
|
||||
|
||||
.chat-participants {
|
||||
.ext-chat-participants {
|
||||
width: 240px;
|
||||
min-width: 240px;
|
||||
border-left: 1px solid var(--border);
|
||||
@@ -479,7 +479,7 @@
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.chat-participants__header {
|
||||
.ext-chat-participants__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -489,20 +489,20 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.chat-participants__list {
|
||||
.ext-chat-participants__list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.chat-participants__item {
|
||||
.ext-chat-participants__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 16px;
|
||||
}
|
||||
|
||||
.chat-participants__name {
|
||||
.ext-chat-participants__name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
@@ -510,14 +510,14 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chat-participants__badge {
|
||||
.ext-chat-participants__badge {
|
||||
font-size: 10px;
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.chat-participants__status {
|
||||
.ext-chat-participants__status {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
@@ -525,11 +525,11 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-participants__status--online {
|
||||
.ext-chat-participants__status--online {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.chat-participants__remove {
|
||||
.ext-chat-participants__remove {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-3);
|
||||
@@ -539,25 +539,25 @@
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.chat-participants__remove:hover {
|
||||
.ext-chat-participants__remove:hover {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* ── New Conversation Dialog ────────────── */
|
||||
|
||||
.chat-new {
|
||||
.ext-chat-new {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
.chat-new__type {
|
||||
.ext-chat-new__type {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.chat-new__type label {
|
||||
.ext-chat-new__type label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@@ -565,7 +565,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-new__title {
|
||||
.ext-chat-new__title {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
@@ -575,13 +575,13 @@
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.chat-new__selected {
|
||||
.ext-chat-new__selected {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.chat-new__chip {
|
||||
.ext-chat-new__chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -593,7 +593,7 @@
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.chat-new__chip button {
|
||||
.ext-chat-new__chip button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
@@ -607,10 +607,10 @@
|
||||
/* Allow the autocomplete dropdown to overflow the dialog body.
|
||||
Applies to both New Conversation and Add Participant dialogs. */
|
||||
|
||||
.sw-dialog__body:has(.sw-user-picker) {
|
||||
[data-ext="chat"] .sw-dialog__body:has(.sw-user-picker) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.sw-dialog:has(.sw-user-picker) {
|
||||
[data-ext="chat"] .sw-dialog:has(.sw-user-picker) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
@@ -123,60 +123,60 @@
|
||||
var sMsgs = showSearch ? (searchResults.messages || []) : [];
|
||||
|
||||
return html`
|
||||
<div class="chat-sidebar">
|
||||
<div class="chat-sidebar__header">
|
||||
<span class="chat-sidebar__title">Conversations</span>
|
||||
<div class="ext-chat-sidebar">
|
||||
<div class="ext-chat-sidebar__header">
|
||||
<span class="ext-chat-sidebar__title">Conversations</span>
|
||||
<${Button} size="sm" onClick=${onNew}>New<//>
|
||||
</div>
|
||||
<div class="chat-sidebar__search">
|
||||
<input class="chat-sidebar__search-input"
|
||||
<div class="ext-chat-sidebar__search">
|
||||
<input class="ext-chat-sidebar__search-input"
|
||||
type="text"
|
||||
value=${searchQuery}
|
||||
placeholder="Search\u2026"
|
||||
onInput=${handleSearchInput} />
|
||||
${searchQuery && html`
|
||||
<button class="chat-sidebar__search-clear" onClick=${clearSearch}>\u00d7</button>`}
|
||||
<button class="ext-chat-sidebar__search-clear" onClick=${clearSearch}>\u00d7</button>`}
|
||||
</div>
|
||||
${showSearch ? html`
|
||||
<div class="chat-sidebar__search-results">
|
||||
${searching && html`<div class="chat-sidebar__search-loading"><${Spinner} size="sm" /></div>`}
|
||||
<div class="ext-chat-sidebar__search-results">
|
||||
${searching && html`<div class="ext-chat-sidebar__search-loading"><${Spinner} size="sm" /></div>`}
|
||||
${!searching && sConvs.length === 0 && sMsgs.length === 0 && html`
|
||||
<div class="chat-sidebar__empty">No results</div>`}
|
||||
<div class="ext-chat-sidebar__empty">No results</div>`}
|
||||
${sConvs.length > 0 && html`
|
||||
<div class="chat-sidebar__search-section">Conversations</div>
|
||||
<div class="ext-chat-sidebar__search-section">Conversations</div>
|
||||
${sConvs.map(c => html`
|
||||
<div key=${c.id} class="chat-sidebar__item" onClick=${() => selectFromSearch(c.id)}>
|
||||
<div class="chat-sidebar__item-top">
|
||||
<span class="chat-sidebar__item-title">${c.title || 'Untitled'}</span>
|
||||
<span class="chat-sidebar__item-time">${timeAgo(c.updated_at || c.created_at)}</span>
|
||||
<div key=${c.id} class="ext-chat-sidebar__item" onClick=${() => selectFromSearch(c.id)}>
|
||||
<div class="ext-chat-sidebar__item-top">
|
||||
<span class="ext-chat-sidebar__item-title">${c.title || 'Untitled'}</span>
|
||||
<span class="ext-chat-sidebar__item-time">${timeAgo(c.updated_at || c.created_at)}</span>
|
||||
</div>
|
||||
</div>`)}`}
|
||||
${sMsgs.length > 0 && html`
|
||||
<div class="chat-sidebar__search-section">Messages</div>
|
||||
<div class="ext-chat-sidebar__search-section">Messages</div>
|
||||
${sMsgs.map(m => html`
|
||||
<div key=${m.id} class="chat-sidebar__item chat-sidebar__item--search-msg" onClick=${() => selectFromSearch(m.conversation_id)}>
|
||||
<div class="chat-sidebar__item-top">
|
||||
<span class="chat-sidebar__item-preview">${truncate(m.content, 80)}</span>
|
||||
<div key=${m.id} class="ext-chat-sidebar__item ext-chat-sidebar__item--search-msg" onClick=${() => selectFromSearch(m.conversation_id)}>
|
||||
<div class="ext-chat-sidebar__item-top">
|
||||
<span class="ext-chat-sidebar__item-preview">${truncate(m.content, 80)}</span>
|
||||
</div>
|
||||
<div class="chat-sidebar__item-bottom">
|
||||
<span class="chat-sidebar__item-time">${timeAgo(m.created_at)}</span>
|
||||
<div class="ext-chat-sidebar__item-bottom">
|
||||
<span class="ext-chat-sidebar__item-time">${timeAgo(m.created_at)}</span>
|
||||
</div>
|
||||
</div>`)}`}
|
||||
</div>
|
||||
` : html`
|
||||
<div class="chat-sidebar__list">
|
||||
<div class="ext-chat-sidebar__list">
|
||||
${conversations.length === 0 && html`
|
||||
<div class="chat-sidebar__empty">No conversations yet</div>`}
|
||||
<div class="ext-chat-sidebar__empty">No conversations yet</div>`}
|
||||
${conversations.map(c => html`
|
||||
<div key=${c.id}
|
||||
class=${'chat-sidebar__item' + (selected === c.id ? ' chat-sidebar__item--active' : '')}
|
||||
class=${'ext-chat-sidebar__item' + (selected === c.id ? ' ext-chat-sidebar__item--active' : '')}
|
||||
onClick=${() => onSelect(c.id)}>
|
||||
<div class="chat-sidebar__item-top">
|
||||
<span class="chat-sidebar__item-title">${c.title || 'Untitled'}</span>
|
||||
<span class="chat-sidebar__item-time">${timeAgo(c.updated_at || c.created_at)}</span>
|
||||
<div class="ext-chat-sidebar__item-top">
|
||||
<span class="ext-chat-sidebar__item-title">${c.title || 'Untitled'}</span>
|
||||
<span class="ext-chat-sidebar__item-time">${timeAgo(c.updated_at || c.created_at)}</span>
|
||||
</div>
|
||||
<div class="chat-sidebar__item-bottom">
|
||||
<span class="chat-sidebar__item-preview">
|
||||
<div class="ext-chat-sidebar__item-bottom">
|
||||
<span class="ext-chat-sidebar__item-preview">
|
||||
${c.last_message
|
||||
? truncate(c.last_message.content_type === 'system'
|
||||
? '\u2022 ' + c.last_message.content
|
||||
@@ -184,7 +184,7 @@
|
||||
: 'No messages yet'}
|
||||
</span>
|
||||
${(unread[c.id] || 0) > 0 && html`
|
||||
<span class="chat-sidebar__badge">${unread[c.id]}</span>`}
|
||||
<span class="ext-chat-sidebar__badge">${unread[c.id]}</span>`}
|
||||
</div>
|
||||
</div>`)}
|
||||
</div>
|
||||
@@ -204,14 +204,14 @@
|
||||
|
||||
if (msg._deleted) {
|
||||
return html`
|
||||
<div class="chat-msg chat-msg--deleted">
|
||||
<div class="ext-chat-msg ext-chat-msg--deleted">
|
||||
<em>This message was deleted</em>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (msg.content_type === 'system') {
|
||||
return html`
|
||||
<div class="chat-msg chat-msg--system">
|
||||
<div class="ext-chat-msg ext-chat-msg--system">
|
||||
<span>${msg.content}</span>
|
||||
</div>`;
|
||||
}
|
||||
@@ -239,37 +239,37 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class=${'chat-msg' + (isOwn ? ' chat-msg--own' : '')}
|
||||
<div class=${'ext-chat-msg' + (isOwn ? ' ext-chat-msg--own' : '')}
|
||||
onMouseEnter=${() => setHover(true)}
|
||||
onMouseLeave=${() => setHover(false)}>
|
||||
${!isOwn && html`
|
||||
<${Avatar} name=${msg._display_name || msg.participant_id} size="sm" />`}
|
||||
<div class="chat-msg__body">
|
||||
${!isOwn && html`<span class="chat-msg__name">${msg._display_name || msg.participant_id}</span>`}
|
||||
<div class="ext-chat-msg__body">
|
||||
${!isOwn && html`<span class="ext-chat-msg__name">${msg._display_name || msg.participant_id}</span>`}
|
||||
${editing ? html`
|
||||
<div class="chat-msg__edit">
|
||||
<textarea class="chat-msg__edit-input"
|
||||
<div class="ext-chat-msg__edit">
|
||||
<textarea class="ext-chat-msg__edit-input"
|
||||
value=${editText}
|
||||
onInput=${e => setEditText(e.target.value)}
|
||||
onKeyDown=${onEditKeyDown}
|
||||
rows="2" />
|
||||
<div class="chat-msg__edit-actions">
|
||||
<div class="ext-chat-msg__edit-actions">
|
||||
<${Button} size="sm" variant="secondary" onClick=${cancelEdit}>Cancel<//>
|
||||
<${Button} size="sm" onClick=${saveEdit}>Save<//>
|
||||
</div>
|
||||
</div>
|
||||
` : msg.content_type === 'markdown' && sw?.markdown?.ready ? html`
|
||||
<div class="chat-msg__content" dangerouslySetInnerHTML=${{ __html: sw.markdown.renderSync(msg.content, { sanitize: true }) }} />` : html`
|
||||
<div class="chat-msg__content">${msg.content}</div>`}
|
||||
<div class="chat-msg__meta">
|
||||
<span class="chat-msg__time">${timeAgo(msg.created_at)}</span>
|
||||
${msg.edited_at && html`<span class="chat-msg__edited">(edited)</span>`}
|
||||
<div class="ext-chat-msg__content" dangerouslySetInnerHTML=${{ __html: sw.markdown.renderSync(msg.content, { sanitize: true }) }} />` : html`
|
||||
<div class="ext-chat-msg__content">${msg.content}</div>`}
|
||||
<div class="ext-chat-msg__meta">
|
||||
<span class="ext-chat-msg__time">${timeAgo(msg.created_at)}</span>
|
||||
${msg.edited_at && html`<span class="ext-chat-msg__edited">(edited)</span>`}
|
||||
</div>
|
||||
</div>
|
||||
${hover && isOwn && !editing && html`
|
||||
<div class="chat-msg__actions">
|
||||
<button class="chat-msg__action" onClick=${startEdit} title="Edit">✎</button>
|
||||
<button class="chat-msg__action chat-msg__action--danger" onClick=${() => onDelete(msg.id)} title="Delete">🗑</button>
|
||||
<div class="ext-chat-msg__actions">
|
||||
<button class="ext-chat-msg__action" onClick=${startEdit} title="Edit">✎</button>
|
||||
<button class="ext-chat-msg__action ext-chat-msg__action--danger" onClick=${() => onDelete(msg.id)} title="Delete">🗑</button>
|
||||
</div>`}
|
||||
</div>`;
|
||||
}
|
||||
@@ -442,18 +442,18 @@
|
||||
|
||||
if (!conversationId) {
|
||||
return html`
|
||||
<div class="chat-thread chat-thread--empty">
|
||||
<div class="ext-chat-thread chat-thread--empty">
|
||||
<p>Select a conversation or start a new one</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="chat-thread">
|
||||
<div class="chat-thread__messages" ref=${listRef}>
|
||||
${loading && messages.length === 0 && html`<div class="chat-thread__loading"><${Spinner} /></div>`}
|
||||
${loading && messages.length > 0 && html`<div class="chat-thread__loading-more"><${Spinner} size="sm" /></div>`}
|
||||
<div class="ext-chat-thread">
|
||||
<div class="ext-chat-thread__messages" ref=${listRef}>
|
||||
${loading && messages.length === 0 && html`<div class="ext-chat-thread__loading"><${Spinner} /></div>`}
|
||||
${loading && messages.length > 0 && html`<div class="ext-chat-thread__loading-more"><${Spinner} size="sm" /></div>`}
|
||||
${hasMore && !loading && html`
|
||||
<button class="chat-thread__load-more" onClick=${loadMore}>
|
||||
<button class="ext-chat-thread__load-more" onClick=${loadMore}>
|
||||
Load older messages
|
||||
</button>`}
|
||||
${messages.map(m => html`
|
||||
@@ -466,7 +466,7 @@
|
||||
/>`)}
|
||||
<div ref=${bottomRef} />
|
||||
</div>
|
||||
${typingText && html`<div class="chat-thread__typing">${typingText}</div>`}
|
||||
${typingText && html`<div class="ext-chat-thread__typing">${typingText}</div>`}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -533,8 +533,8 @@
|
||||
if (!conversationId) return null;
|
||||
|
||||
return html`
|
||||
<div class="chat-compose">
|
||||
<textarea class="chat-compose__input"
|
||||
<div class="ext-chat-compose">
|
||||
<textarea class="ext-chat-compose__input"
|
||||
ref=${textareaRef}
|
||||
value=${text}
|
||||
placeholder="Type a message\u2026"
|
||||
@@ -580,22 +580,22 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="chat-participants">
|
||||
<div class="chat-participants__header">
|
||||
<div class="ext-chat-participants">
|
||||
<div class="ext-chat-participants__header">
|
||||
<span>Participants (${(participants || []).length})</span>
|
||||
${isAdmin && html`<${Button} size="sm" onClick=${() => setAddOpen(true)}>Add<//>` }
|
||||
</div>
|
||||
<div class="chat-participants__list">
|
||||
<div class="ext-chat-participants__list">
|
||||
${(participants || []).map(p => html`
|
||||
<div key=${p.participant_id} class="chat-participants__item">
|
||||
<div key=${p.participant_id} class="ext-chat-participants__item">
|
||||
<${Avatar} name=${p.display_name || p.participant_id} size="sm" />
|
||||
<span class="chat-participants__name">
|
||||
<span class="ext-chat-participants__name">
|
||||
${p.display_name || p.participant_id}
|
||||
${p.role === 'admin' && html`<span class="chat-participants__badge">admin</span>`}
|
||||
${p.role === 'admin' && html`<span class="ext-chat-participants__badge">admin</span>`}
|
||||
</span>
|
||||
<span class=${'chat-participants__status' + (presence[p.participant_id] ? ' chat-participants__status--online' : '')} />
|
||||
<span class=${'chat-participants__status' + (presence[p.participant_id] ? ' ext-chat-participants__status--online' : '')} />
|
||||
${isAdmin && p.participant_id !== currentUserId() && html`
|
||||
<button class="chat-participants__remove" onClick=${() => removeUser(p.participant_id)} title="Remove">\u00d7</button>`}
|
||||
<button class="ext-chat-participants__remove" onClick=${() => removeUser(p.participant_id)} title="Remove">\u00d7</button>`}
|
||||
</div>`)}
|
||||
</div>
|
||||
|
||||
@@ -671,8 +671,8 @@
|
||||
|
||||
return html`
|
||||
<${Dialog} open=${open} title="New Conversation" onClose=${() => { reset(); onClose(); }} actions=${actions}>
|
||||
<div class="chat-new">
|
||||
<div class="chat-new__type">
|
||||
<div class="ext-chat-new">
|
||||
<div class="ext-chat-new__type">
|
||||
<label>
|
||||
<input type="radio" name="convType" value="group"
|
||||
checked=${type === 'group'} onChange=${() => { setType('group'); setSelected([]); }} />
|
||||
@@ -685,14 +685,14 @@
|
||||
</label>
|
||||
</div>
|
||||
${type === 'group' && html`
|
||||
<input class="chat-new__title" type="text" value=${title}
|
||||
<input class="ext-chat-new__title" type="text" value=${title}
|
||||
placeholder="Conversation title (optional)"
|
||||
onInput=${e => setTitle(e.target.value)} />`}
|
||||
<${UserPicker} onSelect=${addUser} placeholder=${type === 'direct' ? 'Search for a user\u2026' : 'Add participants\u2026'} />
|
||||
${selected.length > 0 && html`
|
||||
<div class="chat-new__selected">
|
||||
<div class="ext-chat-new__selected">
|
||||
${selected.map(u => html`
|
||||
<span key=${u.id} class="chat-new__chip">
|
||||
<span key=${u.id} class="ext-chat-new__chip">
|
||||
${u.display_name || u.username}
|
||||
<button onClick=${() => removeSelected(u.id)}>\u00d7</button>
|
||||
</span>`)}
|
||||
@@ -807,27 +807,27 @@
|
||||
var threadTitle = selectedConv ? (selectedConv.title || 'Direct Message') : '';
|
||||
|
||||
if (loading) {
|
||||
return html`<div class="chat-loading"><${Spinner} /></div>`;
|
||||
return html`<div class="ext-chat-loading"><${Spinner} /></div>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="chat-app">
|
||||
<div class="ext-chat-app">
|
||||
<${Topbar} title="Chat">
|
||||
${selectedId && html`
|
||||
<span class="chat-topbar__thread-title">${threadTitle}</span>
|
||||
<span class="ext-chat-topbar__thread-title">${threadTitle}</span>
|
||||
<${Button} size="sm" variant="secondary"
|
||||
onClick=${() => setShowParticipants(!showParticipants)}>
|
||||
${showParticipants ? 'Hide' : 'People'}
|
||||
<//>`}
|
||||
<//>
|
||||
<div class="chat-body">
|
||||
<div class="ext-chat-body">
|
||||
<${ConversationList}
|
||||
selected=${selectedId}
|
||||
onSelect=${selectConversation}
|
||||
onNew=${() => setShowNew(true)}
|
||||
conversations=${conversations}
|
||||
unread=${unread} />
|
||||
<div class="chat-main">
|
||||
<div class="ext-chat-main">
|
||||
<${MessageThread}
|
||||
conversationId=${selectedId}
|
||||
participants=${participants} />
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
All SDK components style themselves.
|
||||
========================================== */
|
||||
|
||||
.surface-dashboard {
|
||||
.ext-dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
/* ── Topbar ──────────────────────────────── */
|
||||
|
||||
.dashboard-topbar {
|
||||
.ext-dashboard-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
@@ -26,7 +26,7 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.dashboard-topbar-back {
|
||||
.ext-dashboard-topbar-back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -39,30 +39,30 @@
|
||||
transition: color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.dashboard-topbar-back:hover {
|
||||
.ext-dashboard-topbar-back:hover {
|
||||
color: var(--text);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.dashboard-topbar-title {
|
||||
.ext-dashboard-topbar-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dashboard-topbar-sep {
|
||||
.ext-dashboard-topbar-sep {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.dashboard-topbar-spacer {
|
||||
.ext-dashboard-topbar-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ── Body ────────────────────────────────── */
|
||||
|
||||
.dashboard-body {
|
||||
.ext-dashboard-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
/* ── Sidebar ─────────────────────────────── */
|
||||
|
||||
.dashboard-sidebar {
|
||||
.ext-dashboard-sidebar {
|
||||
width: 300px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
@@ -81,34 +81,34 @@
|
||||
|
||||
/* ── Main Content ────────────────────────── */
|
||||
|
||||
.dashboard-main {
|
||||
.ext-dashboard-main {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard-greeting {
|
||||
.ext-dashboard-greeting {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.dashboard-subtitle {
|
||||
.ext-dashboard-subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--text-3);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dashboard-filter-bar {
|
||||
.ext-dashboard-filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dashboard-filter-label {
|
||||
.ext-dashboard-filter-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-2);
|
||||
@@ -118,13 +118,13 @@
|
||||
|
||||
/* ── Cards Grid ──────────────────────────── */
|
||||
|
||||
.dashboard-cards {
|
||||
.ext-dashboard-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
.ext-dashboard-card {
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
@@ -135,17 +135,17 @@
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.dashboard-card:hover {
|
||||
.ext-dashboard-card:hover {
|
||||
border-color: var(--border-elevated);
|
||||
}
|
||||
|
||||
.dashboard-card-header {
|
||||
.ext-dashboard-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dashboard-card-title {
|
||||
.ext-dashboard-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
@@ -155,24 +155,24 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dashboard-card-meta {
|
||||
.ext-dashboard-card-meta {
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.dashboard-card-desc {
|
||||
.ext-dashboard-card-desc {
|
||||
font-size: 12px;
|
||||
color: var(--text-2);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.dashboard-card-actions {
|
||||
.ext-dashboard-card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.dashboard-empty {
|
||||
.ext-dashboard-empty {
|
||||
text-align: center;
|
||||
color: var(--text-3);
|
||||
font-size: 13px;
|
||||
@@ -181,13 +181,13 @@
|
||||
|
||||
/* ── Admin Section ───────────────────────── */
|
||||
|
||||
.dashboard-admin-section {
|
||||
.ext-dashboard-admin-section {
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.dashboard-section-title {
|
||||
.ext-dashboard-section-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-2);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
if (!mount) return;
|
||||
|
||||
const surface = document.createElement('div');
|
||||
surface.className = 'surface-dashboard';
|
||||
surface.className = 'ext-dashboard';
|
||||
surface.id = 'dashboardSurface';
|
||||
mount.appendChild(surface);
|
||||
|
||||
@@ -41,12 +41,12 @@
|
||||
|
||||
// ── Body ──
|
||||
const body = document.createElement('div');
|
||||
body.className = 'dashboard-body';
|
||||
body.className = 'ext-dashboard-body';
|
||||
surface.appendChild(body);
|
||||
|
||||
// ── Sidebar (tabs: activity + notes) ──
|
||||
const sidebar = document.createElement('div');
|
||||
sidebar.className = 'dashboard-sidebar';
|
||||
sidebar.className = 'ext-dashboard-sidebar';
|
||||
body.appendChild(sidebar);
|
||||
|
||||
// sw.tabs — two tabs
|
||||
@@ -83,14 +83,14 @@
|
||||
|
||||
// ── Main content area ──
|
||||
const main = document.createElement('div');
|
||||
main.className = 'dashboard-main';
|
||||
main.className = 'ext-dashboard-main';
|
||||
body.appendChild(main);
|
||||
|
||||
_buildMainContent(main);
|
||||
|
||||
// ── Theme reactivity ──
|
||||
sw.theme.on('change', function (resolved) {
|
||||
const cards = main.querySelectorAll('.dashboard-card');
|
||||
const cards = main.querySelectorAll('.ext-dashboard-card');
|
||||
cards.forEach(function (c) {
|
||||
c.style.borderColor = ''; // reset to CSS default for new theme
|
||||
});
|
||||
@@ -98,7 +98,7 @@
|
||||
|
||||
// ── Cross-component events ──
|
||||
sw.on('dashboard.filter.changed', function (payload) {
|
||||
_loadChannels(main.querySelector('.dashboard-cards'), payload.value);
|
||||
_loadChannels(main.querySelector('.ext-dashboard-cards'), payload.value);
|
||||
});
|
||||
|
||||
console.log('[DashboardPkg] Mounted');
|
||||
@@ -108,15 +108,15 @@
|
||||
|
||||
function _buildTopbar() {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'dashboard-topbar';
|
||||
el.className = 'ext-dashboard-topbar';
|
||||
el.innerHTML =
|
||||
'<a href="' + esc(base) + '/" class="dashboard-topbar-back" title="Back to chat">' +
|
||||
'<a href="' + esc(base) + '/" class="ext-dashboard-topbar-back" title="Back to chat">' +
|
||||
'<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>' +
|
||||
'Back' +
|
||||
'</a>' +
|
||||
'<div class="dashboard-topbar-sep"></div>' +
|
||||
'<span class="dashboard-topbar-title">Dashboard</span>' +
|
||||
'<div class="dashboard-topbar-spacer"></div>';
|
||||
'<div class="ext-dashboard-topbar-sep"></div>' +
|
||||
'<span class="ext-dashboard-topbar-title">Dashboard</span>' +
|
||||
'<div class="ext-dashboard-topbar-spacer"></div>';
|
||||
|
||||
// sw.toolbar — action buttons
|
||||
const toolbarItems = [
|
||||
@@ -125,7 +125,7 @@
|
||||
icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>',
|
||||
title: 'Refresh',
|
||||
onClick: function () {
|
||||
const cards = document.querySelector('.dashboard-cards');
|
||||
const cards = document.querySelector('.ext-dashboard-cards');
|
||||
if (cards) _loadChannels(cards, _currentFilter);
|
||||
sw.toast('Refreshed', 'success');
|
||||
},
|
||||
@@ -155,22 +155,22 @@
|
||||
// sw.user — greeting
|
||||
const user = sw.user;
|
||||
const greeting = document.createElement('div');
|
||||
greeting.className = 'dashboard-greeting';
|
||||
greeting.className = 'ext-dashboard-greeting';
|
||||
greeting.textContent = 'Welcome back' + (user ? ', ' + (user.display_name || user.username) : '');
|
||||
main.appendChild(greeting);
|
||||
|
||||
const subtitle = document.createElement('div');
|
||||
subtitle.className = 'dashboard-subtitle';
|
||||
subtitle.className = 'ext-dashboard-subtitle';
|
||||
subtitle.textContent = 'Your recent conversations' + (sw.isAdmin ? ' \u00b7 Admin' : '');
|
||||
main.appendChild(subtitle);
|
||||
|
||||
// ── Filter bar ──
|
||||
const filterBar = document.createElement('div');
|
||||
filterBar.className = 'dashboard-filter-bar';
|
||||
filterBar.className = 'ext-dashboard-filter-bar';
|
||||
main.appendChild(filterBar);
|
||||
|
||||
const filterLabel = document.createElement('span');
|
||||
filterLabel.className = 'dashboard-filter-label';
|
||||
filterLabel.className = 'ext-dashboard-filter-label';
|
||||
filterLabel.textContent = 'Filter';
|
||||
filterBar.appendChild(filterLabel);
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
|
||||
// ── Cards ──
|
||||
const cards = document.createElement('div');
|
||||
cards.className = 'dashboard-cards';
|
||||
cards.className = 'ext-dashboard-cards';
|
||||
main.appendChild(cards);
|
||||
|
||||
_loadChannels(cards, '');
|
||||
@@ -200,16 +200,16 @@
|
||||
// ── Admin section (sw.isAdmin) ──
|
||||
if (sw.isAdmin) {
|
||||
const adminSection = document.createElement('div');
|
||||
adminSection.className = 'dashboard-admin-section';
|
||||
adminSection.className = 'ext-dashboard-admin-section';
|
||||
main.appendChild(adminSection);
|
||||
|
||||
const sectionTitle = document.createElement('div');
|
||||
sectionTitle.className = 'dashboard-section-title';
|
||||
sectionTitle.className = 'ext-dashboard-section-title';
|
||||
sectionTitle.textContent = 'Administration';
|
||||
adminSection.appendChild(sectionTitle);
|
||||
|
||||
const adminCards = document.createElement('div');
|
||||
adminCards.className = 'dashboard-cards';
|
||||
adminCards.className = 'ext-dashboard-cards';
|
||||
adminSection.appendChild(adminCards);
|
||||
|
||||
_loadAdminCards(adminCards);
|
||||
@@ -220,7 +220,7 @@
|
||||
|
||||
async function _loadChannels(container, typeFilter) {
|
||||
if (!container) return;
|
||||
container.innerHTML = '<div class="dashboard-empty">Loading\u2026</div>';
|
||||
container.innerHTML = '<div class="ext-dashboard-empty">Loading\u2026</div>';
|
||||
|
||||
try {
|
||||
// sw.api — real API call
|
||||
@@ -231,7 +231,7 @@
|
||||
|
||||
container.innerHTML = '';
|
||||
if (!channels.length) {
|
||||
container.innerHTML = '<div class="dashboard-empty">No channels found</div>';
|
||||
container.innerHTML = '<div class="ext-dashboard-empty">No channels found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -239,31 +239,31 @@
|
||||
container.appendChild(_buildChannelCard(ch));
|
||||
});
|
||||
} catch (e) {
|
||||
container.innerHTML = '<div class="dashboard-empty">Failed to load: ' + (typeof esc === 'function' ? esc(e.message) : e.message) + '</div>';
|
||||
container.innerHTML = '<div class="ext-dashboard-empty">Failed to load: ' + (typeof esc === 'function' ? esc(e.message) : e.message) + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function _buildChannelCard(ch) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'dashboard-card';
|
||||
card.className = 'ext-dashboard-card';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'dashboard-card-header';
|
||||
header.className = 'ext-dashboard-card-header';
|
||||
card.appendChild(header);
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.className = 'dashboard-card-title';
|
||||
title.className = 'ext-dashboard-card-title';
|
||||
title.textContent = ch.title || ch.name || 'Untitled';
|
||||
header.appendChild(title);
|
||||
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'dashboard-card-meta';
|
||||
meta.className = 'ext-dashboard-card-meta';
|
||||
meta.textContent = ch.type || '';
|
||||
card.appendChild(meta);
|
||||
|
||||
if (ch.description) {
|
||||
const desc = document.createElement('div');
|
||||
desc.className = 'dashboard-card-desc';
|
||||
desc.className = 'ext-dashboard-card-desc';
|
||||
desc.textContent = ch.description.slice(0, 120);
|
||||
card.appendChild(desc);
|
||||
}
|
||||
@@ -271,14 +271,14 @@
|
||||
const updated = ch.updated_at || ch.created_at;
|
||||
if (updated) {
|
||||
const date = document.createElement('div');
|
||||
date.className = 'dashboard-card-meta';
|
||||
date.className = 'ext-dashboard-card-meta';
|
||||
date.textContent = new Date(updated).toLocaleDateString();
|
||||
card.appendChild(date);
|
||||
}
|
||||
|
||||
// ── Card action menu ──
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'dashboard-card-actions';
|
||||
actions.className = 'ext-dashboard-card-actions';
|
||||
card.appendChild(actions);
|
||||
|
||||
const menuBtn = document.createElement('button');
|
||||
@@ -372,7 +372,7 @@
|
||||
// sw.toast — success feedback
|
||||
sw.toast('Channel deleted', 'success');
|
||||
// Refresh cards
|
||||
const cards = document.querySelector('.dashboard-cards');
|
||||
const cards = document.querySelector('.ext-dashboard-cards');
|
||||
if (cards) _loadChannels(cards, _currentFilter);
|
||||
} catch (e) {
|
||||
// sw.toast — error feedback
|
||||
@@ -390,21 +390,21 @@
|
||||
container.innerHTML = '';
|
||||
packages.forEach(function (pkg) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'dashboard-card';
|
||||
card.className = 'ext-dashboard-card';
|
||||
card.innerHTML =
|
||||
'<div class="dashboard-card-header">' +
|
||||
'<div class="dashboard-card-title">' + (typeof esc === 'function' ? esc(pkg.title || pkg.id) : (pkg.title || pkg.id)) + '</div>' +
|
||||
'<div class="ext-dashboard-card-header">' +
|
||||
'<div class="ext-dashboard-card-title">' + (typeof esc === 'function' ? esc(pkg.title || pkg.id) : (pkg.title || pkg.id)) + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="dashboard-card-meta">' + (typeof esc === 'function' ? esc(pkg.type || '') : (pkg.type || '')) + ' \u00b7 ' + (typeof esc === 'function' ? esc(pkg.tier || '') : (pkg.tier || '')) + '</div>' +
|
||||
'<div class="dashboard-card-desc">' + (typeof esc === 'function' ? esc(pkg.description || '') : (pkg.description || '')) + '</div>';
|
||||
'<div class="ext-dashboard-card-meta">' + (typeof esc === 'function' ? esc(pkg.type || '') : (pkg.type || '')) + ' \u00b7 ' + (typeof esc === 'function' ? esc(pkg.tier || '') : (pkg.tier || '')) + '</div>' +
|
||||
'<div class="ext-dashboard-card-desc">' + (typeof esc === 'function' ? esc(pkg.description || '') : (pkg.description || '')) + '</div>';
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
if (!packages.length) {
|
||||
container.innerHTML = '<div class="dashboard-empty">No packages installed</div>';
|
||||
container.innerHTML = '<div class="ext-dashboard-empty">No packages installed</div>';
|
||||
}
|
||||
} catch (_) {
|
||||
container.innerHTML = '<div class="dashboard-empty">Failed to load packages</div>';
|
||||
container.innerHTML = '<div class="ext-dashboard-empty">Failed to load packages</div>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
/* ── Surface Shell ─────────────────────────── */
|
||||
|
||||
.surface-editor {
|
||||
.ext-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
/* ── Topbar ────────────────────────────────── */
|
||||
|
||||
.editor-topbar {
|
||||
.ext-editor-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -32,7 +32,7 @@
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.editor-topbar-back {
|
||||
.ext-editor-topbar-back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -45,18 +45,18 @@
|
||||
transition: color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.editor-topbar-back:hover {
|
||||
.ext-editor-topbar-back:hover {
|
||||
color: var(--text, #eee);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.editor-topbar-sep {
|
||||
.ext-editor-topbar-sep {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: var(--border, #2a2a2e);
|
||||
}
|
||||
|
||||
.editor-topbar-name {
|
||||
.ext-editor-topbar-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text, #eee);
|
||||
@@ -64,11 +64,11 @@
|
||||
|
||||
/* ── Workspace Selector ────────────────────── */
|
||||
|
||||
.editor-ws-selector {
|
||||
.ext-editor-ws-selector {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.editor-ws-selector-btn {
|
||||
.ext-editor-ws-selector-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@@ -84,12 +84,12 @@
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.editor-ws-selector-btn:hover {
|
||||
.ext-editor-ws-selector-btn:hover {
|
||||
border-color: var(--border, #2a2a2e);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.editor-ws-dropdown {
|
||||
.ext-editor-ws-dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
@@ -106,14 +106,14 @@
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.editor-ws-dropdown.open { display: block; }
|
||||
.ext-editor-ws-dropdown.open { display: block; }
|
||||
|
||||
.editor-ws-list {
|
||||
.ext-editor-ws-list {
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.editor-ws-dropdown-item {
|
||||
.ext-editor-ws-dropdown-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
@@ -127,29 +127,29 @@
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.editor-ws-dropdown-item:hover {
|
||||
.ext-editor-ws-dropdown-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.editor-ws-dropdown-item.active {
|
||||
.ext-editor-ws-dropdown-item.active {
|
||||
color: var(--accent, #b38a4e);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.editor-ws-dropdown-divider {
|
||||
.ext-editor-ws-dropdown-divider {
|
||||
height: 1px;
|
||||
background: var(--border, #2a2a2e);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.editor-ws-new {
|
||||
.ext-editor-ws-new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--accent, #b38a4e);
|
||||
}
|
||||
|
||||
.editor-topbar-branch {
|
||||
.ext-editor-topbar-branch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -158,7 +158,7 @@
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.editor-topbar-branch-text {
|
||||
.ext-editor-topbar-branch-text {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--purple, #a078ff);
|
||||
@@ -167,7 +167,7 @@
|
||||
|
||||
/* ── Body ──────────────────────────────────── */
|
||||
|
||||
.editor-body {
|
||||
.ext-editor-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
@@ -175,14 +175,14 @@
|
||||
|
||||
/* ── Bootstrap (no workspace) ──────────────── */
|
||||
|
||||
.editor-bootstrap {
|
||||
.ext-editor-bootstrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.editor-bootstrap-card {
|
||||
.ext-editor-bootstrap-card {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: var(--bg-secondary, #151517);
|
||||
@@ -191,7 +191,7 @@
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.editor-bootstrap-input {
|
||||
.ext-editor-bootstrap-input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg, #0e0e10);
|
||||
@@ -205,11 +205,11 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.editor-bootstrap-input:focus {
|
||||
.ext-editor-bootstrap-input:focus {
|
||||
border-color: var(--accent, #b38a4e);
|
||||
}
|
||||
|
||||
.editor-bootstrap-btn {
|
||||
.ext-editor-bootstrap-btn {
|
||||
width: 100%;
|
||||
padding: 10px 16px;
|
||||
background: var(--accent, #b38a4e);
|
||||
@@ -223,11 +223,11 @@
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.editor-bootstrap-btn:hover { opacity: 0.9; }
|
||||
.editor-bootstrap-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.ext-editor-bootstrap-btn:hover { opacity: 0.9; }
|
||||
.ext-editor-bootstrap-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
/* Workspace list in bootstrap */
|
||||
.editor-bootstrap-ws-item {
|
||||
.ext-editor-bootstrap-ws-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -245,30 +245,30 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.editor-bootstrap-ws-item:hover {
|
||||
.ext-editor-bootstrap-ws-item:hover {
|
||||
border-color: var(--accent, #b38a4e);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.editor-bootstrap-ws-name {
|
||||
.ext-editor-bootstrap-ws-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.editor-bootstrap-ws-date {
|
||||
.ext-editor-bootstrap-ws-date {
|
||||
font-size: 11px;
|
||||
color: var(--text-3, #777);
|
||||
}
|
||||
|
||||
/* ── FileTree overrides (in editor context) ── */
|
||||
|
||||
.surface-editor .file-tree {
|
||||
.ext-editor .file-tree {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid var(--border, #2a2a2e);
|
||||
}
|
||||
|
||||
.surface-editor .file-tree-header {
|
||||
.ext-editor .file-tree-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@@ -276,7 +276,7 @@
|
||||
border-bottom: 1px solid var(--border, #2a2a2e);
|
||||
}
|
||||
|
||||
.surface-editor .file-tree-title {
|
||||
.ext-editor .file-tree-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-2, #999);
|
||||
@@ -285,13 +285,13 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.surface-editor .file-tree-items {
|
||||
.ext-editor .file-tree-items {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.surface-editor .file-tree-row {
|
||||
.ext-editor .file-tree-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -302,29 +302,29 @@
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.surface-editor .file-tree-row:hover {
|
||||
.ext-editor .file-tree-row:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.surface-editor .file-tree-row.active {
|
||||
.ext-editor .file-tree-row.active {
|
||||
background: var(--accent-dim, rgba(179, 138, 78, 0.15));
|
||||
color: var(--text, #eee);
|
||||
}
|
||||
|
||||
.surface-editor .file-tree-arrow {
|
||||
.ext-editor .file-tree-arrow {
|
||||
width: 12px;
|
||||
font-size: 10px;
|
||||
color: var(--text-3, #555);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.surface-editor .file-tree-icon {
|
||||
.ext-editor .file-tree-icon {
|
||||
font-size: 13px;
|
||||
width: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.surface-editor .file-tree-name {
|
||||
.ext-editor .file-tree-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -332,13 +332,13 @@
|
||||
}
|
||||
|
||||
/* Git status indicators */
|
||||
.surface-editor .file-tree-row.git-modified .file-tree-name { color: var(--warning, #e5a842); }
|
||||
.surface-editor .file-tree-row.git-added .file-tree-name { color: var(--success, #4caf50); }
|
||||
.surface-editor .file-tree-row.git-untracked .file-tree-name { color: var(--text-3, #555); font-style: italic; }
|
||||
.surface-editor .file-tree-row.git-deleted .file-tree-name { color: var(--danger, #f44336); text-decoration: line-through; }
|
||||
.ext-editor .file-tree-row.git-modified .file-tree-name { color: var(--warning, #e5a842); }
|
||||
.ext-editor .file-tree-row.git-added .file-tree-name { color: var(--success, #4caf50); }
|
||||
.ext-editor .file-tree-row.git-untracked .file-tree-name { color: var(--text-3, #555); font-style: italic; }
|
||||
.ext-editor .file-tree-row.git-deleted .file-tree-name { color: var(--danger, #f44336); text-decoration: line-through; }
|
||||
|
||||
/* Context menu */
|
||||
.file-tree-ctx-menu {
|
||||
.ext-editor-file-tree-ctx-menu {
|
||||
position: fixed;
|
||||
background: var(--bg-secondary, #1a1a1e);
|
||||
border: 1px solid var(--border, #2a2a2e);
|
||||
@@ -349,26 +349,26 @@
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.file-tree-ctx-item {
|
||||
.ext-editor-file-tree-ctx-item {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
color: var(--text, #eee);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-tree-ctx-item:hover {
|
||||
.ext-editor-file-tree-ctx-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
/* ── CodeEditor overrides ──────────────────── */
|
||||
|
||||
.surface-editor .code-editor {
|
||||
.ext-editor .code-editor {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-tabs {
|
||||
.ext-editor .code-editor-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
@@ -379,9 +379,9 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-tabs::-webkit-scrollbar { height: 0; }
|
||||
.ext-editor .code-editor-tabs::-webkit-scrollbar { height: 0; }
|
||||
|
||||
.surface-editor .code-editor-tab {
|
||||
.ext-editor .code-editor-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -395,14 +395,14 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-tab:hover { background: var(--bg-hover); }
|
||||
.surface-editor .code-editor-tab.active { color: var(--text, #eee); background: var(--bg, #0e0e10); }
|
||||
.surface-editor .code-editor-tab.modified .code-editor-tab-modified { color: var(--warning, #e5a842); }
|
||||
.ext-editor .code-editor-tab:hover { background: var(--bg-hover); }
|
||||
.ext-editor .code-editor-tab.active { color: var(--text, #eee); background: var(--bg, #0e0e10); }
|
||||
.ext-editor .code-editor-tab.modified .code-editor-tab-modified { color: var(--warning, #e5a842); }
|
||||
|
||||
.surface-editor .code-editor-tab-icon { font-size: 12px; }
|
||||
.surface-editor .code-editor-tab-modified { font-size: 10px; color: var(--text-3); }
|
||||
.ext-editor .code-editor-tab-icon { font-size: 12px; }
|
||||
.ext-editor .code-editor-tab-modified { font-size: 10px; color: var(--text-3); }
|
||||
|
||||
.surface-editor .code-editor-tab-close {
|
||||
.ext-editor .code-editor-tab-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-3, #555);
|
||||
@@ -414,32 +414,32 @@
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-tab-close:hover {
|
||||
.ext-editor .code-editor-tab-close:hover {
|
||||
background: var(--danger-dim, rgba(244, 67, 54, 0.15));
|
||||
color: var(--danger, #f44336);
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-content {
|
||||
.ext-editor .code-editor-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-welcome {
|
||||
.ext-editor .code-editor-welcome {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-cm-wrap {
|
||||
.ext-editor .code-editor-cm-wrap {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-cm-wrap .cm-editor {
|
||||
.ext-editor .code-editor-cm-wrap .cm-editor {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-statusbar {
|
||||
.ext-editor .code-editor-statusbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
@@ -453,7 +453,7 @@
|
||||
font-family: var(--mono, 'SF Mono', monospace);
|
||||
}
|
||||
|
||||
.surface-editor .code-editor-textarea-fallback {
|
||||
.ext-editor .code-editor-textarea-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg, #0e0e10);
|
||||
@@ -469,18 +469,18 @@
|
||||
|
||||
/* ── Tabbed assist pane overrides ──────────── */
|
||||
|
||||
.surface-editor .pane-tabbed {
|
||||
.ext-editor .pane-tabbed {
|
||||
border-left: 1px solid var(--border, #2a2a2e);
|
||||
}
|
||||
|
||||
/* ChatPane in editor tabbed pane */
|
||||
.surface-editor .chat-pane {
|
||||
.ext-editor .chat-pane {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
/* Notes in editor pane */
|
||||
.surface-editor .note-editor {
|
||||
.ext-editor .ext-notes-editor {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
@@ -488,20 +488,20 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.surface-editor .note-editor-list-view {
|
||||
.ext-editor .ext-notes-editor-list-view {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.surface-editor .notes-list {
|
||||
.ext-editor .ext-notes-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Compact notes toolbar for narrow pane */
|
||||
.surface-editor .notes-toolbar {
|
||||
.ext-editor .ext-notes-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
@@ -509,22 +509,22 @@
|
||||
border-bottom: 1px solid var(--border, #2a2a2e);
|
||||
}
|
||||
|
||||
.surface-editor .notes-toolbar .sw-btn--sm {
|
||||
.ext-editor .ext-notes-toolbar .sw-btn--sm {
|
||||
font-size: 11px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
.surface-editor .notes-search-row {
|
||||
.ext-editor .ext-notes-search-row {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.surface-editor .notes-filter-row {
|
||||
.ext-editor .ext-notes-filter-row {
|
||||
padding: 2px 8px 4px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.surface-editor .notes-filter-select {
|
||||
.ext-editor .ext-notes-filter-select {
|
||||
font-size: 11px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
// Wrap in surface container for CSS scoping
|
||||
const surface = document.createElement('div');
|
||||
surface.className = 'surface-editor';
|
||||
surface.className = 'ext-editor';
|
||||
surface.id = 'editorSurface';
|
||||
mount.appendChild(surface);
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
// Build body + bootstrap
|
||||
const body = document.createElement('div');
|
||||
body.className = 'editor-body';
|
||||
body.className = 'ext-editor-body';
|
||||
body.id = 'editorBody';
|
||||
surface.appendChild(body);
|
||||
|
||||
@@ -113,32 +113,32 @@
|
||||
|
||||
function _buildTopbar(wsName) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'editor-topbar';
|
||||
el.className = 'ext-editor-topbar';
|
||||
el.id = 'editorTopbar';
|
||||
el.innerHTML =
|
||||
'<a href="' + esc(base) + '/" class="editor-topbar-back" title="Back to chat">' +
|
||||
'<a href="' + esc(base) + '/" class="ext-editor-topbar-back" title="Back to chat">' +
|
||||
'<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>' +
|
||||
'Back' +
|
||||
'</a>' +
|
||||
'<div class="editor-topbar-sep"></div>' +
|
||||
'<div class="ext-editor-topbar-sep"></div>' +
|
||||
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--accent);"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>' +
|
||||
'<div class="editor-ws-selector" id="editorWsSelector">' +
|
||||
'<button class="editor-ws-selector-btn" id="editorWsSelectorBtn">' +
|
||||
'<div class="ext-editor-ws-selector" id="editorWsSelector">' +
|
||||
'<button class="ext-editor-ws-selector-btn" id="editorWsSelectorBtn">' +
|
||||
'<span id="editorWorkspaceName">' + esc(wsName || 'Editor') + '</span>' +
|
||||
'<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>' +
|
||||
'</button>' +
|
||||
'<div class="editor-ws-dropdown" id="editorWsDropdown">' +
|
||||
'<div id="editorWsList" class="editor-ws-list"></div>' +
|
||||
'<div class="editor-ws-dropdown-divider"></div>' +
|
||||
'<button class="editor-ws-dropdown-item editor-ws-new" id="editorWsNewBtn">' +
|
||||
'<div class="ext-editor-ws-dropdown" id="editorWsDropdown">' +
|
||||
'<div id="editorWsList" class="ext-editor-ws-list"></div>' +
|
||||
'<div class="ext-editor-ws-dropdown-divider"></div>' +
|
||||
'<button class="ext-editor-ws-dropdown-item ext-editor-ws-new" id="editorWsNewBtn">' +
|
||||
'<svg width="12" height="12" 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>' +
|
||||
'New Workspace' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="editor-topbar-branch" id="editorBranchBadge" style="display:none;">' +
|
||||
'<div class="ext-editor-topbar-branch" id="editorBranchBadge" style="display:none;">' +
|
||||
'<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" stroke-width="2"><circle cx="12" cy="18" r="3"/><circle cx="12" cy="6" r="3"/><line x1="12" y1="9" x2="12" y2="15"/></svg>' +
|
||||
'<span id="editorBranchName" class="editor-topbar-branch-text">main</span>' +
|
||||
'<span id="editorBranchName" class="ext-editor-topbar-branch-text">main</span>' +
|
||||
'</div>' +
|
||||
'<div style="flex:1;"></div>' +
|
||||
'<button class="icon-btn" id="editorRefreshBtn" title="Refresh files">' +
|
||||
@@ -151,11 +151,11 @@
|
||||
|
||||
function _buildBootstrap() {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'editor-bootstrap';
|
||||
el.className = 'ext-editor-bootstrap';
|
||||
el.id = 'editorBootstrap';
|
||||
el.style.display = 'none';
|
||||
el.innerHTML =
|
||||
'<div class="editor-bootstrap-card">' +
|
||||
'<div class="ext-editor-bootstrap-card">' +
|
||||
'<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="1.5" style="opacity:0.6;margin-bottom:12px;">' +
|
||||
'<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>' +
|
||||
'</svg>' +
|
||||
@@ -168,8 +168,8 @@
|
||||
'<span style="font-size:11px;color:var(--text-3);text-transform:uppercase;">or create new</span>' +
|
||||
'<div style="flex:1;height:1px;background:var(--border);"></div>' +
|
||||
'</div>' +
|
||||
'<input type="text" id="editorBootstrapName" class="editor-bootstrap-input" placeholder="Workspace name" value="workspace">' +
|
||||
'<button id="editorBootstrapBtn" class="editor-bootstrap-btn">Create Workspace</button>' +
|
||||
'<input type="text" id="editorBootstrapName" class="ext-editor-bootstrap-input" placeholder="Workspace name" value="workspace">' +
|
||||
'<button id="editorBootstrapBtn" class="ext-editor-bootstrap-btn">Create Workspace</button>' +
|
||||
'</div>';
|
||||
return el;
|
||||
}
|
||||
@@ -218,7 +218,7 @@
|
||||
}
|
||||
workspaces.forEach(ws => {
|
||||
const item = document.createElement('button');
|
||||
item.className = 'editor-ws-dropdown-item' + (ws.id === currentWsId ? ' active' : '');
|
||||
item.className = 'ext-editor-ws-dropdown-item' + (ws.id === currentWsId ? ' active' : '');
|
||||
item.textContent = ws.name || ws.id?.slice(0, 8);
|
||||
item.addEventListener('click', () => { window.location.href = base + '/s/editor?ws=' + ws.id; });
|
||||
listEl.appendChild(item);
|
||||
@@ -243,10 +243,10 @@
|
||||
listEl.innerHTML = '';
|
||||
workspaces.forEach(ws => {
|
||||
const item = document.createElement('button');
|
||||
item.className = 'editor-bootstrap-ws-item';
|
||||
item.className = 'ext-editor-bootstrap-ws-item';
|
||||
item.innerHTML =
|
||||
'<span class="editor-bootstrap-ws-name">' + esc(ws.name || ws.id?.slice(0, 8)) + '</span>' +
|
||||
'<span class="editor-bootstrap-ws-date">' + esc(ws.created_at ? new Date(ws.created_at).toLocaleDateString() : '') + '</span>';
|
||||
'<span class="ext-editor-bootstrap-ws-name">' + esc(ws.name || ws.id?.slice(0, 8)) + '</span>' +
|
||||
'<span class="ext-editor-bootstrap-ws-date">' + esc(ws.created_at ? new Date(ws.created_at).toLocaleDateString() : '') + '</span>';
|
||||
item.addEventListener('click', () => { window.location.href = base + '/s/editor?ws=' + ws.id; });
|
||||
listEl.appendChild(item);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* Git Board — Surface Styles */
|
||||
|
||||
.gb-shell {
|
||||
.ext-git-board-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
/* ── Header ──────────────────────────────── */
|
||||
|
||||
.gb-header {
|
||||
.ext-git-board-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -18,21 +18,21 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.gb-header__left,
|
||||
.gb-header__right {
|
||||
.ext-git-board-header__left,
|
||||
.ext-git-board-header__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.gb-title {
|
||||
.ext-git-board-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gb-repo-picker {
|
||||
.ext-git-board-repo-picker {
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
@@ -45,20 +45,20 @@
|
||||
|
||||
/* ── Connection Setup ─────────────────────── */
|
||||
|
||||
.gb-setup {
|
||||
.ext-git-board-setup {
|
||||
max-width: 480px;
|
||||
margin: 60px auto;
|
||||
text-align: center;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.gb-setup h2 {
|
||||
.ext-git-board-setup h2 {
|
||||
color: var(--text);
|
||||
font-size: 20px;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.gb-setup p {
|
||||
.ext-git-board-setup p {
|
||||
color: var(--text-2);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
@@ -66,14 +66,14 @@
|
||||
}
|
||||
|
||||
|
||||
.gb-setup__hint {
|
||||
.ext-git-board-setup__hint {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* ── Kanban Board ────────────────────────── */
|
||||
|
||||
.gb-board {
|
||||
.ext-git-board-board {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
@@ -82,7 +82,7 @@
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.gb-column {
|
||||
.ext-git-board-column {
|
||||
min-width: 260px;
|
||||
max-width: 320px;
|
||||
flex: 1;
|
||||
@@ -94,7 +94,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gb-column__header {
|
||||
.ext-git-board-column__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -102,7 +102,7 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.gb-column__title {
|
||||
.ext-git-board-column__title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
@@ -110,7 +110,7 @@
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.gb-column__count {
|
||||
.ext-git-board-column__count {
|
||||
background: var(--bg-raised);
|
||||
color: var(--text-2);
|
||||
font-size: 11px;
|
||||
@@ -119,7 +119,7 @@
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.gb-column__cards {
|
||||
.ext-git-board-column__cards {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
@@ -130,7 +130,7 @@
|
||||
|
||||
/* ── Cards ───────────────────────────────── */
|
||||
|
||||
.gb-card {
|
||||
.ext-git-board-card {
|
||||
display: block;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
@@ -142,35 +142,35 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gb-card:hover {
|
||||
.ext-git-board-card:hover {
|
||||
border-color: var(--accent);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.gb-card--pr {
|
||||
.ext-git-board-card--pr {
|
||||
border-left: 3px solid var(--accent);
|
||||
}
|
||||
|
||||
.gb-card__header {
|
||||
.ext-git-board-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.gb-card__number {
|
||||
.ext-git-board-card__number {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.gb-card__assignee {
|
||||
.ext-git-board-card__assignee {
|
||||
font-size: 11px;
|
||||
color: var(--accent);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.gb-card__branch {
|
||||
.ext-git-board-card__branch {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
@@ -181,26 +181,26 @@
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.gb-card__title {
|
||||
.ext-git-board-card__title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.gb-card__labels {
|
||||
.ext-git-board-card__labels {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.gb-card__labels .badge {
|
||||
.ext-git-board-card__labels .ext-git-board-badge {
|
||||
font-size: 10px;
|
||||
padding: 1px 6px;
|
||||
}
|
||||
|
||||
.gb-card__meta {
|
||||
.ext-git-board-card__meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 6px;
|
||||
@@ -210,25 +210,25 @@
|
||||
|
||||
/* ── DnD States ─────────────────────────── */
|
||||
|
||||
.gb-card[draggable="true"] {
|
||||
.ext-git-board-card[draggable="true"] {
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
.gb-card[draggable="true"]:active {
|
||||
.ext-git-board-card[draggable="true"]:active {
|
||||
cursor: grabbing;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.gb-column--dragover {
|
||||
.ext-git-board-column--dragover {
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 6%, var(--bg-surface));
|
||||
}
|
||||
.gb-column--dragover .gb-column__header {
|
||||
.ext-git-board-column--dragover .ext-git-board-column__header {
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── Empty state ─────────────────────────── */
|
||||
|
||||
.gb-empty {
|
||||
.ext-git-board-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -239,7 +239,7 @@
|
||||
|
||||
/* ── Issue Detail Modal ─────────────────── */
|
||||
|
||||
.gb-modal-overlay {
|
||||
.ext-git-board-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
@@ -251,7 +251,7 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.gb-modal {
|
||||
.ext-git-board-modal {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
@@ -264,7 +264,7 @@
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.gb-modal__header {
|
||||
.ext-git-board-modal__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
@@ -273,7 +273,7 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.gb-modal__title-row {
|
||||
.ext-git-board-modal__title-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
@@ -281,7 +281,7 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gb-modal__title {
|
||||
.ext-git-board-modal__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
@@ -289,7 +289,7 @@
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.gb-modal__close {
|
||||
.ext-git-board-modal__close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-3);
|
||||
@@ -299,19 +299,19 @@
|
||||
border-radius: var(--radius);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.gb-modal__close:hover {
|
||||
.ext-git-board-modal__close:hover {
|
||||
color: var(--text);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.gb-modal__body {
|
||||
.ext-git-board-modal__body {
|
||||
overflow-y: auto;
|
||||
padding: 16px 20px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.gb-modal__meta {
|
||||
.ext-git-board-modal__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
@@ -321,27 +321,27 @@
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.gb-modal__date {
|
||||
.ext-git-board-modal__date {
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.gb-modal__extlink {
|
||||
.ext-git-board-modal__extlink {
|
||||
margin-left: auto;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
}
|
||||
.gb-modal__extlink:hover {
|
||||
.ext-git-board-modal__extlink:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.gb-modal__description {
|
||||
.ext-git-board-modal__description {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.gb-modal__body-text {
|
||||
.ext-git-board-modal__body-text {
|
||||
font-family: var(--font);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
@@ -354,14 +354,14 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.gb-modal__empty {
|
||||
.ext-git-board-modal__empty {
|
||||
color: var(--text-3);
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gb-modal__section-title {
|
||||
.ext-git-board-modal__section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-2);
|
||||
@@ -372,31 +372,31 @@
|
||||
|
||||
/* ── Comments ────────────────────────────── */
|
||||
|
||||
.gb-comment {
|
||||
.ext-git-board-comment {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.gb-comment:last-child {
|
||||
.ext-git-board-comment:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.gb-comment__header {
|
||||
.ext-git-board-comment__header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.gb-comment__header strong {
|
||||
.ext-git-board-comment__header strong {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.gb-comment__date {
|
||||
.ext-git-board-comment__date {
|
||||
color: var(--text-3);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.gb-comment__body {
|
||||
.ext-git-board-comment__body {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--text);
|
||||
@@ -406,13 +406,13 @@
|
||||
|
||||
/* ── Add Comment ─────────────────────────── */
|
||||
|
||||
.gb-modal__add-comment {
|
||||
.ext-git-board-modal__add-comment {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.gb-modal__textarea {
|
||||
.ext-git-board-modal__textarea {
|
||||
width: 100%;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--border);
|
||||
@@ -425,11 +425,11 @@
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.gb-modal__textarea:focus {
|
||||
.ext-git-board-modal__textarea:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.gb-modal__actions {
|
||||
.ext-git-board-modal__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
@@ -438,11 +438,11 @@
|
||||
|
||||
/* ── Badge variants ──────────────────────── */
|
||||
|
||||
.badge--green {
|
||||
.ext-git-board-badge--green {
|
||||
background: var(--green);
|
||||
color: #fff;
|
||||
}
|
||||
.badge--muted {
|
||||
.ext-git-board-badge--muted {
|
||||
background: var(--bg-raised);
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
@@ -160,21 +160,21 @@
|
||||
return html`
|
||||
<div class="user-menu-container" ref=${menuRef}></div>
|
||||
${needsConn ? html`<${ConnectionSetup} />` : html`
|
||||
<div class="gb-shell">
|
||||
<header class="gb-header">
|
||||
<div class="gb-header__left">
|
||||
<h1 class="gb-title">Git Board</h1>
|
||||
<div class="ext-git-board-shell">
|
||||
<header class="ext-git-board-header">
|
||||
<div class="ext-git-board-header__left">
|
||||
<h1 class="ext-git-board-title">Git Board</h1>
|
||||
<${RepoPicker} repos=${repos} owner=${owner} repo=${repo}
|
||||
onSelect=${function (o, r) { setOwner(o); setRepo(r); }} />
|
||||
</div>
|
||||
<div class="gb-header__right">
|
||||
<div class="ext-git-board-header__right">
|
||||
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${loadBoard} disabled=${loading}>
|
||||
${loading ? '↻' : 'Refresh'}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
${board ? html`<${Board} data=${board} onDrop=${onDrop} onCardClick=${openModal} />` : html`
|
||||
<div class="gb-empty">${loading ? 'Loading…' : 'Select a repository'}</div>
|
||||
<div class="ext-git-board-empty">${loading ? 'Loading…' : 'Select a repository'}</div>
|
||||
`}
|
||||
</div>
|
||||
`}
|
||||
@@ -186,11 +186,11 @@
|
||||
|
||||
function ConnectionSetup() {
|
||||
return html`
|
||||
<div class="gb-shell">
|
||||
<header class="gb-header">
|
||||
<div class="gb-header__left"><h1 class="gb-title">Git Board</h1></div>
|
||||
<div class="ext-git-board-shell">
|
||||
<header class="ext-git-board-header">
|
||||
<div class="ext-git-board-header__left"><h1 class="ext-git-board-title">Git Board</h1></div>
|
||||
</header>
|
||||
<div class="gb-setup">
|
||||
<div class="ext-git-board-setup">
|
||||
<h2>Connect to Gitea</h2>
|
||||
<p>Git Board requires a Gitea connection to fetch repositories, issues, and pull requests.</p>
|
||||
<p>Ask your admin to add a <strong>Gitea</strong> connection in
|
||||
@@ -200,7 +200,7 @@
|
||||
onClick=${function () { window.location.href = base + '/settings'; }}>
|
||||
Open Settings
|
||||
</button>
|
||||
<p class="gb-setup__hint">Connections are managed centrally — no API tokens in extension settings.</p>
|
||||
<p class="ext-git-board-setup__hint">Connections are managed centrally — no API tokens in extension settings.</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -211,7 +211,7 @@
|
||||
function RepoPicker({ repos, owner, repo, onSelect }) {
|
||||
var current = owner + '/' + repo;
|
||||
return html`
|
||||
<select class="gb-repo-picker" value=${current}
|
||||
<select class="ext-git-board-repo-picker" value=${current}
|
||||
onChange=${function (e) {
|
||||
var parts = e.target.value.split('/');
|
||||
onSelect(parts[0], parts.slice(1).join('/'));
|
||||
@@ -233,7 +233,7 @@
|
||||
var inProgress = issues.filter(function (i) { return i.state === 'open' && !!i.assignee; });
|
||||
|
||||
return html`
|
||||
<div class="gb-board">
|
||||
<div class="ext-git-board-board">
|
||||
<${Column} id="open" title="Open" count=${openUnassigned.length} onDrop=${onDrop}>
|
||||
${openUnassigned.map(function (i) {
|
||||
return html`<${IssueCard} key=${i.number} issue=${i} onClick=${onCardClick} />`;
|
||||
@@ -268,19 +268,19 @@
|
||||
} : {};
|
||||
|
||||
return html`
|
||||
<div class="gb-column ${over ? 'gb-column--dragover' : ''}" ...${handlers}>
|
||||
<div class="gb-column__header">
|
||||
<span class="gb-column__title">${title}</span>
|
||||
<span class="gb-column__count">${count || 0}</span>
|
||||
<div class="ext-git-board-column ${over ? 'ext-git-board-column--dragover' : ''}" ...${handlers}>
|
||||
<div class="ext-git-board-column__header">
|
||||
<span class="ext-git-board-column__title">${title}</span>
|
||||
<span class="ext-git-board-column__count">${count || 0}</span>
|
||||
</div>
|
||||
<div class="gb-column__cards">${children}</div>
|
||||
<div class="ext-git-board-column__cards">${children}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function IssueCard({ issue, onClick }) {
|
||||
return html`
|
||||
<div class="gb-card" draggable="true"
|
||||
<div class="ext-git-board-card" draggable="true"
|
||||
onDragStart=${function (e) {
|
||||
e.dataTransfer.setData('text/plain', String(issue.number));
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
@@ -289,14 +289,14 @@
|
||||
e.preventDefault();
|
||||
if (onClick) onClick(issue.number);
|
||||
}}>
|
||||
<div class="gb-card__header">
|
||||
<span class="gb-card__number">#${issue.number}</span>
|
||||
${issue.assignee && html`<span class="gb-card__assignee">@${esc(issue.assignee)}</span>`}
|
||||
<div class="ext-git-board-card__header">
|
||||
<span class="ext-git-board-card__number">#${issue.number}</span>
|
||||
${issue.assignee && html`<span class="ext-git-board-card__assignee">@${esc(issue.assignee)}</span>`}
|
||||
</div>
|
||||
<div class="gb-card__title">${esc(issue.title)}</div>
|
||||
<div class="gb-card__labels">
|
||||
<div class="ext-git-board-card__title">${esc(issue.title)}</div>
|
||||
<div class="ext-git-board-card__labels">
|
||||
${(issue.labels || []).map(function (l) {
|
||||
return html`<span key=${l} class="badge">${esc(l)}</span>`;
|
||||
return html`<span key=${l} class="ext-git-board-badge">${esc(l)}</span>`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
@@ -305,13 +305,13 @@
|
||||
|
||||
function PRCard({ pr }) {
|
||||
return html`
|
||||
<a class="gb-card gb-card--pr" href=${pr.html_url} target="_blank" rel="noopener">
|
||||
<div class="gb-card__header">
|
||||
<span class="gb-card__number">#${pr.number}</span>
|
||||
<span class="gb-card__branch">${esc(pr.head)} → ${esc(pr.base)}</span>
|
||||
<a class="ext-git-board-card ext-git-board-card--pr" href=${pr.html_url} target="_blank" rel="noopener">
|
||||
<div class="ext-git-board-card__header">
|
||||
<span class="ext-git-board-card__number">#${pr.number}</span>
|
||||
<span class="ext-git-board-card__branch">${esc(pr.head)} → ${esc(pr.base)}</span>
|
||||
</div>
|
||||
<div class="gb-card__title">${esc(pr.title)}</div>
|
||||
<div class="gb-card__meta">
|
||||
<div class="ext-git-board-card__title">${esc(pr.title)}</div>
|
||||
<div class="ext-git-board-card__meta">
|
||||
<span>@${esc(pr.user)}</span>
|
||||
<span>${timeAgo(pr.created_at)}</span>
|
||||
</div>
|
||||
@@ -387,68 +387,68 @@
|
||||
}, [issue, owner, repo, number]);
|
||||
|
||||
return html`
|
||||
<div class="gb-modal-overlay" onClick=${function (e) {
|
||||
if (e.target.classList.contains('gb-modal-overlay')) onClose(changed);
|
||||
<div class="ext-git-board-modal-overlay" onClick=${function (e) {
|
||||
if (e.target.classList.contains('ext-git-board-modal-overlay')) onClose(changed);
|
||||
}}>
|
||||
<div class="gb-modal">
|
||||
<div class="gb-modal__header">
|
||||
<div class="gb-modal__title-row">
|
||||
<div class="ext-git-board-modal">
|
||||
<div class="ext-git-board-modal__header">
|
||||
<div class="ext-git-board-modal__title-row">
|
||||
${loading ? 'Loading…' : html`
|
||||
<span class="gb-card__number">#${number}</span>
|
||||
<h2 class="gb-modal__title">${esc(issue && issue.title)}</h2>
|
||||
<span class="ext-git-board-card__number">#${number}</span>
|
||||
<h2 class="ext-git-board-modal__title">${esc(issue && issue.title)}</h2>
|
||||
`}
|
||||
</div>
|
||||
<button class="gb-modal__close" onClick=${function () { onClose(changed); }}>✕</button>
|
||||
<button class="ext-git-board-modal__close" onClick=${function () { onClose(changed); }}>✕</button>
|
||||
</div>
|
||||
|
||||
${!loading && issue && html`
|
||||
<div class="gb-modal__body" ref=${bodyRef}>
|
||||
<div class="gb-modal__meta">
|
||||
<span class="badge ${issue.state === 'open' ? 'badge--green' : 'badge--muted'}">${issue.state}</span>
|
||||
${issue.assignee && html`<span class="gb-card__assignee">@${esc(issue.assignee)}</span>`}
|
||||
${issue.created_at && html`<span class="gb-modal__date">${new Date(issue.created_at).toLocaleDateString()}</span>`}
|
||||
<div class="ext-git-board-modal__body" ref=${bodyRef}>
|
||||
<div class="ext-git-board-modal__meta">
|
||||
<span class="ext-git-board-badge ${issue.state === 'open' ? 'ext-git-board-badge--green' : 'ext-git-board-badge--muted'}">${issue.state}</span>
|
||||
${issue.assignee && html`<span class="ext-git-board-card__assignee">@${esc(issue.assignee)}</span>`}
|
||||
${issue.created_at && html`<span class="ext-git-board-modal__date">${new Date(issue.created_at).toLocaleDateString()}</span>`}
|
||||
<a href=${issue.html_url || '#'} target="_blank" rel="noopener"
|
||||
class="gb-modal__extlink">Open in Gitea ↗</a>
|
||||
class="ext-git-board-modal__extlink">Open in Gitea ↗</a>
|
||||
</div>
|
||||
|
||||
${issue.labels && issue.labels.length > 0 && html`
|
||||
<div class="gb-card__labels" style="margin-bottom:12px;">
|
||||
${issue.labels.map(function (l) { return html`<span key=${l} class="badge">${esc(l)}</span>`; })}
|
||||
<div class="ext-git-board-card__labels" style="margin-bottom:12px;">
|
||||
${issue.labels.map(function (l) { return html`<span key=${l} class="ext-git-board-badge">${esc(l)}</span>`; })}
|
||||
</div>
|
||||
`}
|
||||
|
||||
<div class="gb-modal__description">
|
||||
${issue.body ? html`<pre class="gb-modal__body-text">${esc(issue.body)}</pre>`
|
||||
: html`<p class="gb-modal__empty">No description.</p>`}
|
||||
<div class="ext-git-board-modal__description">
|
||||
${issue.body ? html`<pre class="ext-git-board-modal__body-text">${esc(issue.body)}</pre>`
|
||||
: html`<p class="ext-git-board-modal__empty">No description.</p>`}
|
||||
</div>
|
||||
|
||||
<div class="gb-modal__comments">
|
||||
<h3 class="gb-modal__section-title">Comments (${(issue.comments || []).length})</h3>
|
||||
<div class="ext-git-board-modal__comments">
|
||||
<h3 class="ext-git-board-modal__section-title">Comments (${(issue.comments || []).length})</h3>
|
||||
${(issue.comments || []).length === 0 && html`
|
||||
<p class="gb-modal__empty">No comments yet.</p>
|
||||
<p class="ext-git-board-modal__empty">No comments yet.</p>
|
||||
`}
|
||||
${(issue.comments || []).map(function (c, i) {
|
||||
return html`
|
||||
<div class="gb-comment" key=${i}>
|
||||
<div class="gb-comment__header">
|
||||
<div class="ext-git-board-comment" key=${i}>
|
||||
<div class="ext-git-board-comment__header">
|
||||
<strong>@${esc(c.user)}</strong>
|
||||
<span class="gb-comment__date">${timeAgo(c.created_at)}</span>
|
||||
<span class="ext-git-board-comment__date">${timeAgo(c.created_at)}</span>
|
||||
</div>
|
||||
<div class="gb-comment__body">${esc(c.body)}</div>
|
||||
<div class="ext-git-board-comment__body">${esc(c.body)}</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div class="gb-modal__add-comment">
|
||||
<textarea class="gb-modal__textarea" rows="3"
|
||||
<div class="ext-git-board-modal__add-comment">
|
||||
<textarea class="ext-git-board-modal__textarea" rows="3"
|
||||
placeholder="Add a comment…"
|
||||
value=${comment}
|
||||
onInput=${function (e) { setComment(e.target.value); }}
|
||||
onKeyDown=${function (e) {
|
||||
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) postComment();
|
||||
}} />
|
||||
<div class="gb-modal__actions">
|
||||
<div class="ext-git-board-modal__actions">
|
||||
<button class="sw-btn sw-btn--primary sw-btn--sm" disabled=${posting || !comment.trim()}
|
||||
onClick=${postComment}>
|
||||
${posting ? 'Posting…' : 'Comment'}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
Uses CSS custom properties from the platform theme system (variables.css).
|
||||
See EXTENSION-SURFACES.md for the full property reference. */
|
||||
|
||||
.hello-dashboard { max-width: 720px; margin: 0 auto; padding: 40px 24px; }
|
||||
.hello-header { margin-bottom: 32px; }
|
||||
.hello-header h1 { font-size: 28px; font-weight: 700; color: var(--text); margin: 0 0 8px 0; }
|
||||
.hello-subtitle { font-size: 14px; color: var(--text-2); margin: 0; }
|
||||
.hello-subtitle code,
|
||||
.hello-card-value code { background: var(--bg-raised); padding: 2px 6px; border-radius: 4px; font-size: 13px; }
|
||||
.hello-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||||
.hello-card { background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px; }
|
||||
.hello-card-title { font-size: 12px; font-weight: 600; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }
|
||||
.hello-card-value { font-size: 20px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
|
||||
.hello-card-detail { font-size: 12px; color: var(--text-3); }
|
||||
.hello-actions { display: flex; gap: 12px; margin-bottom: 24px; }
|
||||
.hello-manifest { background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-size: 12px; color: var(--text-2); overflow-x: auto; white-space: pre-wrap; font-family: var(--mono); line-height: 1.5; }
|
||||
.ext-hello-dashboard { max-width: 720px; margin: 0 auto; padding: 40px 24px; }
|
||||
.ext-hello-dashboard-header { margin-bottom: 32px; }
|
||||
.ext-hello-dashboard-header h1 { font-size: 28px; font-weight: 700; color: var(--text); margin: 0 0 8px 0; }
|
||||
.ext-hello-dashboard-subtitle { font-size: 14px; color: var(--text-2); margin: 0; }
|
||||
.ext-hello-dashboard-subtitle code,
|
||||
.ext-hello-dashboard-card-value code { background: var(--bg-raised); padding: 2px 6px; border-radius: 4px; font-size: 13px; }
|
||||
.ext-hello-dashboard-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||||
.ext-hello-dashboard-card { background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px; }
|
||||
.ext-hello-dashboard-card-title { font-size: 12px; font-weight: 600; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }
|
||||
.ext-hello-dashboard-card-value { font-size: 20px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
|
||||
.ext-hello-dashboard-card-detail { font-size: 12px; color: var(--text-3); }
|
||||
.ext-hello-dashboard-actions { display: flex; gap: 12px; margin-bottom: 24px; }
|
||||
.ext-hello-dashboard-manifest { background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-size: 12px; color: var(--text-2); overflow-x: auto; white-space: pre-wrap; font-family: var(--mono); line-height: 1.5; }
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
var name = user.display_name || user.username || 'World';
|
||||
|
||||
mount.innerHTML =
|
||||
'<div class="hello-dashboard">' +
|
||||
'<div class="hello-header">' +
|
||||
'<div class="ext-hello-dashboard">' +
|
||||
'<div class="ext-hello-dashboard-header">' +
|
||||
'<h1>Hello, ' + esc(name) + '!</h1>' +
|
||||
'<p class="hello-subtitle">Extension surface <code>' + esc(manifest.id || 'unknown') + '</code> loaded successfully.</p>' +
|
||||
'<p class="ext-hello-dashboard-subtitle">Extension surface <code>' + esc(manifest.id || 'unknown') + '</code> loaded successfully.</p>' +
|
||||
'</div>' +
|
||||
'<div class="hello-cards">' +
|
||||
'<div class="ext-hello-dashboard-cards">' +
|
||||
card('Platform Access', (typeof UI !== 'undefined' ? '\u2713 Connected' : '\u2717 Unavailable'),
|
||||
'API, Theme, UI primitives available', 'var(--accent)') +
|
||||
card('Theme', isDark ? '\uD83C\uDF19 Dark' : '\u2600\uFE0F Light',
|
||||
@@ -33,11 +33,11 @@
|
||||
card('Route', '<code>' + esc(manifest.route || '/s/hello-dashboard') + '</code>',
|
||||
'Registered via surface manifest', '') +
|
||||
'</div>' +
|
||||
'<div class="hello-actions">' +
|
||||
'<div class="ext-hello-dashboard-actions">' +
|
||||
'<button class="sw-btn sw-btn--primary sw-btn--md" id="helloToast">Show Toast</button>' +
|
||||
'<button class="sw-btn sw-btn--secondary sw-btn--md" id="helloApi">Test API</button>' +
|
||||
'</div>' +
|
||||
'<pre class="hello-manifest">' + esc(JSON.stringify(manifest, null, 2)) + '</pre>' +
|
||||
'<pre class="ext-hello-dashboard-manifest">' + esc(JSON.stringify(manifest, null, 2)) + '</pre>' +
|
||||
'</div>';
|
||||
|
||||
// Wire toast button
|
||||
@@ -69,10 +69,10 @@
|
||||
|
||||
function card(title, value, detail, color) {
|
||||
var style = color ? ' style="color:' + color + ';"' : '';
|
||||
return '<div class="hello-card">' +
|
||||
'<div class="hello-card-title">' + esc(title) + '</div>' +
|
||||
'<div class="hello-card-value"' + style + '>' + value + '</div>' +
|
||||
'<div class="hello-card-detail">' + esc(detail) + '</div>' +
|
||||
return '<div class="ext-hello-dashboard-card">' +
|
||||
'<div class="ext-hello-dashboard-card-title">' + esc(title) + '</div>' +
|
||||
'<div class="ext-hello-dashboard-card-value"' + style + '>' + value + '</div>' +
|
||||
'<div class="ext-hello-dashboard-card-detail">' + esc(detail) + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
/* ICD Test Runner — Surface Styles */
|
||||
|
||||
#extension-mount {
|
||||
.ext-icd-test-runner-root {
|
||||
font-family: var(--font, 'DM Sans', sans-serif);
|
||||
}
|
||||
|
||||
#extension-mount h1 {
|
||||
.ext-icd-test-runner-root h1 {
|
||||
font-family: var(--font, 'DM Sans', sans-serif);
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
#extension-mount table {
|
||||
.ext-icd-test-runner-root table {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
#extension-mount table td,
|
||||
#extension-mount table th {
|
||||
.ext-icd-test-runner-root table td,
|
||||
.ext-icd-test-runner-root table th {
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
#extension-mount table tbody tr:last-child {
|
||||
.ext-icd-test-runner-root table tbody tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#extension-mount table tbody tr:hover {
|
||||
.ext-icd-test-runner-root table tbody tr:hover {
|
||||
background: var(--bg-hover) !important;
|
||||
}
|
||||
|
||||
/* Buttons — uses kernel sw-btn system, minor overrides for weight */
|
||||
#extension-mount .sw-btn {
|
||||
.ext-icd-test-runner-root .sw-btn {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Status dot animation for running state */
|
||||
@keyframes pulse-dot {
|
||||
@keyframes ext-icd-test-runner-pulse-dot {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
var mount = document.getElementById('extension-mount');
|
||||
if (!mount) return;
|
||||
mount.classList.add('ext-icd-test-runner-root');
|
||||
|
||||
// ─── Boot Preact SDK (v0.37.14) ───────────────────────────
|
||||
// Extension surfaces don't load Preact vendors or the SDK.
|
||||
|
||||
@@ -337,7 +337,7 @@
|
||||
await T.test('sdk', 'pipe-render', 'Render filter modifies DOM', async function () {
|
||||
sw.pipe.render(1, function (ctx) {
|
||||
if (ctx.element.dataset.sdkRenderTest) {
|
||||
ctx.element.classList.add('sdk-render-modified');
|
||||
ctx.element.classList.add('ext-icd-test-runner-sdk-render-modified');
|
||||
}
|
||||
return ctx;
|
||||
}, { source: '_test-render-dom' });
|
||||
@@ -348,7 +348,7 @@
|
||||
element: el, message: null,
|
||||
channel: { id: 'fake', type: 'direct' }
|
||||
});
|
||||
T.assert(el.classList.contains('sdk-render-modified'), 'element should have class');
|
||||
T.assert(el.classList.contains('ext-icd-test-runner-sdk-render-modified'), 'element should have class');
|
||||
});
|
||||
|
||||
await T.test('sdk', 'pipe-render', 'Priority ordering in render chain', async function () {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/* ── Notes Surface Styles ─────────────────── */
|
||||
|
||||
.notes-app {
|
||||
.ext-notes-app {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Sidebar ─────────────────────────────── */
|
||||
.notes-sidebar {
|
||||
.ext-notes-sidebar {
|
||||
width: 280px;
|
||||
min-width: 220px;
|
||||
border-right: 1px solid var(--border);
|
||||
@@ -16,7 +16,7 @@
|
||||
background: var(--bg-raised);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.notes-sidebar__header {
|
||||
.ext-notes-sidebar__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -24,30 +24,30 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.notes-sidebar__title {
|
||||
.ext-notes-sidebar__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
flex: 1;
|
||||
}
|
||||
.notes-sidebar__count {
|
||||
.ext-notes-sidebar__count {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.notes-sidebar__actions {
|
||||
.ext-notes-sidebar__actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* ── Folder Tree ────────────────────────── */
|
||||
.folder-tree {
|
||||
.ext-notes-folder-tree {
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
max-height: 40%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.folder-tree__item {
|
||||
.ext-notes-folder-tree__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -60,33 +60,33 @@
|
||||
transition: var(--transition);
|
||||
user-select: none;
|
||||
}
|
||||
.folder-tree__item:hover {
|
||||
.ext-notes-folder-tree__item:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text);
|
||||
}
|
||||
.folder-tree__item--active {
|
||||
.ext-notes-folder-tree__item--active {
|
||||
background: var(--bg-active);
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
.folder-tree__toggle {
|
||||
.ext-notes-folder-tree__toggle {
|
||||
width: 14px;
|
||||
font-size: 9px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.folder-tree__icon {
|
||||
.ext-notes-folder-tree__icon {
|
||||
font-size: 13px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.folder-tree__name {
|
||||
.ext-notes-folder-tree__name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
.folder-tree__edit {
|
||||
.ext-notes-folder-tree__edit {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
padding: 1px 4px;
|
||||
@@ -97,23 +97,23 @@
|
||||
font-family: var(--font);
|
||||
outline: none;
|
||||
}
|
||||
.folder-tree__add {
|
||||
.ext-notes-folder-tree__add {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.folder-tree__add:hover {
|
||||
.ext-notes-folder-tree__add:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── Search ──────────────────────────────── */
|
||||
.notes-search {
|
||||
.ext-notes-search {
|
||||
padding: 8px 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.notes-search input {
|
||||
.ext-notes-search input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
font-size: 13px;
|
||||
@@ -123,27 +123,27 @@
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--font);
|
||||
}
|
||||
.notes-search input:focus {
|
||||
.ext-notes-search input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.notes-search input::placeholder {
|
||||
.ext-notes-search input::placeholder {
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* ── Note List ───────────────────────────── */
|
||||
.notes-list {
|
||||
.ext-notes-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.notes-list__empty {
|
||||
.ext-notes-list__empty {
|
||||
padding: 40px 16px;
|
||||
text-align: center;
|
||||
color: var(--text-3);
|
||||
font-size: 13px;
|
||||
}
|
||||
.notes-list__loading {
|
||||
.ext-notes-list__loading {
|
||||
padding: 40px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -151,20 +151,20 @@
|
||||
}
|
||||
|
||||
/* ── Note Card (sidebar item) ────────────── */
|
||||
.note-card {
|
||||
.ext-notes-note-card {
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.note-card:hover {
|
||||
.ext-notes-note-card:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.note-card--active {
|
||||
.ext-notes-note-card--active {
|
||||
background: var(--bg-active);
|
||||
}
|
||||
.note-card__title {
|
||||
.ext-notes-note-card__title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
@@ -175,11 +175,11 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.note-card__pin {
|
||||
.ext-notes-note-card__pin {
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.note-card__snippet {
|
||||
.ext-notes-note-card__snippet {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
margin-top: 3px;
|
||||
@@ -188,21 +188,21 @@
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.note-card__date {
|
||||
.ext-notes-note-card__date {
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
/* ── Editor Pane ─────────────────────────── */
|
||||
.notes-editor {
|
||||
.ext-notes-editor {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.notes-editor__empty {
|
||||
.ext-notes-editor__empty {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -212,7 +212,7 @@
|
||||
}
|
||||
|
||||
/* ── Editor Header ───────────────────────── */
|
||||
.notes-editor__header {
|
||||
.ext-notes-editor__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -220,7 +220,7 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.notes-editor__title-input {
|
||||
.ext-notes-editor__title-input {
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
@@ -231,17 +231,17 @@
|
||||
padding: 4px 0;
|
||||
font-family: var(--font);
|
||||
}
|
||||
.notes-editor__title-input::placeholder {
|
||||
.ext-notes-editor__title-input::placeholder {
|
||||
color: var(--text-3);
|
||||
}
|
||||
.notes-editor__actions {
|
||||
.ext-notes-editor__actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ── Folder select in editor ────────────── */
|
||||
.notes-editor__folder-select {
|
||||
.ext-notes-editor__folder-select {
|
||||
font-size: 12px;
|
||||
padding: 3px 6px;
|
||||
border: 1px solid var(--border);
|
||||
@@ -252,13 +252,13 @@
|
||||
cursor: pointer;
|
||||
max-width: 140px;
|
||||
}
|
||||
.notes-editor__folder-select:focus {
|
||||
.ext-notes-editor__folder-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── Editor Content (body + outline wrapper) */
|
||||
.notes-editor__content {
|
||||
.ext-notes-editor__content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
@@ -266,20 +266,20 @@
|
||||
}
|
||||
|
||||
/* ── Editor Body ─────────────────────────── */
|
||||
.notes-editor__body {
|
||||
.ext-notes-editor__body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.notes-editor__body--split {
|
||||
.ext-notes-editor__body--split {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.notes-editor__body--split .notes-preview {
|
||||
.ext-notes-editor__body--split .ext-notes-preview {
|
||||
border-left: 1px solid var(--border);
|
||||
}
|
||||
.notes-editor__textarea {
|
||||
.ext-notes-editor__textarea {
|
||||
flex: 1;
|
||||
resize: none;
|
||||
padding: 20px;
|
||||
@@ -292,39 +292,39 @@
|
||||
font-family: var(--mono);
|
||||
tab-size: 2;
|
||||
}
|
||||
.notes-editor__textarea::placeholder {
|
||||
.ext-notes-editor__textarea::placeholder {
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* ── CodeMirror 6 container ─────────────── */
|
||||
.notes-editor__cm {
|
||||
.ext-notes-editor__cm {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.notes-editor__cm .cm-editor {
|
||||
.ext-notes-editor__cm .cm-editor {
|
||||
height: 100%;
|
||||
max-height: none;
|
||||
}
|
||||
.notes-editor__cm .cm-scroller {
|
||||
.ext-notes-editor__cm .cm-scroller {
|
||||
overflow: auto;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
/* ── Preview ─────────────────────────────── */
|
||||
.notes-preview {
|
||||
.ext-notes-preview {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
border-left: 1px solid var(--border);
|
||||
background: var(--bg-surface);
|
||||
}
|
||||
.notes-preview h1 { font-size: 24px; font-weight: 700; color: var(--text); margin: 0 0 12px; }
|
||||
.notes-preview h2 { font-size: 20px; font-weight: 600; color: var(--text); margin: 20px 0 8px; }
|
||||
.notes-preview h3 { font-size: 16px; font-weight: 600; color: var(--text); margin: 16px 0 6px; }
|
||||
.notes-preview p { font-size: 14px; color: var(--text); line-height: 1.7; margin: 0 0 10px; }
|
||||
.notes-preview ul, .notes-preview ol { padding-left: 24px; margin: 0 0 10px; color: var(--text); font-size: 14px; line-height: 1.7; }
|
||||
.notes-preview li { margin-bottom: 2px; }
|
||||
.notes-preview code {
|
||||
.ext-notes-preview h1 { font-size: 24px; font-weight: 700; color: var(--text); margin: 0 0 12px; }
|
||||
.ext-notes-preview h2 { font-size: 20px; font-weight: 600; color: var(--text); margin: 20px 0 8px; }
|
||||
.ext-notes-preview h3 { font-size: 16px; font-weight: 600; color: var(--text); margin: 16px 0 6px; }
|
||||
.ext-notes-preview p { font-size: 14px; color: var(--text); line-height: 1.7; margin: 0 0 10px; }
|
||||
.ext-notes-preview ul, .ext-notes-preview ol { padding-left: 24px; margin: 0 0 10px; color: var(--text); font-size: 14px; line-height: 1.7; }
|
||||
.ext-notes-preview li { margin-bottom: 2px; }
|
||||
.ext-notes-preview code {
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
background: var(--bg-raised);
|
||||
@@ -332,7 +332,7 @@
|
||||
border-radius: 3px;
|
||||
color: var(--accent);
|
||||
}
|
||||
.notes-preview pre {
|
||||
.ext-notes-preview pre {
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
@@ -340,30 +340,30 @@
|
||||
overflow-x: auto;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
.notes-preview pre code {
|
||||
.ext-notes-preview pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
}
|
||||
.notes-preview blockquote {
|
||||
.ext-notes-preview blockquote {
|
||||
border-left: 3px solid var(--accent);
|
||||
margin: 0 0 12px;
|
||||
padding: 4px 16px;
|
||||
color: var(--text-2);
|
||||
}
|
||||
.notes-preview a { color: var(--accent); text-decoration: none; }
|
||||
.notes-preview a:hover { text-decoration: underline; }
|
||||
.notes-preview hr {
|
||||
.ext-notes-preview a { color: var(--accent); text-decoration: none; }
|
||||
.ext-notes-preview a:hover { text-decoration: underline; }
|
||||
.ext-notes-preview hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 16px 0;
|
||||
}
|
||||
.notes-preview strong { font-weight: 600; }
|
||||
.notes-preview em { font-style: italic; }
|
||||
.ext-notes-preview strong { font-weight: 600; }
|
||||
.ext-notes-preview em { font-style: italic; }
|
||||
|
||||
/* ── Toggle button ───────────────────────── */
|
||||
.notes-toggle {
|
||||
.ext-notes-toggle {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--border);
|
||||
@@ -373,11 +373,11 @@
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.notes-toggle:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.notes-toggle--active { background: var(--accent); color: #fff; border-color: var(--accent); }
|
||||
.ext-notes-toggle:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.ext-notes-toggle--active { background: var(--accent); color: #fff; border-color: var(--accent); }
|
||||
|
||||
/* ── Inline buttons ──────────────────────── */
|
||||
.notes-btn {
|
||||
.ext-notes-btn {
|
||||
border: none;
|
||||
background: var(--bg-raised);
|
||||
color: var(--text-2);
|
||||
@@ -387,23 +387,23 @@
|
||||
padding: 4px 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.notes-btn:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.notes-btn--danger:hover { background: var(--danger-dim); color: var(--danger); }
|
||||
.notes-btn--accent { background: var(--accent); color: #fff; }
|
||||
.notes-btn--accent:hover { opacity: 0.9; color: #fff; }
|
||||
.ext-notes-btn:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.ext-notes-btn--danger:hover { background: var(--danger-dim); color: var(--danger); }
|
||||
.ext-notes-btn--accent { background: var(--accent); color: #fff; }
|
||||
.ext-notes-btn--accent:hover { opacity: 0.9; color: #fff; }
|
||||
|
||||
/* ── Saved indicator ─────────────────────── */
|
||||
.notes-saved {
|
||||
.ext-notes-saved {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
padding: 2px 8px;
|
||||
}
|
||||
.notes-saved--dirty {
|
||||
.ext-notes-saved--dirty {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
/* ── Tag Pills (shared) ─────────────────── */
|
||||
.tag-pill {
|
||||
.ext-notes-tag-pill {
|
||||
display: inline-block;
|
||||
padding: 1px 8px;
|
||||
font-size: 11px;
|
||||
@@ -417,23 +417,23 @@
|
||||
line-height: 1.6;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.tag-pill:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.tag-pill--active { background: var(--accent); color: #fff; border-color: var(--accent); }
|
||||
.tag-pill__remove {
|
||||
.ext-notes-tag-pill:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.ext-notes-tag-pill--active { background: var(--accent); color: #fff; border-color: var(--accent); }
|
||||
.ext-notes-tag-pill__remove {
|
||||
margin-left: 4px;
|
||||
font-size: 10px;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tag-pill__remove:hover { opacity: 1; }
|
||||
.ext-notes-tag-pill__remove:hover { opacity: 1; }
|
||||
|
||||
/* ── Tag Filter (sidebar) ──────────────── */
|
||||
.tag-filter {
|
||||
.ext-notes-tag-filter {
|
||||
padding: 6px 14px 4px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tag-filter__label {
|
||||
.ext-notes-tag-filter__label {
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
margin-bottom: 3px;
|
||||
@@ -441,7 +441,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.tag-filter__clear {
|
||||
.ext-notes-tag-filter__clear {
|
||||
font-size: 10px;
|
||||
color: var(--accent);
|
||||
cursor: pointer;
|
||||
@@ -450,18 +450,18 @@
|
||||
padding: 0;
|
||||
font-family: var(--font);
|
||||
}
|
||||
.tag-filter__clear:hover { text-decoration: underline; }
|
||||
.tag-filter__pills {
|
||||
.ext-notes-tag-filter__clear:hover { text-decoration: underline; }
|
||||
.ext-notes-tag-filter__pills {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 3px;
|
||||
max-height: 60px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.tag-filter__pills .tag-pill { font-size: 10px; padding: 0 6px; }
|
||||
.ext-notes-tag-filter__pills .ext-notes-tag-pill { font-size: 10px; padding: 0 6px; }
|
||||
|
||||
/* ── Tag Input (editor) ────────────────── */
|
||||
.tag-input {
|
||||
.ext-notes-tag-input {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
@@ -470,7 +470,7 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
position: relative;
|
||||
}
|
||||
.tag-input__field {
|
||||
.ext-notes-tag-input__field {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 12px;
|
||||
@@ -481,11 +481,11 @@
|
||||
padding: 2px 0;
|
||||
font-family: var(--font);
|
||||
}
|
||||
.tag-input__field::placeholder { color: var(--text-3); }
|
||||
.tag-input .tag-pill { font-size: 11px; }
|
||||
.ext-notes-tag-input__field::placeholder { color: var(--text-3); }
|
||||
.ext-notes-tag-input .ext-notes-tag-pill { font-size: 11px; }
|
||||
|
||||
/* ── Tag Autocomplete ──────────────────── */
|
||||
.tag-autocomplete {
|
||||
.ext-notes-tag-autocomplete {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 20px;
|
||||
@@ -498,23 +498,23 @@
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
min-width: 140px;
|
||||
}
|
||||
.tag-autocomplete__item {
|
||||
.ext-notes-tag-autocomplete__item {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: var(--text);
|
||||
}
|
||||
.tag-autocomplete__item:hover { background: var(--bg-hover); }
|
||||
.ext-notes-tag-autocomplete__item:hover { background: var(--bg-hover); }
|
||||
|
||||
/* ── Note Card Tags ────────────────────── */
|
||||
.note-card__tags {
|
||||
.ext-notes-note-card__tags {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
margin-top: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.note-card__tags .tag-pill { font-size: 10px; padding: 0 6px; cursor: default; }
|
||||
.note-card__tags .tag-pill--overflow {
|
||||
.ext-notes-note-card__tags .ext-notes-tag-pill { font-size: 10px; padding: 0 6px; cursor: default; }
|
||||
.ext-notes-note-card__tags .ext-notes-tag-pill--overflow {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-3);
|
||||
@@ -523,7 +523,7 @@
|
||||
}
|
||||
|
||||
/* ── Folder Context Menu ───────────────── */
|
||||
.folder-context-menu {
|
||||
.ext-notes-folder-context-menu {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
background: var(--bg-surface);
|
||||
@@ -533,52 +533,52 @@
|
||||
min-width: 150px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.folder-context-menu__item {
|
||||
.ext-notes-folder-context-menu__item {
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.folder-context-menu__item:hover {
|
||||
.ext-notes-folder-context-menu__item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.folder-context-menu__item--danger {
|
||||
.ext-notes-folder-context-menu__item--danger {
|
||||
color: var(--danger);
|
||||
}
|
||||
.folder-context-menu__item--danger:hover {
|
||||
.ext-notes-folder-context-menu__item--danger:hover {
|
||||
background: var(--danger-dim);
|
||||
}
|
||||
|
||||
/* ── Drag and Drop ─────────────────────── */
|
||||
.note-card--dragging { opacity: 0.4; }
|
||||
.folder-tree__item--drop-target {
|
||||
.ext-notes-note-card--dragging { opacity: 0.4; }
|
||||
.ext-notes-folder-tree__item--drop-target {
|
||||
background: color-mix(in srgb, var(--accent) 10%, transparent) !important;
|
||||
outline: 2px dashed var(--accent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* ── Wikilinks ──────────────────────────── */
|
||||
.wikilink {
|
||||
.ext-notes-wikilink {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dashed var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
.wikilink:hover {
|
||||
.ext-notes-wikilink:hover {
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
.wikilink.wikilink--unresolved {
|
||||
.ext-notes-wikilink.ext-notes-wikilink--unresolved {
|
||||
color: var(--danger, #e53e3e);
|
||||
border-bottom-color: var(--danger, #e53e3e);
|
||||
opacity: 0.7;
|
||||
}
|
||||
.wikilink.wikilink--unresolved:hover {
|
||||
.ext-notes-wikilink.ext-notes-wikilink--unresolved:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ── Backlinks Panel ────────────────────── */
|
||||
.backlinks-panel {
|
||||
.ext-notes-backlinks-panel {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px 16px;
|
||||
background: var(--bg-raised);
|
||||
@@ -586,7 +586,7 @@
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.backlinks-panel__header {
|
||||
.ext-notes-backlinks-panel__header {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-2);
|
||||
@@ -599,11 +599,11 @@
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.backlinks-panel__toggle {
|
||||
.ext-notes-backlinks-panel__toggle {
|
||||
font-size: 9px;
|
||||
color: var(--text-2);
|
||||
}
|
||||
.backlinks-panel__count {
|
||||
.ext-notes-backlinks-panel__count {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-2);
|
||||
font-size: 10px;
|
||||
@@ -611,7 +611,7 @@
|
||||
border-radius: 8px;
|
||||
line-height: 16px;
|
||||
}
|
||||
.backlinks-panel__item {
|
||||
.ext-notes-backlinks-panel__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -620,25 +620,25 @@
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
.backlinks-panel__item:hover {
|
||||
.ext-notes-backlinks-panel__item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.backlinks-panel__title {
|
||||
.ext-notes-backlinks-panel__title {
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
.backlinks-panel__context {
|
||||
.ext-notes-backlinks-panel__context {
|
||||
color: var(--text-2);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* ── Sidebar Tabs ────────────────────────── */
|
||||
.sidebar-tabs {
|
||||
.ext-notes-sidebar-tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar-tabs__tab {
|
||||
.ext-notes-sidebar-tabs__tab {
|
||||
flex: 1;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
@@ -652,37 +652,37 @@
|
||||
text-align: center;
|
||||
font-family: var(--font);
|
||||
}
|
||||
.sidebar-tabs__tab:hover {
|
||||
.ext-notes-sidebar-tabs__tab:hover {
|
||||
color: var(--text);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.sidebar-tabs__tab--active {
|
||||
.ext-notes-sidebar-tabs__tab--active {
|
||||
color: var(--text);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
.sidebar-tabs__tab--disabled {
|
||||
.ext-notes-sidebar-tabs__tab--disabled {
|
||||
color: var(--text-3);
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.sidebar-tabs__tab--disabled:hover {
|
||||
.ext-notes-sidebar-tabs__tab--disabled:hover {
|
||||
background: none;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* ── Sidebar Outline ─────────────────────── */
|
||||
.sidebar-outline {
|
||||
.ext-notes-sidebar-outline {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.sidebar-outline__empty {
|
||||
.ext-notes-sidebar-outline__empty {
|
||||
padding: 40px 16px;
|
||||
text-align: center;
|
||||
color: var(--text-3);
|
||||
font-size: 13px;
|
||||
}
|
||||
.sidebar-outline__item {
|
||||
.ext-notes-sidebar-outline__item {
|
||||
padding: 5px 12px;
|
||||
font-size: 13px;
|
||||
color: var(--text-2);
|
||||
@@ -694,34 +694,34 @@
|
||||
border-radius: var(--radius);
|
||||
margin: 1px 6px;
|
||||
}
|
||||
.sidebar-outline__item:hover {
|
||||
.ext-notes-sidebar-outline__item:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text);
|
||||
}
|
||||
.sidebar-outline__item--active {
|
||||
.ext-notes-sidebar-outline__item--active {
|
||||
background: var(--bg-active);
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
.sidebar-outline__item--h1 { font-weight: 600; color: var(--text); }
|
||||
.sidebar-outline__item--h2 { font-weight: 500; }
|
||||
.ext-notes-sidebar-outline__item--h1 { font-weight: 600; color: var(--text); }
|
||||
.ext-notes-sidebar-outline__item--h2 { font-weight: 500; }
|
||||
|
||||
/* ── Graph Pane ─────────────────────────── */
|
||||
.graph-pane {
|
||||
.ext-notes-graph-pane {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--bg-surface, #fff);
|
||||
min-height: 0;
|
||||
}
|
||||
.graph-pane canvas {
|
||||
.ext-notes-graph-pane canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: grab;
|
||||
}
|
||||
.graph-pane canvas:active { cursor: grabbing; }
|
||||
.graph-toolbar {
|
||||
.ext-notes-graph-pane canvas:active { cursor: grabbing; }
|
||||
.ext-notes-graph-toolbar {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
@@ -732,15 +732,15 @@
|
||||
font-size: 12px;
|
||||
color: var(--text-2, #666);
|
||||
}
|
||||
.graph-toolbar label {
|
||||
.ext-notes-graph-toolbar label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.graph-toolbar input[type="checkbox"] { margin: 0; }
|
||||
.graph-tooltip {
|
||||
.ext-notes-graph-toolbar input[type="checkbox"] { margin: 0; }
|
||||
.ext-notes-graph-tooltip {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
background: var(--bg-raised, #fff);
|
||||
@@ -752,30 +752,30 @@
|
||||
max-width: 220px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.graph-tooltip__title {
|
||||
.ext-notes-graph-tooltip__title {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.graph-tooltip__tags {
|
||||
.ext-notes-graph-tooltip__tags {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.graph-tooltip__tags .tag-pill {
|
||||
.ext-notes-graph-tooltip__tags .ext-notes-tag-pill {
|
||||
font-size: 10px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-hover, #eee);
|
||||
color: var(--text-2, #666);
|
||||
}
|
||||
.graph-tooltip__edges {
|
||||
.ext-notes-graph-tooltip__edges {
|
||||
font-size: 11px;
|
||||
color: var(--text-3, #999);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.graph-empty {
|
||||
.ext-notes-graph-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -786,10 +786,10 @@
|
||||
|
||||
/* ── Responsive ──────────────────────────── */
|
||||
@media (max-width: 700px) {
|
||||
.notes-sidebar { width: 100%; border-right: none; border-bottom: 1px solid var(--border); max-height: 40vh; }
|
||||
.notes-app { flex-direction: column; }
|
||||
.folder-tree { max-height: 30vh; }
|
||||
.notes-editor__body--split { grid-template-columns: 1fr; }
|
||||
.notes-editor__body--split .notes-preview { display: none; }
|
||||
.graph-pane { min-height: 50vh; }
|
||||
.ext-notes-sidebar { width: 100%; border-right: none; border-bottom: 1px solid var(--border); max-height: 40vh; }
|
||||
.ext-notes-app { flex-direction: column; }
|
||||
.ext-notes-folder-tree { max-height: 30vh; }
|
||||
.ext-notes-editor__body--split { grid-template-columns: 1fr; }
|
||||
.ext-notes-editor__body--split .ext-notes-preview { display: none; }
|
||||
.ext-notes-graph-pane { min-height: 50vh; }
|
||||
}
|
||||
|
||||
@@ -132,16 +132,16 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="tag-input">
|
||||
<div class="ext-notes-tag-input">
|
||||
${(tags || []).map(function(tag) {
|
||||
return html`
|
||||
<span key=${tag} class="tag-pill">
|
||||
<span key=${tag} class="ext-notes-tag-pill">
|
||||
${tag}
|
||||
<span class="tag-pill__remove" onClick=${function() { removeTag(tag); }}>×</span>
|
||||
<span class="ext-notes-tag-pill__remove" onClick=${function() { removeTag(tag); }}>×</span>
|
||||
</span>
|
||||
`;
|
||||
})}
|
||||
<input ref=${inputRef} class="tag-input__field" type="text"
|
||||
<input ref=${inputRef} class="ext-notes-tag-input__field" type="text"
|
||||
placeholder=${(tags && tags.length) ? 'Add tag…' : 'Add tags (comma to separate)…'}
|
||||
value=${input}
|
||||
onInput=${handleInput}
|
||||
@@ -149,9 +149,9 @@
|
||||
onFocus=${function() { if (input.trim()) setShowAC(true); }}
|
||||
onBlur=${function() { setTimeout(function() { setShowAC(false); }, 150); }} />
|
||||
${showAC && suggestions.length > 0 && html`
|
||||
<div class="tag-autocomplete">
|
||||
<div class="ext-notes-tag-autocomplete">
|
||||
${suggestions.map(function(s) {
|
||||
return html`<div key=${s} class="tag-autocomplete__item"
|
||||
return html`<div key=${s} class="ext-notes-tag-autocomplete__item"
|
||||
onClick=${function() { addTag(s); }}>${s}</div>`;
|
||||
})}
|
||||
</div>
|
||||
@@ -169,17 +169,17 @@
|
||||
if (!tags || tags.length === 0) return null;
|
||||
|
||||
return html`
|
||||
<div class="tag-filter">
|
||||
<div class="tag-filter__label">
|
||||
<div class="ext-notes-tag-filter">
|
||||
<div class="ext-notes-tag-filter__label">
|
||||
<span>Tags</span>
|
||||
${activeTag && html`
|
||||
<button class="tag-filter__clear" onClick=${function() { onSelect(null); }}>Clear</button>
|
||||
<button class="ext-notes-tag-filter__clear" onClick=${function() { onSelect(null); }}>Clear</button>
|
||||
`}
|
||||
</div>
|
||||
<div class="tag-filter__pills">
|
||||
<div class="ext-notes-tag-filter__pills">
|
||||
${tags.map(function(tag) {
|
||||
return html`
|
||||
<span key=${tag} class="tag-pill ${activeTag === tag ? 'tag-pill--active' : ''}"
|
||||
<span key=${tag} class="ext-notes-tag-pill ${activeTag === tag ? 'ext-notes-tag-pill--active' : ''}"
|
||||
onClick=${function() { onSelect(activeTag === tag ? null : tag); }}>
|
||||
${tag}
|
||||
</span>
|
||||
@@ -216,23 +216,23 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="note-card ${active ? 'note-card--active' : ''} ${dragging ? 'note-card--dragging' : ''}"
|
||||
<div class="ext-notes-note-card ${active ? 'ext-notes-note-card--active' : ''} ${dragging ? 'ext-notes-note-card--dragging' : ''}"
|
||||
onClick=${onClick}
|
||||
draggable="true"
|
||||
onDragStart=${handleDragStart}
|
||||
onDragEnd=${handleDragEnd}>
|
||||
<div class="note-card__title">
|
||||
${note.pinned ? html`<span class="note-card__pin">📌</span>` : null}
|
||||
<div class="ext-notes-note-card__title">
|
||||
${note.pinned ? html`<span class="ext-notes-note-card__pin">📌</span>` : null}
|
||||
${note.title || 'Untitled'}
|
||||
</div>
|
||||
${note.snippet && html`<div class="note-card__snippet">${note.snippet}</div>`}
|
||||
${note.snippet && html`<div class="ext-notes-note-card__snippet">${note.snippet}</div>`}
|
||||
${showTags.length > 0 && html`
|
||||
<div class="note-card__tags">
|
||||
${showTags.map(function(t) { return html`<span key=${t} class="tag-pill">${t}</span>`; })}
|
||||
${overflow > 0 && html`<span class="tag-pill tag-pill--overflow">+${overflow}</span>`}
|
||||
<div class="ext-notes-note-card__tags">
|
||||
${showTags.map(function(t) { return html`<span key=${t} class="ext-notes-tag-pill">${t}</span>`; })}
|
||||
${overflow > 0 && html`<span class="ext-notes-tag-pill ext-notes-tag-pill--overflow">+${overflow}</span>`}
|
||||
</div>
|
||||
`}
|
||||
${dateStr && html`<div class="note-card__date">${dateStr}</div>`}
|
||||
${dateStr && html`<div class="ext-notes-note-card__date">${dateStr}</div>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -254,10 +254,10 @@
|
||||
}, [onClose]);
|
||||
|
||||
return html`
|
||||
<div ref=${menuRef} class="folder-context-menu" style="top: ${y}px; left: ${x}px">
|
||||
<div class="folder-context-menu__item" onClick=${onAddSub}>Add subfolder</div>
|
||||
<div class="folder-context-menu__item" onClick=${onRename}>Rename</div>
|
||||
<div class="folder-context-menu__item folder-context-menu__item--danger" onClick=${onDelete}>Delete</div>
|
||||
<div ref=${menuRef} class="ext-notes-folder-context-menu" style="top: ${y}px; left: ${x}px">
|
||||
<div class="ext-notes-folder-context-menu__item" onClick=${onAddSub}>Add subfolder</div>
|
||||
<div class="ext-notes-folder-context-menu__item" onClick=${onRename}>Rename</div>
|
||||
<div class="ext-notes-folder-context-menu__item ext-notes-folder-context-menu__item--danger" onClick=${onDelete}>Delete</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -310,25 +310,25 @@
|
||||
|
||||
return html`
|
||||
<div>
|
||||
<div class="folder-tree__item ${isActive ? 'folder-tree__item--active' : ''} ${dropOver ? 'folder-tree__item--drop-target' : ''}"
|
||||
<div class="ext-notes-folder-tree__item ${isActive ? 'ext-notes-folder-tree__item--active' : ''} ${dropOver ? 'ext-notes-folder-tree__item--drop-target' : ''}"
|
||||
style="padding-left: ${indent}px"
|
||||
onClick=${function() { onSelect(node.folder.id); }}
|
||||
onContextMenu=${handleContext}
|
||||
onDragOver=${handleDragOver}
|
||||
onDragLeave=${handleDragLeave}
|
||||
onDrop=${handleDrop}>
|
||||
<span class="folder-tree__toggle" onClick=${handleToggle}>
|
||||
<span class="ext-notes-folder-tree__toggle" onClick=${handleToggle}>
|
||||
${hasChildren ? (expanded ? '▼' : '▶') : '·'}
|
||||
</span>
|
||||
<span class="folder-tree__icon">${isActive ? '📂' : '📁'}</span>
|
||||
<span class="ext-notes-folder-tree__icon">${isActive ? '📂' : '📁'}</span>
|
||||
${editing
|
||||
? html`<input class="folder-tree__edit" value=${editName}
|
||||
? html`<input class="ext-notes-folder-tree__edit" value=${editName}
|
||||
onInput=${function(e) { setEditName(e.target.value); }}
|
||||
onBlur=${handleRename}
|
||||
onKeyDown=${function(e) { if (e.key === 'Enter') handleRename(); if (e.key === 'Escape') setEditing(false); }}
|
||||
onClick=${function(e) { e.stopPropagation(); }}
|
||||
ref=${function(el) { if (el) el.focus(); }} />`
|
||||
: html`<span class="folder-tree__name">${node.folder.name}</span>`
|
||||
: html`<span class="ext-notes-folder-tree__name">${node.folder.name}</span>`
|
||||
}
|
||||
</div>
|
||||
${menu && html`<${FolderContextMenu} x=${menu.x} y=${menu.y}
|
||||
@@ -399,18 +399,18 @@
|
||||
var unfiledDrop = makeDropHandlers(setDropUnfiled);
|
||||
|
||||
return html`
|
||||
<div class="folder-tree">
|
||||
<div class="folder-tree__item ${!activeFolderId && !showUnfiled ? 'folder-tree__item--active' : ''} ${dropAll ? 'folder-tree__item--drop-target' : ''}"
|
||||
<div class="ext-notes-folder-tree">
|
||||
<div class="ext-notes-folder-tree__item ${!activeFolderId && !showUnfiled ? 'ext-notes-folder-tree__item--active' : ''} ${dropAll ? 'ext-notes-folder-tree__item--drop-target' : ''}"
|
||||
onClick=${onSelectAll}
|
||||
onDragOver=${allDrop.onDragOver} onDragLeave=${allDrop.onDragLeave} onDrop=${allDrop.onDrop}>
|
||||
<span class="folder-tree__icon">📋</span>
|
||||
<span class="folder-tree__name">All Notes</span>
|
||||
<span class="ext-notes-folder-tree__icon">📋</span>
|
||||
<span class="ext-notes-folder-tree__name">All Notes</span>
|
||||
</div>
|
||||
<div class="folder-tree__item ${showUnfiled ? 'folder-tree__item--active' : ''} ${dropUnfiled ? 'folder-tree__item--drop-target' : ''}"
|
||||
<div class="ext-notes-folder-tree__item ${showUnfiled ? 'ext-notes-folder-tree__item--active' : ''} ${dropUnfiled ? 'ext-notes-folder-tree__item--drop-target' : ''}"
|
||||
onClick=${onSelectUnfiled}
|
||||
onDragOver=${unfiledDrop.onDragOver} onDragLeave=${unfiledDrop.onDragLeave} onDrop=${unfiledDrop.onDrop}>
|
||||
<span class="folder-tree__icon">📄</span>
|
||||
<span class="folder-tree__name">Unfiled</span>
|
||||
<span class="ext-notes-folder-tree__icon">📄</span>
|
||||
<span class="ext-notes-folder-tree__name">Unfiled</span>
|
||||
</div>
|
||||
${tree.map(function(node) {
|
||||
return html`<${FolderNode} key=${node.folder.id} node=${node} depth=${0}
|
||||
@@ -419,7 +419,7 @@
|
||||
onDelete=${onDeleteFolder} onCreateSub=${onCreateFolder}
|
||||
onDropNote=${onDropNote} />`;
|
||||
})}
|
||||
<div class="folder-tree__add" onClick=${function() { onCreateFolder(''); }}>
|
||||
<div class="ext-notes-folder-tree__add" onClick=${function() { onCreateFolder(''); }}>
|
||||
+ New Folder
|
||||
</div>
|
||||
</div>
|
||||
@@ -446,18 +446,18 @@
|
||||
if (!backlinks.length) return null;
|
||||
|
||||
return html`
|
||||
<div class="backlinks-panel">
|
||||
<div class="backlinks-panel__header" onClick=${function() { setCollapsed(!collapsed); }}>
|
||||
<span class="backlinks-panel__toggle">${collapsed ? '▶' : '▼'}</span>
|
||||
<div class="ext-notes-backlinks-panel">
|
||||
<div class="ext-notes-backlinks-panel__header" onClick=${function() { setCollapsed(!collapsed); }}>
|
||||
<span class="ext-notes-backlinks-panel__toggle">${collapsed ? '▶' : '▼'}</span>
|
||||
<span>Backlinks</span>
|
||||
<span class="backlinks-panel__count">${backlinks.length}</span>
|
||||
<span class="ext-notes-backlinks-panel__count">${backlinks.length}</span>
|
||||
</div>
|
||||
${!collapsed && backlinks.map(function(bl) {
|
||||
return html`
|
||||
<div key=${bl.source_id} class="backlinks-panel__item"
|
||||
<div key=${bl.source_id} class="ext-notes-backlinks-panel__item"
|
||||
onClick=${function() { if (onNavigate) onNavigate(bl.source_id); }}>
|
||||
<span class="backlinks-panel__title">${bl.source_title}</span>
|
||||
<span class="backlinks-panel__context">via [[${bl.link_text}]]</span>
|
||||
<span class="ext-notes-backlinks-panel__title">${bl.source_title}</span>
|
||||
<span class="ext-notes-backlinks-panel__context">via [[${bl.link_text}]]</span>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
@@ -547,10 +547,10 @@
|
||||
|
||||
function SidebarTabs({ activeTab, onTabChange, noteSelected }) {
|
||||
return html`
|
||||
<div class="sidebar-tabs">
|
||||
<button class="sidebar-tabs__tab ${activeTab === 'notes' ? 'sidebar-tabs__tab--active' : ''}"
|
||||
<div class="ext-ext-notes-sidebar-tabs">
|
||||
<button class="ext-ext-notes-sidebar-tabs__tab ${activeTab === 'notes' ? 'ext-ext-notes-sidebar-tabs__tab--active' : ''}"
|
||||
onClick=${function() { onTabChange('notes'); }}>Notes</button>
|
||||
<button class="sidebar-tabs__tab ${activeTab === 'outline' ? 'sidebar-tabs__tab--active' : ''} ${!noteSelected ? 'sidebar-tabs__tab--disabled' : ''}"
|
||||
<button class="ext-ext-notes-sidebar-tabs__tab ${activeTab === 'outline' ? 'ext-ext-notes-sidebar-tabs__tab--active' : ''} ${!noteSelected ? 'ext-ext-notes-sidebar-tabs__tab--disabled' : ''}"
|
||||
onClick=${function() { if (noteSelected) onTabChange('outline'); }}>Outline</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -563,14 +563,14 @@
|
||||
|
||||
function SidebarOutline({ headings, activeHeadingIdx, onScrollToHeading }) {
|
||||
if (!headings || headings.length === 0) {
|
||||
return html`<div class="sidebar-outline__empty">No headings found</div>`;
|
||||
return html`<div class="ext-ext-notes-sidebar-outline__empty">No headings found</div>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="sidebar-outline">
|
||||
<div class="ext-ext-notes-sidebar-outline">
|
||||
${headings.map(function(h, i) {
|
||||
return html`
|
||||
<div key=${i} class="sidebar-outline__item sidebar-outline__item--h${h.level} ${i === activeHeadingIdx ? 'sidebar-outline__item--active' : ''}"
|
||||
<div key=${i} class="ext-ext-notes-sidebar-outline__item ext-ext-notes-sidebar-outline__item--h${h.level} ${i === activeHeadingIdx ? 'ext-ext-notes-sidebar-outline__item--active' : ''}"
|
||||
style="padding-left: ${10 + (h.level - 1) * 14}px"
|
||||
onClick=${function() { onScrollToHeading(h); }}>
|
||||
${h.text}
|
||||
@@ -772,8 +772,8 @@
|
||||
for (var i = 0; i < outgoingLinks.length; i++) {
|
||||
var link = outgoingLinks[i];
|
||||
if (!link.target_id) {
|
||||
var search = 'class="wikilink" data-wikilink="' + _escAttr(link.link_text) + '"';
|
||||
var replace = 'class="wikilink wikilink--unresolved" data-wikilink="' + _escAttr(link.link_text) + '"';
|
||||
var search = 'class="ext-notes-wikilink" data-wikilink="' + _escAttr(link.link_text) + '"';
|
||||
var replace = 'class="ext-notes-wikilink ext-notes-wikilink--unresolved" data-wikilink="' + _escAttr(link.link_text) + '"';
|
||||
h = h.split(search).join(replace);
|
||||
}
|
||||
}
|
||||
@@ -787,7 +787,7 @@
|
||||
if (!el || viewMode === 'edit') return;
|
||||
function handleClick(e) {
|
||||
var target = e.target;
|
||||
if (target.classList && target.classList.contains('wikilink')) {
|
||||
if (target.classList && target.classList.contains('ext-notes-wikilink')) {
|
||||
e.preventDefault();
|
||||
var linkText = target.getAttribute('data-wikilink');
|
||||
if (linkText && onNavigateToTitle) onNavigateToTitle(linkText);
|
||||
@@ -939,44 +939,44 @@
|
||||
}, [folders]);
|
||||
|
||||
if (!note) {
|
||||
return html`<div class="notes-editor__empty">Select a note or create a new one</div>`;
|
||||
return html`<div class="ext-notes-editor__empty">Select a note or create a new one</div>`;
|
||||
}
|
||||
|
||||
// ── Render editor body ─────────────────────
|
||||
function renderEditorBody() {
|
||||
if (viewMode === 'rendered') {
|
||||
return html`<div class="notes-preview" ref=${previewRef} dangerouslySetInnerHTML=${{ __html: previewHtml }} />`;
|
||||
return html`<div class="ext-notes-preview" ref=${previewRef} dangerouslySetInnerHTML=${{ __html: previewHtml }} />`;
|
||||
}
|
||||
if (viewMode === 'split') {
|
||||
var editor = hasCM
|
||||
? html`<div class="notes-editor__cm" ref=${cmContainerRef} />`
|
||||
: html`<textarea class="notes-editor__textarea" ref=${textareaRef}
|
||||
? html`<div class="ext-notes-editor__cm" ref=${cmContainerRef} />`
|
||||
: html`<textarea class="ext-notes-editor__textarea" ref=${textareaRef}
|
||||
value=${body} onInput=${handleBodyChange}
|
||||
onKeyDown=${handleKeyDown}
|
||||
placeholder="Start writing in Markdown…" />`;
|
||||
return [
|
||||
editor,
|
||||
html`<div class="notes-preview" ref=${previewRef} dangerouslySetInnerHTML=${{ __html: previewHtml }} />`
|
||||
html`<div class="ext-notes-preview" ref=${previewRef} dangerouslySetInnerHTML=${{ __html: previewHtml }} />`
|
||||
];
|
||||
}
|
||||
// edit mode
|
||||
if (hasCM) {
|
||||
return html`<div class="notes-editor__cm" ref=${cmContainerRef} />`;
|
||||
return html`<div class="ext-notes-editor__cm" ref=${cmContainerRef} />`;
|
||||
}
|
||||
return html`<textarea class="notes-editor__textarea" ref=${textareaRef}
|
||||
return html`<textarea class="ext-notes-editor__textarea" ref=${textareaRef}
|
||||
value=${body} onInput=${handleBodyChange}
|
||||
onKeyDown=${handleKeyDown}
|
||||
placeholder="Start writing in Markdown…" />`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="notes-editor">
|
||||
<div class="notes-editor__header">
|
||||
<input class="notes-editor__title-input" type="text"
|
||||
<div class="ext-notes-editor">
|
||||
<div class="ext-notes-editor__header">
|
||||
<input class="ext-notes-editor__title-input" type="text"
|
||||
value=${title} onInput=${handleTitleChange}
|
||||
placeholder="Note title" />
|
||||
<div class="notes-editor__actions">
|
||||
<select class="notes-editor__folder-select"
|
||||
<div class="ext-notes-editor__actions">
|
||||
<select class="ext-notes-editor__folder-select"
|
||||
value=${note.folder_id || ''}
|
||||
onChange=${async function(e) {
|
||||
await api.post('/notes/move', { note_id: note.id, folder_id: e.target.value });
|
||||
@@ -987,28 +987,28 @@
|
||||
return html`<option key=${f.id} value=${f.id}>${f.label}</option>`;
|
||||
})}
|
||||
</select>
|
||||
<span class=${dirty ? 'notes-saved notes-saved--dirty' : 'notes-saved'}>
|
||||
<span class=${dirty ? 'ext-notes-saved ext-notes-saved--dirty' : 'ext-notes-saved'}>
|
||||
${saving ? 'Saving…' : (dirty ? 'Unsaved' : 'Saved')}
|
||||
</span>
|
||||
<button class="notes-toggle" onClick=${function() {
|
||||
<button class="ext-notes-toggle" onClick=${function() {
|
||||
var next = viewMode === 'rendered' ? 'edit' : viewMode === 'edit' ? 'split' : 'rendered';
|
||||
_modeStore.set('editor_mode', next);
|
||||
setViewMode(next);
|
||||
}}>
|
||||
${viewMode === 'rendered' ? 'Edit' : viewMode === 'edit' ? 'Split' : 'Read'}
|
||||
</button>
|
||||
<button class="notes-btn" onClick=${handleExport} title="Export as .md">Export</button>
|
||||
<button class="notes-btn" onClick=${handlePin}
|
||||
<button class="ext-notes-btn" onClick=${handleExport} title="Export as .md">Export</button>
|
||||
<button class="ext-notes-btn" onClick=${handlePin}
|
||||
title=${note.pinned ? 'Unpin' : 'Pin'}>
|
||||
${note.pinned ? '📌' : '📍'}
|
||||
</button>
|
||||
<button class="notes-btn notes-btn--danger" onClick=${handleArchive}
|
||||
<button class="ext-notes-btn ext-notes-btn--danger" onClick=${handleArchive}
|
||||
title="Archive">🗑</button>
|
||||
</div>
|
||||
</div>
|
||||
<${TagInput} tags=${noteTags} allTags=${allTags} onChange=${handleTagsChange} />
|
||||
<div class="notes-editor__content">
|
||||
<div class="notes-editor__body ${viewMode === 'split' ? 'notes-editor__body--split' : ''}">
|
||||
<div class="ext-notes-editor__content">
|
||||
<div class="ext-notes-editor__body ${viewMode === 'split' ? 'ext-notes-editor__body--split' : ''}">
|
||||
${renderEditorBody()}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1405,17 +1405,17 @@
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return html`<div class="graph-pane"><div class="graph-empty"><${Spinner} size="md" /></div></div>`;
|
||||
return html`<div class="ext-notes-graph-pane"><div class="ext-notes-graph-empty"><${Spinner} size="md" /></div></div>`;
|
||||
}
|
||||
if (!graphData || graphData.nodes.length === 0) {
|
||||
return html`<div class="graph-pane"><div class="graph-empty">No notes to graph. Create notes with [[wikilinks]] to see connections.</div></div>`;
|
||||
return html`<div class="ext-notes-graph-pane"><div class="ext-notes-graph-empty">No notes to graph. Create notes with [[wikilinks]] to see connections.</div></div>`;
|
||||
}
|
||||
|
||||
var adj = graphData.adj;
|
||||
|
||||
return html`
|
||||
<div class="graph-pane">
|
||||
<div class="graph-toolbar">
|
||||
<div class="ext-notes-graph-pane">
|
||||
<div class="ext-notes-graph-toolbar">
|
||||
<label>
|
||||
<input type="checkbox" checked=${hideOrphans}
|
||||
onChange=${function(e) { setHideOrphans(e.target.checked); }} />
|
||||
@@ -1430,14 +1430,14 @@
|
||||
onMouseUp=${handleMouseUp}
|
||||
onMouseLeave=${handleMouseUp} />
|
||||
${hoveredNode && html`
|
||||
<div class="graph-tooltip" style=${{ left: (mousePos.x + 14) + 'px', top: (mousePos.y - 10) + 'px' }}>
|
||||
<div class="graph-tooltip__title">${hoveredNode.title}</div>
|
||||
<div class="ext-notes-graph-tooltip" style=${{ left: (mousePos.x + 14) + 'px', top: (mousePos.y - 10) + 'px' }}>
|
||||
<div class="ext-notes-graph-tooltip__title">${hoveredNode.title}</div>
|
||||
${hoveredNode.tags && hoveredNode.tags.length > 0 && html`
|
||||
<div class="graph-tooltip__tags">
|
||||
${hoveredNode.tags.map(function(t) { return html`<span class="tag-pill" key=${t}>${t}</span>`; })}
|
||||
<div class="ext-notes-graph-tooltip__tags">
|
||||
${hoveredNode.tags.map(function(t) { return html`<span class="ext-notes-tag-pill" key=${t}>${t}</span>`; })}
|
||||
</div>
|
||||
`}
|
||||
<div class="graph-tooltip__edges">${(adj[hoveredNode.id] ? adj[hoveredNode.id].size : 0)} connections</div>
|
||||
<div class="ext-notes-graph-tooltip__edges">${(adj[hoveredNode.id] ? adj[hoveredNode.id].size : 0)} connections</div>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
@@ -1736,8 +1736,8 @@
|
||||
<${Button} variant=${showGraph ? 'primary' : 'secondary'} size="sm"
|
||||
onClick=${function() { setShowGraph(!showGraph); }}>Graph<//>
|
||||
<//>
|
||||
<div class="notes-app">
|
||||
<div class="notes-sidebar">
|
||||
<div class="ext-notes-app">
|
||||
<div class="ext-notes-sidebar">
|
||||
<${SidebarTabs} activeTab=${sidebarTab} onTabChange=${setSidebarTab} noteSelected=${!!activeNote} />
|
||||
${sidebarTab === 'notes' && html`
|
||||
<${FolderTree} folders=${folders}
|
||||
@@ -1749,14 +1749,14 @@
|
||||
onDeleteFolder=${handleDeleteFolder}
|
||||
onDropNote=${handleDropNote} />
|
||||
<${TagFilter} tags=${allTags} activeTag=${activeTag} onSelect=${handleTagFilter} />
|
||||
<div class="notes-search">
|
||||
<div class="ext-notes-search">
|
||||
<input type="text" placeholder="Search notes…"
|
||||
value=${searchTerm} onInput=${handleSearch} />
|
||||
</div>
|
||||
<div class="notes-list">
|
||||
${loading && html`<div class="notes-list__loading"><${Spinner} size="md" /></div>`}
|
||||
<div class="ext-notes-list">
|
||||
${loading && html`<div class="ext-notes-list__loading"><${Spinner} size="md" /></div>`}
|
||||
${!loading && notes.length === 0 && html`
|
||||
<div class="notes-list__empty">
|
||||
<div class="ext-notes-list__empty">
|
||||
${searchTerm ? 'No matching notes' : (activeTag ? 'No notes with tag "' + activeTag + '"' : 'No notes yet. Click + New Note to start.')}
|
||||
</div>
|
||||
`}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* ── Schedules Surface ───────────────────── */
|
||||
|
||||
.sched-app {
|
||||
.ext-schedules-app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -8,7 +8,7 @@
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.sched-header {
|
||||
.ext-schedules-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -16,13 +16,13 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.sched-header h1 {
|
||||
.ext-schedules-header h1 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sched-body {
|
||||
.ext-schedules-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
@@ -31,28 +31,28 @@
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sched-count {
|
||||
.ext-schedules-count {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* ── Table ────────────────────────────────── */
|
||||
|
||||
.sched-list__loading,
|
||||
.sched-list__empty {
|
||||
.ext-schedules-list__loading,
|
||||
.ext-schedules-list__empty {
|
||||
padding: 40px 16px;
|
||||
text-align: center;
|
||||
color: var(--text-3);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.sched-table {
|
||||
.ext-schedules-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.sched-table th {
|
||||
.ext-schedules-table th {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
font-weight: 600;
|
||||
@@ -63,19 +63,19 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.sched-table td {
|
||||
.ext-schedules-table td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.sched-row:hover {
|
||||
.ext-schedules-row:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.sched-row__name { min-width: 160px; }
|
||||
.ext-schedules-row__name { min-width: 160px; }
|
||||
|
||||
.sched-link {
|
||||
.ext-schedules-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--accent);
|
||||
@@ -85,17 +85,17 @@
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
}
|
||||
.sched-link:hover { text-decoration: underline; }
|
||||
.ext-schedules-link:hover { text-decoration: underline; }
|
||||
|
||||
.sched-row__desc {
|
||||
.ext-schedules-row__desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-3);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.sched-row__cron { white-space: nowrap; }
|
||||
.ext-schedules-row__cron { white-space: nowrap; }
|
||||
|
||||
.sched-cron-badge {
|
||||
.ext-schedules-cron-badge {
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
background: var(--bg-raised);
|
||||
@@ -104,19 +104,19 @@
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.sched-cron-human {
|
||||
.ext-schedules-cron-human {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-3);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.sched-row__next {
|
||||
.ext-schedules-row__next {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sched-row__actions {
|
||||
.ext-schedules-row__actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
@@ -124,7 +124,7 @@
|
||||
|
||||
/* ── Toggle button ────────────────────────── */
|
||||
|
||||
.sched-toggle-btn {
|
||||
.ext-schedules-toggle-btn {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
padding: 2px 10px;
|
||||
@@ -134,22 +134,22 @@
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.sched-toggle-btn--on {
|
||||
.ext-schedules-toggle-btn--on {
|
||||
background: var(--success);
|
||||
color: #fff;
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.sched-toggle-btn--off {
|
||||
.ext-schedules-toggle-btn--off {
|
||||
background: var(--bg-raised);
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.sched-toggle-btn:hover { opacity: 0.85; }
|
||||
.ext-schedules-toggle-btn:hover { opacity: 0.85; }
|
||||
|
||||
/* ── Action buttons ───────────────────────── */
|
||||
|
||||
.sched-btn {
|
||||
.ext-schedules-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-2);
|
||||
@@ -157,14 +157,14 @@
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.sched-btn:hover { background: var(--bg-hover); }
|
||||
.ext-schedules-btn:hover { background: var(--bg-hover); }
|
||||
|
||||
.sched-btn--sm {
|
||||
.ext-schedules-btn--sm {
|
||||
padding: 3px 8px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.sched-btn--danger:hover {
|
||||
.ext-schedules-btn--danger:hover {
|
||||
background: var(--danger-dim);
|
||||
color: var(--danger);
|
||||
border-color: var(--danger);
|
||||
@@ -172,28 +172,28 @@
|
||||
|
||||
/* ── Form ─────────────────────────────────── */
|
||||
|
||||
.sched-form { display: flex; flex-direction: column; gap: 12px; }
|
||||
.sched-form__row { display: flex; align-items: center; gap: 12px; }
|
||||
.sched-form__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; }
|
||||
.ext-schedules-form { display: flex; flex-direction: column; gap: 12px; }
|
||||
.ext-schedules-form__row { display: flex; align-items: center; gap: 12px; }
|
||||
.ext-schedules-form__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; }
|
||||
|
||||
.sched-cron-input {
|
||||
.ext-schedules-cron-input {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.sched-cron-preview {
|
||||
.ext-schedules-cron-preview {
|
||||
font-size: 0.8rem;
|
||||
color: var(--accent);
|
||||
margin-top: 4px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.sched-script {
|
||||
.ext-schedules-script {
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.sched-toggle {
|
||||
.ext-schedules-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -203,14 +203,14 @@
|
||||
|
||||
/* ── Logs panel ───────────────────────────── */
|
||||
|
||||
.sched-logs {
|
||||
.ext-schedules-logs {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sched-logs__header {
|
||||
.ext-schedules-logs__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -220,15 +220,15 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sched-logs__loading,
|
||||
.sched-logs__empty {
|
||||
.ext-schedules-logs__loading,
|
||||
.ext-schedules-logs__empty {
|
||||
padding: 20px 12px;
|
||||
text-align: center;
|
||||
color: var(--text-3);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.sched-log-entry {
|
||||
.ext-schedules-log-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
@@ -236,28 +236,28 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.sched-log-entry:last-child { border-bottom: none; }
|
||||
.ext-schedules-log-entry:last-child { border-bottom: none; }
|
||||
|
||||
.sched-log-entry__status {
|
||||
.ext-schedules-log-entry__status {
|
||||
font-weight: 700;
|
||||
width: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
.sched-log-entry__status--ok { color: var(--success); }
|
||||
.sched-log-entry__status--fail { color: var(--danger); }
|
||||
.ext-schedules-log-entry__status--ok { color: var(--success); }
|
||||
.ext-schedules-log-entry__status--fail { color: var(--danger); }
|
||||
|
||||
.sched-log-entry__time {
|
||||
.ext-schedules-log-entry__time {
|
||||
color: var(--text-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sched-log-entry__duration {
|
||||
.ext-schedules-log-entry__duration {
|
||||
color: var(--text-3);
|
||||
font-family: monospace;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.sched-log-entry__error {
|
||||
.ext-schedules-log-entry__error {
|
||||
color: var(--danger);
|
||||
font-size: 0.75rem;
|
||||
overflow: hidden;
|
||||
@@ -269,9 +269,9 @@
|
||||
/* ── Responsive ───────────────────────────── */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sched-table th:nth-child(3),
|
||||
.sched-table td:nth-child(3) {
|
||||
.ext-schedules-table th:nth-child(3),
|
||||
.ext-schedules-table td:nth-child(3) {
|
||||
display: none;
|
||||
}
|
||||
.sched-body { padding: 8px; }
|
||||
.ext-schedules-body { padding: 8px; }
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
|
||||
return html`
|
||||
<${Dialog} open onClose=${onClose} title=${schedule ? 'Edit Schedule' : 'New Schedule'}>
|
||||
<form onSubmit=${handleSubmit} class="sched-form">
|
||||
<form onSubmit=${handleSubmit} class="ext-schedules-form">
|
||||
${error && html`<${Banner} variant="danger" text=${error} />`}
|
||||
<${FormField} label="Name" required>
|
||||
<input type="text" value=${name} onInput=${e => setName(e.target.value)}
|
||||
@@ -160,22 +160,22 @@
|
||||
<//>
|
||||
<${FormField} label="Cron Expression" required>
|
||||
<input type="text" value=${cron} onInput=${e => setCron(e.target.value)}
|
||||
placeholder="e.g. 0 9 * * 1-5" class="sched-cron-input" />
|
||||
placeholder="e.g. 0 9 * * 1-5" class="ext-schedules-cron-input" />
|
||||
${preview && preview !== cron && html`
|
||||
<div class="sched-cron-preview">${preview}</div>
|
||||
<div class="ext-schedules-cron-preview">${preview}</div>
|
||||
`}
|
||||
<//>
|
||||
<${FormField} label="Script">
|
||||
<textarea rows="6" value=${script} onInput=${e => setScript(e.target.value)}
|
||||
placeholder="Starlark script (optional)" class="sched-script" />
|
||||
placeholder="Starlark script (optional)" class="ext-schedules-script" />
|
||||
<//>
|
||||
<div class="sched-form__row">
|
||||
<label class="sched-toggle">
|
||||
<div class="ext-schedules-form__row">
|
||||
<label class="ext-schedules-toggle">
|
||||
<input type="checkbox" checked=${enabled} onChange=${e => setEnabled(e.target.checked)} />
|
||||
<span>Enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="sched-form__actions">
|
||||
<div class="ext-schedules-form__actions">
|
||||
<${Button} variant="ghost" type="button" onClick=${onClose}>Cancel<//>
|
||||
<${Button} variant="primary" type="submit" disabled=${saving}>
|
||||
${saving ? 'Saving…' : (schedule ? 'Update' : 'Create')}
|
||||
@@ -208,25 +208,25 @@
|
||||
}, [scheduleId]);
|
||||
|
||||
return html`
|
||||
<div class="sched-logs">
|
||||
<div class="sched-logs__header">
|
||||
<div class="ext-schedules-logs">
|
||||
<div class="ext-schedules-logs__header">
|
||||
<span>Execution History</span>
|
||||
<button class="sched-btn sched-btn--sm" onClick=${onClose}>Close</button>
|
||||
<button class="ext-schedules-btn ext-schedules-btn--sm" onClick=${onClose}>Close</button>
|
||||
</div>
|
||||
${loading && html`<div class="sched-logs__loading"><${Spinner} size="sm" /></div>`}
|
||||
${loading && html`<div class="ext-schedules-logs__loading"><${Spinner} size="sm" /></div>`}
|
||||
${!loading && logs.length === 0 && html`
|
||||
<div class="sched-logs__empty">No executions yet</div>
|
||||
<div class="ext-schedules-logs__empty">No executions yet</div>
|
||||
`}
|
||||
${!loading && logs.map(function (log) {
|
||||
var success = log.status === 'success' || log.status === 'ok';
|
||||
return html`
|
||||
<div class="sched-log-entry" key=${log.id || log.started_at}>
|
||||
<span class="sched-log-entry__status sched-log-entry__status--${success ? 'ok' : 'fail'}">
|
||||
<div class="ext-schedules-log-entry" key=${log.id || log.started_at}>
|
||||
<span class="ext-schedules-log-entry__status ext-schedules-log-entry__status--${success ? 'ok' : 'fail'}">
|
||||
${success ? '✓' : '✗'}
|
||||
</span>
|
||||
<span class="sched-log-entry__time">${_formatTime(log.started_at)}</span>
|
||||
<span class="sched-log-entry__duration">${_formatDuration(log.duration_ms)}</span>
|
||||
${log.error && html`<span class="sched-log-entry__error" title=${log.error}>${log.error}</span>`}
|
||||
<span class="ext-schedules-log-entry__time">${_formatTime(log.started_at)}</span>
|
||||
<span class="ext-schedules-log-entry__duration">${_formatDuration(log.duration_ms)}</span>
|
||||
${log.error && html`<span class="ext-schedules-log-entry__error" title=${log.error}>${log.error}</span>`}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
@@ -286,13 +286,13 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="sched-list">
|
||||
${loading && html`<div class="sched-list__loading"><${Spinner} size="md" /></div>`}
|
||||
<div class="ext-schedules-list">
|
||||
${loading && html`<div class="ext-schedules-list__loading"><${Spinner} size="md" /></div>`}
|
||||
${!loading && items.length === 0 && html`
|
||||
<div class="sched-list__empty">No schedules yet. Create one to get started.</div>
|
||||
<div class="ext-schedules-list__empty">No schedules yet. Create one to get started.</div>
|
||||
`}
|
||||
${!loading && items.length > 0 && html`
|
||||
<table class="sched-table">
|
||||
<table class="ext-schedules-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
@@ -305,27 +305,27 @@
|
||||
<tbody>
|
||||
${items.map(function (s) {
|
||||
return html`
|
||||
<tr key=${s.id} class="sched-row">
|
||||
<td class="sched-row__name">
|
||||
<button class="sched-link" onClick=${() => onEdit(s)}>${s.name}</button>
|
||||
${s.description && html`<div class="sched-row__desc">${s.description}</div>`}
|
||||
<tr key=${s.id} class="ext-schedules-row">
|
||||
<td class="ext-schedules-row__name">
|
||||
<button class="ext-schedules-link" onClick=${() => onEdit(s)}>${s.name}</button>
|
||||
${s.description && html`<div class="ext-schedules-row__desc">${s.description}</div>`}
|
||||
</td>
|
||||
<td class="sched-row__cron">
|
||||
<span class="sched-cron-badge">${s.cron_expr}</span>
|
||||
<div class="sched-cron-human">${describeCron(s.cron_expr)}</div>
|
||||
<td class="ext-schedules-row__cron">
|
||||
<span class="ext-schedules-cron-badge">${s.cron_expr}</span>
|
||||
<div class="ext-schedules-cron-human">${describeCron(s.cron_expr)}</div>
|
||||
</td>
|
||||
<td class="sched-row__next">${s.next_fire_at ? _formatTime(s.next_fire_at) : '—'}</td>
|
||||
<td class="ext-schedules-row__next">${s.next_fire_at ? _formatTime(s.next_fire_at) : '—'}</td>
|
||||
<td>
|
||||
<button class="sched-toggle-btn sched-toggle-btn--${s.enabled ? 'on' : 'off'}"
|
||||
<button class="ext-schedules-toggle-btn ext-schedules-toggle-btn--${s.enabled ? 'on' : 'off'}"
|
||||
onClick=${() => handleToggle(s)}
|
||||
title=${s.enabled ? 'Disable' : 'Enable'}>
|
||||
${s.enabled ? 'On' : 'Off'}
|
||||
</button>
|
||||
</td>
|
||||
<td class="sched-row__actions">
|
||||
<button class="sched-btn sched-btn--sm" onClick=${() => handleRun(s)} title="Run now">▶</button>
|
||||
<button class="sched-btn sched-btn--sm" onClick=${() => onShowLogs(s.id)} title="View logs">📋</button>
|
||||
<button class="sched-btn sched-btn--sm sched-btn--danger" onClick=${() => handleDelete(s)}
|
||||
<td class="ext-schedules-row__actions">
|
||||
<button class="ext-schedules-btn ext-schedules-btn--sm" onClick=${() => handleRun(s)} title="Run now">▶</button>
|
||||
<button class="ext-schedules-btn ext-schedules-btn--sm" onClick=${() => onShowLogs(s.id)} title="View logs">📋</button>
|
||||
<button class="ext-schedules-btn ext-schedules-btn--sm ext-schedules-btn--danger" onClick=${() => handleDelete(s)}
|
||||
title="Delete">×</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -371,14 +371,14 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="sched-app">
|
||||
<div class="ext-schedules-app">
|
||||
${Topbar ? html`
|
||||
<${Topbar} title="Schedules">
|
||||
<span class="sched-count">${items.length} schedule${items.length !== 1 ? 's' : ''}</span>
|
||||
<span class="ext-schedules-count">${items.length} schedule${items.length !== 1 ? 's' : ''}</span>
|
||||
<${Button} variant="primary" size="sm" onClick=${() => setEditing({})}>+ New Schedule<//>
|
||||
<//>
|
||||
` : html`
|
||||
<div class="sched-header">
|
||||
<div class="ext-schedules-header">
|
||||
<h1>Schedules</h1>
|
||||
<${Button} variant="primary" size="sm" onClick=${() => setEditing({})}>+ New Schedule<//>
|
||||
</div>
|
||||
@@ -386,7 +386,7 @@
|
||||
|
||||
${error && html`<${Banner} variant="danger" text=${error} />`}
|
||||
|
||||
<div class="sched-body">
|
||||
<div class="ext-schedules-body">
|
||||
<${ScheduleList}
|
||||
items=${items}
|
||||
loading=${loading}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
/* SDK Test Runner — Styles */
|
||||
|
||||
.sdkr-header {
|
||||
.ext-sdk-test-runner-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.sdkr-header h1 {
|
||||
.ext-sdk-test-runner-header h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--text, #e0e0e0);
|
||||
}
|
||||
.sdkr-version {
|
||||
.ext-sdk-test-runner-version {
|
||||
font-size: 12px;
|
||||
color: var(--text-3, #888);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.sdkr-desc {
|
||||
.ext-sdk-test-runner-desc {
|
||||
font-size: 13px;
|
||||
color: var(--text-2, #aaa);
|
||||
margin: 0 0 16px 0;
|
||||
@@ -25,27 +25,27 @@
|
||||
}
|
||||
|
||||
/* Fixtures panel */
|
||||
.sdkr-fixtures {
|
||||
.ext-sdk-test-runner-fixtures {
|
||||
margin-bottom: 16px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--border, #333);
|
||||
border-radius: 6px;
|
||||
background: var(--bg-raised, #1a1a1a);
|
||||
}
|
||||
.sdkr-fixture-row {
|
||||
.ext-sdk-test-runner-fixture-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.sdkr-fixture-label {
|
||||
.ext-sdk-test-runner-fixture-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-2, #aaa);
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sdkr-select {
|
||||
.ext-sdk-test-runner-select {
|
||||
padding: 4px 8px;
|
||||
font-size: 13px;
|
||||
background: var(--bg-raised, #222);
|
||||
@@ -54,7 +54,7 @@
|
||||
border-radius: 4px;
|
||||
min-width: 130px;
|
||||
}
|
||||
.sdkr-input {
|
||||
.ext-sdk-test-runner-input {
|
||||
padding: 4px 8px;
|
||||
font-size: 13px;
|
||||
background: var(--bg-raised, #222);
|
||||
@@ -66,16 +66,16 @@
|
||||
max-width: 400px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.sdkr-input::placeholder {
|
||||
.ext-sdk-test-runner-input::placeholder {
|
||||
color: var(--text-3, #666);
|
||||
font-family: inherit;
|
||||
}
|
||||
.sdkr-fixture-status {
|
||||
.ext-sdk-test-runner-fixture-status {
|
||||
font-size: 12px;
|
||||
color: var(--text-3, #888);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.sdkr-fixture-note {
|
||||
.ext-sdk-test-runner-fixture-note {
|
||||
font-size: 11px;
|
||||
color: var(--text-3, #666);
|
||||
margin-top: 6px;
|
||||
@@ -83,26 +83,26 @@
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.sdkr-controls {
|
||||
.ext-sdk-test-runner-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.sdkr-filters {
|
||||
.ext-sdk-test-runner-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.sdkr-filter-label {
|
||||
.ext-sdk-test-runner-filter-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-2, #aaa);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.sdkr-check {
|
||||
.ext-sdk-test-runner-check {
|
||||
font-size: 13px;
|
||||
color: var(--text, #e0e0e0);
|
||||
cursor: pointer;
|
||||
@@ -110,12 +110,12 @@
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
.sdkr-check input { cursor: pointer; }
|
||||
.sdkr-btn-row {
|
||||
.ext-sdk-test-runner-check input { cursor: pointer; }
|
||||
.ext-sdk-test-runner-btn-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.sdkr-btn {
|
||||
.ext-sdk-test-runner-btn {
|
||||
padding: 6px 14px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border, #444);
|
||||
@@ -125,22 +125,22 @@
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.sdkr-btn:hover { background: var(--bg-hover, #383838); }
|
||||
.sdkr-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.sdkr-btn-primary {
|
||||
.ext-sdk-test-runner-btn:hover { background: var(--bg-hover, #383838); }
|
||||
.ext-sdk-test-runner-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.ext-sdk-test-runner-btn-primary {
|
||||
background: var(--accent, #3b82f6);
|
||||
border-color: var(--accent, #3b82f6);
|
||||
color: #fff;
|
||||
}
|
||||
.sdkr-btn-primary:hover { filter: brightness(1.15); }
|
||||
.sdkr-btn-danger {
|
||||
.ext-sdk-test-runner-btn-primary:hover { filter: brightness(1.15); }
|
||||
.ext-sdk-test-runner-btn-danger {
|
||||
background: var(--danger, #ef4444);
|
||||
border-color: var(--danger, #ef4444);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Summary */
|
||||
.sdkr-summary {
|
||||
.ext-sdk-test-runner-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
@@ -150,7 +150,7 @@
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border, #333);
|
||||
}
|
||||
.sdkr-badge {
|
||||
.ext-sdk-test-runner-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
@@ -160,17 +160,17 @@
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.sdkr-table-wrap {
|
||||
.ext-sdk-test-runner-table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--border, #333);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.sdkr-table {
|
||||
.ext-sdk-test-runner-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
.sdkr-table th {
|
||||
.ext-sdk-test-runner-table th {
|
||||
text-align: left;
|
||||
padding: 8px 10px;
|
||||
background: var(--bg-raised, #1e1e1e);
|
||||
@@ -184,31 +184,31 @@
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.sdkr-table td {
|
||||
.ext-sdk-test-runner-table td {
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid var(--border-subtle, #2a2a2a);
|
||||
color: var(--text, #e0e0e0);
|
||||
vertical-align: top;
|
||||
}
|
||||
.sdkr-table tbody tr:hover {
|
||||
.ext-sdk-test-runner-table tbody tr:hover {
|
||||
background: var(--bg-hover, rgba(255,255,255,0.03));
|
||||
}
|
||||
.sdkr-test-name {
|
||||
.ext-sdk-test-runner-test-name {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.sdkr-verdict {
|
||||
.ext-sdk-test-runner-verdict {
|
||||
font-weight: 700;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
.sdkr-ms {
|
||||
.ext-sdk-test-runner-ms {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 11px;
|
||||
color: var(--text-3, #888);
|
||||
text-align: right;
|
||||
}
|
||||
.sdkr-detail {
|
||||
.ext-sdk-test-runner-detail {
|
||||
font-size: 11px;
|
||||
color: var(--text-3, #888);
|
||||
max-width: 400px;
|
||||
@@ -216,7 +216,7 @@
|
||||
}
|
||||
|
||||
/* Row verdict highlights */
|
||||
.sdkr-row-sdk_bug td { background: rgba(245, 158, 11, 0.06); }
|
||||
.sdkr-row-icd_bug td { background: rgba(239, 68, 68, 0.06); }
|
||||
.sdkr-row-shape_bug td { background: rgba(168, 85, 247, 0.06); }
|
||||
.sdkr-row-error td { background: rgba(239, 68, 68, 0.08); }
|
||||
.ext-sdk-test-runner-row-sdk_bug td { background: rgba(245, 158, 11, 0.06); }
|
||||
.ext-sdk-test-runner-row-icd_bug td { background: rgba(239, 68, 68, 0.06); }
|
||||
.ext-sdk-test-runner-row-shape_bug td { background: rgba(168, 85, 247, 0.06); }
|
||||
.ext-sdk-test-runner-row-error td { background: rgba(239, 68, 68, 0.08); }
|
||||
|
||||
@@ -58,25 +58,25 @@
|
||||
mount.innerHTML = '';
|
||||
|
||||
// Header
|
||||
var header = el('div', { className: 'sdkr-header' }, [
|
||||
var header = el('div', { className: 'ext-sdk-test-runner-header' }, [
|
||||
el('h1', {}, 'SDK Test Runner'),
|
||||
el('span', { className: 'sdkr-version' }, 'v' + (T.manifest.version || '?'))
|
||||
el('span', { className: 'ext-sdk-test-runner-version' }, 'v' + (T.manifest.version || '?'))
|
||||
]);
|
||||
mount.appendChild(header);
|
||||
|
||||
// Description
|
||||
mount.appendChild(el('p', { className: 'sdkr-desc' },
|
||||
mount.appendChild(el('p', { className: 'ext-sdk-test-runner-desc' },
|
||||
'Dual-path validation: SDK call → raw ICD fetch. ' +
|
||||
'SDK fail + ICD pass = SDK bug. Both fail = ICD bug.'));
|
||||
|
||||
// ── Fixtures Panel ──
|
||||
var fixturePanel = el('div', { className: 'sdkr-fixtures' });
|
||||
var fixturePanel = el('div', { className: 'ext-sdk-test-runner-fixtures' });
|
||||
|
||||
var fixRow = el('div', { className: 'sdkr-fixture-row' });
|
||||
fixRow.appendChild(el('span', { className: 'sdkr-fixture-label' }, 'FIXTURES:'));
|
||||
var fixRow = el('div', { className: 'ext-sdk-test-runner-fixture-row' });
|
||||
fixRow.appendChild(el('span', { className: 'ext-sdk-test-runner-fixture-label' }, 'FIXTURES:'));
|
||||
|
||||
// Provider dropdown
|
||||
var provSelect = el('select', { className: 'sdkr-select' });
|
||||
var provSelect = el('select', { className: 'ext-sdk-test-runner-select' });
|
||||
provSelect.appendChild(el('option', { value: '' }, '— provider —'));
|
||||
['openrouter', 'openai', 'anthropic', 'venice'].forEach(function (p) {
|
||||
provSelect.appendChild(el('option', { value: p }, p));
|
||||
@@ -85,18 +85,18 @@
|
||||
|
||||
// API key input
|
||||
var keyInput = el('input', {
|
||||
type: 'password', className: 'sdkr-input',
|
||||
type: 'password', className: 'ext-sdk-test-runner-input',
|
||||
placeholder: 'API key (optional — enables model tests)'
|
||||
});
|
||||
fixRow.appendChild(keyInput);
|
||||
|
||||
// Fixture status
|
||||
var fixStatus = el('span', { className: 'sdkr-fixture-status' }, '');
|
||||
var fixStatus = el('span', { className: 'ext-sdk-test-runner-fixture-status' }, '');
|
||||
|
||||
fixRow.appendChild(fixStatus);
|
||||
fixturePanel.appendChild(fixRow);
|
||||
|
||||
var fixNote = el('div', { className: 'sdkr-fixture-note' },
|
||||
var fixNote = el('div', { className: 'ext-sdk-test-runner-fixture-note' },
|
||||
'Provide a provider + API key to unlock embedding/model tests. ' +
|
||||
'Without a key, those tests are skipped. Key is never stored.');
|
||||
fixturePanel.appendChild(fixNote);
|
||||
@@ -107,16 +107,16 @@
|
||||
T._fixtureInput = { provSelect: provSelect, keyInput: keyInput, statusEl: fixStatus };
|
||||
|
||||
// Controls row
|
||||
var controlRow = el('div', { className: 'sdkr-controls' });
|
||||
var controlRow = el('div', { className: 'ext-sdk-test-runner-controls' });
|
||||
|
||||
// Domain filter checkboxes
|
||||
var filterBox = el('div', { className: 'sdkr-filters' });
|
||||
filterBox.appendChild(el('span', { className: 'sdkr-filter-label' }, 'Domains: '));
|
||||
var filterBox = el('div', { className: 'ext-sdk-test-runner-filters' });
|
||||
filterBox.appendChild(el('span', { className: 'ext-sdk-test-runner-filter-label' }, 'Domains: '));
|
||||
var domainNames = Object.keys(T.domains).sort();
|
||||
var domainChecks = {};
|
||||
|
||||
domainNames.forEach(function (name) {
|
||||
var label = el('label', { className: 'sdkr-check' });
|
||||
var label = el('label', { className: 'ext-sdk-test-runner-check' });
|
||||
var cb = el('input', { type: 'checkbox', checked: 'checked' });
|
||||
domainChecks[name] = cb;
|
||||
label.appendChild(cb);
|
||||
@@ -126,11 +126,11 @@
|
||||
controlRow.appendChild(filterBox);
|
||||
|
||||
// Buttons
|
||||
var btnRow = el('div', { className: 'sdkr-btn-row' });
|
||||
var runBtn = el('button', { className: 'sdkr-btn sdkr-btn-primary', onClick: onRun }, 'Run All');
|
||||
var abortBtn = el('button', { className: 'sdkr-btn sdkr-btn-danger', onClick: function () { T.abort(); } }, 'Abort');
|
||||
var btnRow = el('div', { className: 'ext-sdk-test-runner-btn-row' });
|
||||
var runBtn = el('button', { className: 'ext-sdk-test-runner-btn ext-sdk-test-runner-btn-primary', onClick: onRun }, 'Run All');
|
||||
var abortBtn = el('button', { className: 'ext-sdk-test-runner-btn ext-sdk-test-runner-btn-danger', onClick: function () { T.abort(); } }, 'Abort');
|
||||
abortBtn.style.display = 'none';
|
||||
var exportBtn = el('button', { className: 'sdkr-btn', onClick: onExport }, 'Export JSON');
|
||||
var exportBtn = el('button', { className: 'ext-sdk-test-runner-btn', onClick: onExport }, 'Export JSON');
|
||||
btnRow.appendChild(runBtn);
|
||||
btnRow.appendChild(abortBtn);
|
||||
btnRow.appendChild(exportBtn);
|
||||
@@ -138,12 +138,12 @@
|
||||
mount.appendChild(controlRow);
|
||||
|
||||
// Summary bar
|
||||
var summaryBar = el('div', { className: 'sdkr-summary' });
|
||||
var summaryBar = el('div', { className: 'ext-sdk-test-runner-summary' });
|
||||
mount.appendChild(summaryBar);
|
||||
|
||||
// Results table
|
||||
var tableWrap = el('div', { className: 'sdkr-table-wrap' });
|
||||
var table = el('table', { className: 'sdkr-table' });
|
||||
var tableWrap = el('div', { className: 'ext-sdk-test-runner-table-wrap' });
|
||||
var table = el('table', { className: 'ext-sdk-test-runner-table' });
|
||||
var thead = el('thead', {}, [
|
||||
el('tr', {}, [
|
||||
el('th', {}, '#'),
|
||||
@@ -176,7 +176,7 @@
|
||||
];
|
||||
items.forEach(function (item) {
|
||||
var badge = el('span', {
|
||||
className: 'sdkr-badge',
|
||||
className: 'ext-sdk-test-runner-badge',
|
||||
style: {
|
||||
background: item[1] > 0 ? COLORS[item[0]] : 'var(--bg2, #333)',
|
||||
color: item[1] > 0 ? '#fff' : 'var(--text3, #888)'
|
||||
@@ -184,7 +184,7 @@
|
||||
}, LABELS[item[0]] + ': ' + item[1]);
|
||||
summaryBar.appendChild(badge);
|
||||
});
|
||||
summaryBar.appendChild(el('span', { className: 'sdkr-badge',
|
||||
summaryBar.appendChild(el('span', { className: 'ext-sdk-test-runner-badge',
|
||||
style: { background: 'var(--bg2, #333)' }
|
||||
}, 'Total: ' + total));
|
||||
}
|
||||
@@ -197,19 +197,19 @@
|
||||
if (entry.rawErr) detail += (detail ? ' | ' : '') + 'ICD: ' + esc(entry.rawErr);
|
||||
if (entry.note) detail += (detail ? ' | ' : '') + esc(entry.note);
|
||||
|
||||
var row = el('tr', { className: 'sdkr-row sdkr-row-' + v.toLowerCase() }, [
|
||||
var row = el('tr', { className: 'ext-sdk-test-runner-row ext-sdk-test-runner-row-' + v.toLowerCase() }, [
|
||||
el('td', {}, String(idx)),
|
||||
el('td', {}, esc(entry.domain)),
|
||||
el('td', {}, esc(entry.group)),
|
||||
el('td', { className: 'sdkr-test-name' }, esc(entry.name)),
|
||||
el('td', { className: 'ext-sdk-test-runner-test-name' }, esc(entry.name)),
|
||||
el('td', {}, [
|
||||
el('span', {
|
||||
className: 'sdkr-verdict',
|
||||
className: 'ext-sdk-test-runner-verdict',
|
||||
style: { color: COLORS[v] || '#fff' }
|
||||
}, LABELS[v] || v)
|
||||
]),
|
||||
el('td', { className: 'sdkr-ms' }, String(entry.durationMs)),
|
||||
el('td', { className: 'sdkr-detail' }, detail)
|
||||
el('td', { className: 'ext-sdk-test-runner-ms' }, String(entry.durationMs)),
|
||||
el('td', { className: 'ext-sdk-test-runner-detail' }, detail)
|
||||
]);
|
||||
tbody.appendChild(row);
|
||||
updateSummary();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* ── Tasks Surface Styles ────────────────── */
|
||||
|
||||
.tasks-app {
|
||||
.ext-tasks-app {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 20px;
|
||||
@@ -11,36 +11,36 @@
|
||||
}
|
||||
|
||||
/* ── Header ──────────────────────────────── */
|
||||
.tasks-header {
|
||||
.ext-tasks-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tasks-title {
|
||||
.ext-tasks-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.tasks-stats {
|
||||
.ext-tasks-stats {
|
||||
font-size: 13px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.tasks-header__right {
|
||||
.ext-tasks-header__right {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.tasks-view-tabs {
|
||||
.ext-tasks-view-tabs {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
background: var(--bg-raised);
|
||||
border-radius: var(--radius);
|
||||
padding: 2px;
|
||||
}
|
||||
.tasks-view-tab {
|
||||
.ext-tasks-view-tab {
|
||||
padding: 5px 14px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
@@ -50,25 +50,25 @@
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.tasks-view-tab:hover { color: var(--text); background: var(--bg-hover); }
|
||||
.tasks-view-tab--active { color: var(--text); background: var(--bg-surface); }
|
||||
.ext-tasks-view-tab:hover { color: var(--text); background: var(--bg-hover); }
|
||||
.ext-tasks-view-tab--active { color: var(--text); background: var(--bg-surface); }
|
||||
|
||||
/* ── List View ───────────────────────────── */
|
||||
.task-list {
|
||||
.ext-tasks-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.task-list__filters {
|
||||
.ext-tasks-list__filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.task-filter {
|
||||
.ext-tasks-filter {
|
||||
padding: 5px 10px;
|
||||
font-size: 13px;
|
||||
background: var(--bg-raised);
|
||||
@@ -76,11 +76,11 @@
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.task-list__count {
|
||||
.ext-tasks-list__count {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.task-list__loading, .task-list__empty {
|
||||
.ext-tasks-list__loading, .ext-tasks-list__empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 40px 0;
|
||||
@@ -89,43 +89,43 @@
|
||||
}
|
||||
|
||||
/* ── Task Card ───────────────────────────── */
|
||||
.task-card {
|
||||
.ext-tasks-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px 14px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.task-card:hover {
|
||||
.ext-tasks-card:hover {
|
||||
border-color: var(--border-light);
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
.task-card__header {
|
||||
.ext-tasks-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.task-card__priority { font-size: 10px; }
|
||||
.task-card__title {
|
||||
.ext-tasks-card__priority { font-size: 10px; }
|
||||
.ext-tasks-card__title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.task-card__title:hover { color: var(--accent); }
|
||||
.task-card__status {
|
||||
.ext-tasks-card__title:hover { color: var(--accent); }
|
||||
.ext-tasks-card__status {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.task-card__status--todo { background: var(--accent-dim); color: var(--accent); }
|
||||
.task-card__status--in_progress { background: var(--warning-dim); color: var(--warning); }
|
||||
.task-card__status--done { background: var(--success-dim); color: var(--success); }
|
||||
.task-card__status--cancelled { background: var(--bg-raised); color: var(--text-3); }
|
||||
.ext-tasks-card__status--todo { background: var(--accent-dim); color: var(--accent); }
|
||||
.ext-tasks-card__status--in_progress { background: var(--warning-dim); color: var(--warning); }
|
||||
.ext-tasks-card__status--done { background: var(--success-dim); color: var(--success); }
|
||||
.ext-tasks-card__status--cancelled { background: var(--bg-raised); color: var(--text-3); }
|
||||
|
||||
.task-card__desc {
|
||||
.ext-tasks-card__desc {
|
||||
font-size: 13px;
|
||||
color: var(--text-2);
|
||||
margin-top: 6px;
|
||||
@@ -134,7 +134,7 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-card__footer {
|
||||
.ext-tasks-card__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
@@ -142,17 +142,17 @@
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.task-card__actions {
|
||||
.ext-tasks-card__actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.task-card:hover .task-card__actions { opacity: 1; }
|
||||
.ext-tasks-card:hover .ext-tasks-card__actions { opacity: 1; }
|
||||
|
||||
/* ── Inline buttons ──────────────────────── */
|
||||
.task-btn {
|
||||
.ext-tasks-btn {
|
||||
border: none;
|
||||
background: var(--bg-raised);
|
||||
color: var(--text-2);
|
||||
@@ -160,12 +160,12 @@
|
||||
border-radius: var(--radius);
|
||||
transition: var(--transition);
|
||||
}
|
||||
.task-btn--sm { padding: 2px 8px; font-size: 13px; }
|
||||
.task-btn:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.task-btn--danger:hover { background: var(--danger-dim); color: var(--danger); }
|
||||
.ext-tasks-btn--sm { padding: 2px 8px; font-size: 13px; }
|
||||
.ext-tasks-btn:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.ext-tasks-btn--danger:hover { background: var(--danger-dim); color: var(--danger); }
|
||||
|
||||
/* ── Board View ──────────────────────────── */
|
||||
.task-board {
|
||||
.ext-tasks-board {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
@@ -173,14 +173,14 @@
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.task-board__col {
|
||||
.ext-tasks-board__col {
|
||||
background: var(--bg-raised);
|
||||
border-radius: var(--radius);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
.task-board__col-header {
|
||||
.ext-tasks-board__col-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -191,14 +191,14 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.task-board__col-count {
|
||||
.ext-tasks-board__col-count {
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
background: var(--bg-hover);
|
||||
padding: 1px 7px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.task-board__col-body {
|
||||
.ext-tasks-board__col-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
@@ -206,7 +206,7 @@
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.task-board__card {
|
||||
.ext-tasks-board__card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
@@ -214,10 +214,10 @@
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.task-board__card:hover {
|
||||
.ext-tasks-board__card:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.task-board__card-title {
|
||||
.ext-tasks-board__card-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
@@ -225,19 +225,19 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.task-board__card-due {
|
||||
.ext-tasks-board__card-due {
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ── Form ────────────────────────────────── */
|
||||
.task-form { display: flex; flex-direction: column; gap: 12px; }
|
||||
.task-form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.task-form__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
|
||||
.task-form textarea,
|
||||
.task-form input,
|
||||
.task-form select {
|
||||
.ext-tasks-form { display: flex; flex-direction: column; gap: 12px; }
|
||||
.ext-tasks-form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.ext-tasks-form__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
|
||||
.ext-tasks-form textarea,
|
||||
.ext-tasks-form input,
|
||||
.ext-tasks-form select {
|
||||
width: 100%;
|
||||
padding: 7px 10px;
|
||||
font-size: 13px;
|
||||
@@ -247,15 +247,15 @@
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--font);
|
||||
}
|
||||
.task-form textarea:focus,
|
||||
.task-form input:focus,
|
||||
.task-form select:focus {
|
||||
.ext-tasks-form textarea:focus,
|
||||
.ext-tasks-form input:focus,
|
||||
.ext-tasks-form select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── Statusbar widget ────────────────────── */
|
||||
.task-status-widget {
|
||||
.ext-tasks-status-widget {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
padding: 4px 10px;
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
return html`
|
||||
<${Dialog} open onClose=${onClose} title=${task ? 'Edit Task' : 'New Task'}>
|
||||
<form onSubmit=${handleSubmit} class="task-form">
|
||||
<form onSubmit=${handleSubmit} class="ext-tasks-form">
|
||||
${error && html`<${Banner} variant="danger" text=${error} />`}
|
||||
<${FormField} label="Title" required>
|
||||
<input type="text" value=${title} onInput=${e => setTitle(e.target.value)}
|
||||
@@ -98,7 +98,7 @@
|
||||
<textarea rows="3" value=${desc} onInput=${e => setDesc(e.target.value)}
|
||||
placeholder="Details (optional)" />
|
||||
<//>
|
||||
<div class="task-form__row">
|
||||
<div class="ext-tasks-form__row">
|
||||
<${FormField} label="Status">
|
||||
<select value=${status} onChange=${e => setStatus(e.target.value)}>
|
||||
${STATUSES.map(s => html`<option value=${s}>${STATUS_LABELS[s] || s}</option>`)}
|
||||
@@ -110,7 +110,7 @@
|
||||
</select>
|
||||
<//>
|
||||
</div>
|
||||
<div class="task-form__row">
|
||||
<div class="ext-tasks-form__row">
|
||||
<${FormField} label="Due Date">
|
||||
<input type="date" value=${dueDate} onInput=${e => setDueDate(e.target.value)} />
|
||||
<//>
|
||||
@@ -119,7 +119,7 @@
|
||||
placeholder="comma-separated" />
|
||||
<//>
|
||||
</div>
|
||||
<div class="task-form__actions">
|
||||
<div class="ext-tasks-form__actions">
|
||||
<${Button} variant="ghost" type="button" onClick=${onClose}>Cancel<//>
|
||||
<${Button} variant="primary" type="submit" disabled=${saving}>
|
||||
${saving ? 'Saving…' : (task ? 'Update' : 'Create')}
|
||||
@@ -155,45 +155,45 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="task-list">
|
||||
<div class="task-list__filters">
|
||||
<select value=${filter} onChange=${e => setFilter(e.target.value)} class="task-filter">
|
||||
<div class="ext-tasks-list">
|
||||
<div class="ext-tasks-list__filters">
|
||||
<select value=${filter} onChange=${e => setFilter(e.target.value)} class="ext-tasks-filter">
|
||||
<option value="all">All</option>
|
||||
${STATUSES.map(s => html`<option value=${s}>${STATUS_LABELS[s] || s}</option>`)}
|
||||
</select>
|
||||
<span class="task-list__count">${filtered.length} task${filtered.length !== 1 ? 's' : ''}</span>
|
||||
<span class="ext-tasks-list__count">${filtered.length} task${filtered.length !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
|
||||
${loading && html`<div class="task-list__loading"><${Spinner} size="md" /></div>`}
|
||||
${loading && html`<div class="ext-tasks-list__loading"><${Spinner} size="md" /></div>`}
|
||||
|
||||
${!loading && filtered.length === 0 && html`
|
||||
<div class="task-list__empty">No tasks${filter !== 'all' ? ' matching filter' : ''}. Create one to get started.</div>
|
||||
<div class="ext-tasks-list__empty">No tasks${filter !== 'all' ? ' matching filter' : ''}. Create one to get started.</div>
|
||||
`}
|
||||
|
||||
${!loading && filtered.map(function(t) {
|
||||
return html`
|
||||
<div class="task-card" key=${t.id}>
|
||||
<div class="task-card__header">
|
||||
<span class="task-card__priority" style="color:${PRIORITY_COLORS[t.priority] || 'var(--text-3)'}">
|
||||
<div class="ext-tasks-card" key=${t.id}>
|
||||
<div class="ext-tasks-card__header">
|
||||
<span class="ext-tasks-card__priority" style="color:${PRIORITY_COLORS[t.priority] || 'var(--text-3)'}">
|
||||
●
|
||||
</span>
|
||||
<span class="task-card__title" onClick=${() => onEdit(t)}>${t.title}</span>
|
||||
<span class="task-card__status task-card__status--${t.status}">
|
||||
<span class="ext-tasks-card__title" onClick=${() => onEdit(t)}>${t.title}</span>
|
||||
<span class="ext-tasks-card__status ext-tasks-card__status--${t.status}">
|
||||
${STATUS_LABELS[t.status] || t.status}
|
||||
</span>
|
||||
</div>
|
||||
${t.description && html`
|
||||
<div class="task-card__desc">${t.description}</div>
|
||||
<div class="ext-tasks-card__desc">${t.description}</div>
|
||||
`}
|
||||
<div class="task-card__footer">
|
||||
${t.due_date && html`<span class="task-card__due">Due: ${t.due_date}</span>`}
|
||||
${t.tags && html`<span class="task-card__tags">${t.tags}</span>`}
|
||||
<div class="task-card__actions">
|
||||
<div class="ext-tasks-card__footer">
|
||||
${t.due_date && html`<span class="ext-tasks-card__due">Due: ${t.due_date}</span>`}
|
||||
${t.tags && html`<span class="ext-tasks-card__tags">${t.tags}</span>`}
|
||||
<div class="ext-tasks-card__actions">
|
||||
${t.status !== 'done' && html`
|
||||
<button class="task-btn task-btn--sm" onClick=${() => handleQuickStatus(t.id, 'done')}
|
||||
<button class="ext-tasks-btn ext-tasks-btn--sm" onClick=${() => handleQuickStatus(t.id, 'done')}
|
||||
title="Mark done">✓</button>
|
||||
`}
|
||||
<button class="task-btn task-btn--sm task-btn--danger" onClick=${() => handleDelete(t.id)}
|
||||
<button class="ext-tasks-btn ext-tasks-btn--sm ext-tasks-btn--danger" onClick=${() => handleDelete(t.id)}
|
||||
title="Delete">×</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -216,25 +216,25 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="task-board">
|
||||
${loading && html`<div class="task-list__loading"><${Spinner} size="md" /></div>`}
|
||||
<div class="ext-tasks-board">
|
||||
${loading && html`<div class="ext-tasks-list__loading"><${Spinner} size="md" /></div>`}
|
||||
${!loading && STATUSES.map(function(status) {
|
||||
var col = items.filter(function(t) { return t.status === status; });
|
||||
return html`
|
||||
<div class="task-board__col" key=${status}>
|
||||
<div class="task-board__col-header">
|
||||
<span class="task-board__col-title">${STATUS_LABELS[status] || status}</span>
|
||||
<span class="task-board__col-count">${col.length}</span>
|
||||
<div class="ext-tasks-board__col" key=${status}>
|
||||
<div class="ext-tasks-board__col-header">
|
||||
<span class="ext-tasks-board__col-title">${STATUS_LABELS[status] || status}</span>
|
||||
<span class="ext-tasks-board__col-count">${col.length}</span>
|
||||
</div>
|
||||
<div class="task-board__col-body">
|
||||
<div class="ext-tasks-board__col-body">
|
||||
${col.map(function(t) {
|
||||
return html`
|
||||
<div class="task-board__card" key=${t.id} onClick=${() => onEdit(t)}>
|
||||
<div class="task-board__card-title">
|
||||
<span class="task-card__priority" style="color:${PRIORITY_COLORS[t.priority] || 'var(--text-3)'}">●</span>
|
||||
<div class="ext-tasks-board__card" key=${t.id} onClick=${() => onEdit(t)}>
|
||||
<div class="ext-tasks-board__card-title">
|
||||
<span class="ext-tasks-card__priority" style="color:${PRIORITY_COLORS[t.priority] || 'var(--text-3)'}">●</span>
|
||||
${t.title}
|
||||
</div>
|
||||
${t.due_date && html`<div class="task-board__card-due">Due: ${t.due_date}</div>`}
|
||||
${t.due_date && html`<div class="ext-tasks-board__card-due">Due: ${t.due_date}</div>`}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
@@ -307,22 +307,22 @@
|
||||
var Topbar = sw.shell?.Topbar;
|
||||
|
||||
return html`
|
||||
<div class="tasks-app">
|
||||
<div class="ext-tasks-app">
|
||||
${Topbar ? html`
|
||||
<${Topbar} title="Tasks">
|
||||
${stats && html`<span class="tasks-stats">${stats.total || 0} total</span>`}
|
||||
<div class="tasks-view-tabs">
|
||||
${stats && html`<span class="ext-tasks-stats">${stats.total || 0} total</span>`}
|
||||
<div class="ext-tasks-view-tabs">
|
||||
${tabs.map(function(tab) {
|
||||
return html`<button key=${tab.id}
|
||||
class="tasks-view-tab ${view === tab.id ? 'tasks-view-tab--active' : ''}"
|
||||
class="ext-tasks-view-tab ${view === tab.id ? 'ext-tasks-view-tab--active' : ''}"
|
||||
onClick=${() => setView(tab.id)}>${tab.label}</button>`;
|
||||
})}
|
||||
</div>
|
||||
<${Button} variant="primary" size="sm" onClick=${() => setEditTask({})}>+ New Task<//>
|
||||
<//>
|
||||
` : html`
|
||||
<div class="tasks-header">
|
||||
<h1 class="tasks-title">Tasks</h1>
|
||||
<div class="ext-tasks-header">
|
||||
<h1 class="ext-tasks-title">Tasks</h1>
|
||||
<${Button} variant="primary" size="sm" onClick=${() => setEditTask({})}>+ New Task<//>
|
||||
</div>
|
||||
`}
|
||||
@@ -381,7 +381,7 @@
|
||||
}, []);
|
||||
|
||||
if (count === null) return null;
|
||||
return html`<span class="task-status-widget" title="Open tasks">${count} open task${count !== 1 ? 's' : ''}</span>`;
|
||||
return html`<span class="ext-tasks-status-widget" title="Open tasks">${count} open task${count !== 1 ? 's' : ''}</span>`;
|
||||
}
|
||||
|
||||
sw.slots.register('statusbar', {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Uses platform CSS variables exclusively for theming.
|
||||
*/
|
||||
|
||||
.tal-shell {
|
||||
.ext-team-activity-log-shell {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
@@ -14,34 +14,34 @@
|
||||
|
||||
/* ── Header ────────────────────────────────── */
|
||||
|
||||
.tal-header {
|
||||
.ext-team-activity-log-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tal-header__left {
|
||||
.ext-team-activity-log-header__left {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tal-title {
|
||||
.ext-team-activity-log-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tal-subtitle {
|
||||
.ext-team-activity-log-subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* ── Entry Form ────────────────────────────── */
|
||||
|
||||
.tal-form {
|
||||
.ext-team-activity-log-form {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
background: var(--bg-surface);
|
||||
@@ -50,7 +50,7 @@
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.tal-form__select {
|
||||
.ext-team-activity-log-form__select {
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
@@ -61,7 +61,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tal-form__input {
|
||||
.ext-team-activity-log-form__input {
|
||||
flex: 1;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--border);
|
||||
@@ -74,15 +74,15 @@
|
||||
transition: border-color var(--transition);
|
||||
}
|
||||
|
||||
.tal-form__input:focus {
|
||||
.ext-team-activity-log-form__input:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.tal-form__input::placeholder {
|
||||
.ext-team-activity-log-form__input::placeholder {
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.tal-form__btn {
|
||||
.ext-team-activity-log-form__btn {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
@@ -95,25 +95,25 @@
|
||||
transition: background var(--transition);
|
||||
}
|
||||
|
||||
.tal-form__btn:hover:not(:disabled) {
|
||||
.ext-team-activity-log-form__btn:hover:not(:disabled) {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.tal-form__btn:disabled {
|
||||
.ext-team-activity-log-form__btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* ── Filters ───────────────────────────────── */
|
||||
|
||||
.tal-filters {
|
||||
.ext-team-activity-log-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tal-filter {
|
||||
.ext-team-activity-log-filter {
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
@@ -125,18 +125,18 @@
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.tal-filter:hover {
|
||||
.ext-team-activity-log-filter:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.tal-filter.active {
|
||||
.ext-team-activity-log-filter.ext-team-activity-log-active {
|
||||
background: var(--accent-dim);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.tal-count {
|
||||
.ext-team-activity-log-count {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
@@ -144,13 +144,13 @@
|
||||
|
||||
/* ── Entry List ────────────────────────────── */
|
||||
|
||||
.tal-entries {
|
||||
.ext-team-activity-log-entries {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tal-entry {
|
||||
.ext-team-activity-log-entry {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
@@ -158,36 +158,36 @@
|
||||
transition: border-color var(--transition);
|
||||
}
|
||||
|
||||
.tal-entry:hover {
|
||||
.ext-team-activity-log-entry:hover {
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
|
||||
.tal-entry__header {
|
||||
.ext-team-activity-log-entry__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tal-entry__category {
|
||||
.ext-team-activity-log-entry__category {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.tal-entry__user {
|
||||
.ext-team-activity-log-entry__user {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.tal-entry__time {
|
||||
.ext-team-activity-log-entry__time {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.tal-entry__delete {
|
||||
.ext-team-activity-log-entry__delete {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-3);
|
||||
@@ -199,15 +199,15 @@
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.tal-entry:hover .tal-entry__delete {
|
||||
.ext-team-activity-log-entry:hover .ext-team-activity-log-entry__delete {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tal-entry__delete:hover {
|
||||
.ext-team-activity-log-entry__delete:hover {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.tal-entry__message {
|
||||
.ext-team-activity-log-entry__message {
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
line-height: 1.5;
|
||||
@@ -215,8 +215,8 @@
|
||||
|
||||
/* ── Empty / Loading ───────────────────────── */
|
||||
|
||||
.tal-empty,
|
||||
.tal-loading {
|
||||
.ext-team-activity-log-empty,
|
||||
.ext-team-activity-log-loading {
|
||||
text-align: center;
|
||||
padding: 40px 16px;
|
||||
color: var(--text-3);
|
||||
|
||||
@@ -104,34 +104,34 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="tal-shell">
|
||||
<header class="tal-header">
|
||||
<div class="tal-header__left">
|
||||
<h1 class="tal-title">Activity Log</h1>
|
||||
<span class="tal-subtitle">Team activity tracker</span>
|
||||
<div class="ext-team-activity-log-shell">
|
||||
<header class="ext-team-activity-log-header">
|
||||
<div class="ext-team-activity-log-header__left">
|
||||
<h1 class="ext-team-activity-log-title">Activity Log</h1>
|
||||
<span class="ext-team-activity-log-subtitle">Team activity tracker</span>
|
||||
</div>
|
||||
<div class="tal-header__right">
|
||||
<div class="ext-team-activity-log-header__right">
|
||||
<div ref=${menuRef}></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<${EntryForm} onSubmit=${addEntry} />
|
||||
|
||||
<div class="tal-filters">
|
||||
<button class="tal-filter ${filter === '' ? 'active' : ''}"
|
||||
<div class="ext-team-activity-log-filters">
|
||||
<button class="ext-team-activity-log-filter ${filter === '' ? 'ext-team-activity-log-active' : ''}"
|
||||
onClick=${() => setFilter('')}>All</button>
|
||||
${CATEGORIES.map(c => html`
|
||||
<button key=${c}
|
||||
class="tal-filter ${filter === c ? 'active' : ''}"
|
||||
class="ext-team-activity-log-filter ${filter === c ? 'ext-team-activity-log-active' : ''}"
|
||||
onClick=${() => setFilter(c)}>
|
||||
${c}
|
||||
</button>
|
||||
`)}
|
||||
<span class="tal-count">${entries.length} entries</span>
|
||||
<span class="ext-team-activity-log-count">${entries.length} entries</span>
|
||||
</div>
|
||||
|
||||
${loading
|
||||
? html`<div class="tal-loading">Loading…</div>`
|
||||
? html`<div class="ext-team-activity-log-loading">Loading…</div>`
|
||||
: html`<${EntryList} entries=${entries} onDelete=${deleteEntry} />`
|
||||
}
|
||||
</div>
|
||||
@@ -153,17 +153,17 @@
|
||||
}
|
||||
|
||||
return html`
|
||||
<form class="tal-form" onSubmit=${submit}>
|
||||
<select class="tal-form__select" value=${category}
|
||||
<form class="ext-team-activity-log-form" onSubmit=${submit}>
|
||||
<select class="ext-team-activity-log-form__select" value=${category}
|
||||
onChange=${e => setCategory(e.target.value)}>
|
||||
${CATEGORIES.map(c => html`<option key=${c} value=${c}>${c}</option>`)}
|
||||
</select>
|
||||
<input class="tal-form__input" type="text"
|
||||
<input class="ext-team-activity-log-form__input" type="text"
|
||||
placeholder="What happened?"
|
||||
value=${message}
|
||||
onInput=${e => setMessage(e.target.value)}
|
||||
disabled=${submitting} />
|
||||
<button class="tal-form__btn" type="submit" disabled=${submitting || !message.trim()}>
|
||||
<button class="ext-team-activity-log-form__btn" type="submit" disabled=${submitting || !message.trim()}>
|
||||
${submitting ? '…' : 'Log'}
|
||||
</button>
|
||||
</form>
|
||||
@@ -172,21 +172,21 @@
|
||||
|
||||
function EntryList({ entries, onDelete }) {
|
||||
if (!entries.length) {
|
||||
return html`<div class="tal-empty">No entries yet. Log your first activity above.</div>`;
|
||||
return html`<div class="ext-team-activity-log-empty">No entries yet. Log your first activity above.</div>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="tal-entries">
|
||||
<div class="ext-team-activity-log-entries">
|
||||
${entries.map(e => html`
|
||||
<div key=${e.id} class="tal-entry">
|
||||
<div class="tal-entry__header">
|
||||
<span class="tal-entry__category badge">${e.category}</span>
|
||||
<span class="tal-entry__user">${esc(e.username || 'unknown')}</span>
|
||||
<span class="tal-entry__time">${timeAgo(e.created_at)}</span>
|
||||
<button class="tal-entry__delete" onClick=${() => onDelete(e.id)}
|
||||
<div key=${e.id} class="ext-team-activity-log-entry">
|
||||
<div class="ext-team-activity-log-entry__header">
|
||||
<span class="ext-team-activity-log-entry__category badge">${e.category}</span>
|
||||
<span class="ext-team-activity-log-entry__user">${esc(e.username || 'unknown')}</span>
|
||||
<span class="ext-team-activity-log-entry__time">${timeAgo(e.created_at)}</span>
|
||||
<button class="ext-team-activity-log-entry__delete" onClick=${() => onDelete(e.id)}
|
||||
title="Delete">×</button>
|
||||
</div>
|
||||
<div class="tal-entry__message">${esc(e.message)}</div>
|
||||
<div class="ext-team-activity-log-entry__message">${esc(e.message)}</div>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
defined in variables.css for automatic dark/light support.
|
||||
========================================== */
|
||||
|
||||
.surface-workflow-demo {
|
||||
.ext-workflow-demo {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1.5rem 2rem;
|
||||
}
|
||||
|
||||
/* Topbar */
|
||||
.demo-topbar {
|
||||
.ext-workflow-demo-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -19,7 +19,7 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.demo-topbar h1 {
|
||||
.ext-workflow-demo-topbar h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
@@ -27,26 +27,26 @@
|
||||
}
|
||||
|
||||
/* Subtitle */
|
||||
.demo-subtitle {
|
||||
.ext-workflow-demo-subtitle {
|
||||
color: var(--text-2);
|
||||
margin: 0 0 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
.demo-grid {
|
||||
.ext-workflow-demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.demo-grid {
|
||||
.ext-workflow-demo-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.demo-card {
|
||||
.ext-workflow-demo-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
@@ -58,22 +58,22 @@
|
||||
}
|
||||
|
||||
/* Title row */
|
||||
.card-title-row {
|
||||
.ext-workflow-demo-card-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.card-title-row h2 {
|
||||
.ext-workflow-demo-card-title-row h2 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
color: var(--text);
|
||||
}
|
||||
.card-icon {
|
||||
.ext-workflow-demo-card-icon {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
.card-tier {
|
||||
.ext-workflow-demo-card-tier {
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
@@ -81,11 +81,11 @@
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.badge-browser { background: var(--success-dim); color: var(--success-light); }
|
||||
.badge-starlark { background: var(--accent-dim); color: var(--accent-light); }
|
||||
.ext-workflow-demo-badge-browser { background: var(--success-dim); color: var(--success-light); }
|
||||
.ext-workflow-demo-badge-starlark { background: var(--accent-dim); color: var(--accent-light); }
|
||||
|
||||
/* Description */
|
||||
.card-desc {
|
||||
.ext-workflow-demo-card-desc {
|
||||
color: var(--text-2);
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
@@ -93,12 +93,12 @@
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.card-badges {
|
||||
.ext-workflow-demo-card-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.demo-card .badge {
|
||||
.ext-workflow-demo-card .ext-workflow-demo-badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
@@ -106,12 +106,12 @@
|
||||
color: var(--text-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.demo-card .badge-installed { background: var(--success-dim); color: var(--success-light); }
|
||||
.demo-card .badge-not-installed { background: var(--warning-dim); color: var(--warning-light); }
|
||||
.demo-card .badge-needs-publish { background: var(--danger-dim); color: var(--danger-light); }
|
||||
.ext-workflow-demo-card .ext-workflow-demo-badge-installed { background: var(--success-dim); color: var(--success-light); }
|
||||
.ext-workflow-demo-card .ext-workflow-demo-badge-not-installed { background: var(--warning-dim); color: var(--warning-light); }
|
||||
.ext-workflow-demo-card .ext-workflow-demo-badge-needs-publish { background: var(--danger-dim); color: var(--danger-light); }
|
||||
|
||||
/* Stage flow diagram */
|
||||
.stage-flow {
|
||||
.ext-workflow-demo-stage-flow {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.25rem;
|
||||
@@ -122,7 +122,7 @@
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.stage-node {
|
||||
.ext-workflow-demo-stage-node {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem 0.6rem;
|
||||
@@ -132,31 +132,31 @@
|
||||
font-size: 0.75rem;
|
||||
color: var(--text);
|
||||
}
|
||||
.stage-node.stage-public {
|
||||
.ext-workflow-demo-stage-node.ext-workflow-demo-stage-public {
|
||||
border-color: var(--success);
|
||||
background: var(--success-dim);
|
||||
}
|
||||
.stage-node.stage-system {
|
||||
.ext-workflow-demo-stage-node.ext-workflow-demo-stage-system {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent-dim);
|
||||
}
|
||||
|
||||
.stage-label {
|
||||
.ext-workflow-demo-stage-label {
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.stage-meta {
|
||||
.ext-workflow-demo-stage-meta {
|
||||
color: var(--text-3);
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
.stage-note {
|
||||
.ext-workflow-demo-stage-note {
|
||||
color: var(--warning-light);
|
||||
font-size: 0.6rem;
|
||||
font-style: italic;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.stage-arrow {
|
||||
.ext-workflow-demo-stage-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.1rem;
|
||||
@@ -166,25 +166,25 @@
|
||||
}
|
||||
|
||||
/* Collapsible sections */
|
||||
.card-collapsible {
|
||||
.ext-workflow-demo-card-collapsible {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.card-collapsible summary {
|
||||
.ext-workflow-demo-card-collapsible summary {
|
||||
padding: 0.5rem 0.75rem;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
color: var(--text);
|
||||
}
|
||||
.card-collapsible summary:hover {
|
||||
.ext-workflow-demo-card-collapsible summary:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.collapsible-content {
|
||||
.ext-workflow-demo-collapsible-content {
|
||||
padding: 0 0.75rem 0.75rem;
|
||||
}
|
||||
.collapsible-content pre {
|
||||
.ext-workflow-demo-collapsible-content pre {
|
||||
background: var(--bg-raised);
|
||||
color: var(--text);
|
||||
padding: 0.5rem;
|
||||
@@ -197,7 +197,7 @@
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
.card-actions {
|
||||
.ext-workflow-demo-card-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
@@ -205,7 +205,7 @@
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.demo-card .btn {
|
||||
.ext-workflow-demo-card .ext-workflow-demo-btn {
|
||||
padding: 0.4rem 1rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
@@ -216,13 +216,13 @@
|
||||
/* demo-card buttons — uses kernel .sw-btn--primary */
|
||||
|
||||
/* Public link row */
|
||||
.public-link-row {
|
||||
.ext-workflow-demo-public-link-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.public-link-row input {
|
||||
.ext-workflow-demo-public-link-row input {
|
||||
flex: 1;
|
||||
font-size: 0.7rem;
|
||||
padding: 4px 8px;
|
||||
@@ -232,7 +232,7 @@
|
||||
color: var(--text);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
.public-link-row .btn-copy {
|
||||
.ext-workflow-demo-public-link-row .ext-workflow-demo-btn-copy {
|
||||
padding: 4px 10px;
|
||||
font-size: 0.7rem;
|
||||
background: var(--bg-raised);
|
||||
@@ -241,6 +241,6 @@
|
||||
color: var(--text-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
.public-link-row .btn-copy:hover {
|
||||
.ext-workflow-demo-public-link-row .ext-workflow-demo-btn-copy:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
@@ -103,25 +103,25 @@
|
||||
if (!mount) return;
|
||||
|
||||
const surface = document.createElement('div');
|
||||
surface.className = 'surface-workflow-demo';
|
||||
surface.className = 'ext-workflow-demo';
|
||||
mount.appendChild(surface);
|
||||
|
||||
// Topbar with user menu
|
||||
const topbar = document.createElement('div');
|
||||
topbar.className = 'demo-topbar';
|
||||
topbar.className = 'ext-workflow-demo-topbar';
|
||||
topbar.innerHTML = '<h1>Workflow Demo</h1>';
|
||||
surface.appendChild(topbar);
|
||||
sw.userMenu(topbar, { flyout: 'down' });
|
||||
|
||||
// Subtitle
|
||||
const subtitle = document.createElement('p');
|
||||
subtitle.className = 'demo-subtitle';
|
||||
subtitle.className = 'ext-workflow-demo-subtitle';
|
||||
subtitle.textContent = 'Four example workflows that prove the engine works end-to-end. Install any package via Admin > Packages.';
|
||||
surface.appendChild(subtitle);
|
||||
|
||||
// Workflow cards
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'demo-grid';
|
||||
grid.className = 'ext-workflow-demo-grid';
|
||||
surface.appendChild(grid);
|
||||
|
||||
// Fetch installed workflows to check status
|
||||
@@ -145,28 +145,28 @@
|
||||
|
||||
function _buildCard(wf, installedWf) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'demo-card';
|
||||
card.className = 'ext-workflow-demo-card';
|
||||
|
||||
// Title row
|
||||
const titleRow = document.createElement('div');
|
||||
titleRow.className = 'card-title-row';
|
||||
titleRow.innerHTML = '<span class="card-icon">' + wf.icon + '</span>'
|
||||
titleRow.className = 'ext-workflow-demo-card-title-row';
|
||||
titleRow.innerHTML = '<span class="ext-workflow-demo-card-icon">' + wf.icon + '</span>'
|
||||
+ '<h2>' + _esc(wf.title) + '</h2>'
|
||||
+ '<span class="card-tier badge-' + wf.tier + '">' + wf.tier + '</span>';
|
||||
+ '<span class="ext-workflow-demo-card-tier ext-workflow-demo-badge-' + wf.tier + '">' + wf.tier + '</span>';
|
||||
card.appendChild(titleRow);
|
||||
|
||||
// Description
|
||||
const desc = document.createElement('p');
|
||||
desc.className = 'card-desc';
|
||||
desc.className = 'ext-workflow-demo-card-desc';
|
||||
desc.textContent = wf.description;
|
||||
card.appendChild(desc);
|
||||
|
||||
// Badges
|
||||
const badges = document.createElement('div');
|
||||
badges.className = 'card-badges';
|
||||
badges.className = 'ext-workflow-demo-card-badges';
|
||||
for (const b of wf.badges) {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'badge';
|
||||
span.className = 'ext-workflow-demo-badge';
|
||||
span.textContent = b;
|
||||
badges.appendChild(span);
|
||||
}
|
||||
@@ -188,7 +188,7 @@
|
||||
|
||||
// Status + action
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'card-actions';
|
||||
actions.className = 'ext-workflow-demo-card-actions';
|
||||
|
||||
if (installedWf) {
|
||||
const isActive = installedWf.is_active;
|
||||
@@ -199,18 +199,18 @@
|
||||
|
||||
// Installed badge
|
||||
const status = document.createElement('span');
|
||||
status.className = 'badge badge-installed';
|
||||
status.className = 'ext-workflow-demo-badge ext-workflow-demo-badge-installed';
|
||||
status.textContent = 'Installed';
|
||||
actions.appendChild(status);
|
||||
|
||||
if (!isActive) {
|
||||
const hint = document.createElement('span');
|
||||
hint.className = 'badge badge-needs-publish';
|
||||
hint.className = 'ext-workflow-demo-badge ext-workflow-demo-badge-needs-publish';
|
||||
hint.textContent = 'Not Active';
|
||||
actions.appendChild(hint);
|
||||
} else if (!isPublished) {
|
||||
const hint = document.createElement('span');
|
||||
hint.className = 'badge badge-needs-publish';
|
||||
hint.className = 'ext-workflow-demo-badge ext-workflow-demo-badge-needs-publish';
|
||||
hint.textContent = 'Not Published';
|
||||
actions.appendChild(hint);
|
||||
}
|
||||
@@ -235,14 +235,14 @@
|
||||
// Public link for public_link workflows
|
||||
if (installedWf.entry_mode === 'public_link' && isReady) {
|
||||
const linkRow = document.createElement('div');
|
||||
linkRow.className = 'public-link-row';
|
||||
linkRow.className = 'ext-workflow-demo-public-link-row';
|
||||
const linkInput = document.createElement('input');
|
||||
linkInput.type = 'text';
|
||||
linkInput.readOnly = true;
|
||||
linkInput.value = window.location.origin + base + '/api/v1/public/workflows/' + installedWf.id + '/start';
|
||||
linkRow.appendChild(linkInput);
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.className = 'btn-copy';
|
||||
copyBtn.className = 'ext-workflow-demo-btn-copy';
|
||||
copyBtn.textContent = 'Copy';
|
||||
copyBtn.onclick = function () {
|
||||
navigator.clipboard.writeText(linkInput.value).then(function () {
|
||||
@@ -255,7 +255,7 @@
|
||||
}
|
||||
} else {
|
||||
const status = document.createElement('span');
|
||||
status.className = 'badge badge-not-installed';
|
||||
status.className = 'ext-workflow-demo-badge ext-workflow-demo-badge-not-installed';
|
||||
status.textContent = 'Not Installed';
|
||||
actions.appendChild(status);
|
||||
}
|
||||
@@ -268,7 +268,7 @@
|
||||
|
||||
function _buildStageDiagram(wf) {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'stage-flow';
|
||||
container.className = 'ext-workflow-demo-stage-flow';
|
||||
|
||||
const stages = wf.stages;
|
||||
let hasBranch = false;
|
||||
@@ -277,21 +277,21 @@
|
||||
const s = stages[i];
|
||||
|
||||
const node = document.createElement('div');
|
||||
node.className = 'stage-node stage-' + s.audience;
|
||||
node.className = 'ext-workflow-demo-stage-node ext-workflow-demo-stage-' + s.audience;
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'stage-label';
|
||||
label.className = 'ext-workflow-demo-stage-label';
|
||||
label.textContent = s.name;
|
||||
node.appendChild(label);
|
||||
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'stage-meta';
|
||||
meta.className = 'ext-workflow-demo-stage-meta';
|
||||
meta.textContent = s.mode + (s.audience !== 'team' ? ' (' + s.audience + ')' : '');
|
||||
node.appendChild(meta);
|
||||
|
||||
if (s.note) {
|
||||
const note = document.createElement('div');
|
||||
note.className = 'stage-note';
|
||||
note.className = 'ext-workflow-demo-stage-note';
|
||||
note.textContent = s.note;
|
||||
node.appendChild(note);
|
||||
}
|
||||
@@ -301,13 +301,13 @@
|
||||
// Arrow (except after last stage)
|
||||
if (i < stages.length - 1 && !stages[i + 1].branch) {
|
||||
const arrow = document.createElement('div');
|
||||
arrow.className = 'stage-arrow';
|
||||
arrow.className = 'ext-workflow-demo-stage-arrow';
|
||||
arrow.textContent = '\u2192';
|
||||
container.appendChild(arrow);
|
||||
} else if (stages[i + 1] && stages[i + 1].branch) {
|
||||
if (!hasBranch) {
|
||||
const split = document.createElement('div');
|
||||
split.className = 'stage-arrow stage-branch';
|
||||
split.className = 'ext-workflow-demo-stage-arrow ext-workflow-demo-stage-branch';
|
||||
split.textContent = '\u2193\u2197';
|
||||
container.appendChild(split);
|
||||
hasBranch = true;
|
||||
@@ -322,14 +322,14 @@
|
||||
|
||||
function _buildCollapsible(title, innerHtml) {
|
||||
const details = document.createElement('details');
|
||||
details.className = 'card-collapsible';
|
||||
details.className = 'ext-workflow-demo-card-collapsible';
|
||||
|
||||
const summary = document.createElement('summary');
|
||||
summary.textContent = title;
|
||||
details.appendChild(summary);
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'collapsible-content';
|
||||
content.className = 'ext-workflow-demo-collapsible-content';
|
||||
content.innerHTML = innerHtml;
|
||||
details.appendChild(content);
|
||||
|
||||
|
||||
129
scripts/lint-package-css.sh
Executable file
129
scripts/lint-package-css.sh
Executable file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env bash
|
||||
# lint-package-css.sh — Enforce extension CSS prefix convention
|
||||
#
|
||||
# Rule: The FIRST class selector in every rule must start with .ext-{slug}.
|
||||
# Subsequent classes in compound/descendant selectors are allowed (they're
|
||||
# scoped under the extension's namespace). Kernel .sw-* classes are always
|
||||
# allowed. CodeMirror .cm-* classes are allowed as descendants.
|
||||
#
|
||||
# Exemptions: :root, @keyframes blocks, @font-face blocks, @media/@supports
|
||||
# wrappers, CSS comments.
|
||||
#
|
||||
# Usage: bash scripts/lint-package-css.sh [packages-dir]
|
||||
# Exit 0 = all clean, Exit 1 = violations found.
|
||||
|
||||
set -euo pipefail
|
||||
PKG_DIR="${1:-packages}"
|
||||
VIOLATIONS=0
|
||||
|
||||
for css in "$PKG_DIR"/*/css/main.css; do
|
||||
[ -f "$css" ] || continue
|
||||
|
||||
# Derive slug from directory: {pkg_dir}/{slug}/css/main.css
|
||||
slug="$(basename "$(dirname "$(dirname "$css")")")"
|
||||
prefix="ext-${slug}"
|
||||
|
||||
in_keyframes=0
|
||||
in_fontface=0
|
||||
in_comment=0
|
||||
brace_depth=0
|
||||
lineno=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
lineno=$((lineno + 1))
|
||||
|
||||
# Track block comments
|
||||
if (( in_comment )); then
|
||||
[[ "$line" == *"*/"* ]] && in_comment=0
|
||||
continue
|
||||
fi
|
||||
if [[ "$line" == *"/*"* ]] && [[ "$line" != *"*/"* ]]; then
|
||||
in_comment=1
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip single-line comments
|
||||
trimmed="${line#"${line%%[![:space:]]*}"}"
|
||||
[[ "$trimmed" == "" ]] && continue
|
||||
[[ "$trimmed" == /\** ]] && continue
|
||||
|
||||
# Track @keyframes blocks
|
||||
if [[ "$trimmed" =~ ^@keyframes ]]; then
|
||||
in_keyframes=1
|
||||
continue
|
||||
fi
|
||||
if (( in_keyframes )); then
|
||||
[[ "$trimmed" == "}" ]] && in_keyframes=0
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip @font-face
|
||||
if [[ "$trimmed" =~ ^@font-face ]]; then
|
||||
in_fontface=1
|
||||
continue
|
||||
fi
|
||||
if (( in_fontface )); then
|
||||
[[ "$trimmed" == *"}"* ]] && in_fontface=0
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip @-rule wrappers
|
||||
[[ "$trimmed" =~ ^@ ]] && continue
|
||||
|
||||
# Skip :root selectors
|
||||
[[ "$trimmed" =~ ^:root ]] && continue
|
||||
|
||||
# Skip closing braces
|
||||
[[ "$trimmed" == "}" ]] && continue
|
||||
|
||||
# Skip property declarations (contain : but not {)
|
||||
if [[ "$trimmed" == *":"* ]] && [[ "$trimmed" != *"{"* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Selector line — check if it contains a class
|
||||
if [[ "$trimmed" == *"."* ]]; then
|
||||
# Split comma-separated selectors and check each one
|
||||
# We process the whole line as potentially multiple selectors
|
||||
IFS=',' read -ra selectors <<< "$trimmed"
|
||||
for sel in "${selectors[@]}"; do
|
||||
# Trim whitespace
|
||||
sel="${sel#"${sel%%[![:space:]]*}"}"
|
||||
sel="${sel%"${sel##*[![:space:]]}"}"
|
||||
|
||||
# Remove trailing { if present
|
||||
sel="${sel%\{}"
|
||||
sel="${sel%"${sel##*[![:space:]]}"}"
|
||||
|
||||
# Skip if empty
|
||||
[[ -z "$sel" ]] && continue
|
||||
|
||||
# Find the FIRST class selector in this selector
|
||||
if [[ "$sel" =~ \.([a-zA-Z_][a-zA-Z0-9_-]*) ]]; then
|
||||
first_cls="${BASH_REMATCH[1]}"
|
||||
|
||||
# Allow kernel .sw-* as first class
|
||||
[[ "$first_cls" == sw-* ]] && continue
|
||||
|
||||
# Allow [data-ext=...] scoped selectors
|
||||
[[ "$sel" =~ ^\[data-ext ]] && continue
|
||||
|
||||
# Must start with ext-{slug}
|
||||
if [[ "$first_cls" != ${prefix}* ]]; then
|
||||
echo "VIOLATION: $css:$lineno — .${first_cls} (expected .${prefix}-*)"
|
||||
VIOLATIONS=$((VIOLATIONS + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done < "$css"
|
||||
done
|
||||
|
||||
if (( VIOLATIONS > 0 )); then
|
||||
echo ""
|
||||
echo "Found $VIOLATIONS violation(s). First class selector in each rule must start with .ext-{slug}-"
|
||||
exit 1
|
||||
else
|
||||
echo "All package CSS files pass prefix enforcement."
|
||||
exit 0
|
||||
fi
|
||||
@@ -13,6 +13,6 @@
|
||||
{{define "surface-extension"}}
|
||||
<div id="extension-surface" class="extension-surface"
|
||||
data-surface-id="{{.Surface}}">
|
||||
<div id="extension-mount" class="extension-mount"></div>
|
||||
<div id="extension-mount" class="extension-mount" data-ext="{{.Surface}}"></div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -103,7 +103,7 @@ export function createMarkdown(renderers) {
|
||||
renderer(token) {
|
||||
const target = _escapeAttr(token.target);
|
||||
const display = _escapeHtml(token.display);
|
||||
return `<a class="wikilink" data-wikilink="${target}" href="javascript:void(0)">${display}</a>`;
|
||||
return `<a class="ext-notes-wikilink" data-wikilink="${target}" href="javascript:void(0)">${display}</a>`;
|
||||
},
|
||||
}],
|
||||
|
||||
|
||||
Reference in New Issue
Block a user