feat: rework of the full CSS machine to tailwind and modular CSS

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
hugoandCopilot committed 2026-05-04 10:15:01 +02:00
1 parent 6b6c985187
commit b17e9cc3f8
29 files changed
+7030 -8623

No files matched your search

+577
View File
@@ -0,0 +1,577 @@
# Tailwind And Phoenix Asset Tooling For bDS2 Desktop
## Purpose
This document describes the target styling architecture for a Tailwind-integrated Phoenix LiveView desktop app in this repository.
It is written as a handoff for a coding agent that will perform the implementation.
This is not a migration guide from a public web app. It is a target-state guide for a Phoenix LiveView desktop shell with local assets, dense editor surfaces, overlays, resizable panes, and desktop-specific titlebar behavior.
## Verified Current State
The current app does not use the default Phoenix asset pipeline.
- CSS is served directly from `priv/ui/app.css`.
- JS is served directly from `priv/ui/live.js`.
- Static assets are served from `priv/ui` in `lib/bds/desktop/endpoint.ex`.
- The root layout links `/assets/app.css` in `lib/bds/desktop/layouts.ex`, but that path is currently backed by `priv/ui/app.css`, not by generated Phoenix assets.
- There is no `:tailwind` or `:esbuild` setup in `mix.exs`.
Relevant files:
- `lib/bds/desktop/endpoint.ex`
- `lib/bds/desktop/layouts.ex`
- `mix.exs`
- `priv/ui/app.css`
- `priv/ui/live.js`
## Official Phoenix Facts
The implementation target should follow Phoenix defaults where they fit the desktop shell.
Official Phoenix references:
- Phoenix Asset Management: https://hexdocs.pm/phoenix/asset_management.html
- Phoenix Components and HEEx: https://hexdocs.pm/phoenix/components.html
- Phoenix.Component reference: https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html
Facts from Phoenix docs and generator templates:
- Phoenix v1.7+ defaults to Tailwind for CSS and esbuild for JS.
- The default CSS source entrypoint is `assets/css/app.css`.
- The default generated CSS output is `priv/static/assets/css/app.css`.
- The default generated JS output is `priv/static/assets/js/app.js`.
- Phoenix promotes function components and HEEx as the main rendering model.
- Phoenix does not prescribe per-component CSS modules. Tailwind-first HEEx plus shared source CSS is a valid Phoenix-default shape.
## Target Outcome
The target outcome is a solid Phoenix-default asset setup with Tailwind as the main styling system and a generated production stylesheet.
The target should look like this:
- Tailwind source lives in `assets/css/app.css` and imported CSS modules.
- The final stylesheet is generated into `priv/static/assets/css/app.css`.
- LiveView JS entrypoint lives in `assets/js/app.js` and builds to `priv/static/assets/js/app.js`.
- The desktop endpoint serves static assets from `priv/static`.
- Layouts reference the generated CSS and JS outputs.
- HEEx markup carries most layout, spacing, typography, responsive, and state classes.
- Authored CSS remains for global tokens, app-region behavior, pseudo-elements, scrollbars, Monaco integration, hard selectors, and overlay mechanics.
## Architecture Rules
### 1. Tailwind Owns The Common Case
Use Tailwind utility classes directly in HEEx for:
- flex and grid layout
- spacing
- typography
- borders and radii
- colors and opacity
- active, selected, disabled, and hover states
- responsive breakpoints
- overflow and truncation
Do not preserve large semantic wrapper classes when they only encode simple layout decisions.
Good examples to move into HEEx classes:
- tab rows
- button rows
- editor header layout
- metadata field layout
- sidebar list row spacing
- status bar item alignment
### 2. Authored CSS Owns The Desktop-Specific Case
Keep authored CSS source files for:
- `app-region` and `-webkit-app-region`
- pseudo-elements used for icons, handles, and active markers
- custom scrollbars
- Monaco/editor iframe or host integration
- absolute overlay stacks and backdrops
- drag and drop affordances
- complex attribute selectors
- hard-to-read repeated combinations that should become shared component classes
Do not force these into giant utility strings if readability drops.
### 3. Keep A Thin Semantic CSS Layer
It is acceptable to keep a small number of semantic component classes when they encode a repeated desktop UI primitive.
Examples of valid semantic classes in the target state:
- `.window-titlebar`
- `.resizable-panel-divider`
- `.overlay-root`
- `.monaco-host`
- `.panel-entry`
- `.btn-base`, `.btn-theme-primary`, `.btn-theme-danger`
These classes should be implemented in Tailwind source CSS using `@layer components` and should remain small, stable, and reusable.
### 4. Tokens Must Be Centralized
The current stylesheet relies heavily on VS Code-like CSS variables. Preserve that idea.
The new `assets/css/app.css` must define the design tokens once, using Tailwind v4 `@theme` plus any required raw CSS custom properties for runtime-driven values.
Examples from the current app that must survive:
- shell/background colors
- tab active/inactive colors
- status bar colors
- focus border color
- input background and border colors
- sidebar width
- assistant width
- font family and font size
### 5. Desktop Layout Constraints Must Stay Intact
The styling rewrite must preserve these runtime constraints:
- the app occupies full window width and height
- the shell uses `overflow: hidden` at the top level
- the main workbench uses `min-height: 0` and `min-width: 0` correctly
- resizable sidebars remain width-variable
- titlebar remains draggable except for interactive controls
- overlays render above the shell without breaking keyboard focus or pointer behavior
## Proposed Asset Layout
Use the standard Phoenix asset layout.
```text
assets/
css/
app.css
shell.css
sidebar.css
tabs.css
editor.css
forms.css
overlays.css
panel.css
assistant.css
menu_editor.css
media_editor.css
utilities.css
js/
app.js
...
priv/
static/
assets/
css/
js/
```
Recommended responsibilities:
- `assets/css/app.css`: Tailwind import, `@source`, tokens, base layer, imports
- `assets/css/shell.css`: app shell, titlebar, activity bar, pane shells, status bar
- `assets/css/sidebar.css`: sidebar filters, search, chips, calendar tree, load more
- `assets/css/tabs.css`: workbench tabs and editor tabs
- `assets/css/editor.css`: common editor frame, toolbar, meta column, shared form shell
- `assets/css/forms.css`: shared input, textarea, tag chip, picker, inline action primitives
- `assets/css/overlays.css`: overlay root, modal backdrop, dialog shells, gallery/lightbox
- `assets/css/panel.css`: panel tabs, panel entry cards, tasks, output, git log
- `assets/css/assistant.css`: assistant sidebar and chat-specific shared surfaces
- `assets/css/menu_editor.css`: menu tree, drag/drop indicators, picker lists
- `assets/css/media_editor.css`: media preview, linked post picker, detail forms
- `assets/css/utilities.css`: a very small set of custom utilities that are truly reused
## Proposed Phoenix Asset Tooling
The implementation should introduce the standard Phoenix aliases and configs.
### mix.exs
Add:
- `{:tailwind, "~> 0.3", runtime: Mix.env() == :dev}`
- `{:esbuild, "~> 0.10", runtime: Mix.env() == :dev}`
Add aliases similar to:
- `assets.setup`
- `assets.build`
- `assets.deploy`
### Versioning (mandatory)
The Elixir `:tailwind` wrapper still defaults to Tailwind v3. The plan in this document assumes **Tailwind v4** syntax (`@import "tailwindcss"`, `@theme`, `@source`, `@layer components`). Pin both tools explicitly in `config/config.exs`:
- `config :tailwind, version: "4.1.14"` (or current 4.1.x)
- `config :esbuild, version: "0.25.4"` (or current 0.25.x)
Without an explicit v4 pin the build will silently install Tailwind v3 and v4 directives will not resolve.
### No Node.js policy
The Elixir `:tailwind` and `:esbuild` wrappers download self-contained binaries and do **not** require Node.js. The implementation MUST stay Node-free unless a third-party Tailwind plugin is later required (in which case the custom `assets/build.js` route from the Phoenix asset_management guide is used). No `package.json` is added under `assets/`.
### config/config.exs
Configure Tailwind input and output paths.
Target output:
- `assets/css/app.css` -> `priv/static/assets/css/app.css`
Configure esbuild for:
- `assets/js/app.js` -> `priv/static/assets/js/app.js`
esbuild profile must include `--bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/*` and `nodePaths: [Mix.Project.build_path() <> "/../../deps"]` so `phoenix`, `phoenix_html`, and `phoenix_live_view` resolve from `deps/` without an `npm install`.
### config/dev.exs
Add Phoenix watchers for:
- Tailwind `--watch`
- esbuild `--watch`
### No phx.digest in desktop builds
This is a desktop app served through an embedded WebView, not a public web app behind a CDN.
- Do NOT run `mix phx.digest` as part of `assets.deploy`.
- Output filenames stay stable (`app.css`, `app.js`) so the layout can link them by fixed path.
- `assets.deploy` for this repo is: `tailwind default --minify`, `esbuild default --minify`. Nothing else.
### endpoint/layout changes
Update the desktop endpoint and root layout to serve and link generated assets from `priv/static/assets` instead of `priv/ui`.
Specifically:
- Replace the existing `Plug.Static` for `/assets` with `from: {:bds, "priv/static/assets"}` and `only` listing the generated `css` and `js` directories.
- Drop the `/vendor/phoenix` and `/vendor/live_view` `Plug.Static` blocks; those scripts are now bundled by esbuild from `deps/`.
- Add a dedicated `Plug.Static` for `/monaco` pointing at `priv/ui/monaco` (or move it to `priv/static/monaco`). Monaco is a prebuilt vendor drop and MUST NOT be passed through esbuild.
- Remove the `<script src="/vendor/phoenix/...">` and `<script src="/vendor/live_view/...">` tags from `lib/bds/desktop/layouts.ex`. Keep only `/assets/app.css` and `/assets/app.js`.
## JS Pipeline
The JS pipeline mirrors the CSS pipeline.
### Entrypoint
`assets/js/app.js` is the single esbuild entrypoint. It:
- imports `phoenix`, `phoenix_html`, and `phoenix_live_view` (resolved from `deps/` via esbuild `nodePaths`)
- constructs the `LiveSocket` with the desktop CSRF token
- registers all hooks currently defined inline in `priv/ui/live.js`
- wires the native menu / titlebar / shortcut bridges
### Module layout
Split the current `priv/ui/live.js` (1.4k lines) into focused modules under `assets/js/`:
- `assets/js/app.js` - entrypoint, LiveSocket, hook registration
- `assets/js/hooks/` - one file per hook
- `assets/js/bridges/` - native menu, titlebar, shortcut bridges
- `assets/js/monaco/` - thin host glue only; do NOT bundle Monaco itself
### Monaco carve-out
Monaco is loaded as prebuilt assets from `/monaco/...` and is not part of the esbuild graph. The host glue in `assets/js/monaco/` only configures the loader URL and posts messages; it does not `import` Monaco modules.
### Vendor stripping
After the switch:
- `priv/ui/app.css` is deleted (its content has been redistributed under `assets/css/`).
- `priv/ui/live.js` is deleted.
- `priv/ui/monaco/` stays (or moves to `priv/static/monaco/`).
- The `phoenix` and `phoenix_live_view` dep static drops are no longer served.
## Iconography
The app keeps its existing **inline SVG** icon set. Do NOT adopt the Phoenix 1.8 Heroicons-via-Tailwind-plugin pattern.
Reasons:
- The target visual look is defined by the current bDS UI; its icons are part of that look.
- Inline SVG keeps icons under direct control for sizing, stroke, and currentColor styling via Tailwind utility classes.
- Avoiding the Heroicons git dep keeps the build Node-free and dependency-light.
Rules:
- Existing SVG icons stay where they are (HEEx components / inline `<svg>` markup).
- Icon size and color are controlled by Tailwind utilities on the wrapping element (`size-4`, `text-[var(--color-icon)]`, etc.) using `fill="currentColor"` or `stroke="currentColor"`.
- No `:heroicons` mix dep, no Tailwind icon plugin, no `assets/vendor/heroicons.js`.
- If a new icon is needed, add the SVG inline in the appropriate component module.
## HEEx Styling Strategy
### Prefer Utility Classes In Templates
Use utility classes in HEEx for:
- shell flex layout
- editor content spacing
- section stacks
- truncation and overflow
- standard button alignment
- grid column changes at breakpoints
- selected and hovered states that are directly tied to assign state
### Keep Dynamic Class Lists Explicit
LiveView templates should build classes with arrays where state is already in assigns.
Example pattern:
```elixir
class={[
"flex items-center gap-2 px-3 py-2 text-sm",
selected? && "bg-[var(--color-selected-bg)] text-white",
disabled? && "opacity-50 pointer-events-none"
]}
```
### Do Not Hide Structure Behind Giant Custom Class Trees
Avoid re-creating the current `app.css` by keeping every nested selector and merely moving it into `assets/css`.
If a selector exists only because the old CSS owned all layout, move that responsibility into HEEx.
## Tailwind Source Conventions
### app.css
`assets/css/app.css` should use Tailwind v4 import syntax.
`source(none)` disables Tailwind's automatic content detection so only directories listed via `@source` are scanned. Every directory that ships HEEx or class strings MUST be listed. Audit `lib/` for additional renderers (preview, generation, MCP surfaces) and add `@source` lines for any that emit class names consumed at build time.
Runtime-driven values (e.g. resizable panel widths that change via CSS variable assignment from JS) MUST stay as plain CSS custom properties under `:root` or a scoped selector. `@theme` values are baked at build time and are not appropriate for values mutated at runtime. Use `@theme` for stable design tokens (colors, font sizes, spacing scale extensions) that should produce utility classes; use `:root { --foo: … }` for everything that the app writes to at runtime.
Suggested structure:
```css
@import "tailwindcss" source(none);
@source "../css";
@source "../js";
@source "../../lib/bds/desktop";
@theme {
/* app tokens */
}
@layer base {
/* html, body, root shell defaults */
}
@import "./shell.css";
@import "./sidebar.css";
@import "./tabs.css";
@import "./editor.css";
@import "./forms.css";
@import "./panel.css";
@import "./assistant.css";
@import "./overlays.css";
@import "./menu_editor.css";
@import "./media_editor.css";
@import "./utilities.css";
```
### Component CSS Rules
When writing component CSS in imported files:
- use `@layer components` for shared semantic classes
- use `@layer utilities` only for narrowly reusable custom utilities
- keep selectors shallow
- avoid giant descendant chains unless required by generated HTML structure or overlay mechanics
- prefer `@apply` sparingly and only for stable component classes, not as a substitute for writing HEEx utilities
## Current UI Structure Reference
The implementation agent must use the current monolith as a source map, not as the final architecture.
The current stylesheet is `priv/ui/app.css` and is approximately 8.5k lines.
Use the following region map when carrying styling over.
### Current app.css region map
- Lines `1-140`: root variables, base element reset, buttons, top-level shell defaults
- Lines `141-415`: app shell and window titlebar
- `.app`, `.app-main`, `.app-content`
- `.window-titlebar*`
- Lines `416-623`: activity bar and sidebar shells
- `.activity-bar*`
- `.sidebar-shell*`, `.assistant-sidebar-shell*`
- `.sidebar*`, `.assistant-sidebar*`
- `.resizable-panel-divider`
- Lines `624-812`: workbench tab bar
- `.tab-bar*`, `.tab*`
- Lines `813-1100`: shared editor shell and editor tabs
- `.editor-shell`, `.editor-frame`, `.editor-main`, `.editor-meta`
- `.editor-toolbar*`
- `.post-editor .editor-header`, `.editor-tabs`, `.editor-tab*`
- Lines `1100-1700`: post editor forms and metadata/media panel
- `.post-editor .editor-content`
- `.post-editor .editor-field*`
- `.post-editor .post-editor-input`, `.post-editor-textarea`
- `.tag-input*`, `.tag-chip*`
- media insertion and post media list
- Line `1691` onward: first responsive collapse for editor/media layout
- Lines `1736-1833`: early shell/gallery overlay and insert-media grid rules
- Lines `1833+`: more mobile/narrow viewport adjustments
- Lines `1950-2263`: status bar and shell footer controls
- `.status-bar*`
- `.project-selector*`
- Lines `2264-2599`: overlay root and modal system
- `.overlay-root`
- `.ai-suggestions-modal*`
- `.insert-modal*`
- `.language-picker-modal*`
- `.confirm-delete-modal*`
- `.gallery-overlay*`
- Lines `2600-2876`: menu editor
- `.menu-editor-*`
- Lines `2889+`: media editor
- `[data-testid="media-editor"] *`
- Lines `3458+`: style/theme picker surface
- `.style-theme-*`
- Lines `4958`, `6722`, `8268`, `8514`: later breakpoint-specific adjustments for desktop shell and advanced editors
Treat those ranges as source material to be redistributed into the new Tailwind source layout.
## Current HEEx And Component Surface Reference
The styling rewrite needs to move with the component structure, not only with CSS selectors.
Important current rendering surfaces:
- `lib/bds/desktop/shell_live.ex`
- top-level workbench render entry
- `lib/bds/desktop/shell_live/sidebar_components.ex`
- sidebar search, archive tree, tag/category chips, nav/settings lists, load more
- `lib/bds/desktop/shell_live/panel_renderer.ex`
- tasks, output, git log, panel toolbars
- `lib/bds/desktop/shell_live/post_editor.ex`
- post editor render surface
- `lib/bds/desktop/shell_live/media_editor.ex`
- media editor render surface
- `lib/bds/desktop/shell_live/script_editor.ex`
- script editor render surface
- `lib/bds/desktop/shell_live/template_editor.ex`
- template editor render surface
- `lib/bds/desktop/shell_live/chat_editor.ex`
- assistant/chat surface
- `lib/bds/desktop/shell_live/menu_editor.ex`
- menu editor tree surface
Implementation rule:
- move simple layout and state styling into these HEEx/component surfaces
- keep authored CSS for shared primitives and complex desktop behavior
## Desktop-Specific Styling Rules
The app is a desktop shell, not a normal browser page.
The implementation must preserve:
- draggable titlebar regions
- non-draggable controls inside the titlebar
- local, app-like split-pane behavior
- fixed-height titlebar, tabs, and status bar rails
- overlay stacking over the shell
- editor and sidebar widths controlled by CSS variables where appropriate
- visual parity for assistant/sidebar/panel open and closed states
Do not optimize for tiny public web payloads at the expense of shell clarity.
Do optimize for maintainability, explicit component ownership, and predictable desktop behavior.
## Implementation Phases
### Phase 0: Tests And Validation Baseline
- Add a smoke test that requests `/assets/app.css` and `/assets/app.js` and asserts a 200 with non-empty body served from `priv/static/assets`.
- Add a render snapshot test for the top-level shell HEEx so class-string regressions during the rewrite are caught.
- Establish that every phase ends with clean `mix compile --warnings-as-errors`, `mix test`, and `mix dialyzer` runs (per repo AGENTS.md).
### Phase 1: Install Phoenix Asset Tooling
- add Tailwind and esbuild dependencies
- create `assets/css/app.css` and `assets/js/app.js`
- configure `config/config.exs`, `config/dev.exs`, and `mix.exs`
- switch endpoint/layouts to generated assets
- keep current visuals as close as possible
### Phase 2: Split The Monolith Into Source Modules
- move the current `priv/ui/app.css` into the proposed `assets/css/*.css` modules
- keep selectors mostly intact at first
- copy raw selectors only; do NOT rewrite to `@apply` in this phase
- defer all `@apply` and utility-extraction decisions to Phase 3
- verify the desktop shell still renders correctly
### Phase 3: Move Common Layout Into HEEx
- rewrite top-level shell markup, tabs, headers, and common forms to use Tailwind classes directly
- reduce selector nesting
- keep only the thin semantic CSS layer
### Phase 4: Normalize Shared Primitives
- standardize buttons
- standardize inputs and textareas
- standardize tabs and badges
- standardize panel entries and empty states
### Phase 5: Finish Desktop-Specific Surfaces
- overlays
- menu editor drag/drop states
- media preview/detail layouts
- assistant/chat surfaces
- narrow viewport behavior
## Acceptance Criteria
The rewrite is successful when:
- assets are built through Phoenix default tooling
- the desktop endpoint serves generated assets from `priv/static`
- the visual shell matches the existing app closely enough for feature work to continue
- the current `priv/ui/app.css` is no longer the source of truth
- the new styling is split into source modules with clear ownership
- HEEx markup owns common layout and state classes
- authored CSS is limited to tokens, desktop primitives, and complex selectors
- Tailwind v4 and esbuild versions are explicitly pinned in `config/config.exs`
- no Node.js / `package.json` is introduced under `assets/`
- no `mix phx.digest` step exists; generated asset filenames remain stable
- inline SVG iconography from the existing UI is preserved (no Heroicons dep)
- `mix compile --warnings-as-errors`, `mix test`, and `mix dialyzer` are clean after every phase
## Non-Goals
These are not goals of the rewrite:
- blindly replacing every old class with utilities in one pass
- preserving the exact old selector tree
- introducing CSS modules or a React-style styling system
- optimizing for generic web landing-page concerns
- changing product behavior unrelated to styling and asset delivery
## Guidance For The Coding Agent
When implementing this plan:
- start by wiring Phoenix asset tooling, not by rewriting 8k lines of CSS in place
- preserve runtime behavior first, then simplify
- move layout decisions to HEEx only when they become clearer there
- keep desktop-specific mechanics in CSS
- use the current `priv/ui/app.css` region map as a source index, not as a blueprint for the final architecture
+19
View File
@@ -0,0 +1,19 @@
@import "tailwindcss" source(none);
@source "../css";
@source "../js";
@source "../../lib/bds/desktop";
@import "./tokens.css";
@import "./shell.css";
@import "./sidebar.css";
@import "./tabs.css";
@import "./editor.css";
@import "./forms.css";
@import "./panel.css";
@import "./assistant.css";
@import "./overlays.css";
@import "./menu_editor.css";
@import "./media_editor.css";
@import "./import_editor.css";
@import "./utilities.css";
+206
View File
@@ -0,0 +1,206 @@
.settings-view-shell,
.style-view,
.tags-view-shell,
.scripts-view-shell,
.templates-view-shell,
.chat-panel {
height: 100%;
background: var(--vscode-editor-background);
}
.chat-panel {
display: flex;
min-height: 0;
flex-direction: column;
color: var(--vscode-editor-foreground);
}
.chat-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 16px;
border-bottom: 1px solid var(--vscode-panel-border);
background: var(--vscode-sideBar-background);
}
.chat-panel-title {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 10px;
overflow: visible;
font-size: 14px;
font-weight: 600;
}
.chat-panel-title-main {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-panel-header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.chat-model-selector-wrap {
position: relative;
display: inline-flex;
min-width: 0;
}
.chat-model-selector-button,
.chat-model-selector-option {
border: 1px solid var(--vscode-input-border);
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
}
.chat-model-selector-menu {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: auto;
border: 1px solid var(--vscode-dropdown-border, var(--vscode-panel-border));
background: var(--vscode-dropdown-background, var(--vscode-sideBar-background));
color: var(--vscode-dropdown-foreground, var(--vscode-foreground));
z-index: 20;
}
.chat-panel .chat-model-selector-button.chat-model-selector-inline {
display: inline-flex;
align-items: center;
gap: 6px;
}
.chat-panel .chat-model-selector-caret {
position: static;
font-size: 10px;
}
.chat-messages,
.chat-surface-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
}
.chat-messages {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
}
.chat-message {
display: flex;
max-width: 100%;
}
.chat-message.user {
justify-content: flex-end;
}
.chat-message-content {
max-width: min(760px, 100%);
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
padding: 12px 14px;
background: var(--vscode-sideBar-background);
color: var(--vscode-editor-foreground);
}
.chat-panel .chat-message.user .chat-message-content {
background: transparent;
color: var(--vscode-list-activeSelectionForeground);
border: 0;
padding: 6px 12px;
line-height: 1.35;
}
.chat-tool-surface-table {
width: 100%;
border-collapse: collapse;
}
.chat-tool-surface-table th,
.chat-tool-surface-table td {
border-bottom: 1px solid var(--vscode-panel-border);
padding: 6px 8px;
text-align: left;
}
.chat-tool-surface-json {
overflow: auto;
padding: 10px 12px;
border: 1px solid var(--vscode-panel-border);
border-radius: 4px;
background: var(--vscode-textCodeBlock-background);
}
.chat-panel .chat-input-container {
--chat-input-line-height: 20px;
--chat-input-min-height: 20px;
border-top: 1px solid var(--vscode-panel-border);
padding: 8px 16px;
background: var(--vscode-sideBar-background);
}
.chat-panel .chat-input-wrapper {
display: flex;
align-items: flex-end;
gap: 8px;
min-height: 30px;
border: 1px solid var(--vscode-input-border);
border-radius: 6px;
padding: 4px 6px;
background: var(--vscode-input-background);
}
.chat-panel .chat-input-wrapper:focus-within {
border-color: var(--vscode-focusBorder);
}
.chat-panel .chat-input {
flex: 1;
box-sizing: border-box;
height: var(--chat-input-min-height);
min-height: var(--chat-input-min-height);
margin: 0;
padding: 6px 8px;
line-height: var(--chat-input-line-height);
max-height: 160px;
resize: vertical;
border: 0;
background: transparent;
color: var(--vscode-input-foreground);
overflow-y: hidden;
}
.chat-panel .chat-input::placeholder {
color: var(--vscode-input-placeholderForeground);
}
.chat-panel .chat-send-button {
flex: 0 0 auto;
width: 22px;
height: 22px;
max-width: 22px;
max-height: 22px;
padding: 0;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
.chat-panel .chat-send-button:hover:not(:disabled) {
background: var(--vscode-button-hoverBackground);
}
.chat-panel .chat-send-button:disabled {
opacity: 0.5;
}
File diff suppressed because it is too large. Load diff
+141
View File
@@ -0,0 +1,141 @@
.settings-view,
.style-view {
height: 100%;
display: flex;
flex-direction: column;
}
.settings-header,
.style-view-header {
padding: 18px 20px;
border-bottom: 1px solid var(--line, #3c3c3c);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.settings-search input {
width: min(320px, 40vw);
}
.settings-content {
padding: 20px;
overflow: auto;
display: flex;
flex-direction: column;
gap: 18px;
}
.setting-section {
border: 1px solid var(--line, #3c3c3c);
border-radius: 12px;
background: var(--panel-2, #252526);
}
.setting-section-header {
padding: 14px 16px;
border-bottom: 1px solid var(--line, #3c3c3c);
}
.setting-section-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 14px;
}
.setting-row {
display: grid;
grid-template-columns: minmax(180px, 240px) minmax(0, 1fr);
gap: 16px;
align-items: start;
}
.setting-label {
font-weight: 600;
}
.setting-control,
.setting-input-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.setting-actions {
padding: 0 16px 16px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.style-theme-picker {
padding: 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 14px;
}
.style-theme-option {
border: 1px solid var(--line, #3c3c3c);
background: var(--panel-2, #252526);
border-radius: 14px;
padding: 14px;
text-align: left;
cursor: pointer;
}
.style-theme-option.selected {
border-color: var(--accent-color);
box-shadow: 0 0 0 1px var(--accent-color);
}
.style-theme-swatch {
display: flex;
flex-direction: column;
gap: 12px;
}
.style-theme-tones {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
gap: 8px;
}
.style-theme-tone {
height: 42px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.08);
}
.style-apply-row {
padding: 0 20px 20px;
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
}
.style-preview-container {
padding: 0 20px 20px;
flex: 1;
min-height: 0;
}
.style-preview-frame {
width: 100%;
height: 100%;
min-height: 420px;
border: 1px solid var(--line, #3c3c3c);
border-radius: 14px;
background: #ffffff;
}
@media (max-width: 1100px) {
.setting-row {
grid-template-columns: 1fr;
}
}
+689
View File
@@ -0,0 +1,689 @@
.import-analysis {
display: flex;
flex-direction: column;
gap: 16px;
padding: 18px 20px 26px;
color: var(--vscode-foreground);
}
.import-analysis-header {
display: flex;
flex-direction: column;
gap: 8px;
}
.import-analysis-header p {
margin: 0;
color: var(--vscode-descriptionForeground);
font-size: 13px;
line-height: 1.5;
}
.import-definition-name {
width: min(480px, 100%);
border: 1px solid var(--vscode-input-border, transparent);
background: var(--vscode-input-background);
color: var(--vscode-input-foreground, var(--vscode-foreground));
border-radius: 6px;
padding: 10px 12px;
font-size: 18px;
font-weight: 600;
}
.import-file-selectors {
display: grid;
gap: 12px;
}
.import-file-row {
display: grid;
grid-template-columns: 150px minmax(0, 1fr) auto;
gap: 12px;
align-items: center;
padding: 12px 14px;
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
background: color-mix(in srgb, var(--vscode-editor-background) 76%, var(--vscode-input-background));
}
.import-file-row label {
font-size: 12px;
font-weight: 600;
color: var(--vscode-descriptionForeground);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.import-file-path {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: var(--vscode-editor-font-family, ui-monospace, monospace);
font-size: 12px;
}
.import-file-path.placeholder {
color: var(--vscode-descriptionForeground);
}
.import-analysis button,
.import-analysis select {
border: 1px solid var(--vscode-button-border, transparent);
border-radius: 6px;
font-size: 12px;
}
.import-analysis button {
background: var(--vscode-button-secondaryBackground, var(--vscode-button-background));
color: var(--vscode-button-secondaryForeground, var(--vscode-button-foreground));
padding: 8px 12px;
cursor: pointer;
}
.import-analysis button:hover:not(:disabled) {
background: var(--vscode-button-secondaryHoverBackground, var(--vscode-button-hoverBackground));
}
.import-analyze-btn,
.import-execute-btn {
background: var(--vscode-button-background) !important;
color: var(--vscode-button-foreground) !important;
}
.import-analysis button:disabled {
opacity: 0.65;
cursor: not-allowed;
}
.import-loading {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border: 1px solid var(--vscode-panel-border);
border-radius: 10px;
background: color-mix(in srgb, var(--vscode-editor-background) 84%, var(--vscode-input-background));
}
.import-spinner {
width: 18px;
height: 18px;
border: 2px solid var(--vscode-descriptionForeground);
border-top-color: var(--vscode-button-background);
border-radius: 50%;
animation: import-spinner-rotate 0.8s linear infinite;
flex-shrink: 0;
}
.import-progress {
display: flex;
flex-direction: column;
gap: 2px;
}
.import-progress-step {
font-size: 13px;
color: var(--vscode-foreground);
}
.import-progress-detail {
font-size: 11px;
color: var(--vscode-descriptionForeground);
}
@keyframes import-spinner-rotate {
to {
transform: rotate(360deg);
}
}
.import-site-info {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.import-site-info-item,
.import-stat-card,
.import-date-distribution,
.import-detail-section,
.import-execute-section {
border: 1px solid var(--vscode-panel-border);
border-radius: 10px;
background: color-mix(in srgb, var(--vscode-editor-background) 84%, var(--vscode-input-background));
}
.import-site-info-item {
display: flex;
flex-direction: column;
gap: 6px;
padding: 14px;
}
.info-label {
font-size: 11px;
font-weight: 600;
color: var(--vscode-descriptionForeground);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.info-value {
font-size: 13px;
font-weight: 500;
overflow-wrap: anywhere;
}
.import-stat-cards {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 12px;
}
.import-stat-card {
padding: 14px;
}
.import-stat-card h3,
.import-date-distribution h3,
.import-detail-section h3,
.taxonomy-group h4 {
margin: 0;
}
.import-stat-number {
margin-top: 10px;
font-size: 28px;
font-weight: 700;
line-height: 1;
}
.import-stat-breakdown,
.import-execute-summary,
.import-taxonomy-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.import-stat-breakdown {
margin-top: 12px;
}
.import-stat-tag,
.import-count-tag,
.import-taxonomy-pill,
.macro-status-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
}
.stat-new,
.import-taxonomy-pill.new-tax {
background: rgba(117, 190, 255, 0.16);
color: #75beff;
}
.stat-update,
.stat-mapped,
.import-taxonomy-pill.exists,
.import-taxonomy-pill.mapped,
.macro-status-badge.mapped,
.import-execution-complete {
background: rgba(115, 201, 145, 0.16);
color: #73c991;
}
.stat-conflict {
background: rgba(255, 166, 87, 0.16);
color: #ffb169;
}
.stat-duplicate,
.stat-missing,
.macro-status-badge.unmapped,
.import-execution-error {
background: rgba(204, 167, 0, 0.16);
color: #cca700;
}
.import-date-distribution,
.import-detail-section,
.import-execute-section {
padding: 16px;
}
.import-section-toggle {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 0;
border: none !important;
background: transparent !important;
color: inherit !important;
font-size: 16px !important;
font-weight: 600;
text-align: left;
}
.import-section-toggle:hover {
background: transparent !important;
opacity: 0.9;
}
.toggle-icon {
font-size: 12px;
color: var(--vscode-descriptionForeground);
}
.distribution-bars {
display: grid;
gap: 10px;
margin-top: 14px;
}
.distribution-row {
display: grid;
grid-template-columns: 56px minmax(0, 1fr) 72px;
gap: 10px;
align-items: center;
}
.distribution-year,
.distribution-count,
.slug-cell {
font-family: var(--vscode-editor-font-family, ui-monospace, monospace);
font-size: 11px;
}
.distribution-bar-container {
height: 10px;
border-radius: 999px;
overflow: hidden;
background: var(--vscode-input-background);
}
.distribution-bar {
height: 100%;
min-width: 8px;
border-radius: inherit;
}
.distribution-bar-posts {
background: linear-gradient(90deg, rgba(117, 190, 255, 0.8), rgba(117, 190, 255, 0.35));
}
.import-execute-section {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.import-execute-summary {
color: var(--vscode-descriptionForeground);
}
.import-execution-complete,
.import-execution-error {
padding: 10px 12px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
}
.import-execution-progress {
display: grid;
gap: 10px;
padding: 16px;
border: 1px solid var(--vscode-panel-border);
border-radius: 10px;
background: color-mix(in srgb, var(--vscode-editor-background) 84%, var(--vscode-input-background));
}
.import-execution-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.import-execution-header h3 {
margin: 0;
font-size: 14px;
}
.import-progress-bar {
height: 10px;
border-radius: 999px;
overflow: hidden;
background: var(--vscode-input-background);
}
.import-progress-fill {
height: 100%;
background: linear-gradient(90deg, rgba(117, 190, 255, 0.85), rgba(117, 190, 255, 0.45));
}
.import-progress-info {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
font-size: 12px;
}
.import-phase {
font-weight: 600;
}
.import-detail,
.import-counter {
color: var(--vscode-descriptionForeground);
}
.import-detail-table {
width: 100%;
border-collapse: collapse;
margin-top: 14px;
}
.import-detail-table th,
.import-detail-table td {
padding: 10px 8px;
text-align: left;
border-bottom: 1px solid var(--vscode-panel-border);
vertical-align: middle;
font-size: 12px;
}
.import-detail-table th {
font-size: 11px;
color: var(--vscode-descriptionForeground);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.import-detail-table .status-badge {
display: inline-flex;
align-items: center;
border-radius: 999px;
padding: 4px 9px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.import-detail-table .status-badge.new {
background: rgba(117, 190, 255, 0.16);
color: #75beff;
}
.import-detail-table .status-badge.update {
background: rgba(115, 201, 145, 0.16);
color: #73c991;
}
.import-detail-table .status-badge.conflict {
background: rgba(255, 166, 87, 0.16);
color: #ffb169;
}
.import-detail-table .status-badge.duplicate,
.import-detail-table .status-badge.missing {
background: rgba(204, 167, 0, 0.16);
color: #cca700;
}
.categories-cell,
.existing-match,
.mime-type-cell,
.post-type-cell {
font-size: 11px;
color: var(--vscode-descriptionForeground);
}
.mime-type-cell,
.post-type-cell,
.existing-match,
.slug-cell {
font-family: var(--vscode-editor-font-family, ui-monospace, monospace);
}
.resolution-select,
.taxonomy-mapping-input {
min-width: 150px;
background: var(--vscode-dropdown-background, var(--vscode-input-background));
color: var(--vscode-dropdown-foreground, var(--vscode-foreground));
border: 1px solid var(--vscode-dropdown-border, var(--vscode-panel-border));
padding: 6px 8px;
}
.taxonomy-analyze-row {
display: flex;
align-items: center;
gap: 12px;
padding: 0 0 12px;
margin-top: 12px;
border-bottom: 1px solid var(--vscode-panel-border);
}
.taxonomy-analyze-dropdown {
position: relative;
}
.taxonomy-analyze-btn {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.taxonomy-model-dropdown {
position: absolute;
top: calc(100% + 6px);
left: 0;
min-width: 220px;
max-height: 280px;
overflow-y: auto;
background: var(--vscode-dropdown-background, var(--vscode-sideBar-background));
border: 1px solid var(--vscode-dropdown-border, var(--vscode-panel-border));
border-radius: 6px;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);
z-index: 20;
}
.taxonomy-model-option {
width: 100%;
display: block;
border: none !important;
border-radius: 0 !important;
background: transparent !important;
color: var(--vscode-foreground) !important;
text-align: left;
padding: 8px 12px !important;
}
.taxonomy-model-option:hover {
background: var(--vscode-list-hoverBackground) !important;
}
.taxonomy-analyze-hint {
font-size: 11px;
color: var(--vscode-descriptionForeground);
}
.import-taxonomy-groups {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
margin-top: 14px;
}
.taxonomy-group {
display: flex;
flex-direction: column;
gap: 12px;
}
.import-taxonomy-entry,
.import-taxonomy-edit-form {
display: inline-flex;
align-items: center;
gap: 8px;
}
.import-taxonomy-entry,
.import-taxonomy-edit-form {
flex-wrap: wrap;
}
.import-taxonomy-pill {
border: none;
cursor: default;
}
button.import-taxonomy-pill {
cursor: pointer;
}
.mapped-target {
background: rgba(115, 201, 145, 0.1);
}
.taxonomy-mapping-arrow {
color: var(--vscode-descriptionForeground);
font-size: 12px;
}
.taxonomy-mapping-input {
min-width: 170px;
border-radius: 6px;
}
.taxonomy-edit-btn,
.taxonomy-clear-btn {
min-width: 28px;
min-height: 28px;
padding: 0 8px !important;
display: inline-flex;
align-items: center;
justify-content: center;
}
.taxonomy-edit-btn.ghost,
.taxonomy-clear-btn {
background: transparent !important;
border: 1px solid var(--vscode-panel-border) !important;
color: var(--vscode-descriptionForeground) !important;
}
.macros-list {
display: grid;
gap: 10px;
margin-top: 14px;
}
.macro-item {
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
background: var(--vscode-input-background);
}
.macro-item.unmapped {
border-left: 3px solid #cca700;
}
.macro-header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
}
.macro-name,
.import-taxonomy-pill {
font-family: var(--vscode-editor-font-family, ui-monospace, monospace);
}
.macro-count {
margin-left: auto;
font-size: 11px;
color: var(--vscode-descriptionForeground);
}
.import-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
padding: 56px 20px;
color: var(--vscode-descriptionForeground);
border: 1px dashed var(--vscode-panel-border);
border-radius: 12px;
}
.import-empty-state p {
margin: 0;
font-size: 13px;
}
@media (max-width: 1100px) {
.import-site-info,
.import-stat-cards,
.import-taxonomy-groups {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 780px) {
.import-analysis {
padding: 14px;
}
.import-file-row,
.distribution-row,
.import-execute-section,
.import-site-info,
.import-stat-cards,
.import-taxonomy-groups {
grid-template-columns: 1fr;
}
.import-execute-section {
align-items: stretch;
}
.import-file-row {
align-items: stretch;
}
.import-analysis button,
.resolution-select,
.taxonomy-mapping-input {
width: 100%;
}
.taxonomy-analyze-row {
flex-direction: column;
align-items: stretch;
}
.import-taxonomy-entry,
.import-taxonomy-edit-form {
width: 100%;
flex-direction: column;
align-items: stretch;
}
}
+474
View File
@@ -0,0 +1,474 @@
[data-testid="media-editor"] {
flex: 1;
display: flex;
flex-direction: column;
background-color: var(--vscode-editor-background);
overflow: hidden;
}
[data-testid="media-editor"] .editor-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 12px;
min-height: 35px;
background-color: var(--vscode-tab-activeBackground);
border-bottom: 1px solid var(--vscode-panel-border);
}
[data-testid="media-editor"] .editor-tabs {
display: flex;
align-items: center;
gap: 2px;
min-width: 0;
}
[data-testid="media-editor"] .editor-tab {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
padding: 6px 12px;
background-color: var(--vscode-tab-inactiveBackground);
color: var(--vscode-tab-inactiveForeground);
font-size: 13px;
border-radius: 4px 4px 0 0;
}
[data-testid="media-editor"] .editor-tab.active {
background-color: var(--vscode-tab-activeBackground);
color: var(--vscode-tab-activeForeground);
}
[data-testid="media-editor"] .editor-tab-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-testid="media-editor"] .editor-tab-dirty {
color: var(--vscode-notificationsWarningIcon-foreground, var(--vscode-editorWarning-foreground));
font-size: 10px;
}
[data-testid="media-editor"] .editor-actions {
display: flex;
align-items: center;
gap: 8px;
}
[data-testid="media-editor"] .editor-actions button {
padding: 4px 10px;
font-size: 12px;
}
[data-testid="media-editor"] .editor-actions button.danger:hover {
background-color: var(--vscode-notificationsErrorIcon-foreground);
}
[data-testid="media-editor"] .auto-save-indicator {
font-size: 11px;
color: var(--vscode-descriptionForeground);
font-style: italic;
}
[data-testid="media-editor"] .quick-actions-wrapper {
position: relative;
}
[data-testid="media-editor"] .quick-actions-btn {
display: inline-flex;
align-items: center;
gap: 6px;
}
[data-testid="media-editor"] .quick-actions-btn-icon {
font-size: 12px;
line-height: 1;
}
[data-testid="media-editor"] .quick-actions-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
width: 280px;
background: var(--vscode-dropdown-background, #252526);
border: 1px solid var(--vscode-dropdown-border, #454545);
border-radius: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
overflow: hidden;
z-index: 30;
}
[data-testid="media-editor"] .quick-actions-divider {
height: 1px;
background: var(--vscode-dropdown-border, #454545);
}
[data-testid="media-editor"] .quick-action-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
width: 100%;
padding: 10px 12px;
background: none;
border: none;
color: var(--vscode-dropdown-foreground, #ccc);
cursor: pointer;
text-align: left;
transition: background 0.1s;
}
[data-testid="media-editor"] .quick-action-item:hover:not(:disabled) {
background: var(--vscode-list-hoverBackground, #2a2d2e);
}
[data-testid="media-editor"] .quick-action-item:disabled {
opacity: 0.5;
cursor: not-allowed;
}
[data-testid="media-editor"] .quick-action-icon {
font-size: 16px;
flex-shrink: 0;
margin-top: 2px;
}
[data-testid="media-editor"] .quick-action-text {
display: flex;
flex: 1;
flex-direction: column;
gap: 2px;
min-width: 0;
}
[data-testid="media-editor"] .quick-action-text strong {
font-size: 13px;
font-weight: 500;
}
[data-testid="media-editor"] .quick-action-text small {
font-size: 11px;
opacity: 0.7;
}
[data-testid="media-editor"] .editor-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 16px;
overflow-y: auto;
gap: 16px;
}
[data-testid="media-editor"] > .editor-content.media-editor {
flex-direction: row;
align-items: stretch;
gap: 24px;
}
[data-testid="media-editor"] .editor-field {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
min-width: 0;
}
[data-testid="media-editor"] .editor-field label {
font-size: 11px;
font-weight: 500;
color: var(--vscode-descriptionForeground);
text-transform: uppercase;
letter-spacing: 0.5px;
}
[data-testid="media-editor"] .editor-field-row {
display: flex;
gap: 12px;
width: 100%;
margin-bottom: 0;
}
[data-testid="media-editor"] .post-editor-input,
[data-testid="media-editor"] .post-editor-textarea {
width: 100%;
padding: 8px 10px;
border: 1px solid var(--vscode-input-border, var(--vscode-panel-border));
border-radius: 4px;
background: var(--vscode-input-background, rgba(255, 255, 255, 0.06));
color: var(--vscode-input-foreground, var(--vscode-foreground));
font: inherit;
}
[data-testid="media-editor"] .post-editor-input.disabled,
[data-testid="media-editor"] .post-editor-input:disabled {
opacity: 0.6;
cursor: not-allowed;
}
[data-testid="media-editor"] .post-editor-textarea {
line-height: 1.5;
resize: vertical;
}
[data-testid="media-editor"] .media-preview {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--vscode-input-background);
border-radius: 8px;
min-height: 300px;
overflow: hidden;
}
[data-testid="media-editor"] .media-preview-placeholder {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
color: var(--vscode-descriptionForeground);
}
[data-testid="media-editor"] .media-preview-image {
display: flex;
align-items: center;
justify-content: center;
align-self: stretch;
width: 100%;
height: 100%;
min-height: 0;
padding: 16px;
box-sizing: border-box;
}
[data-testid="media-editor"] .media-preview-image img {
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 4px;
}
[data-testid="media-editor"] .media-details {
width: 320px;
display: flex;
flex-direction: column;
gap: 12px;
flex-shrink: 0;
}
[data-testid="media-editor"] .media-editor-details-form {
display: flex;
flex-direction: column;
gap: 12px;
}
[data-testid="media-editor"] .media-details textarea {
resize: vertical;
}
[data-testid="media-editor"] .linked-posts-section label {
display: flex;
justify-content: space-between;
align-items: center;
}
[data-testid="media-editor"] .add-link-btn {
background: var(--vscode-button-secondaryBackground);
border: none;
color: var(--vscode-button-secondaryForeground);
padding: 2px 8px;
border-radius: 3px;
cursor: pointer;
font-size: 11px;
}
[data-testid="media-editor"] .add-link-btn:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
[data-testid="media-editor"] .post-picker {
background: var(--vscode-dropdown-background);
border: 1px solid var(--vscode-dropdown-border);
border-radius: 4px;
margin-top: 8px;
max-height: 250px;
overflow-y: auto;
}
[data-testid="media-editor"] .post-picker-search {
padding: 8px;
border-bottom: 1px solid var(--vscode-dropdown-border);
position: sticky;
top: 0;
background: var(--vscode-dropdown-background);
}
[data-testid="media-editor"] .post-picker-search input {
width: 100%;
padding: 6px 10px;
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
border-radius: 3px;
color: var(--vscode-input-foreground);
font-size: 12px;
}
[data-testid="media-editor"] .post-picker-search input:focus {
outline: none;
border-color: var(--vscode-focusBorder);
}
[data-testid="media-editor"] .post-picker-list {
padding: 4px;
}
[data-testid="media-editor"] .post-picker-item {
width: 100%;
padding: 6px 8px;
cursor: pointer;
border: none;
border-radius: 3px;
background: transparent;
color: inherit;
font-size: 12px;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
[data-testid="media-editor"] .post-picker-item:hover {
background: var(--vscode-list-hoverBackground);
}
[data-testid="media-editor"] .post-picker-more {
padding: 6px 8px;
color: var(--vscode-descriptionForeground);
font-size: 11px;
font-style: italic;
}
[data-testid="media-editor"] .no-posts,
[data-testid="media-editor"] .no-linked-posts {
padding: 12px 8px;
color: var(--vscode-descriptionForeground);
font-size: 12px;
font-style: italic;
}
[data-testid="media-editor"] .linked-posts-list {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 4px;
}
[data-testid="media-editor"] .linked-post-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 8px;
background: var(--vscode-sideBar-background);
border-radius: 4px;
}
[data-testid="media-editor"] .linked-post-title,
[data-testid="media-editor"] .linked-post-link {
flex: 1;
min-width: 0;
border: none;
background: transparent;
padding: 0;
color: inherit;
text-align: left;
cursor: pointer;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
[data-testid="media-editor"] .linked-post-title:hover,
[data-testid="media-editor"] .linked-post-link:hover {
color: var(--vscode-textLink-foreground);
text-decoration: underline;
}
[data-testid="media-editor"] .linked-post-item .unlink-btn {
background: none;
border: none;
color: var(--vscode-descriptionForeground);
cursor: pointer;
padding: 0 4px;
font-size: 14px;
opacity: 0;
transition: opacity 0.1s;
}
[data-testid="media-editor"] .linked-post-item:hover .unlink-btn {
opacity: 1;
}
[data-testid="media-editor"] .linked-post-item .unlink-btn:hover {
color: var(--vscode-errorForeground);
}
.translation-modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.68);
display: flex;
align-items: center;
justify-content: center;
pointer-events: auto;
z-index: 10001;
}
.translation-modal {
width: min(640px, calc(100vw - 32px));
background: #1e1e1e;
border: 1px solid #3c3c3c;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
.translation-modal-header,
.translation-modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 20px;
}
.translation-modal-header {
border-bottom: 1px solid #3c3c3c;
}
.translation-modal-footer {
border-top: 1px solid #3c3c3c;
justify-content: flex-end;
gap: 10px;
}
.translation-modal-body {
padding: 20px;
display: flex;
flex-direction: column;
gap: 14px;
}
.translation-modal-close {
border: none;
background: transparent;
color: #c5c5c5;
cursor: pointer;
font-size: 20px;
line-height: 1;
}
+289
View File
@@ -0,0 +1,289 @@
.menu-editor-view {
padding: 1rem;
height: 100%;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
overflow: hidden;
background: var(--vscode-editor-background);
}
.menu-editor-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
}
.menu-editor-header h2 {
margin: 0;
}
.menu-editor-header p {
margin: 0.25rem 0 0;
color: var(--vscode-descriptionForeground);
}
.menu-editor-main {
display: flex;
flex-direction: column;
min-height: 0;
flex: 1;
overflow: hidden;
}
.menu-editor-tree-wrap {
display: flex;
flex-direction: column;
flex: 1;
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
background: var(--vscode-editor-background);
padding: 0.5rem;
min-height: 0;
}
.menu-editor-toolbar {
display: flex;
align-items: center;
gap: 0.2rem;
margin-bottom: 0.5rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid var(--vscode-panel-border);
}
.menu-editor-tool {
width: 1.8rem;
height: 1.8rem;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid transparent;
border-radius: 4px;
background: transparent;
color: var(--vscode-foreground);
cursor: pointer;
padding: 0;
}
.menu-editor-tool:hover:not(:disabled) {
background: var(--vscode-toolbar-hoverBackground);
border-color: var(--vscode-panel-border);
}
.menu-editor-tool:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.menu-editor-tree-shell {
flex: 1;
min-height: 0;
overflow: auto;
}
.menu-editor-tree-level {
list-style: none;
margin: 0;
padding: 0;
}
.menu-editor-tree-item {
margin: 0;
padding: 0;
}
.menu-editor-row {
--menu-editor-indent: calc(var(--menu-editor-depth) * 1rem);
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.3rem 0.45rem 0.3rem calc(0.4rem + var(--menu-editor-indent));
border-radius: 4px;
cursor: pointer;
position: relative;
}
.menu-editor-row.is-selected {
background: var(--vscode-list-activeSelectionBackground);
color: var(--vscode-list-activeSelectionForeground);
}
.menu-editor-row.is-dragging {
opacity: 0.45;
}
.menu-editor-row.is-drop-before::before,
.menu-editor-row.is-drop-after::after {
content: "";
position: absolute;
left: calc(0.4rem + var(--menu-editor-indent));
right: 0.45rem;
height: 2px;
background: var(--vscode-focusBorder);
}
.menu-editor-row.is-drop-before::before {
top: 0;
}
.menu-editor-row.is-drop-after::after {
bottom: 0;
}
.menu-editor-row.is-drop-inside {
box-shadow: inset 0 0 0 1px var(--vscode-focusBorder);
background: var(--vscode-list-hoverBackground);
}
.menu-editor-row-handle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1rem;
min-width: 1rem;
color: var(--vscode-descriptionForeground);
cursor: grab;
user-select: none;
}
.menu-editor-row-handle:active {
cursor: grabbing;
}
.menu-editor-row-kind {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1rem;
min-width: 1rem;
opacity: 0.9;
}
.menu-editor-row-title {
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.menu-editor-row-title.is-editing {
white-space: normal;
overflow: visible;
text-overflow: clip;
}
.menu-editor-entry-form {
display: block;
}
.menu-editor-inline-input {
width: 100%;
border: 1px solid var(--vscode-focusBorder);
border-radius: 4px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
padding: 0.25rem 0.45rem;
min-height: 1.8rem;
}
.menu-editor-inline-search {
margin-top: 0.5rem;
border-top: 1px solid var(--vscode-panel-border);
padding-top: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.4rem;
max-height: 18rem;
overflow: hidden;
}
.menu-editor-inline-search-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.menu-editor-inline-search-head strong {
display: block;
font-size: 0.8rem;
}
.menu-editor-inline-search-head span {
color: var(--vscode-descriptionForeground);
font-size: 0.75rem;
}
.menu-editor-inline-actions {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.menu-editor-inline-action {
border: 1px solid var(--vscode-button-border, transparent);
border-radius: 4px;
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
padding: 0.2rem 0.5rem;
cursor: pointer;
}
.menu-editor-inline-action:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
.menu-editor-picker-list {
display: flex;
flex-direction: column;
gap: 0.35rem;
max-height: 16rem;
overflow-y: auto;
}
.menu-editor-picker-item {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
border: 1px solid var(--vscode-panel-border);
border-radius: 4px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
padding: 0.45rem 0.55rem;
text-align: left;
cursor: pointer;
}
.menu-editor-picker-item:hover {
border-color: var(--vscode-focusBorder);
background: var(--vscode-list-hoverBackground);
}
.menu-editor-picker-item small,
.menu-editor-picker-state {
color: var(--vscode-descriptionForeground);
}
.menu-editor-empty {
color: var(--vscode-descriptionForeground);
padding: 0.5rem 0.25rem;
}
@media (max-width: 720px) {
.menu-editor-inline-search-head {
flex-direction: column;
align-items: flex-start;
}
.menu-editor-inline-actions {
width: 100%;
justify-content: flex-start;
flex-wrap: wrap;
}
}
+336
View File
@@ -0,0 +1,336 @@
.overlay-root {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 10000;
}
.overlay-root:empty {
display: none;
}
.editor-shared-actions {
position: relative;
margin-bottom: 14px;
}
.ai-suggestions-modal-backdrop,
.insert-modal-backdrop,
.language-picker-modal-backdrop,
.confirm-delete-modal-backdrop,
.confirm-dialog-overlay,
.gallery-overlay,
.lightbox-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.68);
display: flex;
align-items: center;
justify-content: center;
pointer-events: auto;
}
.ai-suggestions-modal,
.insert-modal,
.language-picker-modal,
.confirm-delete-modal,
.confirm-dialog,
.gallery-overlay-content {
background: #1e1e1e;
border: 1px solid #3c3c3c;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
.ai-suggestions-modal,
.language-picker-modal,
.confirm-delete-modal,
.confirm-dialog {
width: min(680px, calc(100vw - 32px));
max-height: calc(100vh - 48px);
display: flex;
flex-direction: column;
}
.insert-modal {
width: min(680px, calc(100vw - 32px));
max-height: calc(100vh - 48px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.gallery-overlay-content {
width: min(980px, calc(100vw - 48px));
max-height: calc(100vh - 48px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.ai-suggestions-modal-header,
.language-picker-modal-header,
.confirm-delete-modal-header,
.insert-modal-header,
.gallery-overlay-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 20px;
border-bottom: 1px solid #3c3c3c;
}
.insert-modal-header {
flex-direction: column;
align-items: stretch;
gap: 12px;
}
.insert-modal-header.media-header-only {
flex-direction: row;
align-items: center;
}
.ai-suggestions-modal-header h2,
.language-picker-modal-header h2,
.confirm-delete-modal-header h2,
.gallery-overlay-header h2,
.insert-modal-title,
.confirm-dialog h3 {
margin: 0;
color: #ffffff;
}
.ai-suggestions-modal-close,
.confirm-delete-modal-close,
.gallery-overlay-close,
.shared-popover-close,
.lightbox-close {
border: none;
background: transparent;
color: #c5c5c5;
cursor: pointer;
font-size: 20px;
line-height: 1;
}
.ai-suggestions-modal-body,
.language-picker-modal-body,
.confirm-delete-modal-body {
padding: 20px;
overflow: auto;
}
.ai-suggestions-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.ai-suggestion-item {
display: flex;
gap: 12px;
padding: 16px;
border: 1px solid #3c3c3c;
border-radius: 6px;
background: #252526;
}
.ai-suggestion-checkbox {
position: relative;
display: flex;
align-items: flex-start;
cursor: pointer;
}
.ai-suggestion-checkbox input {
position: absolute;
opacity: 0;
}
.checkmark {
width: 20px;
height: 20px;
border: 2px solid #555555;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
background: #1e1e1e;
}
.ai-suggestion-checkbox input:checked + .checkmark,
.ai-suggestion-checkbox input:checked ~ .checkmark {
background: #0078d4;
border-color: #0078d4;
}
.ai-suggestion-checkbox input:checked + .checkmark::after,
.ai-suggestion-checkbox input:checked ~ .checkmark::after {
content: "✓";
color: #ffffff;
font-size: 12px;
}
.ai-suggestion-content {
flex: 1;
min-width: 0;
}
.ai-suggestion-label {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-weight: 600;
}
.ai-suggestion-has-value,
.language-picker-badge,
.insert-modal-similarity-badge {
display: inline-flex;
align-items: center;
padding: 2px 6px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
color: #c5c5c5;
font-size: 11px;
}
.ai-suggestion-comparison {
display: grid;
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
gap: 12px;
align-items: center;
}
.ai-suggestion-column {
display: flex;
flex-direction: column;
gap: 4px;
padding: 10px 12px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.03);
}
.ai-suggestion-column.muted {
color: #9d9d9d;
}
.ai-suggestion-column.highlighted {
border: 1px solid rgba(0, 122, 204, 0.4);
color: #ffffff;
}
.ai-suggestion-column-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.ai-suggestion-arrow {
color: #9d9d9d;
}
.ai-suggestion-value {
min-height: 1.4em;
}
.ai-suggestion-value.loading {
color: var(--accent-color);
font-style: italic;
}
.ai-suggestions-error {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px 16px;
margin-bottom: 16px;
border-radius: 6px;
background: rgba(220, 50, 50, 0.12);
border: 1px solid rgba(220, 50, 50, 0.35);
color: #ff6b6b;
}
.ai-suggestions-modal-footer,
.confirm-delete-modal-footer,
.confirm-dialog-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 16px 20px;
border-top: 1px solid #3c3c3c;
}
.button-cancel,
.button-delete,
.button-apply,
.confirm-dialog-actions button,
.insert-modal-submit,
.language-picker-row,
.shared-popover-entry,
.colour-swatch {
cursor: pointer;
}
.button-cancel,
.confirm-dialog-actions button,
.insert-modal-submit {
border: 1px solid #4c4c4c;
border-radius: 4px;
padding: 8px 14px;
background: transparent;
color: #f0f0f0;
}
.button-apply,
.confirm-dialog-actions .primary,
.insert-modal-submit {
background: #0e639c;
border-color: #0e639c;
}
.button-delete {
border: none;
border-radius: 4px;
padding: 8px 14px;
background: #c73c3c;
color: #ffffff;
}
.insert-modal-tabs {
display: flex;
margin: 0 -20px;
}
.insert-modal-tab {
flex: 1;
border: none;
border-bottom: 2px solid transparent;
background: transparent;
color: #9d9d9d;
padding: 10px 16px;
}
.insert-modal-tab.active {
color: #ffffff;
border-bottom-color: #0e639c;
background: #252526;
}
.insert-modal-search {
border-bottom: 1px solid #3c3c3c;
}
.insert-modal-input,
.shared-popover-input {
width: 100%;
border: none;
background: transparent;
color: #f0f0f0;
padding: 14px 20px;
font: inherit;
}
+541
View File
@@ -0,0 +1,541 @@
.panel-shell {
min-height: 160px;
max-height: 160px;
border-top: 1px solid var(--line);
}
.panel-shell.is-hidden {
display: none;
}
.panel-tabs {
display: flex;
gap: 8px;
}
.panel-tabs {
display: flex;
gap: 2px;
}
.panel-tab {
background: transparent;
border: none;
padding: 6px 12px;
font-size: 12px;
color: var(--vscode-tab-inactiveForeground);
cursor: pointer;
border-bottom: 2px solid transparent;
border-radius: 0;
}
.panel-tab:hover {
color: var(--vscode-tab-activeForeground);
background: transparent;
}
.panel-tab.active {
color: var(--vscode-tab-activeForeground);
border-bottom-color: var(--vscode-focusBorder);
background: transparent;
}
.assistant-content {
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px;
}
.assistant-sidebar-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.assistant-sidebar-heading {
display: flex;
flex-direction: column;
gap: 4px;
}
.assistant-sidebar-description,
.assistant-sidebar-context-text,
.assistant-sidebar-message-content {
color: var(--vscode-descriptionForeground);
}
.assistant-sidebar-status {
border-radius: 999px;
border: 1px solid var(--vscode-panel-border);
padding: 2px 8px;
font-size: 11px;
line-height: 1.4;
}
.assistant-sidebar-status.is-offline {
background: rgba(255, 196, 0, 0.18);
border-color: rgba(255, 196, 0, 0.35);
color: var(--vscode-editor-foreground);
}
.assistant-sidebar-context {
display: flex;
flex-direction: column;
gap: 10px;
padding: 8px;
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
background: var(--vscode-editorWidget-background, rgba(0, 0, 0, 0.2));
}
.assistant-sidebar-context-row {
display: flex;
justify-content: space-between;
gap: 12px;
}
.assistant-sidebar-context-label,
.assistant-sidebar-message-role {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--vscode-descriptionForeground);
}
.assistant-sidebar-context-value {
text-align: right;
color: var(--vscode-editor-foreground);
}
.assistant-sidebar-context-text,
.assistant-sidebar-message-content {
margin: 0;
white-space: pre-wrap;
}
.assistant-sidebar-prompt-form,
.assistant-sidebar-welcome,
.assistant-sidebar-transcript {
display: flex;
flex-direction: column;
gap: 10px;
}
.assistant-sidebar-prompt {
width: 100%;
min-height: 120px;
resize: vertical;
border: 1px solid var(--vscode-input-border);
border-radius: 6px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
padding: 10px;
font: inherit;
}
.assistant-sidebar-prompt:focus {
outline: 1px solid var(--vscode-focusBorder);
outline-offset: 1px;
}
.assistant-sidebar-start-button {
align-self: flex-start;
border: 1px solid var(--vscode-button-border, transparent);
border-radius: 999px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
padding: 7px 14px;
cursor: pointer;
}
.assistant-sidebar-start-button:disabled {
cursor: default;
opacity: 0.55;
}
.assistant-card,
.assistant-sidebar-message {
display: flex;
flex-direction: column;
gap: 6px;
padding: 12px;
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
border-bottom-width: 1px;
background: var(--vscode-editorWidget-background, rgba(0, 0, 0, 0.2));
}
.assistant-sidebar-message.user {
background: var(--vscode-list-hoverBackground);
}
.assistant-sidebar-message.assistant {
background: var(--vscode-editorWidget-background, rgba(0, 0, 0, 0.2));
}
.status-bar {
height: 22px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--vscode-statusBar-background);
color: var(--vscode-statusBar-foreground);
font-size: 12px;
padding: 0 8px;
user-select: none;
flex-wrap: nowrap;
gap: 0;
border-top: none;
}
.status-bar-left,
.status-bar-right {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
min-width: 0;
}
.status-bar-item {
display: flex;
align-items: center;
gap: 6px;
padding: 0 8px;
height: 100%;
max-width: none;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
border-radius: 0;
background: transparent;
font-size: 12px;
}
.status-bar-item .task-message-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 300px;
}
.task-spinner {
width: 10px;
height: 10px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.panel-content {
padding: 8px;
}
.task-list {
gap: 4px;
}
.output-list,
.git-log-list {
gap: 6px;
}
.task-entry {
padding: 8px;
border-bottom: none;
border-radius: 4px;
background-color: var(--vscode-sideBar-background);
}
.output-entry {
padding: 8px;
border-bottom: none;
border-radius: 4px;
background-color: var(--vscode-sideBar-background);
font-size: 12px;
color: var(--vscode-editor-foreground);
}
@media (max-width: 1100px) {
.editor-frame {
grid-template-columns: 1fr;
}
.assistant-sidebar-shell {
display: none;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
}
.text-muted {
color: var(--vscode-descriptionForeground);
}
.editor-empty {
flex: 1;
display: flex;
align-items: flex-start;
justify-content: center;
background-color: var(--vscode-editor-background);
overflow-y: auto;
padding: 40px 20px;
}
.dashboard-content {
max-width: 720px;
width: 100%;
}
.dashboard-content h1 {
font-size: 24px;
font-weight: 400;
margin: 0 0 4px;
color: var(--vscode-editor-foreground);
}
.dashboard-content > .text-muted {
margin-bottom: 24px;
display: block;
}
.dashboard-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
margin-bottom: 24px;
}
.stat-card {
padding: 16px;
background-color: var(--vscode-sideBar-background);
border-radius: 6px;
}
.stat-number {
font-size: 32px;
font-weight: 600;
color: var(--vscode-editor-foreground);
line-height: 1;
margin-bottom: 4px;
}
.stat-label {
font-size: 12px;
color: var(--vscode-descriptionForeground);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 10px;
}
.stat-breakdown {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.stat-tag {
font-size: 11px;
padding: 2px 8px;
border-radius: 3px;
background-color: var(--vscode-input-background);
color: var(--vscode-descriptionForeground);
}
.stat-published {
color: var(--vscode-testing-iconPassed);
}
.stat-draft {
color: var(--vscode-editorWarning-foreground);
}
.stat-archived {
color: var(--vscode-descriptionForeground);
}
.dashboard-section {
background-color: var(--vscode-sideBar-background);
border-radius: 6px;
padding: 16px;
margin-bottom: 12px;
}
.dashboard-section h4 {
font-size: 11px;
font-weight: 600;
color: var(--vscode-descriptionForeground);
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 0 0 12px;
}
.timeline-chart {
display: flex;
align-items: flex-end;
gap: 4px;
height: 100px;
}
.timeline-bar-container {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}
.timeline-bar {
width: 100%;
max-width: 40px;
background-color: var(--vscode-activityBarBadge-background);
border-radius: 3px 3px 0 0;
margin-top: auto;
min-height: 4px;
position: relative;
transition: opacity 0.15s;
}
.timeline-bar:hover {
opacity: 0.8;
}
.timeline-bar-count {
position: absolute;
top: -16px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
color: var(--vscode-descriptionForeground);
}
.timeline-bar-label {
display: flex;
flex-direction: column;
align-items: center;
font-size: 9px;
color: var(--vscode-descriptionForeground);
margin-top: 4px;
line-height: 1.15;
}
.timeline-bar-label-month {
white-space: nowrap;
}
.timeline-bar-label-year {
font-size: 8px;
}
.tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 6px 10px;
align-items: baseline;
line-height: 1.6;
}
.dashboard-tag {
padding: 2px 8px;
border-radius: 10px;
background-color: var(--vscode-input-background);
color: var(--vscode-editor-foreground);
cursor: default;
transition: opacity 0.15s;
white-space: nowrap;
}
.dashboard-tag:hover {
opacity: 0.75;
}
.dashboard-tag.has-color {
border-radius: 12px;
}
.dashboard-tag.has-color:hover {
opacity: 0.85;
}
.tag-cloud-more {
font-size: 11px;
}
.tag-count {
font-size: 10px;
opacity: 0.5;
margin-left: 2px;
}
.dashboard-category {
font-size: 12px;
border: 1px solid var(--vscode-input-border);
}
.recent-posts-list {
display: flex;
flex-direction: column;
}
.recent-post-item {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
width: 100%;
border: none;
background: transparent;
text-align: left;
color: inherit;
}
.recent-post-item:hover {
background-color: var(--vscode-list-hoverBackground);
}
.recent-post-title {
flex: 1;
color: var(--vscode-editor-foreground);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.recent-post-status {
font-size: 10px;
padding: 1px 6px;
border-radius: 3px;
background-color: var(--vscode-input-background);
text-transform: uppercase;
letter-spacing: 0.3px;
}
.recent-post-status.status-published {
color: var(--vscode-testing-iconPassed);
}
.recent-post-status.status-draft {
color: var(--vscode-editorWarning-foreground);
}
.recent-post-status.status-archived {
color: var(--vscode-descriptionForeground);
}
.recent-post-date {
color: var(--vscode-descriptionForeground);
white-space: nowrap;
}
+807
View File
@@ -0,0 +1,807 @@
.app {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--vscode-editor-background);
}
.app-main {
flex: 1;
display: flex;
overflow: hidden;
min-height: 0;
}
.app-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}
.window-titlebar {
position: relative;
height: 34px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--vscode-editorGroupHeader-tabsBackground);
border-bottom: 1px solid var(--vscode-editorGroupHeader-tabsBorder);
flex-shrink: 0;
app-region: drag;
-webkit-app-region: drag;
padding-right: calc(10px + var(--bds-titlebar-overlay-right, 0px));
}
.window-titlebar-menu-bar {
display: flex;
align-items: center;
height: 100%;
margin-left: 6px;
gap: 2px;
app-region: no-drag;
-webkit-app-region: no-drag;
z-index: 2;
}
.window-titlebar-menu-group {
position: relative;
display: flex;
align-items: center;
height: 100%;
}
.window-titlebar-menu-bar.is-hidden {
display: none;
}
.window-titlebar.is-mac .window-titlebar-menu-bar {
margin-left: max(var(--bds-titlebar-macos-left-inset, 78px), calc(6px + var(--bds-titlebar-overlay-left, 0px)));
}
.window-titlebar-menu-button {
height: 24px;
border: none;
background: transparent;
color: var(--vscode-titleBar-activeForeground);
padding: 0 8px;
border-radius: 4px;
font-size: 12px;
line-height: 1;
cursor: pointer;
}
.window-titlebar-menu-button:hover,
.window-titlebar-action-button:hover {
background-color: var(--vscode-toolbar-hoverBackground);
}
.window-titlebar-menu-button.is-active {
background-color: var(--vscode-toolbar-hoverBackground);
}
.window-titlebar-menu-button:focus,
.window-titlebar-menu-button:focus-visible,
.window-titlebar-action-button:focus,
.window-titlebar-action-button:focus-visible {
outline: none;
box-shadow: none;
}
.window-titlebar-menu-dropdown {
position: absolute;
top: 30px;
left: 0;
min-width: 210px;
padding: 6px;
display: flex;
flex-direction: column;
gap: 2px;
background-color: var(--vscode-menu-background, var(--vscode-editorWidget-background));
border: 1px solid var(--vscode-menu-border, var(--vscode-panel-border));
border-radius: 6px;
box-shadow: var(--vscode-widget-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));
app-region: no-drag;
-webkit-app-region: no-drag;
z-index: 10;
}
.window-titlebar-menu-item {
border: none;
background: transparent;
color: var(--vscode-menu-foreground, var(--vscode-foreground));
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
text-align: left;
border-radius: 4px;
padding: 6px 8px;
font-size: 12px;
cursor: pointer;
}
.window-titlebar-menu-item:focus,
.window-titlebar-menu-item:focus-visible {
outline: none;
box-shadow: none;
background-color: var(--vscode-toolbar-hoverBackground);
}
.window-titlebar-menu-item:hover,
.window-titlebar-menu-item.is-keyboard-active {
background-color: var(--vscode-menu-selectionBackground, var(--vscode-toolbar-hoverBackground));
}
.window-titlebar-menu-item-accelerator {
opacity: 0.8;
}
.window-titlebar-menu-separator {
height: 1px;
margin: 4px 2px;
background-color: var(--vscode-menu-separatorBackground, rgba(255, 255, 255, 0.08));
}
.window-titlebar-drag-region {
flex: 1;
height: 100%;
}
.window-titlebar-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
max-width: 45%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: var(--vscode-titleBar-activeForeground);
font-size: 12px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
pointer-events: none;
}
.window-titlebar-actions {
height: 100%;
display: flex;
align-items: center;
margin-right: 6px;
app-region: no-drag;
-webkit-app-region: no-drag;
}
.window-titlebar-action-button {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
padding: 0;
line-height: 0;
background: transparent;
border: none;
color: var(--vscode-foreground);
cursor: pointer;
border-radius: 4px;
}
.window-titlebar-sidebar-icon,
.window-titlebar-panel-icon,
.window-titlebar-assistant-icon {
width: 14px;
height: 14px;
border: 1.5px solid currentColor;
border-radius: 2px;
display: block;
position: relative;
overflow: hidden;
}
.window-titlebar-sidebar-icon::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 33.3333%;
width: 1.5px;
transform: translateX(-50%);
background-color: currentColor;
}
.window-titlebar-panel-icon::before {
content: "";
position: absolute;
left: 0;
right: 0;
top: 66.6667%;
height: 1.5px;
transform: translateY(-50%);
background-color: currentColor;
}
.window-titlebar-assistant-icon::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 66.6667%;
width: 1.5px;
transform: translateX(-50%);
background-color: currentColor;
}
.window-titlebar-sidebar-pane,
.window-titlebar-panel-pane,
.window-titlebar-assistant-pane {
position: absolute;
background-color: currentColor;
transition: opacity 120ms ease;
}
.window-titlebar-sidebar-pane {
left: 0;
top: 0;
width: 33.3333%;
height: 100%;
}
.window-titlebar-panel-pane {
left: 0;
bottom: 0;
width: 100%;
height: 33.3333%;
}
.window-titlebar-assistant-pane {
right: 0;
top: 0;
width: 33.3333%;
height: 100%;
}
.window-titlebar-sidebar-icon.is-inactive .window-titlebar-sidebar-pane,
.window-titlebar-panel-icon.is-inactive .window-titlebar-panel-pane,
.window-titlebar-assistant-icon.is-inactive .window-titlebar-assistant-pane {
opacity: 0;
}
.panel-shell {
height: 200px;
border-top: 1px solid var(--vscode-panel-border);
background: var(--vscode-panel-background);
display: flex;
flex-direction: column;
}
.panel-shell.is-hidden {
display: none;
}
.editor-toolbar-button.is-destructive {
color: #f48771;
}
.shell-overlay-backdrop,
.gallery-overlay-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.68);
display: flex;
align-items: center;
justify-content: center;
pointer-events: auto;
z-index: 10000;
}
.shell-overlay-dismiss {
position: absolute;
inset: 0;
border: none;
background: transparent;
padding: 0;
}
.gallery-overlay {
position: relative;
width: min(980px, calc(100vw - 48px));
max-height: calc(100vh - 48px);
display: flex;
flex-direction: column;
overflow: hidden;
background: #1e1e1e;
border: 1px solid #3c3c3c;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
z-index: 1;
}
.insert-modal-media-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
padding: 16px;
}
.insert-modal-media-item {
display: flex;
flex-direction: column;
gap: 8px;
border: 1px solid #3c3c3c;
border-radius: 8px;
background: #252526;
color: inherit;
padding: 10px;
text-align: left;
}
.insert-modal-media-thumb {
width: 100%;
min-height: 112px;
border-radius: 6px;
object-fit: cover;
background: rgba(255, 255, 255, 0.04);
}
.insert-modal-media-title {
font-weight: 600;
color: #ffffff;
}
.language-picker-options {
display: flex;
flex-direction: column;
gap: 8px;
}
.language-picker-option {
width: 100%;
display: grid;
grid-template-columns: 28px 1fr auto;
gap: 12px;
align-items: center;
border: none;
border-radius: 4px;
padding: 12px 16px;
background: transparent;
color: inherit;
text-align: left;
}
.language-picker-label,
.language-picker-status,
.lightbox-counter {
color: #9d9d9d;
font-size: 12px;
}
.lightbox-counter {
margin-top: 4px;
}
@media (max-width: 720px) {
.insert-modal-media-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.panel-header {
height: 35px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8px;
background-color: var(--vscode-sideBar-background);
border-bottom: 1px solid var(--vscode-panel-border);
}
.panel-tabs {
display: flex;
align-items: stretch;
height: 100%;
}
.panel-tab {
border: none;
background: transparent;
color: var(--vscode-descriptionForeground);
padding: 0 12px;
cursor: pointer;
}
.panel-tab.active {
color: var(--vscode-tab-activeForeground);
}
.panel-close {
background: transparent;
border: none;
color: var(--vscode-descriptionForeground);
font-size: 18px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 4px;
padding: 0;
}
.panel-close:hover {
background-color: var(--vscode-list-hoverBackground);
color: var(--vscode-editor-foreground);
}
.panel-content {
flex: 1;
overflow: auto;
padding: 12px 14px;
}
.panel-entry,
.assistant-card {
display: flex;
flex-direction: column;
gap: 4px;
padding: 10px 12px;
border-bottom: 1px solid var(--vscode-panel-border);
}
.output-list,
.git-log-list {
display: flex;
flex-direction: column;
}
.task-list {
display: flex;
flex-direction: column;
}
.task-entry-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.task-status {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--vscode-descriptionForeground);
}
.task-status-running {
color: var(--vscode-terminal-ansiGreen, var(--vscode-statusBar-foreground));
}
.task-status-pending {
color: var(--vscode-terminal-ansiYellow, var(--vscode-statusBar-foreground));
}
.panel-empty-state {
min-height: 100%;
justify-content: center;
}
.status-bar {
height: 22px;
background: var(--vscode-statusBar-background);
color: var(--vscode-statusBar-foreground);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8px;
font-size: 12px;
flex-shrink: 0;
}
.status-bar-left,
.status-bar-right {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
.status-bar-left {
flex-shrink: 1;
min-width: 0;
}
.status-shell-controls {
display: flex;
align-items: stretch;
gap: 2px;
flex-shrink: 0;
}
.status-shell-toggle-button {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 100%;
padding: 0;
line-height: 0;
background: transparent;
border: none;
color: inherit;
cursor: pointer;
border-radius: 3px;
}
.status-shell-toggle-button:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.status-shell-toggle-button:focus,
.status-shell-toggle-button:focus-visible {
outline: none;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
}
.status-shell-toggle-button .window-titlebar-sidebar-icon,
.status-shell-toggle-button .window-titlebar-panel-icon,
.status-shell-toggle-button .window-titlebar-assistant-icon {
width: 12px;
height: 12px;
}
.status-bar-item {
display: flex;
align-items: center;
gap: 6px;
padding: 0 8px;
height: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.status-bar-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.status-bar-task-button {
border: none;
background: transparent;
color: inherit;
cursor: pointer;
}
.status-bar-item.theme-badge {
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 3px;
}
.status-bar-item.language-badge {
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 3px;
gap: 4px;
}
.status-bar-item.offline-badge {
border: none;
background: transparent;
color: inherit;
cursor: pointer;
opacity: 0.4;
font-size: 13px;
padding: 0 4px;
}
.status-bar-item.offline-badge.active {
background-color: rgba(255, 196, 0, 0.28);
opacity: 1;
}
.project-selector {
position: relative;
flex-shrink: 0;
}
.project-selector-trigger {
display: flex;
align-items: center;
gap: 6px;
padding: 0 8px;
height: 22px;
background: transparent;
border: none;
color: var(--vscode-statusBar-foreground);
cursor: pointer;
font-size: 12px;
text-align: left;
}
.project-selector-trigger:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.project-selector-trigger:focus {
outline: none;
}
.project-icon,
.dropdown-arrow,
.project-check-icon {
flex-shrink: 0;
}
.project-name,
.project-item-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.project-name {
max-width: 180px;
}
.dropdown-arrow {
opacity: 0.6;
}
.project-dropdown {
position: absolute;
left: 0;
bottom: 100%;
min-width: 220px;
margin-bottom: 4px;
background-color: #252526;
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 4px;
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.3);
z-index: 1000;
overflow: hidden;
}
.project-dropdown-header {
padding: 8px 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--vscode-descriptionForeground);
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.project-list {
max-height: 200px;
overflow-y: auto;
}
.project-item {
display: flex;
align-items: center;
width: 100%;
gap: 8px;
padding: 8px 12px;
border: none;
background: transparent;
color: inherit;
cursor: pointer;
}
.project-item:hover,
.project-item.active {
background-color: var(--vscode-list-hoverBackground);
}
.project-item.active .project-check-icon {
color: #89d185;
}
.project-dropdown-footer {
padding: 8px;
border-top: 1px solid rgba(255, 255, 255, 0.12);
display: grid;
gap: 6px;
}
.create-project-btn,
.existing-project-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
width: 100%;
padding: 6px 12px;
background-color: rgba(255, 255, 255, 0.12);
color: inherit;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
}
.create-project-btn:hover,
.existing-project-btn:hover {
background-color: rgba(255, 255, 255, 0.18);
}
.status-bar-language-select {
background: transparent;
border: none;
color: inherit;
font: inherit;
padding: 0;
}
.status-bar-language-select:focus {
outline: none;
}
.status-bar-count {
font-size: 11px;
opacity: 0.85;
}
.status-bar-item.brand {
font-weight: 600;
}
@media (max-width: 960px) {
.editor-frame {
grid-template-columns: minmax(0, 1fr);
}
.editor-meta {
border-left: none;
border-top: 1px solid var(--vscode-panel-border);
padding-left: 0;
padding-top: 10px;
}
}
.editor-section ul {
margin: 12px 0 0;
padding-left: 18px;
}
.editor-toolbar {
display: flex;
gap: 10px;
}
.editor-toolbar button {
padding: 9px 14px;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--panel-3);
color: var(--ink);
}
.editor-meta {
display: flex;
flex-direction: column;
gap: 12px;
}
.editor-meta-card,
.assistant-card,
.panel-entry {
padding: 16px;
}
.sidebar-header,
.assistant-header,
.panel-header {
display: flex;
justify-content: space-between;
gap: 12px;
padding: 16px 18px;
border-bottom: 1px solid var(--line);
}
File diff suppressed because it is too large. Load diff
+189
View File
@@ -0,0 +1,189 @@
.tab-bar {
display: flex;
align-items: center;
background-color: var(--vscode-editorGroupHeader-tabsBackground);
border-bottom: 1px solid var(--vscode-editorGroupHeader-tabsBorder);
height: 35px;
overflow: hidden;
flex-shrink: 0;
position: relative;
}
.tab-bar-tabs {
display: flex;
align-items: center;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
flex: 1;
}
.tab-bar-tabs::-webkit-scrollbar {
height: 0;
display: none;
}
.tab-bar-empty {
display: flex;
align-items: center;
height: 100%;
padding: 0 12px;
color: var(--vscode-descriptionForeground);
font-size: 12px;
}
.tab {
display: flex;
align-items: center;
gap: 4px;
padding: 0 6px 0 10px;
height: 100%;
min-width: 100px;
max-width: 180px;
cursor: pointer;
background-color: var(--vscode-tab-inactiveBackground);
border: none;
border-right: 1px solid var(--vscode-tab-border);
color: var(--vscode-tab-inactiveForeground);
font-size: 13px;
user-select: none;
position: relative;
flex-shrink: 0;
}
.tab-select {
display: flex;
align-items: center;
gap: 4px;
min-width: 0;
flex: 1;
height: 100%;
padding: 0;
background: transparent;
border: none;
color: inherit;
font: inherit;
cursor: inherit;
}
.tab:hover {
background-color: var(--vscode-list-hoverBackground);
}
.tab.active {
background-color: var(--vscode-tab-activeBackground);
color: var(--vscode-tab-activeForeground);
}
.tab.active::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background-color: var(--vscode-focusBorder);
}
.tab.transient .tab-title {
font-style: italic;
}
.tab-actions {
display: flex;
align-items: center;
gap: 2px;
margin-left: auto;
flex-shrink: 0;
}
.tab-dirty-indicator {
color: var(--vscode-editorWarning-foreground, #e2c08d);
font-size: 10px;
line-height: 1;
}
.tab-icon {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
opacity: 0.85;
}
.tab-title,
.status-bar-item {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tab-close {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
line-height: 1;
color: var(--vscode-icon-foreground, #c5c5c5);
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
border: none;
background: transparent;
padding: 0;
opacity: 0;
}
.tab:hover .tab-close {
opacity: 0.7;
}
.tab.active .tab-close {
opacity: 0.7;
}
.tab-close:hover {
opacity: 1 !important;
background-color: var(--vscode-toolbar-hoverBackground);
color: var(--vscode-tab-activeForeground);
}
.tab-close:active {
background-color: var(--vscode-toolbar-activeBackground, rgba(99, 102, 103, 0.31));
}
.tab.dirty .tab-dirty-indicator {
display: block;
}
.tab.dirty .tab-close {
display: none;
}
.tab.dirty:hover .tab-close {
display: flex;
opacity: 0.7;
}
.tab.dirty:hover .tab-dirty-indicator {
display: none;
}
.tab:focus-visible {
outline: 1px solid var(--vscode-focusBorder, #007fd4);
outline-offset: -1px;
}
.output-item-details {
margin: 4px 0 0;
padding: 8px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.03);
color: inherit;
font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
white-space: pre-wrap;
user-select: text;
}
+148
View File
@@ -0,0 +1,148 @@
:root {
--accent-color: #007acc;
--accent-color-transparent: rgba(0, 122, 204, 0.25);
--vscode-editor-background: #1e1e1e;
--vscode-editor-foreground: #cccccc;
--vscode-sideBar-background: #252526;
--vscode-activityBar-background: #333333;
--vscode-activityBar-foreground: #ffffff;
--vscode-panel-background: #1e1e1e;
--vscode-titleBar-activeBackground: #252526;
--vscode-titleBar-activeForeground: #cccccc;
--vscode-statusBar-background: #007acc;
--vscode-statusBar-foreground: #ffffff;
--vscode-tab-activeBackground: #1e1e1e;
--vscode-tab-inactiveBackground: #2d2d2d;
--vscode-tab-activeForeground: #ffffff;
--vscode-tab-inactiveForeground: #969696;
--vscode-editorGroupHeader-tabsBackground: #252526;
--vscode-editorGroupHeader-tabsBorder: #1e1e1e;
--vscode-toolbar-hoverBackground: rgba(90, 93, 94, 0.31);
--vscode-toolbar-activeBackground: rgba(99, 102, 103, 0.31);
--vscode-foreground: #cccccc;
--vscode-descriptionForeground: #858585;
--vscode-panel-border: #80808059;
--vscode-sideBar-border: #80808059;
--vscode-tab-border: #252526;
--vscode-focusBorder: #007fd4;
--vscode-input-background: rgba(255, 255, 255, 0.06);
--vscode-input-border: rgba(255, 255, 255, 0.12);
--vscode-list-hoverBackground: #2a2d2e;
--vscode-list-activeSelectionBackground: #094771;
--vscode-list-activeSelectionForeground: #ffffff;
--vscode-activityBarBadge-background: #007acc;
--vscode-activityBarBadge-foreground: #ffffff;
--vscode-testing-iconPassed: #73c991;
--vscode-editorWarning-foreground: #cca700;
--vscode-input-foreground: #cccccc;
--vscode-input-placeholderForeground: #a6a6a6;
--vscode-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--vscode-font-size: 13px;
--panel-1: var(--vscode-editor-background);
--panel-2: var(--vscode-sideBar-background);
--panel-3: var(--vscode-input-background);
--ink: var(--vscode-foreground);
--line: var(--vscode-panel-border);
--accent: var(--vscode-focusBorder);
--accent-soft: var(--vscode-list-hoverBackground);
--success: var(--vscode-testing-iconPassed);
--sidebar-width: 280px;
--assistant-width: 360px;
color-scheme: dark;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 100%;
height: 100%;
background: var(--vscode-editor-background);
color: var(--vscode-foreground);
}
body {
overflow: hidden;
user-select: none;
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
}
body > [data-phx-session],
body > [data-phx-main] {
width: 100%;
height: 100%;
min-height: 0;
}
button {
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
color: var(--vscode-button-foreground);
background-color: var(--vscode-button-background);
border: none;
padding: 6px 14px;
cursor: pointer;
border-radius: 2px;
}
button:hover {
background-color: var(--vscode-button-hoverBackground);
}
button:focus {
outline: 1px solid var(--vscode-focusBorder);
outline-offset: 2px;
}
button.secondary {
background-color: var(--vscode-button-secondaryBackground);
}
button.secondary:hover {
background-color: #4a4d51;
}
button.compact {
padding: 4px 8px;
font-size: 12px;
}
button.primary {
background-color: var(--vscode-button-background);
font-weight: 500;
}
button.primary:hover {
background-color: var(--vscode-button-hoverBackground);
}
button.success {
background-color: #28a745;
}
button.success:hover {
background-color: #218838;
}
button.danger {
background-color: #dc3545;
}
button.danger:hover {
background-color: #c82333;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
button svg,
button svg * {
pointer-events: none;
}
View File
Whitespace-only changes.
+122 -25
View File
@@ -1,3 +1,7 @@
import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import "phoenix_html";
document.addEventListener("DOMContentLoaded", () => {
const csrfToken = document
.querySelector("meta[name='csrf-token']")
@@ -298,15 +302,16 @@ document.addEventListener("DOMContentLoaded", () => {
};
};
const colorIsDark = (value) => {
const normalizeMonacoColor = (value, fallback) => {
const rgb = parseRgbColor(value);
if (!rgb) {
return true;
return fallback;
}
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
return luminance < 0.5;
return `#${[rgb.r, rgb.g, rgb.b]
.map((channel) => clamp(channel, 0, 255).toString(16).padStart(2, "0"))
.join("")}`;
};
const loadScript = (src) =>
@@ -492,14 +497,27 @@ document.addEventListener("DOMContentLoaded", () => {
};
const ensureMonacoTheme = (monaco) => {
const background = cssVar("--vscode-editor-background", cssVar("--vscode-input-background", "#1e1e1e"));
const foreground = cssVar("--vscode-editor-foreground", "#d4d4d4");
const lineNumber = cssVar("--vscode-editorLineNumber-foreground", "#858585");
const activeLineNumber = cssVar("--vscode-editorLineNumber-activeForeground", foreground);
const selection = cssVar("--vscode-editor-selectionBackground", "#264f78");
const inactiveSelection = cssVar("--vscode-editor-inactiveSelectionBackground", "#3a3d41");
const cursor = cssVar("--vscode-editorCursor-foreground", foreground);
const border = cssVar("--vscode-panel-border", "#3c3c3c");
const background = normalizeMonacoColor(
cssVar("--vscode-editor-background", cssVar("--vscode-input-background", "#1e1e1e")),
"#1e1e1e"
);
const foreground = normalizeMonacoColor(cssVar("--vscode-editor-foreground", "#d4d4d4"), "#d4d4d4");
const lineNumber = normalizeMonacoColor(cssVar("--vscode-editorLineNumber-foreground", "#858585"), "#858585");
const activeLineNumber = normalizeMonacoColor(
cssVar("--vscode-editorLineNumber-activeForeground", foreground),
foreground
);
const selection = normalizeMonacoColor(cssVar("--vscode-editor-selectionBackground", "#264f78"), "#264f78");
const inactiveSelection = normalizeMonacoColor(
cssVar("--vscode-editor-inactiveSelectionBackground", "#3a3d41"),
"#3a3d41"
);
const cursor = normalizeMonacoColor(cssVar("--vscode-editorCursor-foreground", foreground), foreground);
const border = normalizeMonacoColor(cssVar("--vscode-panel-border", "#3c3c3c"), "#3c3c3c");
const lineHighlight = normalizeMonacoColor(
cssVar("--vscode-editor-lineHighlightBackground", background),
background
);
const signature = [background, foreground, lineNumber, activeLineNumber, selection, inactiveSelection, cursor, border].join("|");
if (signature === monacoThemeSignature) {
@@ -508,7 +526,7 @@ document.addEventListener("DOMContentLoaded", () => {
}
monaco.editor.defineTheme("bds-theme", {
base: colorIsDark(background) ? "vs-dark" : "vs",
base: "vs-dark",
inherit: true,
rules: [
{ token: "keyword.macro", foreground: "C586C0", fontStyle: "bold" },
@@ -518,7 +536,7 @@ document.addEventListener("DOMContentLoaded", () => {
colors: {
"editor.background": background,
"editor.foreground": foreground,
"editor.lineHighlightBackground": cssVar("--vscode-editor-lineHighlightBackground", background),
"editor.lineHighlightBackground": lineHighlight,
"editorCursor.foreground": cursor,
"editor.selectionBackground": selection,
"editor.inactiveSelectionBackground": inactiveSelection,
@@ -549,11 +567,11 @@ document.addEventListener("DOMContentLoaded", () => {
return monacoLoaderPromise;
}
monacoLoaderPromise = loadScript("/assets/monaco/vs/loader.js")
monacoLoaderPromise = loadScript("/monaco/vs/loader.js")
.then(
() =>
new Promise((resolve, reject) => {
window.require.config({ paths: { vs: "/assets/monaco/vs" } });
window.require.config({ paths: { vs: "/monaco/vs" } });
window.require(["vs/editor/editor.main"], () => {
ensureMonacoTheme(window.monaco);
registerLiquidLanguage(window.monaco);
@@ -1146,6 +1164,84 @@ document.addEventListener("DOMContentLoaded", () => {
this.isApplyingRemoteUpdate = false;
this.lastKnownValue = this.textarea?.value || "";
this.syncEditorFromTextarea = () => {
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
if (!this.textarea || !this.editor) {
return;
}
const value = this.textarea.value || "";
if (this.editor.getValue() !== value) {
this.isApplyingRemoteUpdate = true;
this.editor.setValue(value);
this.isApplyingRemoteUpdate = false;
}
this.lastKnownValue = value;
};
this.layoutEditorSoon = () => {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
if (!this.editor) {
return;
}
this.editor.layout();
});
});
};
this.waitForMonacoVisibleSize = () =>
new Promise((resolve) => {
let settled = false;
let attempts = 0;
const hasVisibleSize = () => {
const rect = this.host?.getBoundingClientRect();
return Boolean(rect && rect.width > 0 && rect.height > 0);
};
const finish = () => {
if (settled) {
return;
}
settled = true;
this.visibleSizeObserver?.disconnect();
this.visibleSizeObserver = null;
resolve();
};
const check = () => {
if (hasVisibleSize() || attempts >= 20) {
finish();
return;
}
attempts += 1;
window.requestAnimationFrame(check);
};
if (hasVisibleSize()) {
finish();
return;
}
if (window.ResizeObserver && this.host) {
this.visibleSizeObserver = new ResizeObserver(() => {
if (hasVisibleSize()) {
finish();
}
});
this.visibleSizeObserver.observe(this.host);
}
window.requestAnimationFrame(check);
});
this.queueSync = () => {
if (!this.textarea || !this.editor) {
return;
@@ -1200,11 +1296,13 @@ document.addEventListener("DOMContentLoaded", () => {
};
loadMonaco()
.then((monaco) => {
.then(async (monaco) => {
if (!this.host || !this.textarea) {
return;
}
await this.waitForMonacoVisibleSize();
ensureMonacoTheme(monaco);
this.editor = monaco.editor.create(this.host, {
@@ -1231,6 +1329,9 @@ document.addEventListener("DOMContentLoaded", () => {
});
monacoEditors.set(this.editorId || this.el.id, this.editor);
monaco.editor.setTheme("bds-theme");
this.syncEditorFromTextarea();
this.layoutEditorSoon();
this.changeSubscription = this.editor.onDidChangeModelContent(() => {
if (this.isApplyingRemoteUpdate) {
@@ -1270,17 +1371,13 @@ document.addEventListener("DOMContentLoaded", () => {
this.editor.updateOptions({ wordWrap: this.wordWrap });
});
if (this.editor.getValue() !== this.textarea.value && this.lastKnownValue !== this.textarea.value) {
this.isApplyingRemoteUpdate = true;
this.editor.setValue(this.textarea.value);
this.isApplyingRemoteUpdate = false;
}
this.lastKnownValue = this.textarea.value;
this.syncEditorFromTextarea();
this.layoutEditorSoon();
},
destroyed() {
window.clearTimeout(this.syncTimer);
this.visibleSizeObserver?.disconnect();
this.changeSubscription?.dispose();
monacoEditors.delete(this.editorId || this.el.id);
this.editor?.dispose();
@@ -1416,7 +1513,7 @@ document.addEventListener("DOMContentLoaded", () => {
}
};
const liveSocket = new LiveView.LiveSocket("/live", Phoenix.Socket, {
const liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
hooks: Hooks,
metadata: {
+25
View File
@@ -27,6 +27,31 @@ config :bds, BDS.Desktop.Endpoint,
pubsub_server: BDS.PubSub,
live_view: [signing_salt: "desktop-live-view"]
config :tailwind,
version: "4.1.14",
default: [
cd: Path.expand("..", __DIR__),
args: ~w(
--input=assets/css/app.css
--output=priv/static/assets/app.css
)
]
config :esbuild,
version: "0.25.4",
default: [
cd: Path.expand("../assets", __DIR__),
args: ~w(
js/app.js
--bundle
--target=es2022
--outdir=../priv/static/assets
--external:/fonts/*
--external:/images/*
),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
config :bds, :scripting,
runtime: BDS.Scripting.Lua,
timeout: 300_000,
+6
View File
@@ -1,3 +1,9 @@
import Config
config :bds, BDS.Repo, pool_size: 5
config :bds, BDS.Desktop.Endpoint,
watchers: [
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]},
esbuild: {Esbuild, :install_and_run, [:default, ~w(--watch)]}
]
+5 -11
View File
@@ -16,20 +16,14 @@ defmodule BDS.Desktop.Endpoint do
plug(Plug.Static,
at: "/assets",
from: {:bds, "priv/ui"},
only: ["app.css", "live.js", "monaco"]
from: {:bds, "priv/static/assets"},
only: ["app.css", "app.js"]
)
plug(Plug.Static,
at: "/vendor/phoenix",
from: {:phoenix, "priv/static"},
only: ["phoenix.min.js"]
)
plug(Plug.Static,
at: "/vendor/live_view",
from: {:phoenix_live_view, "priv/static"},
only: ["phoenix_live_view.min.js"]
at: "/monaco",
from: {:bds, "priv/ui/monaco"},
only: ["vs"]
)
plug(BDS.Desktop.Router)
+1 -3
View File
@@ -16,9 +16,7 @@ defmodule BDS.Desktop.Layouts do
</head>
<body>
<%= @inner_content %>
<script defer phx-track-static src="/vendor/phoenix/phoenix.min.js"></script>
<script defer phx-track-static src="/vendor/live_view/phoenix_live_view.min.js"></script>
<script defer phx-track-static src="/assets/live.js"></script>
<script defer phx-track-static src="/assets/app.js"></script>
</body>
</html>
"""
+73 -25
View File
@@ -1251,7 +1251,9 @@ defmodule BDS.Scripting.ApiDocs do
"",
"**Module APIs**",
"",
Enum.map(methods, fn method -> "- [#{method.module}.#{method.name}](##{method.module}#{method.name})" end),
Enum.map(methods, fn method ->
"- [#{method.module}.#{method.name}](##{method.module}#{method.name})"
end),
"",
Enum.map(methods, &render_method/1),
"[↑ Back to Table of contents](#table-of-contents)",
@@ -1342,23 +1344,56 @@ defmodule BDS.Scripting.ApiDocs do
defp example_argument_value(name, "string") do
case name do
"id" -> "\"id-1\""
suffix when suffix in ["post_id", "media_id", "project_id", "tag_id", "target_tag_id"] -> "\"id-1\""
"source_tag_ids" -> "{\"id-1\", \"id-2\"}"
"language" -> "\"en\""
"status" -> "\"draft\""
"kind" -> "\"post\""
"slug" -> "\"example-slug\""
"title" -> "\"Example Title\""
"name" -> "\"Example Name\""
"query" -> "\"example query\""
"content" -> "\"Example content\""
"message" -> "\"Update content\""
"folder_path" -> "\"/Users/me/Sites/example\""
"source_path" -> "\"/Users/me/Pictures/example.jpg\""
"item_path" -> "\"/Users/me/Sites/example/output/index.html\""
"action" -> "\"save\""
_ -> "\"value\""
"id" ->
"\"id-1\""
suffix when suffix in ["post_id", "media_id", "project_id", "tag_id", "target_tag_id"] ->
"\"id-1\""
"source_tag_ids" ->
"{\"id-1\", \"id-2\"}"
"language" ->
"\"en\""
"status" ->
"\"draft\""
"kind" ->
"\"post\""
"slug" ->
"\"example-slug\""
"title" ->
"\"Example Title\""
"name" ->
"\"Example Name\""
"query" ->
"\"example query\""
"content" ->
"\"Example content\""
"message" ->
"\"Update content\""
"folder_path" ->
"\"/Users/me/Sites/example\""
"source_path" ->
"\"/Users/me/Pictures/example.jpg\""
"item_path" ->
"\"/Users/me/Sites/example/output/index.html\""
"action" ->
"\"save\""
_ ->
"\"value\""
end
end
@@ -1397,10 +1432,17 @@ defmodule BDS.Scripting.ApiDocs do
defp example_response_value(returns) do
cond do
returns == "nil" -> nil
nullable_return?(returns) -> {:nullable, example_response_value(non_nil_return(returns))}
String.ends_with?(returns, "[]") -> [example_value_for_type(String.trim_trailing(returns, "[]"))]
true -> example_value_for_type(returns)
returns == "nil" ->
nil
nullable_return?(returns) ->
{:nullable, example_response_value(non_nil_return(returns))}
String.ends_with?(returns, "[]") ->
[example_value_for_type(String.trim_trailing(returns, "[]"))]
true ->
example_value_for_type(returns)
end
end
@@ -1420,8 +1462,11 @@ defmodule BDS.Scripting.ApiDocs do
defp example_value_for_type(type) do
case Enum.find(@data_structures, &(&1.name == type)) do
nil -> [{"key", "value"}]
structure -> Enum.map(structure.fields, fn field -> {field.name, example_field_value(field.type)} end)
nil ->
[{"key", "value"}]
structure ->
Enum.map(structure.fields, fn field -> {field.name, example_field_value(field.type)} end)
end
end
@@ -1442,7 +1487,10 @@ defmodule BDS.Scripting.ApiDocs do
defp render_lua_value(false, _indent), do: "false"
defp render_lua_value(nil, _indent), do: "nil"
defp render_lua_value(value, _indent) when is_integer(value), do: Integer.to_string(value)
defp render_lua_value(value, _indent) when is_float(value), do: :erlang.float_to_binary(value, [:compact])
defp render_lua_value(value, _indent) when is_float(value),
do: :erlang.float_to_binary(value, [:compact])
defp render_lua_value(value, _indent) when is_binary(value), do: inspect(value)
defp render_lua_value([], _indent), do: "{}"
+5
View File
@@ -36,6 +36,8 @@ defmodule BDS.MixProject do
{:image, "~> 0.65"},
{:stemex, "~> 0.2.1"},
{:gettext, "~> 0.24"},
{:tailwind, "~> 0.3", runtime: Mix.env() == :dev},
{:esbuild, "~> 0.10", runtime: Mix.env() == :dev},
{:lazy_html, ">= 0.1.0", only: :test},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}
]
@@ -46,6 +48,9 @@ defmodule BDS.MixProject do
setup: ["deps.get", "ecto.setup"],
"ecto.setup": ["ecto.create", "ecto.migrate"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
"assets.build": ["tailwind default", "esbuild default"],
"assets.deploy": ["tailwind default --minify", "esbuild default --minify"],
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
validate: ["test", "dialyzer"]
]
+2
View File
@@ -15,6 +15,7 @@
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.22.0", "edab2d0f701b7dd05dcf7e2d97769c106aff62b5cfddc000d1dd6f46b9cbd8c3", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.13.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "5af9e031bffcc5da0b7bca90c271a7b1e7c04a93fecf7f6cd35bc1b1921a64bd"},
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
"ex_dbus": {:hex, :ex_dbus, "0.1.4", "053df83d45b27ba0b9b6ef55a47253922069a3ace12a2a7dd30d3aff58301e17", [:mix], [{:dbus, "~> 0.8.0", [hex: :dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "d8baeaf465eab57b70a47b70e29fdfef6eb09ba110fc37176eebe6ac7874d6d5"},
"ex_sni": {:hex, :ex_sni, "0.2.9", "81f9421035dd3edb6d69f1a4dd5f53c7071b41628130d32ba5ab7bb4bfdc2da0", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_dbus, "~> 0.1", [hex: :ex_dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "921d67d913765ed20ea8354fd1798dabc957bf66990a6842d6aaa7cd5ee5bc06"},
"ex_stemmers": {:hex, :ex_stemmers, "0.1.0", "63a84ae3a6f0c28a1d75768411f0ae15cfe8462fb70589b60977aa1b04c9372d", [:mix], [{:rustler, "~> 0.32.1", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "498826e2188e502f41d1a15f3d90e7738f0d94747e197367f03a2a44c09167c0"},
@@ -46,6 +47,7 @@
"saxy": {:hex, :saxy, "1.4.0", "c7203ad20001f72eaaad07d08f82be063fa94a40924e6bb39d93d55f979abcba", [:mix], [], "hexpm", "3fe790354d3f2234ad0b5be2d99822a23fa2d4e8ccd6657c672901dac172e9a9"},
"stemex": {:hex, :stemex, "0.2.1", "47017c6b10cdd6926a0d523ccf1f801c5f3faf5a0a9c862f49304e07f9b5584f", [:mix], [], "hexpm", "dbfc76d27adfa31d831d183979c595942884e6530a4496714aa5b70d0964c2e4"},
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
"tailwind": {:hex, :tailwind, "0.4.1", "e7bcc222fe96a1e55f948e76d13dd84a1a7653fb051d2a167135db3b4b08d3e9", [:mix], [], "hexpm", "6249d4f9819052911120dbdbe9e532e6bd64ea23476056adb7f730aa25c220d1"},
"telemetry": {:hex, :telemetry, "1.4.1", "ab6de178e2b29b58e8256b92b382ea3f590a47152ca3651ea857a6cae05ac423", [:rebar3], [], "hexpm", "2172e05a27531d3d31dd9782841065c50dd5c3c7699d95266b2edd54c2dafa1c"},
"thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
+2
View File
@@ -0,0 +1,2 @@
/*! tailwindcss v4.1.14 | MIT License | https://tailwindcss.com */
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.block{display:block}.hidden{display:none}.inline{display:inline}.table{display:table}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.rounded{border-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.invert{--tw-invert:invert(100Line truncated
+26
View File
@@ -0,0 +1,26 @@
(()=>{var $e=t=>typeof t=="function"?t:function(){return t},ki=typeof self<"u"?self:null,Pe=typeof window<"u"?window:null,re=ki||Pe||globalThis,Ai="2.0.0",oe={connecting:0,open:1,closing:2,closed:3},Ci=1e4,Ti=1e3,Q={closed:"closed",errored:"errored",joined:"joined",joining:"joining",leaving:"leaving"},de={close:"phx_close",error:"phx_error",join:"phx_join",reply:"phx_reply",leave:"phx_leave"},pt={longpoll:"longpoll",websocket:"websocket"},_i={complete:4},mt="base64url.bearer.phx.",Ye=class{constructor(t,e,i,s){this.channel=t,this.event=e,this.payload=i||function(){return{}},this.receivedResp=null,this.timeout=s,this.timeoutTimer=null,this.recHooks=[],this.sent=!1}resend(t){this.timeout=t,this.reset(),this.send()}send(){this.hasReceived("timeout")||(this.startTimeout(),this.sent=!0,this.channel.socket.push({topic:this.channel.topic,event:this.event,payload:this.payload(),ref:this.ref,join_ref:this.channel.joinRef()}))}receive(t,e){return this.hasReceived(t)&&e(this.receivedResp.response),this.recHooks.push({status:t,callback:e}),this}reset(){this.cancelRefEvent(),this.ref=null,this.refEvent=null,this.receivedResp=null,this.sent=!1}matchReceive({status:t,response:e,_ref:i}){this.recHooks.filter(s=>s.status===t).forEach(s=>s.callback(e))}cancelRefEvent(){this.refEvent&&this.channel.off(this.refEvent)}cancelTimeout(){clearTimeout(this.timeoutTimer),this.timeoutTimer=null}startTimeout(){this.timeoutTimer&&this.cancelTimeout(),this.ref=this.channel.socket.makeRef(),this.refEvent=this.channel.replyEventName(this.ref),this.channel.on(this.refEvent,t=>{this.cancelRefEvent(),this.cancelTimeout(),this.receivedResp=t,this.matchReceive(t)}),this.timeoutTimer=setTimeout(()=>{this.trigger("timeout",{})},this.timeout)}hasReceived(t){return this.receivedResp&&this.receivedResp.status===t}trigger(t,e){this.channel.trigger(this.refEvent,{status:t,response:e})}},Ht=class{constructor(t,e){this.callback=t,this.timerCalc=e,this.timer=null,this.tries=0}reset(){this.tries=0,clearTimeout(this.timer)}scheduleTimeout(){clearTimeout(this.timer),this.timer=setTimeout(()=>{this.tries=this.tries+1,this.callback()},this.timerCalc(this.tries+1))}},Pi=class{constructor(t,e,i){this.state=Q.closed,this.topic=t,this.params=$e(e||{}),this.socket=i,this.bindings=[],this.bindingRef=0,this.timeout=this.socket.timeout,this.joinedOnce=!1,this.joinPush=new Ye(this,de.join,this.params,this.timeout),this.pushBuffer=[],this.stateChangeRefs=[],this.rejoinTimer=new Ht(()=>{this.socket.isConnected()&&this.rejoin()},this.socket.rejoinAfterMs),this.stateChangeRefs.push(this.socket.onError(()=>this.rejoinTimer.reset())),this.stateChangeRefs.push(this.socket.onOpen(()=>{this.rejoinTimer.reset(),this.isErrored()&&this.rejoin()})),this.joinPush.receive("ok",()=>{this.state=Q.joined,this.rejoinTimer.reset(),this.pushBuffer.forEach(s=>s.send()),this.pushBuffer=[]}),this.joinPush.receive("error",()=>{this.state=Q.errored,this.socket.isConnected()&&this.rejoinTimer.scheduleTimeout()}),this.onClose(()=>{this.rejoinTimer.reset(),this.socket.hasLogger()&&this.socket.log("channel",`close ${this.topic} ${this.joinRef()}`),this.state=Q.closed,this.socket.remove(this)}),this.onError(s=>{this.socket.hasLogger()&&this.socket.log("channel",`error ${this.topic}`,s),this.isJoining()&&this.joinPush.reset(),this.state=Q.errored,this.socket.isConnected()&&this.rejoinTimer.scheduleTimeout()}),this.joinPush.receive("timeout",()=>{this.socket.hasLogger()&&this.socket.log("channel",`timeout ${this.topic} (${this.joinRef()})`,this.joinPush.timeout),new Ye(this,de.leave,$e({}),this.timeout).send(),this.state=Q.errored,this.joinPush.reset(),this.socket.isConnected()&&this.rejoinTimer.scheduleTimeout()}),this.on(de.reply,(s,n)=>{this.trigger(this.replyEventName(n),s)})}join(t=this.timeout){if(this.joinedOnce)throw new Error("tried to join multiple times. 'join' can only be called a single time per channel instance");return this.timeout=t,this.joinedOnce=!0,this.rejoin(),this.joinPush}onClose(t){this.on(de.close,t)}onError(t){return this.on(de.error,e=>t(e))}on(t,e){let i=this.bindingRef++;return this.bindings.push({event:t,ref:i,callback:e}),i}off(t,e){this.bindings=this.bindings.filter(i=>!(i.event===t&&(typeof e>"u"||e===i.ref)))}canPush(){return this.socket.isConnected()&&this.isJoined()}push(t,e,i=this.timeout){if(e=e||{},!this.joinedOnce)throw new Error(`tried to push '${t}' to '${this.topic}' before joining. Use channel.join() before pushing events`);let s=new Ye(this,t,function(){return e},i);return this.canPush()?s.send():(s.startTimeout(),this.pushBuffer.push(s)),s}leave(t=this.timeout){this.rejoinTimer.reset(),this.joinPush.cancelTimeout(),this.state=Q.leaving;let e=()=>{this.socket.hasLogger()&&this.socket.log("channel",`leave ${this.topic}`),this.trigger(de.close,"leave")},i=new Ye(this,de.leave,$e({}),t);return i.receive("ok",()=>e()).receive("timeout",()=>e()),i.send(),this.canPush()||i.trigger("ok",{}),i}onMessage(t,e,i){return e}isMember(t,e,i,s){return this.topic!==t?!1:sLine truncated
`),()=>this.onerror("timeout"),e=>{this.awaitingBatchAck=!1,!e||e.status!==200?(this.onerror(e&&e.status),this.closeAndRetry(1011,"internal server error",!1)):this.batchBuffer.length>0&&(this.batchSend(this.batchBuffer),this.batchBuffer=[])})}close(t,e,i){for(let n of this.reqs)n.abort();this.readyState=oe.closed;let s=Object.assign({code:1e3,reason:void 0,wasClean:!0},{code:t,reason:e,wasClean:i});this.batchBuffer=[],clearTimeout(this.currentBatchTimer),this.currentBatchTimer=null,typeof CloseEvent<"u"?this.onclose(new CloseEvent("close",s)):this.onclose(s)}ajax(t,e,i,s,n){let r,o=()=>{this.reqs.delete(r),s()};r=Qe.request(t,this.endpointURL(),e,i,this.timeout,o,a=>{this.reqs.delete(r),this.isActive()&&n(a)}),this.reqs.add(r)}};var Ze={HEADER_LENGTH:1,META_LENGTH:4,KINDS:{push:0,reply:1,broadcast:2},encode(t,e){if(t.payload.constructor===ArrayBuffer)return e(this.binaryEncode(t));{let i=[t.join_ref,t.ref,t.topic,t.event,t.payload];return e(JSON.stringify(i))}},decode(t,e){if(t.constructor===ArrayBuffer)return e(this.binaryDecode(t));{let[i,s,n,r,o]=JSON.parse(t);return e({join_ref:i,ref:s,topic:n,event:r,payload:o})}},binaryEncode(t){let{join_ref:e,ref:i,event:s,topic:n,payload:r}=t,o=this.META_LENGTH+e.length+i.length+n.length+s.length,a=new ArrayBuffer(this.HEADER_LENGTH+o),h=new DataView(a),c=0;h.setUint8(c++,this.KINDS.push),h.setUint8(c++,e.length),h.setUint8(c++,i.length),h.setUint8(c++,n.length),h.setUint8(c++,s.length),Array.from(e,g=>h.setUint8(c++,g.charCodeAt(0))),Array.from(i,g=>h.setUint8(c++,g.charCodeAt(0))),Array.from(n,g=>h.setUint8(c++,g.charCodeAt(0))),Array.from(s,g=>h.setUint8(c++,g.charCodeAt(0)));var f=new Uint8Array(a.byteLength+r.byteLength);return f.set(new Uint8Array(a),0),f.set(new Uint8Array(r),a.byteLength),f.buffer},binaryDecode(t){let e=new DataView(t),i=e.getUint8(0),s=new TextDecoder;switch(i){case this.KINDS.push:return this.decodePush(t,e,s);case this.KINDS.reply:return this.decodeReply(t,e,s);case this.KINDS.broadcast:return this.decodeBroadcast(t,e,s)}},decodePush(t,e,i){let s=e.getUint8(1),n=e.getUint8(2),r=e.getUint8(3),o=this.HEADER_LENGTH+this.META_LENGTH-1,a=i.decode(t.slice(o,o+s));o=o+s;let h=i.decode(t.slice(o,o+n));o=o+n;let c=i.decode(t.slice(o,o+r));o=o+r;let f=t.slice(o,t.byteLength);return{join_ref:a,ref:null,topic:h,event:c,payload:f}},decodeReply(t,e,i){let s=e.getUint8(1),n=e.getUint8(2),r=e.getUint8(3),o=e.getUint8(4),a=this.HEADER_LENGTH+this.META_LENGTH,h=i.decode(t.slice(a,a+s));a=a+s;let c=i.decode(t.slice(a,a+n));a=a+n;let f=i.decode(t.slice(a,a+r));a=a+r;let g=i.decode(t.slice(a,a+o));a=a+o;let b=t.slice(a,t.byteLength),w={status:g,response:b};return{join_ref:h,ref:c,topic:f,event:de.reply,payload:w}},decodeBroadcast(t,e,i){let s=e.getUint8(1),n=e.getUint8(2),r=this.HEADER_LENGTH+2,o=i.decode(t.slice(r,r+s));r=r+s;let a=i.decode(t.slice(r,r+n));r=r+n;let h=t.slice(r,t.byteLength);return{join_ref:null,ref:null,topic:o,event:a,payload:h}}},Nt=class{constructor(t,e={}){this.stateChangeCallbacks={open:[],close:[],error:[],message:[]},this.channels=[],this.sendBuffer=[],this.ref=0,this.fallbackRef=null,this.timeout=e.timeout||Ci,this.transport=e.transport||re.WebSocket||_e,this.primaryPassedHealthCheck=!1,this.longPollFallbackMs=e.longPollFallbackMs,this.fallbackTimer=null,this.sessionStore=e.sessionStorage||re&&re.sessionStorage,this.establishedConnections=0,this.defaultEncoder=Ze.encode.bind(Ze),this.defaultDecoder=Ze.decode.bind(Ze),this.closeWasClean=!0,this.disconnecting=!1,this.binaryType=e.binaryType||"arraybuffer",this.connectClock=1,this.pageHidden=!1,this.transport!==_e?(this.encode=e.encode||this.defaultEncoder,this.decode=e.decode||this.defaultDecoder):(this.encode=this.defaultEncoder,this.decode=this.defaultDecoder);let i=null;Pe&&Pe.addEventListener&&(Pe.addEventListener("pagehide",s=>{this.conn&&(this.disconnect(),i=this.connectClock)}),Pe.addEventListener("pageshow",s=>{i===this.connectClock&&(i=null,this.connect())}),Pe.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.pageHidden=!0:(this.pageHidden=!1,!this.isConnected()&&!this.closeWasClean&&this.teardown(()=>this.connect()))})),this.heartbeatIntervalMs=e.heartbeatIntervalMs||3e4,this.rejoinAfterMs=s=>e.rejoinAfterMs?e.rejoinAfterMs(s):[1e3,2e3,5e3][s-1]||1e4,this.reconnectAfterMs=s=>e.reconnectAfterMs?e.reconnectAfterMs(s):[10,50,100,150,200,250,500,1e3,2e3][s-1]||5e3,this.logger=e.logger||null,!this.logger&&e.debug&&(this.logger=(s,n,r)=>{console.log(`${s}: ${n}`,r)}),this.longpollerTimeout=e.longpollerTimeout||2e4,this.params=$e(e.params||{}),this.endPoint=`${t}/${pt.websocket}`,this.vsn=e.vsn||Ai,this.heartbeatTimeoutTimer=null,this.heartbeatTimer=null,this.pendingHeartbeatRef=null,this.reconnectTimer=new Ht(()=>{if(this.pageHidden){this.log("Not reconnecting as page is hidden!"),this.teardown();return}this.teardown(()=>this.connect())},this.reconnectAfterMs),this.authToken=e.authToken}getLongPollTransport(){return _e}replaceTransport(t){this.con Line truncated
hook attached to non-connected DOM element
ensure you are calling createHook within your connectedCallback. ${t.outerHTML}
`),this.putPrivate(t,"custom-el-hook",e)},getCustomElHook(t){return this.private(t,"custom-el-hook")},isUsedInput(t){return t.nodeType===Node.ELEMENT_NODE&&(this.private(t,dt)||this.private(t,Xe))},resetForm(t){Array.from(t.elements).forEach(e=>{this.deletePrivate(e,dt),this.deletePrivate(e,Xe)})},isPhxChild(t){return t.getAttribute&&t.getAttribute(ke)},isPhxSticky(t){return t.getAttribute&&t.getAttribute(Tt)!==null},isChildOfAny(t,e){return!!e.find(i=>i.contains(t))},firstPhxChild(t){return this.isPhxChild(t)?t:this.all(t,`[${ke}]`)[0]},isPortalTemplate(t){return t.tagName==="TEMPLATE"&&t.hasAttribute(Dt)},closestViewEl(t){let e=t.closest(`[${Ae}],${ze}`);return e?e.hasAttribute(Ae)?this.byId(e.getAttribute(Ae)):e.hasAttribute(ce)?e:null:null},dispatchEvent(t,e,i={}){let s=!0;t.nodeName==="INPUT"&&t.type==="file"&&e==="click"&&(s=!1);let o={bubbles:i.bubbles===void 0?s:!!i.bubbles,cancelable:!0,detail:i.detail||{}},a=e==="click"?new MouseEvent("click",o):new CustomEvent(e,o);t.dispatchEvent(a)},cloneNode(t,e){if(typeof e>"u")return t.cloneNode(!0);{let i=t.cloneNode(!1);return i.innerHTML=e,i}},mergeAttrs(t,e,i={}){let s=new Set(i.exclude||[]),n=i.isIgnored,r=e.attributes;for(let a=r.length-1;a>=0;a--){let h=r[a].name;if(s.has(h)){if(h==="value"){let c=e.value??e.getAttribute(h);t.value===c&&t.setAttribute("value",e.getAttribute(h))}}else{let c=e.getAttribute(h);t.getAttribute(h)!==c&&(!n||n&&h.startsWith("data-"))&&t.setAttribute(h,c)}}let o=t.attributes;for(let a=o.length-1;a>=0;a--){let h=o[a].name;n?h.startsWith("data-")&&!e.hasAttribute(h)&&!zt.includes(h)&&t.removeAttribute(h):e.hasAttribute(h)||t.removeAttribute(h)}},mergeFocusedInput(t,e){t instanceof HTMLSelectElement||ye.mergeAttrs(t,e,{exclude:["value"]}),e.readOnly?t.setAttribute("readonly",!0):t.removeAttribute("readonly")},hasSelectionRange(t){return t.setSelectionRange&&(t.type==="text"||t.type==="textarea")},restoreFocus(t,e,i){if(t instanceof HTMLSelectElement&&t.focus(),!ye.isTextualInput(t))return;t.matches(":focus")||t.focus(),this.hasSelectionRange(t)&&t.setSelectionRange(e,i)},isFormInput(t){return t.localName&&customElements.get(t.localName)?customElements.get(t.localName).formAssociated:/^(?:input|select|textarea)$/i.test(t.tagName)&&t.type!=="button"},syncAttrsToProps(t){t instanceof HTMLInputElement&&gi.indexOf(t.type.toLocaleLowerCase())>=0&&(t.checked=t.getAttribute("checked")!==null)},isTextualInput(t){return Fi.indexOf(t.type)>=0},isNowTriggerFormExternal(t,e){return t.getAttribute&&t.getAttribute(e)!==null&&document.body.contains(t)},cleanChildNodes(t,e){if(ye.isPhxUpdate(t,e,["append","prepend",ht])){let i=[];t.childNodes.forEach(s=>{s.id||(!(s.nodeType===Node.TEXT_NODE&&s.nodeValue.trim()==="")&&s.nodeType!==Node.COMMENT_NODE&&O(`only HTML element tags with an id are allowed inside containers with phx-update.
removing illegal node: "${(s.outerHTML||s.nodeValue).trim()}"
`),i.push(s))}),i.forEach(s=>s.remove())}},replaceRootContainer(t,e,i){let s=new Set(["id",ce,Ee,Lt,fe]);if(t.tagName.toLowerCase()===e.toLowerCase())return Array.from(t.attributes).filter(n=>!s.has(n.name.toLowerCase())).forEach(n=>t.removeAttribute(n.name)),Object.keys(i).filter(n=>!s.has(n.toLowerCase())).forEach(n=>t.setAttribute(n,i[n])),t;{let n=document.createElement(e);return Object.keys(i).forEach(r=>n.setAttribute(r,i[r])),s.forEach(r=>n.setAttribute(r,t.getAttribute(r))),n.innerHTML=t.innerHTML,t.replaceWith(n),n}},getSticky(t,e,i){let s=(ye.private(t,"sticky")||[]).find(([n])=>e===n);if(s){let[n,r,o]=s;return o}else return typeof i=="function"?i():i},deleteSticky(t,e){this.updatePrivate(t,"sticky",[],i=>i.filter(([s,n])=>s!==e))},putSticky(t,e,i){let s=i(t);this.updatePrivate(t,"sticky",[],n=>{let r=n.findIndex(([o])=>e===o);return r>=0?n[r]=[e,i,s]:n.push([e,i,s]),n})},applyStickyOperations(t){let e=ye.private(t,"sticky");e&&e.forEach(([i,s,n])=>this.putSticky(t,i,s))},isLocked(t){return t.hasAttribute&&t.hasAttribute(B)},attributeIgnored(t,e){return e.some(i=>t.name==i||i==="*"||i.includes("*")&&t.name.match(i)!=null)}},u=ye,We=class{static isActive(t,e){let i=e._phxRef===void 0,n=t.getAttribute(kt).split(",").indexOf(W.genFileRef(e))>=0;return e.size>0&&(i||n)}static isPreflighted(t,e){return t.getAttribute(It).split(",").indexOf(W.genFileRef(e))>=0&&this.isActive(t,e)}static isPreflightInProgress(t){return t._preflightInProgress===!0}static markPreflightInProgress(t){t._preflightInProgress=!0}constructor(t,e,i,s){this.ref=W.genFileRef(e),this.fileEl=t,this.file=e,this.view=i,this.meta=null,this._isCancelled=!1,this._isDone=!1,this._progress=0,this._lastProgressSent=-1,this._onDone=function(){},this._onElUpdated=this.onElUpdated.bind(this),this.fileEl.addEventListener(at,this._onElUpdated),this.autoUpload=s}metadata(){return this.meta}progress(t){this._progress=Math.floor(t),this._progress>this._lastProgressSent&&(this._progress>=100?(this._progress=100,this._lastProgressSent=100,this._isDone=!0,this.view.pushFileProgress(this.fileEl,this.ref,100,()=>{W.untrackFile(this.fileEl,this.file),this._onDone()})):(this._lastProgressSent=this._progress,this.view.pushFileProgress(this.fileEl,this.ref,this._progress)))}isCancelled(){return this._isCancelled}cancel(){this.file._preflightInProgress=!1,this._isCancelled=!0,this._isDone=!0,this._onDone()}isDone(){return this._isDone}error(t="failed"){this.fileEl.removeEventListener(at,this._onElUpdated),this.view.pushFileProgress(this.fileEl,this.ref,{error:t}),this.isAutoUpload()||W.clearFiles(this.fileEl)}isAutoUpload(){return this.autoUpload}onDone(t){this._onDone=()=>{this.fileEl.removeEventListener(at,this._onElUpdated),t()}}onElUpdated(){this.fileEl.getAttribute(kt).split(",").indexOf(this.ref)===-1&&(W.untrackFile(this.fileEl,this.file),this.cancel())}toPreflightPayload(){return{last_modified:this.file.lastModified,name:this.file.name,relative_path:this.file.webkitRelativePath,size:this.file.size,type:this.file.type,ref:this.ref,meta:typeof this.file.meta=="function"?this.file.meta():void 0}}uploader(t){if(this.meta.uploader){let e=t[this.meta.uploader]||O(`no uploader configured for ${this.meta.uploader}`);return{name:this.meta.uploader,callback:e}}else return{name:"channel",callback:is}}zipPostFlight(t){this.meta=t.entries[this.ref],this.meta||O(`no preflight upload response returned with ref ${this.ref}`,{input:this.fileEl,response:t})}},rs=0,W=class Pt{static genFileRef(e){let i=e._phxRef;return i!==void 0?i:(e._phxRef=(rs++).toString(),e._phxRef)}static getEntryDataURL(e,i,s){let n=this.activeFiles(e).find(r=>this.genFileRef(r)===i);s(URL.createObjectURL(n))}static hasUploadsInProgress(e){let i=0;return u.findUploadInputs(e).forEach(s=>{s.getAttribute(It)!==s.getAttribute(Hi)&&i++}),i>0}static serializeUploads(e){let i=this.activeFiles(e),s={};return i.forEach(n=>{let r={path:e.name},o=e.getAttribute(pe);s[o]=s[o]||[],r.ref=this.genFileRef(n),r.last_modified=n.lastModified,r.name=n.name||r.ref,r.relative_path=n.webkitRelativePath,r.type=n.type,r.size=n.size,typeof n.meta=="function"&&(r.meta=n.meta()),s[o].push(r)}),s}static clearFiles(e){e.value=null,e.removeAttribute(pe),u.putPrivate(e,"files",[])}static untrackFile(e,i){u.putPrivate(e,"files",u.private(e,"files").filter(s=>!Object.is(s,i)))}static trackFiles(e,i,s){if(e.getAttribute("multiple")!==null){let n=i.filter(r=>!this.activeFiles(e).find(o=>Object.is(o,r)));u.updatePrivate(e,"files",[],r=>r.concat(n)),e.value=null}else s&&s.files.length>0&&(e.files=s.files),u.putPrivate(e,"files",i)}static activeFileInputs(e){let i=u.findUploadInputs(e);return Array.from(i).filter(s=>s.files&&this.activeFiles(s).length>0)}static activeFiles(e){return(u.private(e,"files")||[]).filter(i=>We.isActive(e,i))}static inputsAwaitingPreflight(e){let i=u.findUploadInputs(e);return Array.from(i).filter(s=>this.filesAwaitingPreflight(s).length>0)}static filesAwaitingPreflight(e){return this.activeFiles(e).filter Line truncated
`,T)})),S.length>0&&i.time("post-morph append/prepend restoration",()=>{S.forEach(T=>T.perform())}),i.silenceEvents(()=>u.restoreFocus(o,a,h)),u.dispatchEvent(document,"phx:update"),w.forEach(T=>this.trackAfter("added",T)),p.forEach(T=>this.trackAfter("updated",T)),this.transitionPendingRemoves(),$){i.unload();let T=u.private($,"submitter");if(T&&T.name&&r.contains(T)){let R=document.createElement("input");R.type="hidden";let L=T.getAttribute("form");L&&R.setAttribute("form",L),R.name=T.name,R.value=T.value,T.parentElement.insertBefore(R,T)}Object.getPrototypeOf($).submit.call($)}return!0}onNodeDiscarded(t){(u.isPhxChild(t)||u.isPhxSticky(t))&&this.liveSocket.destroyViewByEl(t),this.trackAfter("discarded",t)}maybePendingRemove(t){return t.getAttribute&&t.getAttribute(this.phxRemove)!==null?(this.pendingRemoves.push(t),!0):!1}removeStreamChildElement(t,e=!1){!e&&!this.view.ownsElement(t)||(this.streamInserts[t.id]?(this.streamComponentRestore[t.id]=t,t.remove()):this.maybePendingRemove(t)||(t.remove(),this.onNodeDiscarded(t)))}getStreamInsert(t){return(t.id?this.streamInserts[t.id]:{})||{}}setStreamRef(t,e){u.putSticky(t,je,i=>i.setAttribute(je,e))}maybeReOrderStream(t,e){let{ref:i,streamAt:s,reset:n}=this.getStreamInsert(t);if(s!==void 0&&(this.setStreamRef(t,i),!(!n&&!e)&&t.parentElement)){if(s===0)t.parentElement.insertBefore(t,t.parentElement.firstElementChild);else if(s>0){let r=Array.from(t.parentElement.children),o=r.indexOf(t);if(s>=r.length-1)t.parentElement.appendChild(t);else{let a=r[s];o>s?t.parentElement.insertBefore(t,a):t.parentElement.insertBefore(t,a.nextElementSibling)}}this.maybeLimitStream(t)}}maybeLimitStream(t){let{limit:e}=this.getStreamInsert(t),i=e!==null&&Array.from(t.parentElement.children);e&&e<0&&i.length>e*-1?i.slice(0,i.length+e).forEach(s=>this.removeStreamChildElement(s)):e&&e>=0&&i.length>e&&i.slice(e).forEach(s=>this.removeStreamChildElement(s))}transitionPendingRemoves(){let{pendingRemoves:t,liveSocket:e}=this;t.length>0&&e.transitionRemoves(t,()=>{t.forEach(i=>{let s=u.firstPhxChild(i);s&&e.destroyViewByEl(s),i.remove()}),this.trackAfter("transitionsDiscarded",t)})}isChangedSelect(t,e){return!(t instanceof HTMLSelectElement)||t.multiple?!1:t.options.length!==e.options.length?!0:(e.value=t.value,!t.isEqualNode(e))}isCIDPatch(){return this.cidPatch}skipCIDSibling(t){return t.nodeType===Node.ELEMENT_NODE&&t.hasAttribute(pi)}targetCIDContainer(t){if(!this.isCIDPatch())return;let[e,...i]=u.findComponentNodeList(this.view.id,this.targetCID);return i.length===0&&u.childNodeLength(t)===1?e:e&&e.parentNode}indexOf(t,e){return Array.from(t.children).indexOf(e)}teleport(t,e){let i=t.getAttribute(Dt),s=document.querySelector(i);if(!s)throw new Error("portal target with selector "+i+" not found");let n=t.content.firstElementChild;if(this.skipCIDSibling(n))return;if(!n?.id)throw new Error("phx-portal template must have a single root element with ID!");let r=document.getElementById(n.id),o;r?(s.contains(r)||s.appendChild(r),o=r):(o=document.createElement(n.tagName),s.appendChild(o)),n.setAttribute(Ae,this.view.id),n.setAttribute(ve,t.id),e(o,n,!0),n.removeAttribute(Ae),n.removeAttribute(ve),this.view.pushPortalElementId(n.id)}handleRuntimeHook(t,e){let i=t.getAttribute(lt),s=t.hasAttribute("nonce")?t.getAttribute("nonce"):null;if(t.hasAttribute("nonce")){let r=document.createElement("template");r.innerHTML=e,s=r.content.querySelector(`script[${lt}="${CSS.escape(i)}"]`).getAttribute("nonce")}let n=document.createElement("script");n.textContent=t.textContent,u.mergeAttrs(n,t,{isIgnored:!1}),s&&(n.nonce=s),t.replaceWith(n),t=n}},As=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),Cs=new Set(["'",'"']),oi=(t,e,i)=>{let s=0,n=!1,r,o,a,h,c,f,g=t.match(/^(\s*(?:<!--.*?-->\s*)*)<([^\s\/>]+)/);if(g===null)throw new Error(`malformed html ${t}`);for(s=g[0].length,r=g[1],a=g[2],h=s,s;s<t.length&&t.charAt(s)!==">";s++)if(t.charAt(s)==="="){let p=t.slice(s-3,s)===" id";s++;let S=t.charAt(s);if(Cs.has(S)){let A=s;for(s++,s;s<t.length&&t.charAt(s)!==S;s++);if(p){c=t.slice(A+1,s);break}}}let b=t.length-1;for(n=!1;b>=r.length+a.length;){let p=t.charAt(b);if(n)p==="-"&&t.slice(b-3,b)==="<!-"?(n=!1,b-=4):b-=1;else if(p===">"&&t.slice(b-2,b)==="--")n=!0,b-=3;else{if(p===">")break;b-=1}}o=t.slice(b+1,t.length);let w=Object.keys(e).map(p=>e[p]===!0?p:`${p}="${e[p]}"`).join(" ");if(i){let p=c?` id="${c}"`:"";As.has(a)?f=`<${a}${p}${w===""?"":" "}${w}/>`:f=`<${a}${p}${w===""?"":" "}${w}></${a}>`}else{let p=t.slice(h,b+1);f=`<${a}${w===""?"":" "}${w}${p}`}return[f,r,o]},ai=class{static extract(t){let{[Gt]:e,[Kt]:i,[Yt]:s}=t;return delete t[Gt],delete t[Kt],delete t[Yt],{diff:t,title:s,reply:e||null,events:i||[]}}constructor(t,e){this.viewId=t,this.rendered={},this.magicId=0,this.mergeDiff(e)}parentViewId(){return this.viewId}toString(t){let{buffer:e,streams:i}=this.recursiveToString(this.rendered,this.rendered[K],t,!0,{});return{buffeLine truncated
An element can only ever be associated with a single view!
Please ensure that you are not trying to initialize multiple LiveSockets on the same page.
This could happen if you're accidentally trying to render your root layout more than once.
Ensure that the template set on the LiveView is different than the root layout.
`,{view:o}),new Error("Cannot bind multiple views to the same DOM element.");u.putPrivate(this.el,"view",this),this.id=this.el.id,this.el.setAttribute(fe,this.root.id),this.ref=0,this.lastAckRef=null,this.childJoins=0,this.loaderTimer=null,this.disconnectedTimer=null,this.pendingDiffs=[],this.pendingForms=new Set,this.redirect=!1,this.href=null,this.joinCount=this.parent?this.parent.joinCount-1:0,this.joinAttempts=0,this.joinPending=!0,this.destroyed=!1,this.joinCallback=function(a){a&&a()},this.stopCallback=function(){},this.pendingJoinOps=[],this.viewHooks={},this.formSubmits=[],this.children=this.parent?null:{},this.root.children[this.id]={},this.formsForRecovery={},this.channel=this.liveSocket.channel(`lv:${this.id}`,()=>{let a=this.href&&this.expandURL(this.href);return{redirect:this.redirect?a:void 0,url:this.redirect?void 0:a||void 0,params:this.connectParams(r),session:this.getSession(),static:this.getStatic(),flash:this.flash,sticky:this.el.hasAttribute(Tt)}}),this.portalElementIds=new Set}setHref(e){this.href=e}setRedirect(e){this.redirect=!0,this.href=e}isMain(){return this.el.hasAttribute(Lt)}connectParams(e){let i=this.liveSocket.params(this.el),s=u.all(document,`[${this.binding(Oi)}]`).map(n=>n.src||n.href).filter(n=>typeof n=="string");return s.length>0&&(i._track_static=s),i._mounts=this.joinCount,i._mount_attempts=this.joinAttempts,i._live_referer=e,this.joinAttempts++,i}isConnected(){return this.channel.canPush()}getSession(){return this.el.getAttribute(ce)}getStatic(){let e=this.el.getAttribute(Ee);return e===""?null:e}destroy(e=function(){}){this.destroyAllChildren(),this.destroyPortalElements(),this.destroyed=!0,u.deletePrivate(this.el,"view"),delete this.root.children[this.id],this.parent&&delete this.root.children[this.parent.id][this.id],clearTimeout(this.loaderTimer);let i=()=>{e();for(let s in this.viewHooks)this.destroyHook(this.viewHooks[s])};u.markPhxChildDestroyed(this.el),this.log("destroyed",()=>["the child has been removed from the parent"]),this.channel.leave().receive("ok",i).receive("error",i).receive("timeout",i)}setContainerClasses(...e){this.el.classList.remove(Ut,be,Re,jt,Fe),this.el.classList.add(...e)}showLoader(e){if(clearTimeout(this.loaderTimer),e)this.loaderTimer=setTimeout(()=>this.showLoader(),e);else{for(let i in this.viewHooks)this.viewHooks[i].__disconnected();this.setContainerClasses(be)}}execAll(e){u.all(this.el,`[${e}]`,i=>this.liveSocket.execJS(i,i.getAttribute(e)))}hideLoader(){clearTimeout(this.loaderTimer),clearTimeout(this.disconnectedTimer),this.setContainerClasses(Ut),this.execAll(this.binding("connected"))}triggerReconnected(){for(let e in this.viewHooks)this.viewHooks[e].__reconnected()}log(e,i){this.liveSocket.log(this,e,i)}transition(e,i,s=function(){}){this.liveSocket.transition(e,i,s)}withinTargets(e,i,s=document){if(e instanceof HTMLElement||e instanceof SVGElement)return this.liveSocket.owner(e,n=>i(n,e));if(ue(e))u.findComponentNodeList(this.id,e,s).length===0?O(`no component found matching phx-target of ${e}`):i(this,parseInt(e));else{let n=Array.from(s.querySelectorAll(e));n.length===0&&O(`nothing found matching the phx-target selector "${e}"`),n.forEach(r=>this.liveSocket.owner(r,o=>i(o,r)))}}applyDiff(e,i,s){this.log(e,()=>["",ct(i)]);let{diff:n,reply:r,events:o,title:a}=ai.extract(i),h=o.reduce((f,g)=>(g.length===3&&g[2]==!0?f.pre.push(g.slice(0,-1)):f.post.push(g),f),{pre:[],post:[]});this.liveSocket.dispatchEvents(h.pre);let c=()=>{s({diff:n,reply:r,events:h.post}),(typeof a=="string"||e=="mount"&&this.isMain())&&window.requestAnimationFrame(()=>u.putTitle(a))};"onDocumentPatch"in this.liveSocket.domCallbacks?this.liveSocket.triggerDOM("onDocumentPatch",[c]):c()}onJoin(e){let{rendered:i,container:s,liveview_version:n,pid:r}=e;if(s){let[o,a]=s;this.el=u.replaceRootContainer(this.el,o,a)}this.childJoins=0,this.joinPending=!0,this.flash=null,this.root===this&&(this.formsForRecovery=this.getFormsForRecovery()),this.isMain()&&window.history.state===null&&Z.pushState("replace",{type:"patch",id:this.id,position:this.liveSocket.currentHistoryPosition}),n!==this.liveSocket.version()&&console.warn(`LiveView asset version mismatch. JavaScript version ${this.liveSocket.version()} vs. server ${n}. To avoid issues, please ensure that your assets use the same version as the server.`),r&&this.el.setAttribute(Bi,r),Z.dropLocal(this.liveSocket.localStorage,window.location.pathname,di),this.applyDiff("mount",i,({diff:o,events:a})=>{this.rendered=new ai(this.id,o);let[h,c]=this.renderContainer(null,"join");this.dropPendingRefs(),this.joinCount++,this.joinAttempts=0,this.maybeRecoverForms(h,()=>{this.onJoinComplete(e,h,c,a)})})}dropPendingRefs(){u.all(document,`[${ie}="${this.refSrc()}"]`,e=>{e.removeAttribute(Me),e.removeAttribute(ie),e.removeAttribute(B)})}onJoinComplete({live_patch:e},i,s,n){if(this.joinCount>1||this.parent&&!this.parent.isJoinPending())return this.applyJoinPatch(e,i,s,n);u.findPhxChildrenInFragment(i,this.id).filter(o=>{let Line truncated
a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
let liveSocket = new LiveSocket("/live", Socket, {...})
`);this.socket=new e(t,i),this.bindingPrefix=i.bindingPrefix||zi,this.opts=i,this.params=qe(i.params||{}),this.viewLogger=i.viewLogger,this.metadataCallbacks=i.metadata||{},this.defaults=Object.assign(ct(Gi),i.defaults||{}),this.prevActive=null,this.silenced=!1,this.main=null,this.outgoingMainEl=null,this.clickStartedAtTarget=null,this.linkRef=1,this.roots={},this.href=window.location.href,this.pendingLink=null,this.currentLocation=ct(window.location),this.hooks=i.hooks||{},this.uploaders=i.uploaders||{},this.loaderTimeout=i.loaderTimeout||Wi,this.disconnectedTimeout=i.disconnectedTimeout||Xi,this.reloadWithJitterTimer=null,this.maxReloads=i.maxReloads||xi,this.reloadJitterMin=i.reloadJitterMin||Ii,this.reloadJitterMax=i.reloadJitterMax||Li,this.failsafeJitter=i.failsafeJitter||Di,this.localStorage=i.localStorage||window.localStorage,this.sessionStorage=i.sessionStorage||window.sessionStorage,this.boundTopLevelEvents=!1,this.boundEventNames=new Set,this.blockPhxChangeWhileComposing=i.blockPhxChangeWhileComposing||!1,this.serverCloseRef=null,this.domCallbacks=Object.assign({jsQuerySelectorAll:null,onPatchStart:qe(),onPatchEnd:qe(),onNodeAdded:qe(),onBeforeElUpdated:qe()},i.dom||{}),this.transitions=new Is,this.currentHistoryPosition=parseInt(this.sessionStorage.getItem(it))||0,window.addEventListener("pagehide",s=>{this.unloaded=!0}),this.socket.onOpen(()=>{this.isUnloaded()&&window.location.reload()})}version(){return"1.1.28"}isProfileEnabled(){return this.sessionStorage.getItem(bt)==="true"}isDebugEnabled(){return this.sessionStorage.getItem(tt)==="true"}isDebugDisabled(){return this.sessionStorage.getItem(tt)==="false"}enableDebug(){this.sessionStorage.setItem(tt,"true")}enableProfiling(){this.sessionStorage.setItem(bt,"true")}disableDebug(){this.sessionStorage.setItem(tt,"false")}disableProfiling(){this.sessionStorage.removeItem(bt)}enableLatencySim(t){this.enableDebug(),console.log("latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable"),this.sessionStorage.setItem(wt,t)}disableLatencySim(){this.sessionStorage.removeItem(wt)}getLatencySim(){let t=this.sessionStorage.getItem(wt);return t?parseInt(t):null}getSocket(){return this.socket}connect(){window.location.hostname==="localhost"&&!this.isDebugDisabled()&&this.enableDebug();let t=()=>{this.resetReloadStatus(),this.joinRootViews()?(this.bindTopLevelEvents(),this.socket.connect()):this.main?this.socket.connect():this.bindTopLevelEvents({dead:!0}),this.joinDeadView()};["complete","loaded","interactive"].indexOf(document.readyState)>=0?t():document.addEventListener("DOMContentLoaded",()=>t())}disconnect(t){clearTimeout(this.reloadWithJitterTimer),this.serverCloseRef&&(this.socket.off(this.serverCloseRef),this.serverCloseRef=null),this.socket.disconnect(t)}replaceTransport(t){clearTimeout(this.reloadWithJitterTimer),this.socket.replaceTransport(t),this.connect()}execJS(t,e,i=null){let s=new CustomEvent("phx:exec",{detail:{sourceElement:t}});this.owner(t,n=>x.exec(s,i,e,n,t))}js(){return wi(this,"js")}unload(){this.unloaded||(this.main&&this.isConnected()&&this.log(this.main,"socket",()=>["disconnect for page nav"]),this.unloaded=!0,this.destroyAllViews(),this.disconnect())}triggerDOM(t,e){this.domCallbacks[t](...e)}time(t,e){if(!this.isProfileEnabled()||!console.time)return e();console.time(t);let i=e();return console.timeEnd(t),i}log(t,e,i){if(this.viewLogger){let[s,n]=i();this.viewLogger(t,e,s,n)}else if(this.isDebugEnabled()){let[s,n]=i();es(t,e,s,n)}}requestDOMUpdate(t){this.transitions.after(t)}asyncTransition(t){this.transitions.addAsyncTransition(t)}transition(t,e,i=function(){}){this.transitions.addTransition(t,e,i)}onChannel(t,e,i){t.on(e,s=>{let n=this.getLatencySim();n?setTimeout(()=>i(s),n):i(s)})}reloadWithJitter(t,e){clearTimeout(this.reloadWithJitterTimer),this.disconnect();let i=this.reloadJitterMin,s=this.reloadJitterMax,n=Math.floor(Math.random()*(s-i+1))+i,r=Z.updateLocal(this.localStorage,window.location.pathname,di,0,o=>o+1);r>=this.maxReloads&&(n=this.failsafeJitter),this.reloadWithJitterTimer=setTimeout(()=>{t.isDestroyed()||t.isConnected()||(t.destroy(),e?e():this.log(t,"join",()=>[`encountered ${r} consecutive reloads`]),r>=this.maxReloads&&this.log(t,"join",()=>[`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`]),this.hasPendingLink()?window.location=this.pendingLink:window.location.reload())},n)}getHookDefinition(t){if(t)return this.maybeInternalHook(t)||this.hooks[t]||this.maybeRuntimeHook(t)}maybeInternalHook(t){return t&&t.startsWith("Phoenix.")&&ls[t.split(".")[1]]}maybeRuntimeHook(t){let e=document.querySelector(`script[${lt}="${CSS.escape(t)}"]`);if(!e)return;let i=window[`phx_hook_${t}`];if(!i||typeof i!="function"){O("a runtime hook must be a function",e);return}let s=i();if(s&&(typeof s=="object"||typeof s=="function"))return s;O("runtime hook must return an object with hook callbacks or an instance of ViewHook",e)}isUnloaded(){return this.u Line truncated
`)?`
`:"",ne=q!==""&&!d.endsWith(`
`)?`
`:"",He=`${se}${d}${ne}`;this.editor.executeEdits("bds-insert-content",[{range:k,text:He,forceMoveMarkers:!0}]),this.editor.focus()},C().then(async l=>{!this.host||!this.textarea||(await this.waitForMonacoVisibleSize(),E(l),this.editor=l.editor.create(this.host,{value:this.textarea.value||"",language:this.language,theme:"bds-theme",automaticLayout:!0,minimap:{enabled:!1},scrollBeyondLastLine:!1,wordWrap:this.wordWrap,lineNumbers:"on",lineNumbersMinChars:3,fontSize:14,fontFamily:"'Cascadia Code', 'Consolas', 'Courier New', monospace",padding:{top:12,bottom:12},roundedSelection:!1,renderLineHighlight:"line",formatOnPaste:!0,cursorStyle:"line",cursorBlinking:"smooth",quickSuggestions:this.language!=="markdown-with-macros",tabSize:2,insertSpaces:!0}),V.set(this.editorId||this.el.id,this.editor),l.editor.setTheme("bds-theme"),this.syncEditorFromTextarea(),this.layoutEditorSoon(),this.changeSubscription=this.editor.onDidChangeModelContent(()=>{this.isApplyingRemoteUpdate||this.queueSync()}),this.insertEvent&&this.handleEvent(this.insertEvent,this.handleInsert))}).catch(l=>{console.error("Failed to load Monaco editor",l)})},updated(){this.textarea=document.getElementById(this.el.dataset.monacoInputId)||this.el.querySelector("textarea"),this.host=this.el.querySelector(".monaco-editor-instance"),this.language=this.el.dataset.monacoLanguage||this.language||"plaintext",this.wordWrap=this.el.dataset.monacoWordWrap||this.wordWrap||"off",!(!this.editor||!this.textarea)&&(C().then(l=>{E(l),l.editor.setTheme("bds-theme"),this.editor.getModel()?.getLanguageId()!==this.language&&l.editor.setModelLanguage(this.editor.getModel(),this.language),this.editor.updateOptions({wordWrap:this.wordWrap})}),this.syncEditorFromTextarea(),this.layoutEditorSoon())},destroyed(){window.clearTimeout(this.syncTimer),this.visibleSizeObserver?.disconnect(),this.changeSubscription?.dispose(),V.delete(this.editorId||this.el.id),this.editor?.dispose()}},MonacoDiffEditor:{mounted(){this.host=this.el.querySelector(".monaco-diff-editor-instance"),this.originalInput=this.el.querySelector(".monaco-diff-original"),this.modifiedInput=this.el.querySelector(".monaco-diff-modified"),this.filePath=this.el.dataset.monacoDiffFilePath||"working-tree",this.language=this.el.dataset.monacoDiffLanguage||"plaintext",this.viewStyle=this.el.dataset.monacoDiffViewStyle||"inline",this.wordWrap=this.el.dataset.monacoDiffWordWrap||"off",this.hideUnchanged=this.el.dataset.monacoDiffHideUnchanged==="true",this.readValues=()=>({original:this.originalInput?.value||"",modified:this.modifiedInput?.value||""}),this.applyDataset=()=>{this.filePath=this.el.dataset.monacoDiffFilePath||"working-tree",this.language=this.el.dataset.monacoDiffLanguage||"plaintext",this.viewStyle=this.el.dataset.monacoDiffViewStyle||"inline",this.wordWrap=this.el.dataset.monacoDiffWordWrap||"off",this.hideUnchanged=this.el.dataset.monacoDiffHideUnchanged==="true"},this.setModels=l=>{let d=this.readValues();this.originalModel?.dispose(),this.modifiedModel?.dispose(),this.originalModel=l.editor.createModel(d.original,this.language,l.Uri.parse(Ce(this.filePath,"original"))),this.modifiedModel=l.editor.createModel(d.modified,this.language,l.Uri.parse(Ce(this.filePath,"modified"))),this.editor.setModel({original:this.originalModel,modified:this.modifiedModel}),this.lastFilePath=this.filePath},C().then(l=>{this.host&&(E(l),this.editor=l.editor.createDiffEditor(this.host,{theme:"bds-theme",automaticLayout:!0,readOnly:!0,renderSideBySide:this.viewStyle==="side-by-side",minimap:{enabled:!1},scrollBeyondLastLine:!1,lineNumbers:"on",diffCodeLens:!1,originalEditable:!1,wordWrap:this.wordWrap,hideUnchangedRegions:{enabled:this.hideUnchanged},ignoreTrimWhitespace:!1}),this.setModels(l))}).catch(l=>{console.error("Failed to load Monaco diff editor",l)})},updated(){this.host=this.el.querySelector(".monaco-diff-editor-instance"),this.originalInput=this.el.querySelector(".monaco-diff-original"),this.modifiedInput=this.el.querySelector(".monaco-diff-modified"),this.applyDataset(),this.editor&&C().then(l=>{if(E(l),l.editor.setTheme("bds-theme"),this.editor.updateOptions({renderSideBySide:this.viewStyle==="side-by-side",wordWrap:this.wordWrap,hideUnchangedRegions:{enabled:this.hideUnchanged}}),this.lastFilePath!==this.filePath){this.setModels(l);return}let d=this.readValues();this.originalModel&&this.originalModel.getLanguageId()!==this.language&&l.editor.setModelLanguage(this.originalModel,this.language),this.modifiedModel&&this.modifiedModel.getLanguageId()!==this.language&&l.editor.setModelLanguage(this.modifiedModel,this.language),this.originalModel&&this.originalModel.getValue()!==d.original&&this.originalModel.setValue(d.original),this.modifiedModel&&this.modifiedModel.getValue()!==d.modified&&this.modifiedModel.setValue(d.modified)})},destroyed(){this.originalModel?.dispose(),this.modifiedModel?.dispose(),this.editor?.dispose()}}},P=new Ei("/live",Nt,{params:{_csrf_token:t},hooks:H,metadata:{keydown:l=>({key:l.key,met Line truncated
-8527
View File
File diff suppressed because it is too large. Load diff
+27 -6
View File
@@ -6,6 +6,27 @@ defmodule BDS.Desktop.ShellLiveTest do
import Phoenix.LiveViewTest
@shell_live_source_root Path.expand("../../../lib/bds/desktop/shell_live", __DIR__)
@css_source_files [
"tokens.css",
"shell.css",
"sidebar.css",
"tabs.css",
"editor.css",
"forms.css",
"panel.css",
"assistant.css",
"overlays.css",
"menu_editor.css",
"media_editor.css",
"import_editor.css",
"utilities.css"
]
defp desktop_css_source do
@css_source_files
|> Enum.map(&File.read!(Path.expand("../../../assets/css/#{&1}", __DIR__)))
|> Enum.join("\n")
end
test "shell live modules use contexts instead of direct Repo.get calls" do
source_files =
@@ -3264,7 +3285,7 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(class="chat-model-selector-button chat-model-selector-inline")
refute html =~ ~s(class="chat-panel-header-actions")
css = File.read!(Path.expand("../../../priv/ui/app.css", __DIR__))
css = desktop_css_source()
assert css =~ ".chat-model-selector-wrap"
assert css =~ "left: 0;"
assert css =~ "right: auto;"
@@ -3313,7 +3334,7 @@ defmodule BDS.Desktop.ShellLiveTest do
assert selector_html =~ ~s(data-testid="chat-model-selector-option")
assert selector_html =~ "llama-current"
css = File.read!(Path.expand("../../../priv/ui/app.css", __DIR__))
css = desktop_css_source()
assert css =~ ".chat-panel-title {"
assert css =~ "overflow: visible;"
@@ -3628,7 +3649,7 @@ defmodule BDS.Desktop.ShellLiveTest do
end
test "chat editor hook reopens server-expanded A2UI surfaces after patches" do
live_js = File.read!(Path.expand("../../../priv/ui/live.js", __DIR__))
live_js = File.read!(Path.expand("../../../assets/js/app.js", __DIR__))
chat_editor =
File.read!(Path.expand("../../../lib/bds/desktop/shell_live/chat_editor.ex", __DIR__))
@@ -3738,7 +3759,7 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~
~s(<div class="chat-message-text chat-user-message-text" data-testid="chat-user-message-text">wie viele Posts sind im Blog?</div>)
css = File.read!(Path.expand("../../../priv/ui/app.css", __DIR__))
css = desktop_css_source()
assert css =~ ".chat-panel .chat-message.user .chat-message-content"
assert css =~ "background: transparent;"
assert css =~ "border: 0;"
@@ -3762,7 +3783,7 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(rows="1")
assert html =~ ~s(class="chat-input chat-surface-input")
css = File.read!(Path.expand("../../../priv/ui/app.css", __DIR__))
css = desktop_css_source()
assert css =~ "--chat-input-line-height: 20px;"
assert css =~ "--chat-input-min-height: 20px;"
assert css =~ ".chat-panel .chat-input-container"
@@ -3784,7 +3805,7 @@ defmodule BDS.Desktop.ShellLiveTest do
assert css =~ "max-height: 22px;"
assert css =~ "padding: 0;"
live_js = File.read!(Path.expand("../../../priv/ui/live.js", __DIR__))
live_js = File.read!(Path.expand("../../../assets/js/app.js", __DIR__))
assert live_js =~
"minHeight = parseFloat(styles.getPropertyValue(\"--chat-input-min-height\"))"
+21 -4
View File
@@ -171,7 +171,9 @@ defmodule BDS.DesktopTest do
test "desktop external links point at the bDS2 GitHub project and issue tracker" do
assert BDS.Desktop.ExternalLinks.github_url() == "https://github.com/rfc1437/bDS2"
assert BDS.Desktop.ExternalLinks.github_issues_url() == "https://github.com/rfc1437/bDS2/issues"
assert BDS.Desktop.ExternalLinks.github_issues_url() ==
"https://github.com/rfc1437/bDS2/issues"
end
test "icon menu quit requests app-owned shutdown" do
@@ -254,7 +256,7 @@ defmodule BDS.DesktopTest do
assert_receive :window_quit_requested
end
test "desktop root html is a LiveView shell and references only the live bootstrap assets" do
test "desktop root html is a LiveView shell and references the generated asset entrypoints" do
conn = conn(:get, "/?k=#{Desktop.Auth.login_key()}")
conn = BDS.Desktop.Endpoint.call(conn, BDS.Desktop.Endpoint.init([]))
@@ -270,9 +272,24 @@ defmodule BDS.DesktopTest do
assert conn.resp_body =~ ~s(class="sidebar")
assert conn.resp_body =~ ~s(class="status-bar")
assert conn.resp_body =~ ~s(data-phx-main)
assert conn.resp_body =~ ~s(src="/assets/live.js")
assert conn.resp_body =~ ~s(href="/assets/app.css")
refute conn.resp_body =~ ~s(src="/assets/app.js")
assert conn.resp_body =~ ~s(src="/assets/app.js")
refute conn.resp_body =~ ~s(src="/assets/live.js")
refute conn.resp_body =~ ~s(src="/vendor/phoenix/phoenix.min.js")
refute conn.resp_body =~ ~s(src="/vendor/live_view/phoenix_live_view.min.js")
end
test "desktop endpoint serves generated Phoenix-style CSS and JS assets" do
css_conn = conn(:get, "/assets/app.css?k=#{Desktop.Auth.login_key()}")
css_conn = BDS.Desktop.Endpoint.call(css_conn, BDS.Desktop.Endpoint.init([]))
js_conn = conn(:get, "/assets/app.js?k=#{Desktop.Auth.login_key()}")
js_conn = BDS.Desktop.Endpoint.call(js_conn, BDS.Desktop.Endpoint.init([]))
assert css_conn.status == 200
assert byte_size(css_conn.resp_body) > 0
assert js_conn.status == 200
assert byte_size(js_conn.resp_body) > 0
end
test "desktop endpoint serves the live shell without extra router-side secret injection" do
+119 -22
View File
@@ -6,6 +6,28 @@ defmodule BDS.UI.ShellTest do
alias BDS.UI.Session
alias BDS.UI.Workbench
@css_sources [
"tokens.css",
"shell.css",
"sidebar.css",
"tabs.css",
"editor.css",
"forms.css",
"panel.css",
"assistant.css",
"overlays.css",
"menu_editor.css",
"media_editor.css",
"import_editor.css",
"utilities.css"
]
defp css_source do
@css_sources
|> Enum.map(&File.read!("/Users/gb/Projects/bDS2/assets/css/#{&1}"))
|> Enum.join("\n")
end
test "registry exposes the shared sidebar and editor contracts for the base shell" do
sidebar_views = Registry.sidebar_views()
editor_routes = Registry.editor_routes()
@@ -101,25 +123,100 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell keeps the compact frame metrics and live bootstrap assets" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
live_js = File.read!("/Users/gb/Projects/bDS2/priv/ui/live.js")
css = css_source()
live_js = File.read!("/Users/gb/Projects/bDS2/assets/js/app.js")
template = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/index.html.heex")
assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/app.css")
assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/live.js")
assert File.exists?("/Users/gb/Projects/bDS2/assets/css/shell.css")
assert File.exists?("/Users/gb/Projects/bDS2/assets/js/app.js")
assert css =~ ".window-titlebar"
assert css =~ "height: 34px"
assert css =~ "width: 48px"
assert css =~ "height: 35px"
assert css =~ "height: 22px"
assert live_js =~ "LiveView.LiveSocket"
assert live_js =~ "Phoenix.Socket"
assert live_js =~ "new LiveSocket"
assert live_js =~ "Socket"
assert template =~ "data-project-id={@projects.active_project_id || \"\"}"
assert template =~ "data-workbench-session={encoded_workbench_session(@workbench)}"
end
test "desktop shell css keeps editor and help docs on the VS Code dark surface" do
css = css_source()
assert css =~ ".post-editor .post-editor-markdown-surface"
assert css =~ ".scripts-monaco.monaco-editor-shell"
assert css =~ ".templates-monaco.monaco-editor-shell"
assert css =~ ".help-doc-markdown"
assert css =~ "background: var(--vscode-editor-background);"
assert css =~ "color: var(--vscode-editor-foreground);"
refute Regex.match?(~r/\.sidebar-item\s*\{[^}]*background:\s*var\(--panel-2\)/s, css)
refute Regex.match?(~r/\.sidebar-item\s*\{[^}]*color:\s*var\(--ink\)/s, css)
end
test "desktop help documentation keeps the old markdown viewer styling contract" do
css = css_source()
assert css =~ ".help-doc-view"
assert css =~ ".help-doc-view .misc-editor-content"
assert css =~ ".documentation-article"
assert css =~ ".documentation-content.markdown-body h1"
assert css =~ ".documentation-content.markdown-body table"
assert css =~ ".documentation-content.markdown-body ul"
assert css =~ "background: var(--doc-surface);"
assert css =~ "box-shadow: 0 10px 24px rgba(0, 0, 0, 0.18);"
end
test "desktop settings editor keeps the old preferences styling contract" do
css = css_source()
assert css =~ ".settings-view"
assert css =~ ".settings-header"
assert css =~ ".settings-content"
assert css =~ ".setting-section"
assert css =~ ".setting-section-header"
assert css =~ ".setting-section-content"
assert css =~ ".setting-row"
assert css =~ ".setting-control"
assert css =~ "grid-template-columns: minmax(180px, 240px) minmax(0, 1fr);"
assert css =~ "background: var(--panel-2, #252526);"
assert css =~ "border: 1px solid var(--line, #3c3c3c);"
end
test "monaco editor styling forces the internal editor surface to the dark theme" do
css = css_source()
live_js = File.read!("/Users/gb/Projects/bDS2/assets/js/app.js")
assert css =~ ".monaco-editor .margin"
assert css =~ ".monaco-editor-background"
assert css =~ "background-color: var(--vscode-editor-background) !important;"
assert css =~ ".monaco-editor .view-line"
assert live_js =~ "base: \"vs-dark\""
assert live_js =~ "monaco.editor.setTheme(\"bds-theme\");"
end
test "monaco editor hook forces first visible layout and textarea content sync" do
live_js = File.read!("/Users/gb/Projects/bDS2/assets/js/app.js")
assert live_js =~ "this.syncEditorFromTextarea"
assert live_js =~ "this.layoutEditorSoon"
assert live_js =~ "this.waitForMonacoVisibleSize"
assert live_js =~ "ResizeObserver"
assert live_js =~ "requestAnimationFrame"
assert live_js =~ "this.editor.layout()"
assert live_js =~ "this.syncEditorFromTextarea()"
end
test "monaco theme uses normalized app colors before defining the dark theme" do
live_js = File.read!("/Users/gb/Projects/bDS2/assets/js/app.js")
assert live_js =~ "normalizeMonacoColor"
assert live_js =~ "base: \"vs-dark\""
assert live_js =~ "\"editor.background\": background"
assert live_js =~ "monaco.editor.defineTheme(\"bds-theme\""
end
test "desktop shell assets persist workbench layout per project" do
live_js = File.read!("/Users/gb/Projects/bDS2/priv/ui/live.js")
live_js = File.read!("/Users/gb/Projects/bDS2/assets/js/app.js")
live_ex = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live.ex")
session_util_ex =
@@ -134,8 +231,8 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell assets reveal loaded media sidebar thumbnails" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
live_js = File.read!("/Users/gb/Projects/bDS2/priv/ui/live.js")
css = css_source()
live_js = File.read!("/Users/gb/Projects/bDS2/assets/js/app.js")
assert css =~ ".media-thumbnail.is-loaded .media-thumbnail-image"
assert css =~ ".media-thumbnail.is-loaded .media-thumbnail-fallback"
@@ -145,7 +242,7 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell css keeps the status bar and hidden menu alignment rules" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
css = css_source()
assert css =~ ".window-titlebar-menu-bar.is-hidden"
assert css =~ "--vscode-statusBar-background: #007acc"
@@ -162,8 +259,8 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell assets keep old activity, tab, focus, and titlebar overlay parity rules" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
live_js = File.read!("/Users/gb/Projects/bDS2/priv/ui/live.js")
css = css_source()
live_js = File.read!("/Users/gb/Projects/bDS2/assets/js/app.js")
template = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/index.html.heex")
assert css =~ "color: var(--vscode-activityBar-foreground)"
@@ -194,14 +291,14 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell keeps sidebar delete buttons visible in the default state" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
css = css_source()
assert Regex.match?(~r/\.sidebar-delete-button\s*\{[^}]*opacity:\s*1;/s, css)
refute Regex.match?(~r/\.sidebar-delete-button\s*\{[^}]*opacity:\s*0;/s, css)
end
test "desktop shell css keeps the old activity bar active marker contrast" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
css = css_source()
assert css =~ "--vscode-activityBar-foreground: #ffffff"
assert css =~ ".activity-bar-item:hover {"
@@ -211,8 +308,8 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell assets keep legacy titlebar menu keyboard and anchoring behavior" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
live_js = File.read!("/Users/gb/Projects/bDS2/priv/ui/live.js")
css = css_source()
live_js = File.read!("/Users/gb/Projects/bDS2/assets/js/app.js")
live_ex = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live.ex")
template = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/index.html.heex")
@@ -232,7 +329,7 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell css keeps the old media editor layout contract" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
css = css_source()
template =
File.read!(
@@ -288,7 +385,7 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell css keeps old panel and output density" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
css = css_source()
assert css =~ ".panel-content {"
assert css =~ "padding: 8px;"
@@ -302,7 +399,7 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell css keeps legacy sidebar header and post list layout" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
css = css_source()
assert css =~ ".sidebar-section {"
assert css =~ "margin-bottom: 4px;"
@@ -317,7 +414,7 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell assets keep the assistant sidebar chat surface contract" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
css = css_source()
template = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/index.html.heex")
assert css =~ ".assistant-sidebar-context"
@@ -332,7 +429,7 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell assets expose the shared overlay render contract" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
css = css_source()
live_ex = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live.ex")
template = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/index.html.heex")
@@ -439,7 +536,7 @@ defmodule BDS.UI.ShellTest do
end
test "desktop shell css keeps the old assistant sidebar panel styling" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
css = css_source()
assert css =~ ".assistant-content {"
assert css =~ "padding: 12px;"