Compare commits
9
Commits
sw-fix
..
4bea48959e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bea48959e | ||
|
|
34a3352e21 | ||
|
|
3cc5f0cc6a | ||
|
|
fd93339ad4 | ||
|
|
b1ecb0c46b | ||
|
|
4656f08802 | ||
|
|
a631e90ea2 | ||
|
|
10270b75ca | ||
|
|
7925866868 |
+30
-28
@@ -30,8 +30,13 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: npm
|
|
||||||
|
|
||||||
|
# No npm / node_modules cache: the act_runner's internal cache server is
|
||||||
|
# unreachable from job containers (`getCacheEntry failed: connect ETIMEDOUT
|
||||||
|
# 172.17.0.2`), so every cache restore hangs ~5 min and then fails — pure
|
||||||
|
# cost, zero benefit. `cache: npm` was removed from Setup Node above for the
|
||||||
|
# same reason. Re-enable both (setup-node `cache: npm` + an actions/cache
|
||||||
|
# node_modules step) once the runner's cache server is reachable from jobs.
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
# Harden against transient registry network failures (ECONNRESET etc.):
|
# Harden against transient registry network failures (ECONNRESET etc.):
|
||||||
# raise npm's built-in fetch retries/timeouts and retry `npm ci` up to
|
# raise npm's built-in fetch retries/timeouts and retry `npm ci` up to
|
||||||
@@ -52,39 +57,36 @@ jobs:
|
|||||||
sleep $((attempt * 15))
|
sleep $((attempt * 15))
|
||||||
done
|
done
|
||||||
|
|
||||||
# ── Critical gate — if this fails, nothing deploys ──────────────────
|
# ── Quality gates run BEFORE the slow build so a format/lint/type/test
|
||||||
|
# error fails in seconds instead of after the ~minutes-long build. All are
|
||||||
|
# hard gates — any failure fails the job and blocks the deploy. The tree is
|
||||||
|
# held clean (prettier formatted, eslint 0 errors, typecheck 0), so these
|
||||||
|
# gate real regressions. NOTE: the lotus-build.sh upstream-merge path can
|
||||||
|
# deploy without CI; a later normal push surfaces any introduced issue here
|
||||||
|
# — fix forward (or briefly re-soften a gate) rather than deploy broken.
|
||||||
|
# eslint gates on errors only (existing no-explicit-any warnings stay
|
||||||
|
# informational — check:eslint has no --max-warnings).
|
||||||
|
- name: Prettier
|
||||||
|
run: npm run check:prettier
|
||||||
|
|
||||||
|
- name: ESLint
|
||||||
|
run: npm run check:eslint
|
||||||
|
|
||||||
|
- name: TypeScript
|
||||||
|
run: npm run typecheck
|
||||||
|
|
||||||
|
# Deterministic pure-logic tests on Node's built-in runner via tsx (no
|
||||||
|
# vitest — Vite 8 is ahead of vitest's range). A failure blocks the deploy.
|
||||||
|
- name: Unit tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
# ── Critical gate — if this fails, nothing deploys. Produces dist/. ──
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||||
VITE_APP_VERSION: ${{ github.sha }}
|
VITE_APP_VERSION: ${{ github.sha }}
|
||||||
|
|
||||||
# Unit tests are a hard gate too — deterministic pure-logic tests on Node's
|
|
||||||
# built-in runner via tsx (no vitest — Vite 8 is ahead of vitest's range).
|
|
||||||
# A failure blocks the deploy.
|
|
||||||
- name: Unit tests
|
|
||||||
run: npm test
|
|
||||||
|
|
||||||
# ── Quality gates (hard — a failure fails the job and blocks deploy) ──
|
|
||||||
# The tree is held clean (typecheck 0, eslint 0 errors, prettier
|
|
||||||
# formatted), so these gate real regressions instead of relying on local
|
|
||||||
# runs. NOTE: an upstream-stable merge (the lotus-build.sh path) could
|
|
||||||
# introduce upstream type/lint/format issues; that path deploys without
|
|
||||||
# CI, but a subsequent normal push would surface the failure here — fix
|
|
||||||
# forward (or briefly re-soften a gate) rather than let it deploy broken.
|
|
||||||
# eslint gates on errors only (existing `no-explicit-any` warnings stay
|
|
||||||
# informational — `check:eslint` has no --max-warnings).
|
|
||||||
- name: TypeScript
|
|
||||||
run: npm run typecheck
|
|
||||||
|
|
||||||
- name: ESLint
|
|
||||||
run: npm run check:eslint
|
|
||||||
|
|
||||||
- name: Prettier Check and Fix
|
|
||||||
run: |
|
|
||||||
npx prettier --write .
|
|
||||||
npm run check:prettier
|
|
||||||
|
|
||||||
# ── Security (informational — findings shouldn't block a deploy) ─────
|
# ── Security (informational — findings shouldn't block a deploy) ─────
|
||||||
- name: Audit (high/critical)
|
- name: Audit (high/critical)
|
||||||
run: npm audit --audit-level=high --omit=dev
|
run: npm audit --audit-level=high --omit=dev
|
||||||
|
|||||||
+1
-3
@@ -1,3 +1 @@
|
|||||||
# These are commented until we enable lint and typecheck
|
npx lint-staged
|
||||||
# npx tsc -p tsconfig.json --noEmit
|
|
||||||
# npx lint-staged
|
|
||||||
|
|||||||
+13
-13
@@ -175,19 +175,19 @@ Decorative CSS-only overlays that activate automatically on holidays and events.
|
|||||||
|
|
||||||
### Themes
|
### Themes
|
||||||
|
|
||||||
| Theme | Window | Effect |
|
| Theme | Window | Effect |
|
||||||
| -------------------- | ------------- | -------------------------------------------------------------------------------------------------- |
|
| -------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| 🎆 New Year | Dec 31–Jan 2 | Radial firework bursts in gold, red, cyan, purple; gold shimmer sweep |
|
| 🎆 New Year | Dec 31–Jan 2 | Radial firework bursts in gold, red, cyan, purple; gold shimmer sweep |
|
||||||
| 🏮 Lunar New Year | Jan 22–Feb 5 | Floating paper lanterns bobbing; silk texture; gold shimmer accent |
|
| 🏮 Lunar New Year | Jan 22–Feb 5 | Floating paper lanterns bobbing; silk texture; gold shimmer accent |
|
||||||
| 💖 Valentine's Day | Feb 10–15 | ♥ hearts floating upward; soft pink ambient glow |
|
| 💖 Valentine's Day | Feb 10–15 | ♥ hearts floating upward; soft pink ambient glow |
|
||||||
| 🍀 St. Patrick's Day | Mar 15–18 | ☘ clovers drifting down; gold metallic shimmer top border |
|
| 🍀 St. Patrick's Day | Mar 15–18 | ☘ clovers drifting down; gold metallic shimmer top border |
|
||||||
| 🃏 April Fool's | Apr 1 | Glitch overlay: RGB channel separation, hue-rotate spikes, scanline sweep, "SIGNAL LOST" watermark |
|
| 🃏 April Fool's | Apr 1 | Glitch overlay: RGB channel separation, hue-rotate spikes, scanline sweep, "SIGNAL LOST" watermark |
|
||||||
| 🌱 Earth Day | Apr 20–23 | 🌿🍃 leaf emoji drift; sage green ambient tint; vine accent on left edge |
|
| 🌱 Earth Day | Apr 20–23 | 🌿🍃 leaf emoji drift; sage green ambient tint; vine accent on left edge |
|
||||||
| 🍂 Autumn | Sep 21–Oct 31 | Warm orange/amber leaf shapes rotating and falling |
|
| 🍂 Autumn | Sep 21–Oct 31 | Warm orange/amber leaf shapes rotating and falling |
|
||||||
| 👾 Arcade Day | Sep 12 | CRT scanlines; blinking pixel corner decorations; "INSERT COIN" prompt |
|
| 👾 Arcade Day | Sep 12 | Synthwave CRT: neon perspective grid framing the timeline (faded through the chat column), broken horizon line, rolling scanlines, pixel sparkles, bottom-right "1UP / INSERT COIN" HUD |
|
||||||
| 🚀 Deep Space Week | Oct 4–10 | Warp-speed star streaks radiating from screen centre; nebula purple/blue ambient |
|
| 🚀 Deep Space Week | Oct 4–10 | Violet void with drifting magenta/cyan nebula clouds, two-depth parallax starfield (~60 twinkling stars + 6 hero gleams), slow galaxy spiral, occasional comet streaks |
|
||||||
| 🎃 Halloween | Oct 15–Nov 1 | Purple and orange glowing particles; SVG spider web in top-left corner; dark purple tint |
|
| 🎃 Halloween | Oct 15–Nov 1 | Purple and orange glowing particles; SVG spider web in top-left corner; dark purple tint |
|
||||||
| ❄️ Christmas | Dec 10–Jan 2 | White dot snowfall in multiple layers at varied speeds |
|
| ❄️ Christmas | Dec 10–Jan 2 | White dot snowfall in multiple layers at varied speeds |
|
||||||
|
|
||||||
### Implementation
|
### Implementation
|
||||||
|
|
||||||
|
|||||||
@@ -13,38 +13,7 @@ The source code is licensed under [AGPLv3](LICENSE), the same license as the ups
|
|||||||
The Lotus Chat logo (`public/res/Lotus.png`) is a derivative work based on the original Cinny logo by Ajay Bura and contributors, used under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The modified logo is © Lotus Guild and is also made available under CC BY 4.0.
|
The Lotus Chat logo (`public/res/Lotus.png`) is a derivative work based on the original Cinny logo by Ajay Bura and contributors, used under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The modified logo is © Lotus Guild and is also made available under CC BY 4.0.
|
||||||
|
|
||||||
---
|
---
|
||||||
## Development Environment Setup
|
|
||||||
#### Getting correct Node version
|
|
||||||
- Ensure you have the correct version of node installed, specified in `.node-version`
|
|
||||||
- Use this command from the terminal to install nvm
|
|
||||||
```bash
|
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
|
|
||||||
```
|
|
||||||
- Reload your terminal shell using (for Ubuntu):
|
|
||||||
```bash
|
|
||||||
source ~/.bashrc
|
|
||||||
```
|
|
||||||
- Install the specified Node version
|
|
||||||
```bash
|
|
||||||
NODE_VERSION="$(tr -d '[:space:]' < .node-version)"
|
|
||||||
nvm install "$NODE_VERSION"
|
|
||||||
nvm use "$NODE_VERSION"
|
|
||||||
```
|
|
||||||
- verify the correct version was installed by running
|
|
||||||
```bash
|
|
||||||
node --version
|
|
||||||
```
|
|
||||||
and comparing the output to what is listed in `.node-version`
|
|
||||||
#### Install npm packages
|
|
||||||
- To install the npm packages listed in `package.json` run:
|
|
||||||
```bash
|
|
||||||
npm i
|
|
||||||
```
|
|
||||||
### Start Development Server
|
|
||||||
```bash
|
|
||||||
npm run start
|
|
||||||
```
|
|
||||||
You should now have an active development server at `localhost:8080`, where you can make changes to the code and see the UI update in real time
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Messaging
|
### Messaging
|
||||||
@@ -160,7 +129,16 @@ Lotus Chat has a desktop app for Windows, macOS, and Linux. It wraps the same we
|
|||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
Download the latest release from the [Releases page on code.lotusguild.org](https://code.lotusguild.org).
|
Operating System | Download
|
||||||
|
---|---
|
||||||
|
Windows | [Get the installer (.exe)](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64-setup.exe)
|
||||||
|
Linux (AppImage, any distro) | [Get the AppImage](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64.AppImage)
|
||||||
|
Linux (Debian/Ubuntu) | [Get the .deb](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64.deb)
|
||||||
|
Linux (Arch/CachyOS/EndeavourOS) | [Get the .pkg.tar.zst](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64.pkg.tar.zst) — install with `pacman -U LotusChat-x86_64.pkg.tar.zst`
|
||||||
|
|
||||||
|
All Linux builds need `webkit2gtk-4.1` and, for calls to work, GStreamer's `good`/`bad`/`ugly`/`libav` plugin sets (the pacman package pulls these in automatically; on the AppImage/.deb, install them via your package manager if joining a call shows "browser does not support WebRTC").
|
||||||
|
|
||||||
|
See the full [Releases page on code.lotusguild.org](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases) for signatures and older builds.
|
||||||
|
|
||||||
### SmartScreen Warning (Windows)
|
### SmartScreen Warning (Windows)
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -4,9 +4,9 @@
|
|||||||
"allowCustomHomeservers": true,
|
"allowCustomHomeservers": true,
|
||||||
"featuredCommunities": {
|
"featuredCommunities": {
|
||||||
"openAsDefault": false,
|
"openAsDefault": false,
|
||||||
"spaces": [],
|
"spaces": ["!-1ZBnAH-JiCOV8MGSKN77zDGTuI3pgSdy8Unu_DrDyc", "#homelab:codestorm.net"],
|
||||||
"rooms": [],
|
"rooms": ["#jellyfin:matrix.org"],
|
||||||
"servers": []
|
"servers": ["matrixrooms.info"]
|
||||||
},
|
},
|
||||||
"hashRouter": {
|
"hashRouter": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
|||||||
+1
-1
@@ -109,7 +109,7 @@ export default [
|
|||||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
|
||||||
],
|
],
|
||||||
'@typescript-eslint/no-shadow': 'error',
|
'@typescript-eslint/no-shadow': 'error',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
|
||||||
// jsx-a11y — media captions not required for this app
|
// jsx-a11y — media captions not required for this app
|
||||||
'jsx-a11y/media-has-caption': 'off',
|
'jsx-a11y/media-has-caption': 'off',
|
||||||
|
|||||||
Generated
+365
-453
File diff suppressed because it is too large
Load Diff
+9
-7
@@ -12,12 +12,13 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "npm run check:eslint && npm run check:prettier",
|
"lint": "npm run check:eslint && npm run check:prettier",
|
||||||
"check:eslint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
|
"check:eslint": "eslint src/*",
|
||||||
"check:prettier": "prettier --check .",
|
"check:prettier": "prettier --check .",
|
||||||
"fix:prettier": "prettier --write .",
|
"fix:prettier": "prettier --write .",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "node --import tsx --test $(find src -name '*.test.ts')",
|
"test": "node --import tsx --test $(find src -name '*.test.ts')",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
|
"commit": "git-cz",
|
||||||
"postinstall": "node scripts/patch-folds.mjs",
|
"postinstall": "node scripts/patch-folds.mjs",
|
||||||
"sync:decorations": "node scripts/syncDecorations.mjs"
|
"sync:decorations": "node scripts/syncDecorations.mjs"
|
||||||
},
|
},
|
||||||
@@ -25,6 +26,11 @@
|
|||||||
"*.{ts,tsx,js,jsx}": "eslint",
|
"*.{ts,tsx,js,jsx}": "eslint",
|
||||||
"*": "prettier --ignore-unknown --write"
|
"*": "prettier --ignore-unknown --write"
|
||||||
},
|
},
|
||||||
|
"config": {
|
||||||
|
"commitizen": {
|
||||||
|
"path": "./node_modules/cz-conventional-changelog"
|
||||||
|
}
|
||||||
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Ajay Bura",
|
"author": "Ajay Bura",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
@@ -37,7 +43,7 @@
|
|||||||
"@fontsource-variable/inter": "5.2.8",
|
"@fontsource-variable/inter": "5.2.8",
|
||||||
"@giphy/js-fetch-api": "5.8.0",
|
"@giphy/js-fetch-api": "5.8.0",
|
||||||
"@giphy/js-types": "5.1.0",
|
"@giphy/js-types": "5.1.0",
|
||||||
"@giphy/js-util": "2.0.0",
|
"@giphy/js-util": "5.2.0",
|
||||||
"@giphy/react-components": "10.1.2",
|
"@giphy/react-components": "10.1.2",
|
||||||
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
||||||
"@tanstack/react-query": "5.100.13",
|
"@tanstack/react-query": "5.100.13",
|
||||||
@@ -89,7 +95,7 @@
|
|||||||
"react-i18next": "17.0.8",
|
"react-i18next": "17.0.8",
|
||||||
"react-range": "1.10.0",
|
"react-range": "1.10.0",
|
||||||
"react-router-dom": "7.15.1",
|
"react-router-dom": "7.15.1",
|
||||||
"sanitize-html": "2.17.6",
|
"sanitize-html": "2.17.4",
|
||||||
"slate": "0.124.1",
|
"slate": "0.124.1",
|
||||||
"slate-dom": "0.124.1",
|
"slate-dom": "0.124.1",
|
||||||
"slate-history": "0.113.1",
|
"slate-history": "0.113.1",
|
||||||
@@ -124,7 +130,6 @@
|
|||||||
"cz-conventional-changelog": "3.3.0",
|
"cz-conventional-changelog": "3.3.0",
|
||||||
"eslint": "9.39.4",
|
"eslint": "9.39.4",
|
||||||
"eslint-config-airbnb": "19.0.4",
|
"eslint-config-airbnb": "19.0.4",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||||
@@ -144,8 +149,5 @@
|
|||||||
"dompurify": ">=3.3.4"
|
"dompurify": ">=3.3.4"
|
||||||
},
|
},
|
||||||
"js-cookie": ">=3.0.6"
|
"js-cookie": ">=3.0.6"
|
||||||
},
|
|
||||||
"allowScripts": {
|
|
||||||
"esbuild@0.28.1": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export default function KaTeX({ latex, displayMode = false }: KaTeXProps) {
|
|||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
// KaTeX output is generated by our own render call (trusted-safe).
|
// KaTeX output is generated by our own render call (trusted-safe).
|
||||||
|
// eslint-disable-next-line react/no-danger
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React, { KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Box, Chip, color, config, Icon, Icons, Text, toRem } from 'folds';
|
import { Box, Chip, color, config, Icon, Icons, Text, toRem } from 'folds';
|
||||||
import { RelationsEvent } from 'matrix-js-sdk/lib/models/relations';
|
import { RelationsEvent } from 'matrix-js-sdk/lib/models/relations';
|
||||||
|
|||||||
@@ -84,7 +84,15 @@ export function SeasonalPreview({ theme }: { theme: SeasonTheme }) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={{ position: 'absolute', inset: 0, overflow: 'hidden', pointerEvents: 'none' }}
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
// Size container so overlays can scale/hide fixed-size details (e.g.
|
||||||
|
// Arcade's HUD text) with `cqw` instead of rendering clipped in a swatch.
|
||||||
|
containerType: 'inline-size',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{buildOverlayContent(theme, true)}
|
{buildOverlayContent(theme, true)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,12 +102,12 @@ export const animSparkleTwinkle = keyframes({
|
|||||||
* Opacity + a hair of scale for a CRT bloom feel.
|
* Opacity + a hair of scale for a CRT bloom feel.
|
||||||
*/
|
*/
|
||||||
export const animCoinBlink = keyframes({
|
export const animCoinBlink = keyframes({
|
||||||
'0%': { opacity: '0.85', transform: 'translateX(-50%) scale(1)' },
|
'0%': { opacity: '0.85', transform: 'scale(1)' },
|
||||||
'6%': { opacity: '1', transform: 'translateX(-50%) scale(1.015)' },
|
'6%': { opacity: '1', transform: 'scale(1.015)' },
|
||||||
'12%': { opacity: '0.85', transform: 'translateX(-50%) scale(1)' },
|
'12%': { opacity: '0.85', transform: 'scale(1)' },
|
||||||
'49%': { opacity: '0.85', transform: 'translateX(-50%) scale(1)' },
|
'49%': { opacity: '0.85', transform: 'scale(1)' },
|
||||||
'50%': { opacity: '0', transform: 'translateX(-50%) scale(1)' },
|
'50%': { opacity: '0', transform: 'scale(1)' },
|
||||||
'100%': { opacity: '0', transform: 'translateX(-50%) scale(1)' },
|
'100%': { opacity: '0', transform: 'scale(1)' },
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ const NEON_CYAN = 'oklch(0.80 0.15 200)';
|
|||||||
const GRID_PURPLE = 'oklch(0.45 0.18 300)';
|
const GRID_PURPLE = 'oklch(0.45 0.18 300)';
|
||||||
|
|
||||||
// The receding grid as an inline SVG data-URI (CSP-safe, no external assets).
|
// The receding grid as an inline SVG data-URI (CSP-safe, no external assets).
|
||||||
|
// Strokes use vector-effect=non-scaling-stroke so a line is ~1px whether the
|
||||||
|
// tile is stretched across a 2000px plane (preserveAspectRatio=none would
|
||||||
|
// otherwise fatten the verticals ~4x) or squeezed into the 76px settings
|
||||||
|
// swatch (where scaled strokes disappeared entirely).
|
||||||
// It is a 1x2 vertical tile of horizontal rule lines + a single set of vertical
|
// It is a 1x2 vertical tile of horizontal rule lines + a single set of vertical
|
||||||
// lines fanning toward a top-center vanishing point. The plane is then placed
|
// lines fanning toward a top-center vanishing point. The plane is then placed
|
||||||
// under a CSS `perspective` rotateX so the lines genuinely recede. Scrolling the
|
// under a CSS `perspective` rotateX so the lines genuinely recede. Scrolling the
|
||||||
@@ -58,7 +62,7 @@ function gridDataUri(): string {
|
|||||||
rows.forEach((y) => {
|
rows.forEach((y) => {
|
||||||
lines.push(
|
lines.push(
|
||||||
`<line x1='0' y1='${y}' x2='600' y2='${y}' stroke='${GRID_PURPLE}' ` +
|
`<line x1='0' y1='${y}' x2='600' y2='${y}' stroke='${GRID_PURPLE}' ` +
|
||||||
`stroke-width='1.4' stroke-opacity='0.9'/>`,
|
`stroke-width='1.2' stroke-opacity='0.9' vector-effect='non-scaling-stroke'/>`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// Vertical lines fanning out from the top-center vanishing point.
|
// Vertical lines fanning out from the top-center vanishing point.
|
||||||
@@ -67,7 +71,7 @@ function gridDataUri(): string {
|
|||||||
const botX = 300 + i * 95; // wide at the foreground
|
const botX = 300 + i * 95; // wide at the foreground
|
||||||
lines.push(
|
lines.push(
|
||||||
`<line x1='${topX}' y1='0' x2='${botX}' y2='600' stroke='${GRID_PURPLE}' ` +
|
`<line x1='${topX}' y1='0' x2='${botX}' y2='600' stroke='${GRID_PURPLE}' ` +
|
||||||
`stroke-width='1.4' stroke-opacity='0.8'/>`,
|
`stroke-width='1.2' stroke-opacity='0.8' vector-effect='non-scaling-stroke'/>`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const svg =
|
const svg =
|
||||||
@@ -105,6 +109,13 @@ const RESTING_SPARKLES: ReadonlyArray<{
|
|||||||
|
|
||||||
const GRID_URI = gridDataUri();
|
const GRID_URI = gridDataUri();
|
||||||
|
|
||||||
|
// HUD text size: 11px on any real viewport, 0px (invisible) inside anything
|
||||||
|
// narrower than ~330px. `cqw` resolves against the nearest size container —
|
||||||
|
// the settings swatch (`SeasonalPreview` sets container-type) — and falls back
|
||||||
|
// to the viewport width when there is no container, i.e. the full-screen
|
||||||
|
// overlay. clamp(0, 100cqw - 320px, 11px) → 76px swatch: 0px; 1440px app: 11px.
|
||||||
|
const HUD_FONT_SIZE = 'clamp(0px, calc(100cqw - 320px), 11px)';
|
||||||
|
|
||||||
export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) {
|
export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) {
|
||||||
// Deterministic sparkle field, computed ONCE. No per-frame state.
|
// Deterministic sparkle field, computed ONCE. No per-frame state.
|
||||||
const sparkles = useMemo<Sparkle[]>(() => {
|
const sparkles = useMemo<Sparkle[]>(() => {
|
||||||
@@ -134,9 +145,9 @@ export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
backgroundImage: [
|
backgroundImage: [
|
||||||
'radial-gradient(140% 80% at 50% -8%, oklch(0.65 0.25 350 / 0.16) 0%, transparent 55%)',
|
'radial-gradient(140% 80% at 50% -8%, oklch(0.65 0.25 350 / 0.12) 0%, transparent 55%)',
|
||||||
'radial-gradient(120% 70% at 50% 112%, oklch(0.45 0.18 300 / 0.20) 0%, transparent 60%)',
|
'radial-gradient(120% 70% at 50% 112%, oklch(0.45 0.18 300 / 0.16) 0%, transparent 60%)',
|
||||||
'linear-gradient(180deg, oklch(0.12 0.05 300 / 0.10) 0%, transparent 38%, oklch(0.10 0.06 310 / 0.16) 100%)',
|
'linear-gradient(180deg, oklch(0.12 0.05 300 / 0.08) 0%, transparent 38%, oklch(0.10 0.06 310 / 0.12) 100%)',
|
||||||
].join(','),
|
].join(','),
|
||||||
contain: 'layout paint style',
|
contain: 'layout paint style',
|
||||||
}}
|
}}
|
||||||
@@ -147,41 +158,59 @@ export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) {
|
|||||||
a vanishing point at the top (the horizon). It lives in the lower
|
a vanishing point at the top (the horizon). It lives in the lower
|
||||||
half of the screen — the "floor". The inner plane scrolls upward by
|
half of the screen — the "floor". The inner plane scrolls upward by
|
||||||
one tile via transform translateY, which reads as the grid flowing
|
one tile via transform translateY, which reads as the grid flowing
|
||||||
toward the viewer. Pure transform; never background-position. */}
|
toward the viewer. Pure transform; never background-position.
|
||||||
|
|
||||||
|
Two masks are nested (multiple mask-images on one element union by
|
||||||
|
default, and `mask-composite: intersect` isn't universal yet): the
|
||||||
|
outer wrapper fades the lattice through the central column where the
|
||||||
|
message timeline lives, so it frames the chat instead of striping
|
||||||
|
the text; the inner box fades it in from the horizon. */}
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '-25%',
|
inset: 0,
|
||||||
right: '-25%',
|
maskImage:
|
||||||
bottom: 0,
|
'linear-gradient(90deg, #000 0%, #000 12%, rgba(0,0,0,0.3) 34%, rgba(0,0,0,0.3) 66%, #000 88%, #000 100%)',
|
||||||
height: '62%',
|
WebkitMaskImage:
|
||||||
overflow: 'hidden',
|
'linear-gradient(90deg, #000 0%, #000 12%, rgba(0,0,0,0.3) 34%, rgba(0,0,0,0.3) 66%, #000 88%, #000 100%)',
|
||||||
perspective: '280px',
|
opacity: reduced ? 0.4 : 0.46,
|
||||||
perspectiveOrigin: '50% 0%',
|
|
||||||
maskImage: 'linear-gradient(180deg, transparent 0%, #000 26%, #000 100%)',
|
|
||||||
WebkitMaskImage: 'linear-gradient(180deg, transparent 0%, #000 26%, #000 100%)',
|
|
||||||
opacity: reduced ? 0.5 : 0.62,
|
|
||||||
contain: 'layout paint style',
|
contain: 'layout paint style',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 0,
|
left: '-25%',
|
||||||
right: 0,
|
right: '-25%',
|
||||||
top: 0,
|
bottom: 0,
|
||||||
height: '200%',
|
height: '62%',
|
||||||
transformOrigin: 'top center',
|
overflow: 'hidden',
|
||||||
transform: 'rotateX(74deg)',
|
perspective: '280px',
|
||||||
backgroundImage: GRID_URI,
|
perspectiveOrigin: '50% 0%',
|
||||||
backgroundRepeat: 'repeat-y',
|
maskImage: 'linear-gradient(180deg, transparent 0%, #000 26%, #000 100%)',
|
||||||
backgroundSize: '100% 50%',
|
WebkitMaskImage: 'linear-gradient(180deg, transparent 0%, #000 26%, #000 100%)',
|
||||||
filter: 'drop-shadow(0 0 3px oklch(0.55 0.22 320 / 0.6))',
|
contain: 'layout paint style',
|
||||||
willChange: reduced ? undefined : 'transform',
|
|
||||||
animation: reduced ? 'none' : `${animGridScroll} 7s linear infinite`,
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
height: '200%',
|
||||||
|
transformOrigin: 'top center',
|
||||||
|
transform: 'rotateX(74deg)',
|
||||||
|
backgroundImage: GRID_URI,
|
||||||
|
backgroundRepeat: 'repeat-y',
|
||||||
|
backgroundSize: '100% 50%',
|
||||||
|
filter: 'drop-shadow(0 0 2px oklch(0.55 0.22 320 / 0.55))',
|
||||||
|
willChange: reduced ? undefined : 'transform',
|
||||||
|
animation: reduced ? 'none' : `${animGridScroll} 7s linear infinite`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 3. Horizon glow + neon horizon line. A soft synthwave sun-bloom sits
|
{/* 3. Horizon glow + neon horizon line. A soft synthwave sun-bloom sits
|
||||||
@@ -197,7 +226,7 @@ export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) {
|
|||||||
height: '34%',
|
height: '34%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
backgroundImage:
|
backgroundImage:
|
||||||
'radial-gradient(60% 100% at 50% 100%, oklch(0.70 0.22 350 / 0.22) 0%, oklch(0.65 0.18 330 / 0.10) 40%, transparent 72%)',
|
'radial-gradient(60% 100% at 50% 100%, oklch(0.70 0.22 350 / 0.16) 0%, oklch(0.65 0.18 330 / 0.08) 40%, transparent 72%)',
|
||||||
contain: 'layout paint style',
|
contain: 'layout paint style',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -209,8 +238,10 @@ export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) {
|
|||||||
right: '12%',
|
right: '12%',
|
||||||
top: '38%',
|
top: '38%',
|
||||||
height: '1.5px',
|
height: '1.5px',
|
||||||
background: `linear-gradient(90deg, transparent 0%, ${NEON_CYAN} 25%, oklch(0.92 0.10 320 / 0.95) 50%, ${NEON_CYAN} 75%, transparent 100%)`,
|
// Bright at the flanks, dropped out through the centre column so the
|
||||||
opacity: 0.55,
|
// rule frames the timeline rather than underlining a message.
|
||||||
|
background: `linear-gradient(90deg, transparent 0%, ${NEON_CYAN} 14%, oklch(0.92 0.10 320 / 0.95) 22%, transparent 34%, transparent 66%, oklch(0.92 0.10 320 / 0.95) 78%, ${NEON_CYAN} 86%, transparent 100%)`,
|
||||||
|
opacity: 0.4,
|
||||||
filter: 'blur(0.4px) drop-shadow(0 0 4px oklch(0.78 0.16 200 / 0.7))',
|
filter: 'blur(0.4px) drop-shadow(0 0 4px oklch(0.78 0.16 200 / 0.7))',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -273,7 +304,7 @@ export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) {
|
|||||||
inset: 0,
|
inset: 0,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
mixBlendMode: 'multiply',
|
mixBlendMode: 'multiply',
|
||||||
opacity: 0.5,
|
opacity: 0.32,
|
||||||
contain: 'layout paint style',
|
contain: 'layout paint style',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -285,7 +316,7 @@ export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) {
|
|||||||
top: '-8px',
|
top: '-8px',
|
||||||
bottom: '-8px',
|
bottom: '-8px',
|
||||||
backgroundImage:
|
backgroundImage:
|
||||||
'repeating-linear-gradient(0deg, oklch(0.10 0.04 300 / 0.55) 0px, oklch(0.10 0.04 300 / 0.55) 1px, transparent 1px, transparent 3px)',
|
'repeating-linear-gradient(0deg, oklch(0.10 0.04 300 / 0.45) 0px, oklch(0.10 0.04 300 / 0.45) 1px, transparent 1px, transparent 4px)',
|
||||||
willChange: reduced ? undefined : 'transform',
|
willChange: reduced ? undefined : 'transform',
|
||||||
animation: reduced ? 'none' : `${animScanRoll} 6s linear infinite`,
|
animation: reduced ? 'none' : `${animScanRoll} 6s linear infinite`,
|
||||||
}}
|
}}
|
||||||
@@ -309,51 +340,54 @@ export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 7a. Glowing "INSERT COIN" attract-mode blip, low-opacity, bottom-center.
|
{/* 7. Attract-mode HUD: a tiny SCORE readout over a glowing "INSERT COIN"
|
||||||
Static scene shows it steady (no blink). */}
|
blip, stacked bottom-right. That corner is the one spot that is
|
||||||
|
clear in every layout (below the members list, or the empty right
|
||||||
|
end of the read-receipt strip) — top-left collided with the space
|
||||||
|
bar and bottom-centre sat on the composer. Static scene shows both
|
||||||
|
steady (no blink). The font-size clamp collapses the text to nothing
|
||||||
|
when the host is narrower than ~330px, so the 76px settings swatch
|
||||||
|
never shows clipped glyphs. */}
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: '5%',
|
right: '14px',
|
||||||
left: '50%',
|
bottom: '8px',
|
||||||
transform: 'translateX(-50%)',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
gap: '3px',
|
||||||
fontFamily: '"Courier New", monospace',
|
fontFamily: '"Courier New", monospace',
|
||||||
fontSize: '12px',
|
fontSize: HUD_FONT_SIZE,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
letterSpacing: '0.32em',
|
lineHeight: 1,
|
||||||
color: NEON_CYAN,
|
|
||||||
textShadow: '0 0 6px oklch(0.80 0.15 200 / 0.9), 0 0 14px oklch(0.65 0.25 350 / 0.5)',
|
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
opacity: reduced ? 0.6 : undefined,
|
|
||||||
animation: reduced ? 'none' : `${animCoinBlink} 1.6s step-end infinite`,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
INSERT COIN
|
<div
|
||||||
</div>
|
style={{
|
||||||
|
letterSpacing: '0.18em',
|
||||||
{/* 7b. Corner SCORE HUD glyph — a tiny pixel score that blips, top-left,
|
color: NEON_MAGENTA,
|
||||||
very low opacity so it reads as ambient chrome, not UI. */}
|
textShadow: '0 0 6px oklch(0.65 0.25 350 / 0.8)',
|
||||||
<div
|
opacity: reduced ? 0.5 : undefined,
|
||||||
aria-hidden="true"
|
animation: reduced ? 'none' : `${animScoreBlip} 2.4s ease-in-out infinite`,
|
||||||
style={{
|
}}
|
||||||
position: 'absolute',
|
>
|
||||||
top: '2.5%',
|
1UP 00<span style={{ color: NEON_CYAN }}>0000</span>
|
||||||
left: '2%',
|
</div>
|
||||||
fontFamily: '"Courier New", monospace',
|
<div
|
||||||
fontSize: '10px',
|
style={{
|
||||||
fontWeight: 700,
|
letterSpacing: '0.32em',
|
||||||
letterSpacing: '0.18em',
|
color: NEON_CYAN,
|
||||||
color: NEON_MAGENTA,
|
textShadow: '0 0 6px oklch(0.80 0.15 200 / 0.9), 0 0 14px oklch(0.65 0.25 350 / 0.5)',
|
||||||
textShadow: '0 0 6px oklch(0.65 0.25 350 / 0.8)',
|
opacity: reduced ? 0.6 : undefined,
|
||||||
userSelect: 'none',
|
animation: reduced ? 'none' : `${animCoinBlink} 1.6s step-end infinite`,
|
||||||
whiteSpace: 'nowrap',
|
}}
|
||||||
opacity: reduced ? 0.5 : undefined,
|
>
|
||||||
animation: reduced ? 'none' : `${animScoreBlip} 2.4s ease-in-out infinite`,
|
INSERT COIN
|
||||||
}}
|
</div>
|
||||||
>
|
|
||||||
1UP 00<span style={{ color: NEON_CYAN }}>0000</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 8. CRT vignette + screen-glow. A radial darkening frames the corners,
|
{/* 8. CRT vignette + screen-glow. A radial darkening frames the corners,
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ function makeStars(count: number, seedBase: number): Star[] {
|
|||||||
|
|
||||||
export function DeepSpaceOverlay({ reduced }: SeasonalOverlayProps) {
|
export function DeepSpaceOverlay({ reduced }: SeasonalOverlayProps) {
|
||||||
// Two parallax depths. Far = dense + faint, Near = sparser + slightly larger.
|
// Two parallax depths. Far = dense + faint, Near = sparser + slightly larger.
|
||||||
const farStars = useMemo<Star[]>(() => makeStars(16, 1000), []);
|
const farStars = useMemo<Star[]>(() => makeStars(40, 1000), []);
|
||||||
const nearStars = useMemo<Star[]>(() => makeStars(12, 2000), []);
|
const nearStars = useMemo<Star[]>(() => makeStars(22, 2000), []);
|
||||||
|
|
||||||
const heroStars = useMemo<HeroStar[]>(
|
const heroStars = useMemo<HeroStar[]>(
|
||||||
() =>
|
() =>
|
||||||
@@ -144,7 +144,7 @@ export function DeepSpaceOverlay({ reduced }: SeasonalOverlayProps) {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
inset: '-6%',
|
inset: '-6%',
|
||||||
contain: 'layout paint style',
|
contain: 'layout paint style',
|
||||||
backgroundColor: 'oklch(0.2 0.12 300 / 0.16)',
|
backgroundColor: 'oklch(0.2 0.12 300 / 0.12)',
|
||||||
backgroundImage: [
|
backgroundImage: [
|
||||||
'radial-gradient(120% 90% at 50% -8%, oklch(0.28 0.13 295 / 0.2) 0%, transparent 60%)',
|
'radial-gradient(120% 90% at 50% -8%, oklch(0.28 0.13 295 / 0.2) 0%, transparent 60%)',
|
||||||
'radial-gradient(100% 80% at 12% 18%, oklch(0.55 0.2 330 / 0.1) 0%, transparent 55%)',
|
'radial-gradient(100% 80% at 12% 18%, oklch(0.55 0.2 330 / 0.1) 0%, transparent 55%)',
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
|||||||
|
|
||||||
const submitAccountData: AccountDataSubmitCallback = useCallback(
|
const submitAccountData: AccountDataSubmitCallback = useCallback(
|
||||||
async (type, content) => {
|
async (type, content) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await mx.setRoomAccountData(room.roomId, type as any, content);
|
await mx.setRoomAccountData(room.roomId, type as any, content);
|
||||||
},
|
},
|
||||||
[mx, room.roomId],
|
[mx, room.roomId],
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export function RoomQuality({ permissions }: RoomQualityProps) {
|
|||||||
const [submitState, submit] = useAsyncCallback(
|
const [submitState, submit] = useAsyncCallback(
|
||||||
useCallback(
|
useCallback(
|
||||||
async (next: RoomQualityContent) => {
|
async (next: RoomQualityContent) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await sendStateEvent(mx, room.roomId, StateEvent.LotusRoomQuality, next);
|
await sendStateEvent(mx, room.roomId, StateEvent.LotusRoomQuality, next);
|
||||||
},
|
},
|
||||||
[mx, room.roomId],
|
[mx, room.roomId],
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export function RoomRetention({ permissions }: RoomRetentionProps) {
|
|||||||
const content: RetentionContent = ms > 0 ? { max_lifetime: ms } : {};
|
const content: RetentionContent = ms > 0 ? { max_lifetime: ms } : {};
|
||||||
// Lotus custom-state convention: cast the type key (RoomRetention isn't a
|
// Lotus custom-state convention: cast the type key (RoomRetention isn't a
|
||||||
// typed key in the SDK's StateEvents map).
|
// typed key in the SDK's StateEvents map).
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await sendStateEvent(mx, room.roomId, StateEvent.RoomRetention, content);
|
await sendStateEvent(mx, room.roomId, StateEvent.RoomRetention, content);
|
||||||
},
|
},
|
||||||
[mx, room.roomId],
|
[mx, room.roomId],
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const extractText = (event: MatrixEvent): ExtractedText | null => {
|
|||||||
const content = event.getContent();
|
const content = event.getContent();
|
||||||
|
|
||||||
if (POLL_START_TYPES.includes(evType)) {
|
if (POLL_START_TYPES.includes(evType)) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const poll = (content['m.poll'] ?? content['org.matrix.msc3381.poll.start']) as any;
|
const poll = (content['m.poll'] ?? content['org.matrix.msc3381.poll.start']) as any;
|
||||||
if (!poll) return null;
|
if (!poll) return null;
|
||||||
const qBody =
|
const qBody =
|
||||||
@@ -56,6 +57,7 @@ const extractText = (event: MatrixEvent): ExtractedText | null => {
|
|||||||
.map(
|
.map(
|
||||||
(a) =>
|
(a) =>
|
||||||
((a['m.text'] as Array<{ body: string }> | undefined)?.[0]?.body ??
|
((a['m.text'] as Array<{ body: string }> | undefined)?.[0]?.body ??
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(a['org.matrix.msc3381.poll.answer'] as any)?.body ??
|
(a['org.matrix.msc3381.poll.answer'] as any)?.body ??
|
||||||
'') as string,
|
'') as string,
|
||||||
)
|
)
|
||||||
@@ -102,6 +104,7 @@ const rowToResultItem = (row: SearchCacheRow): ResultItem => {
|
|||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
rank: 0,
|
rank: 0,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
event: syntheticEvent as any,
|
event: syntheticEvent as any,
|
||||||
context: { events_before: [], events_after: [], profile_info: {} },
|
context: { events_before: [], events_after: [], profile_info: {} },
|
||||||
};
|
};
|
||||||
@@ -224,6 +227,7 @@ export const useLocalMessageSearch = () => {
|
|||||||
};
|
};
|
||||||
memoryItems.push({
|
memoryItems.push({
|
||||||
rank: 0,
|
rank: 0,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
event: syntheticEvent as any,
|
event: syntheticEvent as any,
|
||||||
context: { events_before: [], events_after: [], profile_info: {} },
|
context: { events_before: [], events_after: [], profile_info: {} },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ export const useMessageSearch = (params: MessageSearchParams) => {
|
|||||||
...(fromTs !== undefined && { from_ts: fromTs }),
|
...(fromTs !== undefined && { from_ts: fromTs }),
|
||||||
...(toTs !== undefined && { to_ts: toTs }),
|
...(toTs !== undefined && { to_ts: toTs }),
|
||||||
...(containsUrl !== undefined && { contains_url: containsUrl }),
|
...(containsUrl !== undefined && { contains_url: containsUrl }),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} as any,
|
} as any,
|
||||||
include_state: false,
|
include_state: false,
|
||||||
order_by: order as SearchOrderBy.Recent,
|
order_by: order as SearchOrderBy.Recent,
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import { useSpaceOptionally } from '../../hooks/useSpace';
|
|||||||
import {
|
import {
|
||||||
getRoomNotificationModeIcon,
|
getRoomNotificationModeIcon,
|
||||||
RoomNotificationMode,
|
RoomNotificationMode,
|
||||||
|
setRoomNotificationPreference,
|
||||||
} from '../../hooks/useRoomsNotificationPreferences';
|
} from '../../hooks/useRoomsNotificationPreferences';
|
||||||
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
|
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
|
||||||
import { getRoomCreatorsForRoomId, useRoomCreators } from '../../hooks/useRoomCreators';
|
import { getRoomCreatorsForRoomId, useRoomCreators } from '../../hooks/useRoomCreators';
|
||||||
@@ -298,8 +299,6 @@ export function saveMuteTimers(timers: MuteTimerEntry[]): void {
|
|||||||
// Reverse a timed mute: restore the room's notification mode to Unset and drop
|
// Reverse a timed mute: restore the room's notification mode to Unset and drop
|
||||||
// its persisted timer. Shared by the in-session timer and the boot-time restore.
|
// its persisted timer. Shared by the in-session timer and the boot-time restore.
|
||||||
export async function unmuteRoom(mx: MatrixClient, roomId: string): Promise<void> {
|
export async function unmuteRoom(mx: MatrixClient, roomId: string): Promise<void> {
|
||||||
const { setRoomNotificationPreference } =
|
|
||||||
await import('../../hooks/useRoomsNotificationPreferences');
|
|
||||||
await setRoomNotificationPreference(
|
await setRoomNotificationPreference(
|
||||||
mx,
|
mx,
|
||||||
roomId,
|
roomId,
|
||||||
@@ -392,8 +391,6 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
|
|||||||
|
|
||||||
const handleMuteFor = useCallback(
|
const handleMuteFor = useCallback(
|
||||||
async (durationMs: number | null) => {
|
async (durationMs: number | null) => {
|
||||||
const { setRoomNotificationPreference } =
|
|
||||||
await import('../../hooks/useRoomsNotificationPreferences');
|
|
||||||
const prevMode = notificationMode ?? RoomNotificationMode.Unset;
|
const prevMode = notificationMode ?? RoomNotificationMode.Unset;
|
||||||
await setRoomNotificationPreference(
|
await setRoomNotificationPreference(
|
||||||
mx,
|
mx,
|
||||||
|
|||||||
@@ -341,6 +341,7 @@ export function RoomServerACL({ requestClose }: RoomServerACLProps) {
|
|||||||
variant="Primary"
|
variant="Primary"
|
||||||
/>
|
/>
|
||||||
<Box direction="Column" gap="0">
|
<Box direction="Column" gap="0">
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
<label
|
<label
|
||||||
htmlFor="allow-ip-literals"
|
htmlFor="allow-ip-literals"
|
||||||
style={{ cursor: canEdit ? 'pointer' : 'default' }}
|
style={{ cursor: canEdit ? 'pointer' : 'default' }}
|
||||||
|
|||||||
@@ -318,6 +318,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
|
|||||||
const results = await Promise.allSettled(
|
const results = await Promise.allSettled(
|
||||||
ids.map((id) => {
|
ids.map((id) => {
|
||||||
// threadId-aware overload (P3-8): explicit null = send to the main timeline.
|
// threadId-aware overload (P3-8): explicit null = send to the main timeline.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const sendForward = () => mx.sendEvent(id, null, mEvent.getType() as any, fwdContent);
|
const sendForward = () => mx.sendEvent(id, null, mEvent.getType() as any, fwdContent);
|
||||||
// Send the optional comment first so it reads as a note above the
|
// Send the optional comment first so it reads as a note above the
|
||||||
// forwarded content. The room counts as failed if either send rejects.
|
// forwarded content. The room counts as failed if either send rejects.
|
||||||
@@ -326,6 +327,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
|
|||||||
const needsComment = commentBody && !commentSentRef.current.has(id);
|
const needsComment = commentBody && !commentSentRef.current.has(id);
|
||||||
const step = needsComment
|
const step = needsComment
|
||||||
? mx
|
? mx
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
.sendMessage(id, null, { msgtype: MsgType.Text, body: commentBody } as any)
|
.sendMessage(id, null, { msgtype: MsgType.Text, body: commentBody } as any)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
commentSentRef.current.add(id);
|
commentSentRef.current.add(id);
|
||||||
|
|||||||
@@ -1390,6 +1390,7 @@ export const Message = React.memo(
|
|||||||
after={<Icon size="100" src={Icons.Send} />}
|
after={<Icon size="100" src={Icons.Send} />}
|
||||||
radii="300"
|
radii="300"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(mx as any).resendEvent(mEvent, room);
|
(mx as any).resendEvent(mEvent, room);
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}}
|
}}
|
||||||
@@ -1408,6 +1409,7 @@ export const Message = React.memo(
|
|||||||
after={<Icon size="100" src={Icons.Cross} />}
|
after={<Icon size="100" src={Icons.Cross} />}
|
||||||
radii="300"
|
radii="300"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(mx as any).cancelPendingEvent(mEvent);
|
(mx as any).cancelPendingEvent(mEvent);
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ export const MessageEditor = as<'div', MessageEditorProps>(
|
|||||||
rel_type: RelationType.Replace,
|
rel_type: RelationType.Replace,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return mx.sendMessage(roomId, content as any);
|
return mx.sendMessage(roomId, content as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +236,7 @@ export const MessageEditor = as<'div', MessageEditorProps>(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return mx.sendMessage(roomId, content as any);
|
return mx.sendMessage(roomId, content as any);
|
||||||
}, [
|
}, [
|
||||||
mx,
|
mx,
|
||||||
|
|||||||
@@ -564,6 +564,7 @@ export function ThreadTimeline({ room, thread, editor }: ThreadTimelineProps) {
|
|||||||
mx.sendEvent(
|
mx.sendEvent(
|
||||||
room.roomId,
|
room.roomId,
|
||||||
thread.id,
|
thread.id,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
MessageEvent.Reaction as any,
|
MessageEvent.Reaction as any,
|
||||||
getReactionContent(targetEventId, key, rShortcode),
|
getReactionContent(targetEventId, key, rShortcode),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export function RoomWidgetView({ room, widget }: RoomWidgetViewProps) {
|
|||||||
clientApi.stop();
|
clientApi.stop();
|
||||||
iframe.remove();
|
iframe.remove();
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [mx, room.roomId, widget.id, widget.templateUrl]);
|
}, [mx, room.roomId, widget.id, widget.templateUrl]);
|
||||||
|
|
||||||
if (blocked) {
|
if (blocked) {
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export function WidgetsPanel({ room, requestClose }: WidgetsPanelProps) {
|
|||||||
data: {},
|
data: {},
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await sendStateEvent(mx, room.roomId, StateEvent.Widget, content, id);
|
await sendStateEvent(mx, room.roomId, StateEvent.Widget, content, id);
|
||||||
setAdding(false);
|
setAdding(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -95,6 +96,7 @@ export function WidgetsPanel({ room, requestClose }: WidgetsPanelProps) {
|
|||||||
|
|
||||||
const handleRemove = (id: string) => {
|
const handleRemove = (id: string) => {
|
||||||
if (viewingId === id) setViewingId(null);
|
if (viewingId === id) setViewingId(null);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
sendStateEvent(mx, room.roomId, StateEvent.Widget, {}, id).catch(() => undefined);
|
sendStateEvent(mx, room.roomId, StateEvent.Widget, {}, id).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,13 @@ export function getLocalRoomNamesContent(
|
|||||||
mx: ReturnType<typeof useMatrixClient>,
|
mx: ReturnType<typeof useMatrixClient>,
|
||||||
): LocalRoomNamesContent {
|
): LocalRoomNamesContent {
|
||||||
const raw: unknown = getAccountData<unknown>(mx, LOCAL_ROOM_NAMES_KEY);
|
const raw: unknown = getAccountData<unknown>(mx, LOCAL_ROOM_NAMES_KEY);
|
||||||
if (raw && typeof raw === 'object' && 'rooms' in raw && typeof (raw as any).rooms === 'object') {
|
if (
|
||||||
|
raw &&
|
||||||
|
typeof raw === 'object' &&
|
||||||
|
'rooms' in raw &&
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
typeof (raw as any).rooms === 'object'
|
||||||
|
) {
|
||||||
return raw as LocalRoomNamesContent;
|
return raw as LocalRoomNamesContent;
|
||||||
}
|
}
|
||||||
return { rooms: {} };
|
return { rooms: {} };
|
||||||
|
|||||||
@@ -70,12 +70,12 @@ import {
|
|||||||
THREAD_NOTIFICATIONS_FALLBACK_BEHAVIOR,
|
THREAD_NOTIFICATIONS_FALLBACK_BEHAVIOR,
|
||||||
} from '../../utils/threadNotifications';
|
} from '../../utils/threadNotifications';
|
||||||
|
|
||||||
|
// Grace period after the initial sync settles before invite notifications arm, so
|
||||||
|
// the async invite-atom population lands first and isn't mistaken for new invites.
|
||||||
const LogoSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus.png');
|
const LogoSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus.png');
|
||||||
const LogoUnreadSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus-unread.png');
|
const LogoUnreadSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus-unread.png');
|
||||||
const LogoHighlightSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus-highlight.png');
|
const LogoHighlightSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus-highlight.png');
|
||||||
|
|
||||||
// Grace period after the initial sync settles before invite notifications arm, so
|
|
||||||
// the async invite-atom population lands first and isn't mistaken for new invites.
|
|
||||||
const INVITE_NOTIFY_ARM_DELAY_MS = 3000;
|
const INVITE_NOTIFY_ARM_DELAY_MS = 3000;
|
||||||
|
|
||||||
function SystemEmojiFeature() {
|
function SystemEmojiFeature() {
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ export const setMarkedUnread = (
|
|||||||
unread: boolean,
|
unread: boolean,
|
||||||
): Promise<unknown> =>
|
): Promise<unknown> =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
mx.setRoomAccountData(roomId, AccountDataEvent.MarkedUnread as any, { unread }),
|
mx.setRoomAccountData(roomId, AccountDataEvent.MarkedUnread as any, { unread }),
|
||||||
// Best-effort mirror for older servers; never fail the primary write on it.
|
// Best-effort mirror for older servers; never fail the primary write on it.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
mx.setRoomAccountData(roomId, UNSTABLE_MARKED_UNREAD as any, { unread }).catch(() => undefined),
|
mx.setRoomAccountData(roomId, UNSTABLE_MARKED_UNREAD as any, { unread }).catch(() => undefined),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export function getAccountData<T>(
|
|||||||
mx: MatrixClient,
|
mx: MatrixClient,
|
||||||
eventType: AccountDataEvent | string,
|
eventType: AccountDataEvent | string,
|
||||||
): T | undefined {
|
): T | undefined {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const event = (mx as any).getAccountData(eventType) as MatrixEvent | undefined;
|
const event = (mx as any).getAccountData(eventType) as MatrixEvent | undefined;
|
||||||
return event?.getContent() as T | undefined;
|
return event?.getContent() as T | undefined;
|
||||||
}
|
}
|
||||||
@@ -22,5 +23,6 @@ export function setAccountData<T>(
|
|||||||
eventType: AccountDataEvent | string,
|
eventType: AccountDataEvent | string,
|
||||||
content: T,
|
content: T,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (mx as any).setAccountData(eventType, content);
|
return (mx as any).setAccountData(eventType, content);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export async function buildModelNode(
|
|||||||
model: DenoiseModelId,
|
model: DenoiseModelId,
|
||||||
): Promise<DenoiseNode> {
|
): Promise<DenoiseNode> {
|
||||||
if (model === 'dtln') {
|
if (model === 'dtln') {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const mod: any = await import(/* @vite-ignore */ `${BASE}workadventure/audio-worklet.js`);
|
const mod: any = await import(/* @vite-ignore */ `${BASE}workadventure/audio-worklet.js`);
|
||||||
const handle = await mod.createNoiseSuppressionAudioWorklet(ctx, { bypassUntilReady: true });
|
const handle = await mod.createNoiseSuppressionAudioWorklet(ctx, { bypassUntilReady: true });
|
||||||
return { node: handle.node, dispose: () => handle.dispose() };
|
return { node: handle.node, dispose: () => handle.dispose() };
|
||||||
@@ -80,6 +81,7 @@ export async function buildModelNode(
|
|||||||
// deepfilternet/v2/... Override its cdnUrl to our absolute base so nothing
|
// deepfilternet/v2/... Override its cdnUrl to our absolute base so nothing
|
||||||
// hits the upstream CDN. DeepFilterNet3Core builds the worklet node directly.
|
// hits the upstream CDN. DeepFilterNet3Core builds the worklet node directly.
|
||||||
const dfnBase = new URL(`${BASE}deepfilternet`, window.location.href).href;
|
const dfnBase = new URL(`${BASE}deepfilternet`, window.location.href).href;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const mod: any = await import(/* @vite-ignore */ `${BASE}deepfilternet/index.esm.js`);
|
const mod: any = await import(/* @vite-ignore */ `${BASE}deepfilternet/index.esm.js`);
|
||||||
const core = new mod.DeepFilterNet3Core({
|
const core = new mod.DeepFilterNet3Core({
|
||||||
sampleRate: sampleRateFor(model),
|
sampleRate: sampleRateFor(model),
|
||||||
|
|||||||
@@ -44,10 +44,12 @@ test('onTabPress fires only on Tab', () => {
|
|||||||
|
|
||||||
test('preventScrollWithArrowKey prevents default only on arrows', () => {
|
test('preventScrollWithArrowKey prevents default only on arrows', () => {
|
||||||
const up = evt('ArrowUp', 38);
|
const up = evt('ArrowUp', 38);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
preventScrollWithArrowKey(up as any);
|
preventScrollWithArrowKey(up as any);
|
||||||
assert.equal(up.prevented, true);
|
assert.equal(up.prevented, true);
|
||||||
|
|
||||||
const a = evt('a', 65);
|
const a = evt('a', 65);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
preventScrollWithArrowKey(a as any);
|
preventScrollWithArrowKey(a as any);
|
||||||
assert.equal(a.prevented, false);
|
assert.equal(a.prevented, false);
|
||||||
});
|
});
|
||||||
@@ -93,12 +95,14 @@ test('stopPropagation: stops unless an editable element is focused', () => {
|
|||||||
// nothing focused → stops, returns true
|
// nothing focused → stops, returns true
|
||||||
withActive(null);
|
withActive(null);
|
||||||
let k = makeKeyEvt();
|
let k = makeKeyEvt();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
assert.equal(stopPropagation(k.ev as any), true);
|
assert.equal(stopPropagation(k.ev as any), true);
|
||||||
assert.equal(k.wasStopped(), true);
|
assert.equal(k.wasStopped(), true);
|
||||||
|
|
||||||
// input focused → does not stop, returns false
|
// input focused → does not stop, returns false
|
||||||
withActive({ nodeName: 'INPUT', getAttribute: () => null });
|
withActive({ nodeName: 'INPUT', getAttribute: () => null });
|
||||||
k = makeKeyEvt();
|
k = makeKeyEvt();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
assert.equal(stopPropagation(k.ev as any), false);
|
assert.equal(stopPropagation(k.ev as any), false);
|
||||||
assert.equal(k.wasStopped(), false);
|
assert.equal(k.wasStopped(), false);
|
||||||
|
|
||||||
@@ -108,5 +112,6 @@ test('stopPropagation: stops unless an editable element is focused', () => {
|
|||||||
getAttribute: (a: string) => (a === 'contenteditable' ? 'true' : null),
|
getAttribute: (a: string) => (a === 'contenteditable' ? 'true' : null),
|
||||||
});
|
});
|
||||||
k = makeKeyEvt();
|
k = makeKeyEvt();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
assert.equal(stopPropagation(k.ev as any), false);
|
assert.equal(stopPropagation(k.ev as any), false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const makeMx = (
|
|||||||
if (opts.forgetRejects) throw new Error('forget failed');
|
if (opts.forgetRejects) throw new Error('forget failed');
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} as any;
|
} as any;
|
||||||
return { mx, calls };
|
return { mx, calls };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { M_POLL_KIND_DISCLOSED } from 'matrix-js-sdk';
|
import { M_POLL_KIND_DISCLOSED } from 'matrix-js-sdk';
|
||||||
|
|
||||||
// Pure helpers for poll display. matrix-js-sdk 41.7.0's PollStartEvent /
|
// Pure helpers for poll display. matrix-js-sdk 41.7.0's PollStartEvent /
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export function sendStateEvent<T extends object>(
|
|||||||
content: T,
|
content: T,
|
||||||
stateKey = '',
|
stateKey = '',
|
||||||
): Promise<ISendEventResponse> {
|
): Promise<ISendEventResponse> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return mx.sendStateEvent(roomId, eventType as any, content, stateKey);
|
return mx.sendStateEvent(roomId, eventType as any, content, stateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-28
@@ -22,40 +22,18 @@ document.body.classList.add(configClass, varsClass);
|
|||||||
|
|
||||||
// Register Service Worker
|
// Register Service Worker
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
const isProduction = import.meta.env.PROD;
|
const swUrl =
|
||||||
const swUrl = isProduction
|
import.meta.env.MODE === 'production'
|
||||||
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
|
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
|
||||||
: `/dev-sw.js?dev-sw`;
|
: `/dev-sw.js?dev-sw`;
|
||||||
|
|
||||||
const sendSessionToSW = () => {
|
const sendSessionToSW = () => {
|
||||||
const session = getFallbackSession();
|
const session = getFallbackSession();
|
||||||
pushSessionToSW(session?.baseUrl, session?.accessToken);
|
pushSessionToSW(session?.baseUrl, session?.accessToken);
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerServiceWorker = async () => {
|
navigator.serviceWorker.register(swUrl).then(sendSessionToSW);
|
||||||
try {
|
navigator.serviceWorker.ready.then(sendSessionToSW);
|
||||||
const registration = await navigator.serviceWorker.register(
|
|
||||||
swUrl,
|
|
||||||
isProduction
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
type: 'module',
|
|
||||||
scope: '/',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
sendSessionToSW();
|
|
||||||
|
|
||||||
await navigator.serviceWorker.ready;
|
|
||||||
sendSessionToSW();
|
|
||||||
|
|
||||||
console.info('Service worker registered:', registration.scope);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Service worker registration failed:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
registerServiceWorker();
|
|
||||||
|
|
||||||
navigator.serviceWorker.addEventListener('message', (ev) => {
|
navigator.serviceWorker.addEventListener('message', (ev) => {
|
||||||
const { type } = ev.data ?? {};
|
const { type } = ev.data ?? {};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { globalStyle, keyframes, style } from '@vanilla-extract/css';
|
import { globalStyle, keyframes, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
const glitch1 = keyframes({
|
const glitch1 = keyframes({
|
||||||
|
|||||||
+1
-1
@@ -246,7 +246,7 @@ const vendorChunks = (id) => {
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
appType: 'spa',
|
appType: 'spa',
|
||||||
publicDir: './public/res',
|
publicDir: false,
|
||||||
base: buildConfig.base,
|
base: buildConfig.base,
|
||||||
server: {
|
server: {
|
||||||
port: 8080,
|
port: 8080,
|
||||||
|
|||||||
Reference in New Issue
Block a user