Compare commits
47 Commits
1.7.3
...
2d37c24ba5
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d37c24ba5 | |||
| ffd06ab561 | |||
| eb8dd330b6 | |||
| 6267e52bdf | |||
| ab21008f34 | |||
| 0998639d59 | |||
| eccde07d06 | |||
| 770cdc5f13 | |||
| 6bafb62414 | |||
| 6ce0fbbbe6 | |||
| 8fe42e6f22 | |||
| 47a6209730 | |||
| 24d3f867f8 | |||
| 9db60374e4 | |||
| 8ef4b21dab | |||
| 8f56812dd1 | |||
| 3833cb093d | |||
| 94db65b85e | |||
| 6f731e48d2 | |||
| 99fe0e543c | |||
| c6b0799b2a | |||
| 861f2286db | |||
| 9af3e3b2e9 | |||
| 341c1b45b2 | |||
| 89f5d8cdf5 | |||
| ca3270437d | |||
| bbbc6f7363 | |||
| 8a0abacf6f | |||
| 976ccdabd4 | |||
| 99b26680b6 | |||
| c5be477855 | |||
| 32c1501e9c | |||
| 463837e7d4 | |||
| d74f142cdd | |||
| 53954aae89 | |||
| 24aa62a503 | |||
| 2618bb9c63 | |||
| 32a31045ef | |||
| 56edad77a8 | |||
| fdb604e350 | |||
| 3c66dfd83c | |||
| 81633b0a1e | |||
| 4a967de184 | |||
| 59961cbdb5 | |||
| 95d9d8bf23 | |||
| 2fd9741a2b | |||
| fe9c325580 |
@@ -4,7 +4,6 @@ ios
|
||||
build
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Env files (keep .env for build; exclude local overrides)
|
||||
|
||||
@@ -5,8 +5,8 @@ on:
|
||||
branches: [master]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: coracle-social/flotilla
|
||||
REGISTRY: gitea.coracle.social
|
||||
IMAGE_NAME: coracle/flotilla
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
@@ -23,8 +23,8 @@ jobs:
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
username: hodlbod
|
||||
password: ${{ secrets.PACKAGE_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
# 1.8.0
|
||||
|
||||
* Fix relay badge overflow
|
||||
* Suppress programmatic scroll when user is scrolling
|
||||
* Fix vertical alignment of emoji and overflow buttons in shared event action row
|
||||
* Use type=email for signup/login email inputs, validate password
|
||||
* Improve toggle switch placement on settings screens
|
||||
* Fix relay auth privacy toggle
|
||||
* Improve field layout
|
||||
* Add progress bar to signup flow
|
||||
* Bundle emojis properly
|
||||
* Rework hosting page
|
||||
* Fix padding on pages on small screens
|
||||
* Add richer link preview support
|
||||
* Fix pasting into event summary
|
||||
* Publish fewer join/claim requests
|
||||
* Fix new messages not rendering in safari
|
||||
* Avoid capturing stale cleanup function in chat
|
||||
* Hide keyboard on app resume
|
||||
* Add email rendering support
|
||||
* Fix bunker login
|
||||
* Fix undefined chat draft key
|
||||
* Allow sharing to chat without a message
|
||||
* Make sure to show date on calendar events when embedded
|
||||
* Improve space search
|
||||
|
||||
# 1.7.4
|
||||
|
||||
* Fix safe area inset for FAB
|
||||
|
||||
# 1.7.3
|
||||
|
||||
* Add native share support for space invites
|
||||
|
||||
+15
-20
@@ -1,32 +1,27 @@
|
||||
# Stage 1: Build
|
||||
# Uses .env from build context for config (logo, branding, etc.)
|
||||
# Optional: docker build --build-arg VITE_BUILD_HASH=$(git rev-parse --short HEAD) -t flotilla .
|
||||
# Build and run the Flotilla web server.
|
||||
#
|
||||
# docker build -t flotilla .
|
||||
# docker run -p 3000:3000 flotilla
|
||||
#
|
||||
# Pass --build-arg VITE_BUILD_HASH=$(git rev-parse --short HEAD) to stamp the build.
|
||||
# A .env in the build context is picked up by build.sh for branding config.
|
||||
|
||||
FROM node:20-bookworm AS builder
|
||||
FROM node:22-bookworm
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl
|
||||
|
||||
RUN npm install -g pnpm@latest
|
||||
RUN npm install -g pnpm@10.33.0
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm i
|
||||
|
||||
# Copy everything (including .env when present) - build.sh will source it
|
||||
COPY . .
|
||||
|
||||
ARG VITE_BUILD_HASH
|
||||
ENV VITE_BUILD_HASH=${VITE_BUILD_HASH}
|
||||
RUN pnpm i --frozen-lockfile
|
||||
|
||||
ENV NODE_OPTIONS=--max_old_space_size=16384
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
EXPOSE 3000
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy only the built output - no source, no .env, no dev deps
|
||||
COPY --from=builder /app/build ./build
|
||||
|
||||
CMD ["npx", "serve", "-s", "build"]
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
@@ -31,18 +31,18 @@ To run your own Flotilla, it's as simple as:
|
||||
```sh
|
||||
pnpm install
|
||||
pnpm run build
|
||||
npx serve -s build
|
||||
pnpm run start
|
||||
```
|
||||
|
||||
Or, if you prefer to use a container:
|
||||
|
||||
```sh
|
||||
podman run -d -p 3000:3000 ghcr.io/coracle-social/flotilla:latest
|
||||
docker run -d -p 3000:3000 gitea.coracle.social/coracle/flotilla:latest
|
||||
```
|
||||
|
||||
Alternatively, you can copy the build files into a directory of your choice and serve it yourself:
|
||||
|
||||
```sh
|
||||
mkdir ./mount
|
||||
podman run -v ./mount:/app/mount ghcr.io/coracle-social/flotilla:latest bash -c 'cp -r build/* mount'
|
||||
docker run -v ./mount:/app/mount gitea.coracle.social/coracle/flotilla:latest bash -c 'cp -r build/* mount'
|
||||
```
|
||||
|
||||
@@ -8,8 +8,8 @@ android {
|
||||
applicationId "social.flotilla"
|
||||
minSdk rootProject.ext.minSdkVersion
|
||||
targetSdk rootProject.ext.targetSdkVersion
|
||||
versionCode 45
|
||||
versionName "1.7.3"
|
||||
versionCode 47
|
||||
versionName "1.8.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 48;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -131,8 +131,9 @@
|
||||
504EC2FC1FED79650016851F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 920;
|
||||
LastUpgradeCheck = 920;
|
||||
LastUpgradeCheck = 2630;
|
||||
TargetAttributes = {
|
||||
504EC3031FED79650016851F = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
@@ -257,6 +258,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -264,8 +266,10 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -275,8 +279,10 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
@@ -295,6 +301,7 @@
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
@@ -314,6 +321,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -321,8 +329,10 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -332,8 +342,10 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
@@ -345,7 +357,9 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -358,14 +372,16 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 36;
|
||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||
CURRENT_PROJECT_VERSION = 38;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.7.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.8.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -385,14 +401,16 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 36;
|
||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||
CURRENT_PROJECT_VERSION = 38;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.7.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.8.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
+16
-11
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "flotilla",
|
||||
"version": "1.7.3",
|
||||
"version": "1.8.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "./build.sh",
|
||||
"start": "node server.js",
|
||||
"release:android": "./build.sh && cap build android --androidreleasetype APK --signing-type apksigner",
|
||||
"tauri:dev": "tauri dev",
|
||||
"tauri:build": "tauri build",
|
||||
@@ -60,6 +61,7 @@
|
||||
"@capawesome/capacitor-badge": "^8.0.0",
|
||||
"@getalby/lightning-tools": "^6.1.0",
|
||||
"@getalby/sdk": "^5.1.2",
|
||||
"@hono/node-server": "^2.0.0",
|
||||
"@noble/curves": "^1.9.7",
|
||||
"@pomade/core": "^0.2.3",
|
||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||
@@ -70,22 +72,25 @@
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@vite-pwa/assets-generator": "^0.2.6",
|
||||
"@vite-pwa/sveltekit": "^0.6.8",
|
||||
"@welshman/app": "^0.8.13",
|
||||
"@welshman/content": "^0.8.13",
|
||||
"@welshman/editor": "^0.8.13",
|
||||
"@welshman/feeds": "^0.8.13",
|
||||
"@welshman/lib": "^0.8.13",
|
||||
"@welshman/net": "^0.8.13",
|
||||
"@welshman/router": "^0.8.13",
|
||||
"@welshman/signer": "^0.8.13",
|
||||
"@welshman/store": "^0.8.13",
|
||||
"@welshman/util": "^0.8.13",
|
||||
"@welshman/app": "^0.8.15",
|
||||
"@welshman/content": "^0.8.15",
|
||||
"@welshman/editor": "^0.8.15",
|
||||
"@welshman/feeds": "^0.8.15",
|
||||
"@welshman/lib": "^0.8.15",
|
||||
"@welshman/net": "^0.8.15",
|
||||
"@welshman/router": "^0.8.15",
|
||||
"@welshman/signer": "^0.8.15",
|
||||
"@welshman/store": "^0.8.15",
|
||||
"@welshman/util": "^0.8.15",
|
||||
"cheerio": "^1.2.0",
|
||||
"compressorjs-next": "^1.1.2",
|
||||
"daisyui": "^5.5.19",
|
||||
"date-picker-svelte": "^2.17.0",
|
||||
"dotenv": "^16.6.1",
|
||||
"emoji-picker-element": "^1.28.1",
|
||||
"emoji-picker-element-data": "^1.8.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"hono": "^4.12.15",
|
||||
"husky": "^9.1.7",
|
||||
"idb": "^8.0.3",
|
||||
"livekit-client": "^2.17.2",
|
||||
|
||||
Generated
+259
-110
@@ -62,12 +62,15 @@ importers:
|
||||
'@getalby/sdk':
|
||||
specifier: ^5.1.2
|
||||
version: 5.1.2(typescript@5.9.3)
|
||||
'@hono/node-server':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(hono@4.12.15)
|
||||
'@noble/curves':
|
||||
specifier: ^1.9.7
|
||||
version: 1.9.7
|
||||
'@pomade/core':
|
||||
specifier: ^0.2.3
|
||||
version: 0.2.3(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.13(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
version: 0.2.3(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.15(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@poppanator/sveltekit-svg':
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(rollup@2.80.0)(svelte@5.48.0)(svgo@3.3.2)(vite@5.4.21(@types/node@25.0.10)(lightningcss@1.32.0)(terser@5.46.0))
|
||||
@@ -93,35 +96,38 @@ importers:
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.48.0)(vite@5.4.21(@types/node@25.0.10)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.48.0)(typescript@5.9.3)(vite@5.4.21(@types/node@25.0.10)(lightningcss@1.32.0)(terser@5.46.0)))(@vite-pwa/assets-generator@0.2.6)(vite-plugin-pwa@0.21.2(@vite-pwa/assets-generator@0.2.6)(vite@5.4.21(@types/node@25.0.10)(lightningcss@1.32.0)(terser@5.46.0))(workbox-build@7.3.0)(workbox-window@7.3.0))
|
||||
'@welshman/app':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13(ed9ee8a79a580bcb9fa9bb6eb1a69558)
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15(ff026297546a8274624eb18a0ea86191)
|
||||
'@welshman/content':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/editor':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-editor@1.1.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-image@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(linkifyjs@4.3.2)(nostr-tools@2.20.0(typescript@5.9.3))(prosemirror-markdown@1.13.3)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(tiptap-markdown@0.8.10(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-editor@1.1.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-image@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(linkifyjs@4.3.2)(nostr-tools@2.20.0(typescript@5.9.3))(prosemirror-markdown@1.13.3)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(tiptap-markdown@0.8.10(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/feeds':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13(29451a19e278ea4a9cf66616f05d5557)
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15(6e55dcd4e7516745e7b0228620d35545)
|
||||
'@welshman/lib':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15
|
||||
'@welshman/net':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/router':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))
|
||||
'@welshman/signer':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/store':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(svelte@5.48.0)
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(svelte@5.48.0)
|
||||
'@welshman/util':
|
||||
specifier: ^0.8.13
|
||||
version: 0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
specifier: ^0.8.15
|
||||
version: 0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
cheerio:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
compressorjs-next:
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2
|
||||
@@ -137,9 +143,15 @@ importers:
|
||||
emoji-picker-element:
|
||||
specifier: ^1.28.1
|
||||
version: 1.28.1
|
||||
emoji-picker-element-data:
|
||||
specifier: ^1.8.0
|
||||
version: 1.8.0
|
||||
fuse.js:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0
|
||||
hono:
|
||||
specifier: ^4.12.15
|
||||
version: 4.12.15
|
||||
husky:
|
||||
specifier: ^9.1.7
|
||||
version: 9.1.7
|
||||
@@ -1096,6 +1108,12 @@ packages:
|
||||
resolution: {integrity: sha512-yUF9LhuvdIFOwjV1aG0ryzfwDiGBFk/CRLkRvrrM9dsE38SUjKsf1FDga5jxsKMu80nWcPZR9TiGGASWedoYPA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@hono/node-server@2.0.0':
|
||||
resolution: {integrity: sha512-n3GfHwwCvHCkGmOwKfxUPOlbfzuO64Sbc5XC4NGPIXxkuOnJrdgExdRKmHfF924r914WRJPT397GdqLvdYTeyQ==}
|
||||
engines: {node: '>=20'}
|
||||
peerDependencies:
|
||||
hono: ^4
|
||||
|
||||
'@humanfs/core@0.19.1':
|
||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
@@ -2150,83 +2168,83 @@ packages:
|
||||
'@vite-pwa/assets-generator':
|
||||
optional: true
|
||||
|
||||
'@welshman/app@0.8.13':
|
||||
resolution: {integrity: sha512-+mUMtt5ibotBk/susPFKXnb9jRjqvIQgWMF28poCIzse08V4kfVClJJlzepvgjqRn6Ma/takr6tNkL6eV4rlRQ==}
|
||||
'@welshman/app@0.8.15':
|
||||
resolution: {integrity: sha512-GDo6w+UI/ldnh47c5IEDYWw8nbiyhnH4abJNy/q/jLBUwJ9SuiJ7GVVvhZ+t4XEo5NEMq+y4OLZs08+abf85MQ==}
|
||||
peerDependencies:
|
||||
'@pomade/core': ^0.2.1
|
||||
'@welshman/feeds': 0.8.13
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13
|
||||
'@welshman/router': 0.8.13
|
||||
'@welshman/signer': 0.8.13
|
||||
'@welshman/store': 0.8.13
|
||||
'@welshman/util': 0.8.13
|
||||
'@welshman/feeds': 0.8.15
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15
|
||||
'@welshman/router': 0.8.15
|
||||
'@welshman/signer': 0.8.15
|
||||
'@welshman/store': 0.8.15
|
||||
'@welshman/util': 0.8.15
|
||||
svelte: ^4.0.0 || ^5.0.0
|
||||
|
||||
'@welshman/content@0.8.13':
|
||||
resolution: {integrity: sha512-6ZDKCJ2GKczAULD7P7NZ5DmxFYKw6vfxJ1jpwbQj+0l7alr2IBh8kmaQ8wM1r6n0qOhfcNqeGaaREQxC4VnuHA==}
|
||||
'@welshman/content@0.8.15':
|
||||
resolution: {integrity: sha512-5qe+6Es1r62HkVdeHJPsWkOpLjhdxBTtw3d4+Or1JXl8BgpUE2JV7e+5HQQqnPRVHt3nt14YPt0oirar5p1Fvg==}
|
||||
peerDependencies:
|
||||
nostr-tools: ^2.19.4
|
||||
|
||||
'@welshman/editor@0.8.13':
|
||||
resolution: {integrity: sha512-kr4pSjQ/TZnlyIeGo0UNNAQrTGpp0yMRUFD/LwORVLnC8UGNLwGRmFwOz0WNtCxGxFGquTlX1AkNfViWdkfXHw==}
|
||||
'@welshman/editor@0.8.15':
|
||||
resolution: {integrity: sha512-lqTLQGf54yPioBn2KQsF7F5ExWM6Co31wgGaUAhCSeUGiTzUQgMEut4/N8VB1rFZ0wqU6zyPG5jgeuhFhRJWSw==}
|
||||
peerDependencies:
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/util': 0.8.13
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/util': 0.8.15
|
||||
nostr-editor: ^1.1.1
|
||||
nostr-tools: ^2.19.4
|
||||
|
||||
'@welshman/feeds@0.8.13':
|
||||
resolution: {integrity: sha512-zjjKbGG8wQyyuTtm7/7lAGEFbreTp7IO5Y+DZXwBBu/h2/TP/C/v0J0XrshFBqs/wOOURv7vYZlf/bs2En8UIg==}
|
||||
'@welshman/feeds@0.8.15':
|
||||
resolution: {integrity: sha512-xIQDKdV6uLxOz5qJUbc/2HC6qnikgH1GPoHQwBpwKH7Lga6a7IGLOR6kvghUaPpulKcuF4MxG9gmvEHqgsQkJw==}
|
||||
peerDependencies:
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13
|
||||
'@welshman/router': 0.8.13
|
||||
'@welshman/signer': 0.8.13
|
||||
'@welshman/util': 0.8.13
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15
|
||||
'@welshman/router': 0.8.15
|
||||
'@welshman/signer': 0.8.15
|
||||
'@welshman/util': 0.8.15
|
||||
|
||||
'@welshman/lib@0.8.13':
|
||||
resolution: {integrity: sha512-fXVoe7zx+jPnqZdRMXLNOJvW+N6E708HSpNGfyBGlu1/OXg70wkEK3r9E67HsBg7pLxnl22tcOYq7r11GhpeFA==}
|
||||
'@welshman/lib@0.8.15':
|
||||
resolution: {integrity: sha512-d7o6WUSVYXOstpWTqOBDfkSyr3GOBm/UMbgFx3RXCxzib0cWm7z0w1oLWvy1N7fjHc/Jp65G2KRpT6//B9yAww==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
'@welshman/net@0.8.13':
|
||||
resolution: {integrity: sha512-k9BQA2lJI1mnQrf3pR8e3QhCluPtWSSPz2ywTDKq+/pdVXXIjrnsblHA/62d6SjCCSV/n5fONQ08YMivPzgtGA==}
|
||||
'@welshman/net@0.8.15':
|
||||
resolution: {integrity: sha512-AeJ/Vy7T6ruf1mjzzEUdH+aX5JriQKBzRn1zWZ4l8VEgxwc4w2bVte9a6aPnNJWc7JZT8ws8z+wOi4ECb6NPNA==}
|
||||
peerDependencies:
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/util': 0.8.13
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/util': 0.8.15
|
||||
|
||||
'@welshman/router@0.8.13':
|
||||
resolution: {integrity: sha512-MJh8YfHpoSsRUI96OnqxnBDoQwjqIMh8N57US0id9cd6iOlkYlVPEUeicJK8Kcl5oT0zmN13UT/4o3d7nZrqcA==}
|
||||
'@welshman/router@0.8.15':
|
||||
resolution: {integrity: sha512-3lxcCYMaPX0gFaoM1GjBRvXr4UrnPA3o/mBII2Zm3gJeFuXN3XG+REwIN6QNhvTB7syTCTwx+dRdHgvqHl9N6g==}
|
||||
peerDependencies:
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13
|
||||
'@welshman/util': 0.8.13
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15
|
||||
'@welshman/util': 0.8.15
|
||||
|
||||
'@welshman/signer@0.8.13':
|
||||
resolution: {integrity: sha512-VgyKxZhJ/Br0q4H8KPfRWAa8WC0EVUc69dxq/Bt1cl7MTBg1EbzolUJhgOgGDOVO0gAKmWYMCnjNochaQy/Wpg==}
|
||||
version: 0.8.13
|
||||
'@welshman/signer@0.8.15':
|
||||
resolution: {integrity: sha512-Y96XZtsCHz8h7NK28sSi3CX+8lGG6WhLyVNyhlEhfypAxxx8Zpfr4GlSPApvp4tvm1//YfDtXHIIZTPXbmnqvA==}
|
||||
version: 0.8.15
|
||||
peerDependencies:
|
||||
'@noble/curves': ^1.9.7
|
||||
'@noble/hashes': ^2.0.1
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13
|
||||
'@welshman/util': 0.8.13
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15
|
||||
'@welshman/util': 0.8.15
|
||||
nostr-signer-capacitor-plugin: '*'
|
||||
nostr-tools: ^2.19.4
|
||||
|
||||
'@welshman/store@0.8.13':
|
||||
resolution: {integrity: sha512-tnmbaNa8aqFVbklsMZ5y4h9xlHnbwo7o1l6xxJI0hqZnTuXD3IvN5/V58qhfZveUN/Y5Gz2MWQHFWyRBQ71ANg==}
|
||||
'@welshman/store@0.8.15':
|
||||
resolution: {integrity: sha512-3rQVhAsQ1z5tcUzkJPkzVp3iBkMrUKVoBi07AYefqlhRoddhwB2pDBVhdZYoP2kl9wVPZlPV58vlD6BTo6TEwA==}
|
||||
peerDependencies:
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13
|
||||
'@welshman/util': 0.8.13
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15
|
||||
'@welshman/util': 0.8.15
|
||||
svelte: ^4.0.0 || ^5.0.0
|
||||
|
||||
'@welshman/util@0.8.13':
|
||||
resolution: {integrity: sha512-3+CNqJjiHGXKzLOniDqAN4Oe038fV1RRjKiVP0++FDVbq8lShtdcliR7FDg/NTjhhmzivhYqdflNvqjAqOxekA==}
|
||||
'@welshman/util@0.8.15':
|
||||
resolution: {integrity: sha512-zeNWMyOtIpOqj9/hBAT8qWvnp5w/IyrcT7CmDKLkWt6NU6ZoZ3pF5duTwtOYZqcftYJaHXgohOt0RsHVPR3M7w==}
|
||||
peerDependencies:
|
||||
'@noble/curves': ^1.9.7
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/lib': 0.8.15
|
||||
nostr-tools: ^2.19.4
|
||||
|
||||
'@xml-tools/parser@1.0.11':
|
||||
@@ -2465,6 +2483,13 @@ packages:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
cheerio-select@2.1.0:
|
||||
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
||||
|
||||
cheerio@1.2.0:
|
||||
resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
chevrotain@7.1.1:
|
||||
resolution: {integrity: sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==}
|
||||
|
||||
@@ -2824,12 +2849,18 @@ packages:
|
||||
resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
emoji-picker-element-data@1.8.0:
|
||||
resolution: {integrity: sha512-VfRuRJNEDLS1JKlNS4olaqhjX5S1nnZ+ZHG73b/dV8QeZyi0yPruTPEE72EmF6XO3k/9hj3lybMIYMOYXb/57A==}
|
||||
|
||||
emoji-picker-element@1.28.1:
|
||||
resolution: {integrity: sha512-8c64IPish2PWoV9oYCo2pvuPHwIv+uK9bO0dfpPyMupDAvaWL9ZvYhWNTAR+2sx7BhfRjciImqP6CIUgNX+DMg==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
encoding-sniffer@0.2.1:
|
||||
resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==}
|
||||
|
||||
enhanced-resolve@5.20.1:
|
||||
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -2841,6 +2872,14 @@ packages:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@6.0.1:
|
||||
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@7.0.1:
|
||||
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
env-paths@2.2.1:
|
||||
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3234,6 +3273,10 @@ packages:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
|
||||
hono@4.12.15:
|
||||
resolution: {integrity: sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
hosted-git-info@2.8.9:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
|
||||
@@ -3241,6 +3284,9 @@ packages:
|
||||
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
htmlparser2@10.1.0:
|
||||
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
|
||||
|
||||
husky@9.1.7:
|
||||
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -3249,6 +3295,10 @@ packages:
|
||||
ico-endec@0.1.6:
|
||||
resolution: {integrity: sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ==}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
idb@7.1.1:
|
||||
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
|
||||
|
||||
@@ -4015,6 +4065,15 @@ packages:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
parse5-htmlparser2-tree-adapter@7.1.0:
|
||||
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
|
||||
|
||||
parse5-parser-stream@7.1.2:
|
||||
resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
|
||||
|
||||
parse5@7.3.0:
|
||||
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
|
||||
|
||||
path-exists@3.0.0:
|
||||
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -4456,6 +4515,9 @@ packages:
|
||||
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
sax@1.1.4:
|
||||
resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==}
|
||||
|
||||
@@ -4891,6 +4953,10 @@ packages:
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
undici@7.25.0:
|
||||
resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
unicode-canonical-property-names-ecmascript@2.0.1:
|
||||
resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -5009,6 +5075,15 @@ packages:
|
||||
resolution: {integrity: sha512-5ZZY1+lGq8LEKuDlg9M2RPJHlH3R7OVwyHqMcUsLKCgd9Wvf+QrFTCItkXXYPmrJn8H6gRLXbSgxLLdexiqHxw==}
|
||||
engines: {node: '>=6.0.0', npm: '>=3.10.0'}
|
||||
|
||||
whatwg-encoding@3.1.1:
|
||||
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
|
||||
engines: {node: '>=18'}
|
||||
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
|
||||
|
||||
whatwg-mimetype@4.0.0:
|
||||
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
@@ -6228,6 +6303,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@hono/node-server@2.0.0(hono@4.12.15)':
|
||||
dependencies:
|
||||
hono: 4.12.15
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
|
||||
'@humanfs/node@0.16.7':
|
||||
@@ -6637,15 +6716,15 @@ snapshots:
|
||||
|
||||
'@polka/url@1.0.0-next.29': {}
|
||||
|
||||
'@pomade/core@0.2.3(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.13(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
'@pomade/core@0.2.3(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.15(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@frostr/bifrost': 1.0.7(typescript@5.9.3)
|
||||
'@noble/hashes': 2.0.1
|
||||
'@peculiar/x509': 1.14.3
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/signer': 0.8.13(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/util': 0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/signer': 0.8.15(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/util': 0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
cbor-x: 1.6.0
|
||||
hash-wasm: 4.12.0
|
||||
nostr-tools: 2.20.0(typescript@5.9.3)
|
||||
@@ -7303,26 +7382,26 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@vite-pwa/assets-generator': 0.2.6
|
||||
|
||||
'@welshman/app@0.8.13(ed9ee8a79a580bcb9fa9bb6eb1a69558)':
|
||||
'@welshman/app@0.8.15(ff026297546a8274624eb18a0ea86191)':
|
||||
dependencies:
|
||||
'@pomade/core': 0.2.3(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.13(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/feeds': 0.8.13(29451a19e278ea4a9cf66616f05d5557)
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/router': 0.8.13(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))
|
||||
'@welshman/signer': 0.8.13(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/store': 0.8.13(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(svelte@5.48.0)
|
||||
'@welshman/util': 0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@pomade/core': 0.2.3(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.15(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/feeds': 0.8.15(6e55dcd4e7516745e7b0228620d35545)
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/router': 0.8.15(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))
|
||||
'@welshman/signer': 0.8.15(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/store': 0.8.15(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(svelte@5.48.0)
|
||||
'@welshman/util': 0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
fuse.js: 7.1.0
|
||||
svelte: 5.48.0
|
||||
throttle-debounce: 5.0.2
|
||||
|
||||
'@welshman/content@0.8.13(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
'@welshman/content@0.8.15(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@braintree/sanitize-url': 7.1.1
|
||||
nostr-tools: 2.20.0(typescript@5.9.3)
|
||||
|
||||
'@welshman/editor@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-editor@1.1.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-image@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(linkifyjs@4.3.2)(nostr-tools@2.20.0(typescript@5.9.3))(prosemirror-markdown@1.13.3)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(tiptap-markdown@0.8.10(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))))(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
'@welshman/editor@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-editor@1.1.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-image@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(linkifyjs@4.3.2)(nostr-tools@2.20.0(typescript@5.9.3))(prosemirror-markdown@1.13.3)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(tiptap-markdown@0.8.10(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))))(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.27.2(@tiptap/pm@2.27.2)
|
||||
'@tiptap/extension-code': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))
|
||||
@@ -7337,64 +7416,64 @@ snapshots:
|
||||
'@tiptap/extension-text': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))
|
||||
'@tiptap/pm': 2.27.2
|
||||
'@tiptap/suggestion': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/util': 0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/util': 0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
nostr-editor: 1.1.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-image@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)))(@tiptap/extension-link@2.27.1(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(linkifyjs@4.3.2)(nostr-tools@2.20.0(typescript@5.9.3))(prosemirror-markdown@1.13.3)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(tiptap-markdown@0.8.10(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)))
|
||||
nostr-tools: 2.20.0(typescript@5.9.3)
|
||||
tippy.js: 6.3.7
|
||||
|
||||
'@welshman/feeds@0.8.13(29451a19e278ea4a9cf66616f05d5557)':
|
||||
'@welshman/feeds@0.8.15(6e55dcd4e7516745e7b0228620d35545)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/router': 0.8.13(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))
|
||||
'@welshman/signer': 0.8.13(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/util': 0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/router': 0.8.15(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))
|
||||
'@welshman/signer': 0.8.15(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/util': 0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
trava: 1.2.1
|
||||
|
||||
'@welshman/lib@0.8.13':
|
||||
'@welshman/lib@0.8.15':
|
||||
dependencies:
|
||||
'@scure/base': 1.2.6
|
||||
'@types/events': 3.0.3
|
||||
events: 3.3.0
|
||||
|
||||
'@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)':
|
||||
'@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/util': 0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/util': 0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
events: 3.3.0
|
||||
isomorphic-ws: 5.0.0(ws@8.18.3)
|
||||
transitivePeerDependencies:
|
||||
- ws
|
||||
|
||||
'@welshman/router@0.8.13(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))':
|
||||
'@welshman/router@0.8.15(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/util': 0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/util': 0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
|
||||
'@welshman/signer@0.8.13(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
'@welshman/signer@0.8.15(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@noble/curves': 1.9.7
|
||||
'@noble/hashes': 2.0.1
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/util': 0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/util': 0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
nostr-signer-capacitor-plugin: https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1)
|
||||
nostr-tools: 2.20.0(typescript@5.9.3)
|
||||
|
||||
'@welshman/store@0.8.13(@welshman/lib@0.8.13)(@welshman/net@0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(svelte@5.48.0)':
|
||||
'@welshman/store@0.8.15(@welshman/lib@0.8.15)(@welshman/net@0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(svelte@5.48.0)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/net': 0.8.13(@welshman/lib@0.8.13)(@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/util': 0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/lib': 0.8.15
|
||||
'@welshman/net': 0.8.15(@welshman/lib@0.8.15)(@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
'@welshman/util': 0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
svelte: 5.48.0
|
||||
|
||||
'@welshman/util@0.8.13(@noble/curves@1.9.7)(@welshman/lib@0.8.13)(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
'@welshman/util@0.8.15(@noble/curves@1.9.7)(@welshman/lib@0.8.15)(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@noble/curves': 1.9.7
|
||||
'@types/ws': 8.18.1
|
||||
'@welshman/lib': 0.8.13
|
||||
'@welshman/lib': 0.8.15
|
||||
js-base64: 3.7.8
|
||||
nostr-tools: 2.20.0(typescript@5.9.3)
|
||||
nostr-wasm: 0.1.0
|
||||
@@ -7642,6 +7721,29 @@ snapshots:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
cheerio-select@2.1.0:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-select: 5.2.2
|
||||
css-what: 6.2.2
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
|
||||
cheerio@1.2.0:
|
||||
dependencies:
|
||||
cheerio-select: 2.1.0
|
||||
dom-serializer: 2.0.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
encoding-sniffer: 0.2.1
|
||||
htmlparser2: 10.1.0
|
||||
parse5: 7.3.0
|
||||
parse5-htmlparser2-tree-adapter: 7.1.0
|
||||
parse5-parser-stream: 7.1.2
|
||||
undici: 7.25.0
|
||||
whatwg-mimetype: 4.0.0
|
||||
|
||||
chevrotain@7.1.1:
|
||||
dependencies:
|
||||
regexp-to-ast: 0.5.0
|
||||
@@ -8028,10 +8130,17 @@ snapshots:
|
||||
dependencies:
|
||||
sax: 1.1.4
|
||||
|
||||
emoji-picker-element-data@1.8.0: {}
|
||||
|
||||
emoji-picker-element@1.28.1: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
encoding-sniffer@0.2.1:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
whatwg-encoding: 3.1.1
|
||||
|
||||
enhanced-resolve@5.20.1:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@@ -8041,6 +8150,10 @@ snapshots:
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
entities@6.0.1: {}
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
env-paths@2.2.1: {}
|
||||
|
||||
env-paths@3.0.0: {}
|
||||
@@ -8550,16 +8663,29 @@ snapshots:
|
||||
|
||||
he@1.2.0: {}
|
||||
|
||||
hono@4.12.15: {}
|
||||
|
||||
hosted-git-info@2.8.9: {}
|
||||
|
||||
hosted-git-info@4.1.0:
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
|
||||
htmlparser2@10.1.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
entities: 7.0.1
|
||||
|
||||
husky@9.1.7: {}
|
||||
|
||||
ico-endec@0.1.6: {}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
idb@7.1.1: {}
|
||||
|
||||
idb@8.0.3: {}
|
||||
@@ -9266,6 +9392,19 @@ snapshots:
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
parse5-htmlparser2-tree-adapter@7.1.0:
|
||||
dependencies:
|
||||
domhandler: 5.0.3
|
||||
parse5: 7.3.0
|
||||
|
||||
parse5-parser-stream@7.1.2:
|
||||
dependencies:
|
||||
parse5: 7.3.0
|
||||
|
||||
parse5@7.3.0:
|
||||
dependencies:
|
||||
entities: 6.0.1
|
||||
|
||||
path-exists@3.0.0: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
@@ -9704,6 +9843,8 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
is-regex: 1.2.1
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
sax@1.1.4: {}
|
||||
|
||||
sax@1.4.4: {}
|
||||
@@ -10229,6 +10370,8 @@ snapshots:
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
undici@7.25.0: {}
|
||||
|
||||
unicode-canonical-property-names-ecmascript@2.0.1: {}
|
||||
|
||||
unicode-match-property-ecmascript@2.0.0:
|
||||
@@ -10309,6 +10452,12 @@ snapshots:
|
||||
dependencies:
|
||||
sdp: 3.2.1
|
||||
|
||||
whatwg-encoding@3.1.1:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
|
||||
whatwg-mimetype@4.0.0: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
import path from "node:path"
|
||||
import {promises as fs} from "node:fs"
|
||||
import {fileURLToPath} from "node:url"
|
||||
|
||||
import "dotenv/config"
|
||||
import {serve} from "@hono/node-server"
|
||||
import {serveStatic} from "@hono/node-server/serve-static"
|
||||
import {loadRelay} from "@welshman/app"
|
||||
import {displayRelayUrl, normalizeRelayUrl} from "@welshman/util"
|
||||
import {load} from "cheerio"
|
||||
import {Hono} from "hono"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const BUILD_DIR = path.join(__dirname, "build")
|
||||
const INDEX_PATH = path.join(BUILD_DIR, "index.html")
|
||||
|
||||
const PORT = parseInt(process.env.PORT || "", 10) || 3000
|
||||
const HOST = process.env.HOST || "0.0.0.0"
|
||||
|
||||
let TEMPLATE_HTML = ""
|
||||
try {
|
||||
TEMPLATE_HTML = await fs.readFile(INDEX_PATH, "utf8")
|
||||
} catch (error) {
|
||||
console.error(`Unable to read ${INDEX_PATH}. Run "pnpm run build" first.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const PLATFORM_NAME = process.env.VITE_PLATFORM_NAME
|
||||
const PLATFORM_DESCRIPTION = process.env.VITE_PLATFORM_DESCRIPTION
|
||||
|
||||
// Match client-side decode logic
|
||||
const decodeRelay = url => {
|
||||
try {
|
||||
return normalizeRelayUrl(decodeURIComponent(url))
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const requestUrlFromContext = context => {
|
||||
const requestUrl = new URL(context.req.url)
|
||||
const forwardedProto = context.req.header("x-forwarded-proto")?.split(",")[0]?.trim()
|
||||
const forwardedHost = context.req.header("x-forwarded-host")?.split(",")[0]?.trim()
|
||||
|
||||
if (forwardedProto === "http" || forwardedProto === "https") {
|
||||
requestUrl.protocol = `${forwardedProto}:`
|
||||
}
|
||||
|
||||
if (forwardedHost) {
|
||||
requestUrl.host = forwardedHost
|
||||
}
|
||||
|
||||
return requestUrl
|
||||
}
|
||||
|
||||
const fetchRelayMeta = async relayUrl => {
|
||||
if (!relayUrl) return undefined
|
||||
try {
|
||||
return await loadRelay(normalizeRelayUrl(relayUrl))
|
||||
} catch (err) {
|
||||
console.error(`Failed to fetch relay metadata for ${relayUrl}:`, err)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const buildDefaultImage = requestUrl => {
|
||||
return new URL("/maskable-icon-512x512.png", requestUrl.origin).toString()
|
||||
}
|
||||
|
||||
const getMetadataForInvite = async (url, match) => {
|
||||
const relayParam = url.searchParams.get("r")
|
||||
if (!relayParam) return undefined
|
||||
|
||||
const relayMetadata = await fetchRelayMeta(relayParam)
|
||||
if (!relayMetadata) return undefined
|
||||
|
||||
const relayDisplay = displayRelayUrl(relayParam)
|
||||
const spaceName = relayMetadata.name
|
||||
const relayDescription = relayMetadata.description
|
||||
|
||||
const title = spaceName
|
||||
? `Invite to ${spaceName} on ${PLATFORM_NAME}`
|
||||
: `Invite to a Space on ${PLATFORM_NAME}`
|
||||
|
||||
const parts = []
|
||||
if (spaceName) {
|
||||
parts.push(`You are invited to join ${spaceName} on ${PLATFORM_NAME}.`)
|
||||
} else {
|
||||
parts.push(`You are invited to join a space on ${PLATFORM_NAME}.`)
|
||||
}
|
||||
|
||||
if (relayDisplay) parts.push(`Relay: ${relayDisplay}.`)
|
||||
if (relayDescription) parts.push(relayDescription)
|
||||
else parts.push(PLATFORM_DESCRIPTION)
|
||||
|
||||
const description = parts.join(" ")
|
||||
const image =
|
||||
relayMetadata.icon ||
|
||||
relayMetadata.picture ||
|
||||
relayMetadata.image ||
|
||||
buildDefaultImage(url)
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
url: url.toString(),
|
||||
site: url.origin,
|
||||
}
|
||||
}
|
||||
|
||||
const getMetadataForSpace = async (url, match) => {
|
||||
const relayParam = decodeRelay(match[1])
|
||||
if (!relayParam) return undefined
|
||||
|
||||
const relayMetadata = await fetchRelayMeta(relayParam)
|
||||
if (!relayMetadata) return undefined
|
||||
|
||||
const spaceName = relayMetadata.name || displayRelayUrl(relayParam)
|
||||
|
||||
return {
|
||||
title: `${spaceName} on ${PLATFORM_NAME}`,
|
||||
description: relayMetadata.description || PLATFORM_DESCRIPTION,
|
||||
image:
|
||||
relayMetadata.icon ||
|
||||
relayMetadata.picture ||
|
||||
relayMetadata.image ||
|
||||
buildDefaultImage(url),
|
||||
url: url.toString(),
|
||||
site: url.origin,
|
||||
}
|
||||
}
|
||||
|
||||
const getMetadataForSpaceSection = async (url, match) => {
|
||||
const spaceMeta = await getMetadataForSpace(url, match)
|
||||
if (!spaceMeta) return undefined
|
||||
|
||||
const section = match[2]
|
||||
const sectionName = section.charAt(0).toUpperCase() + section.slice(1)
|
||||
spaceMeta.title = `${sectionName} on ${spaceMeta.title}`
|
||||
return spaceMeta
|
||||
}
|
||||
|
||||
const getMetadataForSpaceItem = async (url, match) => {
|
||||
const spaceMeta = await getMetadataForSpace(url, match)
|
||||
if (!spaceMeta) return undefined
|
||||
|
||||
const section = match[2]
|
||||
let itemType = "Item"
|
||||
if (section === "calendar") itemType = "Event"
|
||||
if (section === "threads") itemType = "Thread"
|
||||
if (section === "polls") itemType = "Poll"
|
||||
if (section === "goals") itemType = "Goal"
|
||||
if (section === "classifieds") itemType = "Listing"
|
||||
|
||||
spaceMeta.title = `${itemType} on ${spaceMeta.title}`
|
||||
return spaceMeta
|
||||
}
|
||||
|
||||
const getMetadataForRoom = async (url, match) => {
|
||||
const spaceMeta = await getMetadataForSpace(url, match)
|
||||
if (!spaceMeta) return undefined
|
||||
|
||||
// Room metadata requires fetching from Nostr, which can be added later.
|
||||
spaceMeta.title = `Room on ${spaceMeta.title}`
|
||||
return spaceMeta
|
||||
}
|
||||
|
||||
const routes = [
|
||||
[/^\/join\/?$/, getMetadataForInvite],
|
||||
[/^\/spaces\/([^/]+)\/(calendar|chat|threads|polls|goals|classifieds|recent)\/?$/, getMetadataForSpaceSection],
|
||||
[/^\/spaces\/([^/]+)\/(calendar|threads|polls|goals|classifieds)\/([^/]+)\/?$/, getMetadataForSpaceItem],
|
||||
[/^\/spaces\/([^/]+)\/([^/]+)\/?$/, getMetadataForRoom],
|
||||
[/^\/spaces\/([^/]+)\/?$/, getMetadataForSpace],
|
||||
]
|
||||
|
||||
const getMetadataForRoute = async url => {
|
||||
for (const [regex, getMetadata] of routes) {
|
||||
const match = url.pathname.match(regex)
|
||||
if (match) {
|
||||
try {
|
||||
return await getMetadata(url, match)
|
||||
} catch (err) {
|
||||
console.error(`Error generating metadata for route ${url.pathname}:`, err)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const injectMeta = metadata => {
|
||||
const $ = load(TEMPLATE_HTML)
|
||||
|
||||
if (metadata.title) {
|
||||
$("title").text(metadata.title)
|
||||
$('meta[property="og:title"]').attr("content", metadata.title)
|
||||
$('meta[name="twitter:title"]').attr("content", metadata.title)
|
||||
}
|
||||
|
||||
if (metadata.description) {
|
||||
$('meta[name="description"]').attr("content", metadata.description)
|
||||
$('meta[property="og:description"]').attr("content", metadata.description)
|
||||
$('meta[name="twitter:description"]').attr("content", metadata.description)
|
||||
}
|
||||
|
||||
if (metadata.image) {
|
||||
$('meta[property="og:image"]').attr("content", metadata.image)
|
||||
$('meta[name="twitter:image"]').attr("content", metadata.image)
|
||||
}
|
||||
|
||||
if (metadata.url) {
|
||||
$('meta[property="og:url"]').attr("content", metadata.url)
|
||||
$('meta[name="twitter:site"]').attr("content", metadata.site)
|
||||
$('meta[name="twitter:url"]').attr("content", metadata.url)
|
||||
$('link[rel="canonical"]').attr("href", metadata.url)
|
||||
}
|
||||
|
||||
return $.html()
|
||||
}
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
// Only allow GET and HEAD requests
|
||||
app.use("*", async (context, next) => {
|
||||
const method = context.req.method
|
||||
if (method !== "GET" && method !== "HEAD") {
|
||||
return context.text("Method Not Allowed", 405, {Allow: "GET, HEAD"})
|
||||
}
|
||||
await next()
|
||||
})
|
||||
|
||||
// Serve static assets with appropriate caching
|
||||
app.use(
|
||||
"*",
|
||||
serveStatic({
|
||||
root: BUILD_DIR,
|
||||
onFound: (filePath, context) => {
|
||||
const isImmutable = filePath.split(path.sep).join("/").includes("/_app/immutable/")
|
||||
const cacheControl =
|
||||
path.basename(filePath) === "index.html"
|
||||
? "no-cache"
|
||||
: isImmutable
|
||||
? "public, max-age=31536000, immutable"
|
||||
: "public, max-age=3600"
|
||||
|
||||
context.header("Cache-Control", cacheControl)
|
||||
|
||||
// Immutable assets are content-hashed by Vite, so the filename is itself a
|
||||
// stable content identifier. Exposing it as an ETag lets clients that
|
||||
// revalidate explicitly (e.g. emoji-picker-element checks its data source
|
||||
// on every load) skip re-downloading large files when nothing changed.
|
||||
if (isImmutable) {
|
||||
context.header("ETag", `"${path.basename(filePath)}"`)
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
// SPA fallback for routes that don't match static files
|
||||
app.get("*", async context => {
|
||||
const requestUrl = requestUrlFromContext(context)
|
||||
|
||||
// If the path has an extension, it's likely a missing static asset, not an SPA route
|
||||
if (path.extname(requestUrl.pathname)) {
|
||||
return context.text("Not found", 404)
|
||||
}
|
||||
|
||||
const metadata = await getMetadataForRoute(requestUrl)
|
||||
const html = metadata ? injectMeta(metadata) : TEMPLATE_HTML
|
||||
|
||||
return context.html(html, 200, {
|
||||
"Cache-Control": metadata ? "no-store" : "no-cache",
|
||||
})
|
||||
})
|
||||
|
||||
serve(
|
||||
{
|
||||
fetch: app.fetch,
|
||||
hostname: HOST,
|
||||
port: PORT,
|
||||
},
|
||||
() => {
|
||||
console.log(`Flotilla server running on http://${HOST}:${PORT}`)
|
||||
},
|
||||
)
|
||||
+24
-11
@@ -2,6 +2,16 @@
|
||||
|
||||
@config "../tailwind.config.js";
|
||||
|
||||
/* root */
|
||||
|
||||
:root {
|
||||
font-family: Lato;
|
||||
--sait: var(--safe-area-inset-top, env(safe-area-inset-top));
|
||||
--saib: var(--safe-area-inset-bottom, env(safe-area-inset-bottom));
|
||||
--sail: var(--safe-area-inset-left, env(safe-area-inset-left));
|
||||
--sair: var(--safe-area-inset-right, env(safe-area-inset-right));
|
||||
}
|
||||
|
||||
@utility pt-sai {
|
||||
padding-top: var(--sait);
|
||||
}
|
||||
@@ -22,16 +32,6 @@
|
||||
@apply pl-sai pr-sai;
|
||||
}
|
||||
|
||||
/* root */
|
||||
|
||||
:root {
|
||||
font-family: Lato;
|
||||
--sait: var(--safe-area-inset-top, env(safe-area-inset-top));
|
||||
--saib: var(--safe-area-inset-bottom, env(safe-area-inset-bottom));
|
||||
--sail: var(--safe-area-inset-left, env(safe-area-inset-left));
|
||||
--sair: var(--safe-area-inset-right, env(safe-area-inset-right));
|
||||
}
|
||||
|
||||
@utility py-sai {
|
||||
@apply pt-sai pb-sai;
|
||||
}
|
||||
@@ -235,6 +235,7 @@
|
||||
|
||||
:root {
|
||||
font-family: Lato;
|
||||
text-size-adjust: 100%;
|
||||
--sait: var(--safe-area-inset-top, env(safe-area-inset-top));
|
||||
--saib: var(--safe-area-inset-bottom, env(safe-area-inset-bottom));
|
||||
--sail: var(--safe-area-inset-left, env(safe-area-inset-left));
|
||||
@@ -332,7 +333,7 @@
|
||||
|
||||
.input-editor .tiptap {
|
||||
--tiptap-object-bg: var(--color-base-200);
|
||||
@apply input h-auto p-[.65rem];
|
||||
@apply input block h-auto p-[.65rem];
|
||||
}
|
||||
|
||||
/* link-content, based on tiptap */
|
||||
@@ -416,12 +417,24 @@ progress[value]::-webkit-progress-value {
|
||||
@apply md:left-[calc(18.5rem+var(--sail))];
|
||||
}
|
||||
|
||||
.left-content-full {
|
||||
@apply md:left-[calc(3.5rem+var(--sail))];
|
||||
}
|
||||
|
||||
/* Keyboard open state adjustments */
|
||||
|
||||
body.keyboard-open {
|
||||
--saib: 0px;
|
||||
}
|
||||
|
||||
body.keyboard-open .hide-on-keyboard {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.keyboard-open .chat__compose {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* chat view */
|
||||
|
||||
.chat__compose {
|
||||
|
||||
+7
-4
@@ -2,15 +2,18 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{NAME}</title>
|
||||
<link rel="canonical" href="{URL}" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
|
||||
<meta name="theme-color" content="{ACCENT}" />
|
||||
<meta name="description" content="{DESCRIPTION}" />
|
||||
<meta name="og:url" content="{URL}" />
|
||||
<meta name="og:type" content="website" />
|
||||
<meta name="og:title" content="{NAME}" />
|
||||
<meta name="og:description" content="{DESCRIPTION}" />
|
||||
<meta property="og:url" content="{URL}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="{NAME}" />
|
||||
<meta property="og:description" content="{DESCRIPTION}" />
|
||||
<meta property="og:image" content="" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:site" content="{URL}" />
|
||||
<meta name="twitter:title" content="{NAME}" />
|
||||
|
||||
+18
-1
@@ -6,11 +6,13 @@ export type VoiceSession = {
|
||||
url: string
|
||||
h: string
|
||||
room: LiveKitRoom
|
||||
muted: boolean
|
||||
cameraOn: boolean
|
||||
screenShareOn: boolean
|
||||
}
|
||||
|
||||
/** Mic mute state is separate so toggling it does not re-render video tiles. */
|
||||
export const voiceMicMuted = writable(true)
|
||||
|
||||
export type Pubkey = string
|
||||
|
||||
export type VoiceParticipant = {pubkey?: Pubkey; identity: string}
|
||||
@@ -41,6 +43,21 @@ export const participantKey = (p: VoiceParticipant) => p.pubkey ?? p.identity
|
||||
|
||||
export const speakingParticipants = writable<VoiceParticipant[]>([])
|
||||
|
||||
export const participantMediaState = writable<Map<string, {muted: boolean; cameraOn: boolean}>>(
|
||||
new Map(),
|
||||
)
|
||||
|
||||
export const mediaStateByIdentity = derived(
|
||||
[participantMediaState, currentVoiceSession, voiceMicMuted],
|
||||
([$media, $session, $micMuted]) =>
|
||||
(identity: string) => {
|
||||
if ($session?.room.localParticipant.identity === identity) {
|
||||
return {muted: $micMuted, cameraOn: $session.cameraOn}
|
||||
}
|
||||
return $media.get(identity) ?? {muted: true, cameraOn: false}
|
||||
},
|
||||
)
|
||||
|
||||
export const isParticipantSpeaking = derived(
|
||||
speakingParticipants,
|
||||
$participants => (p: VoiceParticipant) =>
|
||||
|
||||
+44
-7
@@ -6,14 +6,16 @@ import {
|
||||
DisconnectReason,
|
||||
LocalParticipant,
|
||||
LocalTrackPublication,
|
||||
Participant,
|
||||
Room as LiveKitRoom,
|
||||
RoomEvent,
|
||||
Track,
|
||||
TrackPublication,
|
||||
supportsAudioOutputSelection,
|
||||
type AudioCaptureOptions,
|
||||
} from "livekit-client"
|
||||
import {derived, get} from "svelte/store"
|
||||
import {map, removeUndefined, uniqBy} from "@welshman/lib"
|
||||
import {map, not, removeUndefined, uniqBy} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util"
|
||||
import {signer} from "@welshman/app"
|
||||
@@ -22,8 +24,10 @@ import {AbortError, whenAborted, whenTimeout} from "$lib/util"
|
||||
import {
|
||||
currentVoiceRoom,
|
||||
currentVoiceSession,
|
||||
voiceMicMuted,
|
||||
participantFromLiveKitIdentity,
|
||||
participantKey,
|
||||
participantMediaState,
|
||||
participantPubkeyMap,
|
||||
pubkeyFromLiveKitIdentity,
|
||||
speakingParticipants,
|
||||
@@ -89,6 +93,27 @@ const deleteParticipant = (identity: string) => {
|
||||
next.delete(identity)
|
||||
return next
|
||||
})
|
||||
participantMediaState.update(m => {
|
||||
if (!m.has(identity)) return m
|
||||
const next = new Map(m)
|
||||
next.delete(identity)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const syncParticipantMedia = (participant: Participant) => {
|
||||
const state = {muted: !participant.isMicrophoneEnabled, cameraOn: participant.isCameraEnabled}
|
||||
participantMediaState.update(m => {
|
||||
const prev = m.get(participant.identity)
|
||||
if (prev?.muted === state.muted && prev?.cameraOn === state.cameraOn) return m
|
||||
const next = new Map(m)
|
||||
next.set(participant.identity, state)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const onParticipantMediaChanged = (_publication: TrackPublication, participant: Participant) => {
|
||||
syncParticipantMedia(participant)
|
||||
}
|
||||
|
||||
const fetchLivekitToken = async (
|
||||
@@ -173,6 +198,7 @@ const setUpMicrophone = async (
|
||||
|
||||
const onRoomDisconnected = (reason?: DisconnectReason) => {
|
||||
videoPrimaryTileKey.set(undefined)
|
||||
voiceMicMuted.set(true)
|
||||
currentVoiceSession.set(undefined)
|
||||
resetVideoCallLayout()
|
||||
if (reason !== undefined && reason !== DisconnectReason.CLIENT_INITIATED) {
|
||||
@@ -185,6 +211,7 @@ const onRoomDisconnected = (reason?: DisconnectReason) => {
|
||||
}
|
||||
speakingParticipants.set([])
|
||||
participantPubkeyMap.set(new Map())
|
||||
participantMediaState.set(new Map())
|
||||
}
|
||||
|
||||
const onTrackSubscribed = (track: Track) => {
|
||||
@@ -214,8 +241,9 @@ const playJoinSound = () => {
|
||||
audio.play().catch(() => {})
|
||||
}
|
||||
|
||||
const onParticipantConnected = (participant: {identity: string}) => {
|
||||
const onParticipantConnected = (participant: Participant) => {
|
||||
addParticipant(participant.identity)
|
||||
syncParticipantMedia(participant)
|
||||
playJoinSound()
|
||||
}
|
||||
|
||||
@@ -273,6 +301,11 @@ export const joinVoiceRoom = async (
|
||||
liveKitRoom.on(RoomEvent.TrackUnsubscribed, onTrackUnsubscribed)
|
||||
liveKitRoom.on(RoomEvent.LocalTrackUnpublished, onLocalTrackUnpublished)
|
||||
liveKitRoom.on(RoomEvent.ActiveSpeakersChanged, onActiveSpeakersChanged)
|
||||
liveKitRoom.on(RoomEvent.TrackMuted, onParticipantMediaChanged)
|
||||
liveKitRoom.on(RoomEvent.TrackUnmuted, onParticipantMediaChanged)
|
||||
liveKitRoom.on(RoomEvent.TrackPublished, onParticipantMediaChanged)
|
||||
liveKitRoom.on(RoomEvent.TrackUnpublished, onParticipantMediaChanged)
|
||||
liveKitRoom.on(RoomEvent.LocalTrackPublished, onParticipantMediaChanged)
|
||||
|
||||
try {
|
||||
await Promise.race([
|
||||
@@ -288,18 +321,21 @@ export const joinVoiceRoom = async (
|
||||
}
|
||||
|
||||
participantPubkeyMap.set(new Map())
|
||||
participantMediaState.set(new Map())
|
||||
addParticipant(liveKitRoom.localParticipant.identity)
|
||||
syncParticipantMedia(liveKitRoom.localParticipant)
|
||||
for (const p of liveKitRoom.remoteParticipants.values()) {
|
||||
addParticipant(p.identity)
|
||||
syncParticipantMedia(p)
|
||||
}
|
||||
|
||||
const muted = await setUpMicrophone(startMuted, preferredMicId, liveKitRoom.localParticipant)
|
||||
|
||||
voiceMicMuted.set(muted)
|
||||
currentVoiceSession.set({
|
||||
url,
|
||||
h,
|
||||
room: liveKitRoom,
|
||||
muted,
|
||||
cameraOn: false,
|
||||
screenShareOn: false,
|
||||
})
|
||||
@@ -339,11 +375,13 @@ export const leaveVoiceRoom = async () => {
|
||||
|
||||
voiceState.set(VoiceState.Disconnected)
|
||||
videoPrimaryTileKey.set(undefined)
|
||||
voiceMicMuted.set(true)
|
||||
currentVoiceSession.set(undefined)
|
||||
resetVideoCallLayout()
|
||||
session.room.disconnect()
|
||||
speakingParticipants.set([])
|
||||
participantPubkeyMap.set(new Map())
|
||||
participantMediaState.set(new Map())
|
||||
}
|
||||
|
||||
export const rejoinVoiceRoom = async (): Promise<void> => {
|
||||
@@ -356,18 +394,17 @@ export const toggleMute = async () => {
|
||||
const session = get(currentVoiceSession)
|
||||
if (!session) return
|
||||
|
||||
const muted = !session.muted
|
||||
if (muted) {
|
||||
voiceMicMuted.update(not)
|
||||
if (get(voiceMicMuted)) {
|
||||
// Disable and re-enable microphone to trigger permission prompt
|
||||
session.room.localParticipant.setMicrophoneEnabled(false)
|
||||
currentVoiceSession.set({...session, muted})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await session.room.localParticipant.setMicrophoneEnabled(true)
|
||||
currentVoiceSession.set({...session, muted})
|
||||
} catch (e) {
|
||||
voiceMicMuted.set(true)
|
||||
pushToast({theme: "error", message: "Could not access microphone"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,15 +19,17 @@
|
||||
const end = $derived(parseInt(meta.end))
|
||||
</script>
|
||||
|
||||
<div class="flex grow flex-wrap justify-between gap-2">
|
||||
<p class="text-xl">{meta.title || meta.name}</p>
|
||||
<div class="flex flex-col justify-between gap-1">
|
||||
<p class="text-lg">{meta.title || meta.name}</p>
|
||||
{#if !isNaN(start) && !isNaN(end)}
|
||||
{@const startDateDisplay = formatTimestampAsDate(start)}
|
||||
{@const endDateDisplay = formatTimestampAsDate(end)}
|
||||
{@const isSingleDay = startDateDisplay === endDateDisplay}
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<Icon icon={ClockCircle} size={4} />
|
||||
<span class="hidden sm:block">{formatTimestampAsDate(start)}</span>
|
||||
<div class="flex flex-wrap gap-2 text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon={ClockCircle} size={4} />
|
||||
{formatTimestampAsDate(start)}
|
||||
</div>
|
||||
{formatTimestampAsTime(start)} — {isSingleDay
|
||||
? formatTimestampAsTime(end)
|
||||
: formatTimestamp(end)}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
import ChatComposeEdit from "@app/components/ChatComposeEdit.svelte"
|
||||
import ChatComposeParent from "@app/components/ChatComposeParent.svelte"
|
||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||
import {userSettingsValues, deriveChat} from "@app/core/state"
|
||||
import {userSettingsValues, deriveChat, makeChatId} from "@app/core/state"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {DraftKey} from "@app/util/drafts"
|
||||
import {makeDelete, prependParent} from "@app/core/commands"
|
||||
@@ -66,8 +66,9 @@
|
||||
|
||||
const {pubkeys, info}: Props = $props()
|
||||
|
||||
const chat = deriveChat(pubkeys)
|
||||
const draftKey = new DraftKey<{content?: string | object}>(`dm:${$chat?.id}`)
|
||||
const chatId = makeChatId(pubkeys)
|
||||
const chat = deriveChat(chatId)
|
||||
const draftKey = new DraftKey<{content?: string | object}>(`dm:${chatId}`)
|
||||
const others = remove($pubkey!, pubkeys)
|
||||
const missingRelayLists = $derived(others.filter(pk => !$messagingRelayListsByPubkey.has(pk)))
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
truncate,
|
||||
renderAsHtml,
|
||||
isText,
|
||||
isEmail,
|
||||
isEmoji,
|
||||
isTopic,
|
||||
isCode,
|
||||
@@ -26,6 +27,7 @@
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ContentToken from "@app/components/ContentToken.svelte"
|
||||
import ContentEmoji from "@app/components/ContentEmoji.svelte"
|
||||
import ContentEmail from "@app/components/ContentEmail.svelte"
|
||||
import ContentCode from "@app/components/ContentCode.svelte"
|
||||
import ContentLinkInline from "@app/components/ContentLinkInline.svelte"
|
||||
import ContentLinkBlock from "@app/components/ContentLinkBlock.svelte"
|
||||
@@ -159,6 +161,8 @@
|
||||
<ContentTopic value={parsed.value} />
|
||||
{:else if isEmoji(parsed)}
|
||||
<ContentEmoji value={parsed.value} />
|
||||
{:else if isEmail(parsed)}
|
||||
<ContentEmail value={parsed.value} />
|
||||
{:else if isCode(parsed)}
|
||||
<ContentCode
|
||||
value={parsed.value}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
|
||||
export let value: string
|
||||
</script>
|
||||
|
||||
<Link external href="mailto:{value}">
|
||||
<Icon icon={LinkRound} size={3} />
|
||||
{value}
|
||||
</Link>
|
||||
@@ -7,6 +7,7 @@
|
||||
renderAsHtml,
|
||||
isText,
|
||||
isEmoji,
|
||||
isEmail,
|
||||
isTopic,
|
||||
isCode,
|
||||
isCashu,
|
||||
@@ -24,6 +25,7 @@
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ContentToken from "@app/components/ContentToken.svelte"
|
||||
import ContentEmoji from "@app/components/ContentEmoji.svelte"
|
||||
import ContentEmail from "@app/components/ContentEmail.svelte"
|
||||
import ContentCode from "@app/components/ContentCode.svelte"
|
||||
import ContentLinkInline from "@app/components/ContentLinkInline.svelte"
|
||||
import ContentNewline from "@app/components/ContentNewline.svelte"
|
||||
@@ -109,6 +111,8 @@
|
||||
<ContentTopic value={parsed.value} />
|
||||
{:else if isEmoji(parsed)}
|
||||
<ContentEmoji value={parsed.value} />
|
||||
{:else if isEmail(parsed)}
|
||||
<ContentEmail value={parsed.value} />
|
||||
{:else if isCode(parsed)}
|
||||
<ContentCode
|
||||
value={parsed.value}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
let popover: Instance | undefined = $state()
|
||||
</script>
|
||||
|
||||
<Button class="join rounded-full">
|
||||
<div class="join items-center rounded-full">
|
||||
{#if ENABLE_ZAPS && !hideZap}
|
||||
<ZapButton {url} {event} class="btn join-item btn-neutral btn-xs">
|
||||
<Icon icon={Bolt} size={4} />
|
||||
@@ -52,6 +52,7 @@
|
||||
<Icon icon={SmileCircle} size={4} />
|
||||
</EmojiButton>
|
||||
<Tippy
|
||||
class="flex"
|
||||
bind:popover
|
||||
component={EventMenu}
|
||||
props={{url, noun, event, customActions, onClick: hidePopover}}
|
||||
@@ -60,4 +61,4 @@
|
||||
<Icon icon={MenuDots} size={4} />
|
||||
</Button>
|
||||
</Tippy>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import ProgressBar from "@app/components/ProgressBar.svelte"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {PLATFORM_NAME} from "@app/core/state"
|
||||
|
||||
@@ -22,9 +23,11 @@
|
||||
secret: string
|
||||
next: () => unknown
|
||||
submitText?: string
|
||||
step?: number
|
||||
totalSteps?: number
|
||||
}
|
||||
|
||||
const {secret, next, submitText = "Continue"}: Props = $props()
|
||||
const {secret, next, submitText = "Continue", step, totalSteps}: Props = $props()
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
@@ -150,6 +153,9 @@
|
||||
</Button>
|
||||
</div>
|
||||
</ModalBody>
|
||||
{#if step && totalSteps}
|
||||
<ProgressBar current={step} total={totalSteps} />
|
||||
{/if}
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import LogInOTP from "@app/components/LogInOTP.svelte"
|
||||
import LogInSelect from "@app/components/LogInSelect.svelte"
|
||||
import {deleteDeactivatedPomadeSessions, loginWithPomade} from "@app/util/pomade"
|
||||
import {getPomadeLoginFailureMessage, POMADE_NETWORK_ERROR_MESSAGE} from "@app/util/pomadeErrors"
|
||||
import {pushModal, clearModals} from "@app/util/modal"
|
||||
import {setChecked} from "@app/util/notifications"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
@@ -44,7 +45,7 @@
|
||||
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Sorry, we were unable to log you in.",
|
||||
message: getPomadeLoginFailureMessage(messages),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -64,10 +65,17 @@
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: "Sorry, we were unable to log you in.",
|
||||
message: getPomadeLoginFailureMessage(res.messages),
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error)
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: POMADE_NETWORK_ERROR_MESSAGE,
|
||||
})
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
@@ -90,7 +98,7 @@
|
||||
{#snippet input()}
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<Icon icon={Letter} />
|
||||
<input bind:value={email} />
|
||||
<input type="email" bind:value={email} />
|
||||
</label>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import LogInOTPConfirm from "@app/components/LogInOTPConfirm.svelte"
|
||||
import {POMADE_NETWORK_ERROR_MESSAGE} from "@app/util/pomadeErrors"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
|
||||
@@ -35,11 +36,20 @@
|
||||
if (ok) {
|
||||
pushModal(LogInOTPConfirm, {email, peersByPrefix})
|
||||
} else {
|
||||
console.error("Pomade challenge request failed during OTP login")
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: "Sorry, we were unable to request a login code.",
|
||||
message: POMADE_NETWORK_ERROR_MESSAGE,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: POMADE_NETWORK_ERROR_MESSAGE,
|
||||
})
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
@@ -61,7 +71,7 @@
|
||||
{#snippet input()}
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<Icon icon={Letter} />
|
||||
<input bind:value={email} />
|
||||
<input type="email" bind:value={email} />
|
||||
</label>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import StringMultiInput from "@lib/components/StringMultiInput.svelte"
|
||||
import LogInSelect from "@app/components/LogInSelect.svelte"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {setChecked} from "@app/util/notifications"
|
||||
import {pushModal, clearModals} from "@app/util/modal"
|
||||
import {setChecked} from "@app/util/notifications"
|
||||
import {deleteDeactivatedPomadeSessions, loginWithPomade} from "@app/util/pomade"
|
||||
import {getPomadeLoginFailureMessage, POMADE_NETWORK_ERROR_MESSAGE} from "@app/util/pomadeErrors"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
|
||||
type Props = {
|
||||
email: string
|
||||
@@ -44,7 +45,7 @@
|
||||
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Sorry, we were unable to log you in.",
|
||||
message: getPomadeLoginFailureMessage(messages),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -64,10 +65,17 @@
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: "Sorry, we were unable to log you in.",
|
||||
message: getPomadeLoginFailureMessage(res.messages),
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error)
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: POMADE_NETWORK_ERROR_MESSAGE,
|
||||
})
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import Profile from "@app/components/Profile.svelte"
|
||||
import {deleteDeactivatedPomadeSessions, loginWithPomade} from "@app/util/pomade"
|
||||
import {getPomadeLoginFailureMessage, POMADE_NETWORK_ERROR_MESSAGE} from "@app/util/pomadeErrors"
|
||||
import {setChecked} from "@app/util/notifications"
|
||||
import {clearModals} from "@app/util/modal"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
@@ -46,9 +47,16 @@
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: "Sorry, we were unable to log you in.",
|
||||
message: getPomadeLoginFailureMessage(res.messages),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error)
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: POMADE_NETWORK_ERROR_MESSAGE,
|
||||
})
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -13,13 +13,16 @@
|
||||
|
||||
const onClick = () => goToSpace(url)
|
||||
|
||||
const path = makeSpacePath(url)
|
||||
|
||||
const display = $derived(deriveRelayDisplay(url))
|
||||
</script>
|
||||
|
||||
<PrimaryNavItem
|
||||
href={path}
|
||||
onclick={onClick}
|
||||
title={$display}
|
||||
class="tooltip-right"
|
||||
notification={$notifications.has(makeSpacePath(url))}>
|
||||
notification={$notifications.has(path)}>
|
||||
<RelayIcon {url} size={10} class="rounded-full" />
|
||||
</PrimaryNavItem>
|
||||
|
||||
@@ -23,9 +23,10 @@
|
||||
onsubmit: (values: Values) => void
|
||||
isSignup?: boolean
|
||||
footer: Snippet
|
||||
progressBar?: Snippet
|
||||
}
|
||||
|
||||
const {initialValues, isSignup, onsubmit, footer}: Props = $props()
|
||||
const {initialValues, isSignup, onsubmit, footer, progressBar}: Props = $props()
|
||||
|
||||
const values = $state(initialValues)
|
||||
|
||||
@@ -103,6 +104,9 @@
|
||||
</Field>
|
||||
{/if}
|
||||
</ModalBody>
|
||||
{#if progressBar}
|
||||
{@render progressBar()}
|
||||
{/if}
|
||||
<ModalFooter>
|
||||
{@render footer()}
|
||||
</ModalFooter>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
const {current, total}: {current: number; total: number} = $props()
|
||||
</script>
|
||||
|
||||
<div class="flex w-full">
|
||||
{#each Array(total) as _, i}
|
||||
<div class="h-1 flex-1 transition-colors {i < current ? 'bg-primary' : 'bg-base-300'}"></div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -19,12 +19,12 @@
|
||||
|
||||
<div class="col-4 text-left">
|
||||
<div class="col-2">
|
||||
<div class="relative flex gap-4">
|
||||
<div class="relative flex gap-2 sm:gap-4">
|
||||
<div class="relative">
|
||||
<div class="avatar relative">
|
||||
<div
|
||||
class="center flex! h-12 w-12 min-w-12 rounded-full border-2 border-solid border-base-300 bg-base-300">
|
||||
<RelayIcon {url} />
|
||||
<RelayIcon {url} size={10} />
|
||||
</div>
|
||||
</div>
|
||||
{#if $rooms.includes(url)}
|
||||
@@ -36,13 +36,11 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h2 class="ellipsize whitespace-nowrap text-xl">
|
||||
<RelayName {url} />
|
||||
</h2>
|
||||
<p class="text-sm opacity-75">{url}</p>
|
||||
<RelayName {url} class="ellipsize whitespace-nowrap text-lg sm:text-xl" />
|
||||
<p class="text-xs sm:text-sm opacity-75">{url}</p>
|
||||
</div>
|
||||
</div>
|
||||
<RelayDescription {url} />
|
||||
<RelayDescription {url} class="text-sm sm:text-md" />
|
||||
</div>
|
||||
{#if !hideFavorites && $favorited.size > 0}
|
||||
<div class="row-2 card2 card2-sm bg-alt">
|
||||
|
||||
@@ -68,8 +68,6 @@
|
||||
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||
const tags = ed.storage.nostr.getEditorTags()
|
||||
|
||||
if (!content) return
|
||||
|
||||
onSubmit({content, tags})
|
||||
|
||||
draftKey?.clear()
|
||||
|
||||
@@ -62,9 +62,10 @@
|
||||
|
||||
const flows = {
|
||||
email: {
|
||||
start: () => pushModal(SignUpEmail, {next: flows.email.profile}),
|
||||
profile: () => pushModal(SignUpProfile, {next: flows.email.complete}),
|
||||
complete: () => pushModal(SignUpComplete, {next: flows.email.finalize}),
|
||||
start: () => pushModal(SignUpEmail, {next: flows.email.profile, step: 1, totalSteps: 3}),
|
||||
profile: () => pushModal(SignUpProfile, {next: flows.email.complete, step: 2, totalSteps: 3}),
|
||||
complete: () =>
|
||||
pushModal(SignUpComplete, {next: flows.email.finalize, step: 3, totalSteps: 3}),
|
||||
finalize: () => {
|
||||
const email = getKey<string>("signup.email")!
|
||||
const clientOptions = getKey<ClientOptions>("signup.clientOptions")!
|
||||
@@ -74,9 +75,10 @@
|
||||
},
|
||||
},
|
||||
nostr: {
|
||||
start: () => pushModal(SignUpProfile, {next: flows.nostr.key}),
|
||||
key: () => pushModal(SignUpKey, {next: flows.nostr.complete}),
|
||||
complete: () => pushModal(SignUpComplete, {next: flows.nostr.finalize}),
|
||||
start: () => pushModal(SignUpProfile, {next: flows.nostr.key, step: 1, totalSteps: 3}),
|
||||
key: () => pushModal(SignUpKey, {next: flows.nostr.complete, step: 2, totalSteps: 3}),
|
||||
complete: () =>
|
||||
pushModal(SignUpComplete, {next: flows.nostr.finalize, step: 3, totalSteps: 3}),
|
||||
finalize: () => {
|
||||
const secret = getKey<string>("signup.secret")!
|
||||
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import ProgressBar from "@app/components/ProgressBar.svelte"
|
||||
|
||||
type Props = {
|
||||
next: () => void
|
||||
step?: number
|
||||
totalSteps?: number
|
||||
}
|
||||
|
||||
const {next}: Props = $props()
|
||||
const {next, step, totalSteps}: Props = $props()
|
||||
|
||||
const back = () => history.back()
|
||||
</script>
|
||||
@@ -33,6 +36,9 @@
|
||||
on groups you've already joined. Click below to get started!
|
||||
</p>
|
||||
</ModalBody>
|
||||
{#if step && totalSteps}
|
||||
<ProgressBar current={step} total={totalSteps} />
|
||||
{/if}
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
|
||||
@@ -18,14 +18,17 @@
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import SignUpEmailConfirm from "@app/components/SignUpEmailConfirm.svelte"
|
||||
import ProgressBar from "@app/components/ProgressBar.svelte"
|
||||
import {pushToast, popToast} from "@app/util/toast"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
|
||||
type Props = {
|
||||
next: () => void
|
||||
step?: number
|
||||
totalSteps?: number
|
||||
}
|
||||
|
||||
const {next}: Props = $props()
|
||||
const {next, step, totalSteps}: Props = $props()
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
@@ -81,7 +84,7 @@
|
||||
setKey("signup.clientOptions", clientOptions)
|
||||
|
||||
popToast(toastId)
|
||||
pushModal(SignUpEmailConfirm, {next})
|
||||
pushModal(SignUpEmailConfirm, {next, step, totalSteps})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
@@ -120,7 +123,7 @@
|
||||
{#snippet input()}
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<Icon icon={Letter} />
|
||||
<input bind:value={email} />
|
||||
<input type="email" bind:value={email} />
|
||||
</label>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
@@ -134,8 +137,14 @@
|
||||
<input type="password" bind:value={password} />
|
||||
</label>
|
||||
{/snippet}
|
||||
{#snippet info()}
|
||||
Must be at least 12 characters long.
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
</ModalBody>
|
||||
{#if step && totalSteps}
|
||||
<ProgressBar current={step} total={totalSteps} />
|
||||
{/if}
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
|
||||
@@ -15,12 +15,15 @@
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import ProgressBar from "@app/components/ProgressBar.svelte"
|
||||
|
||||
type Props = {
|
||||
next: () => void
|
||||
step?: number
|
||||
totalSteps?: number
|
||||
}
|
||||
|
||||
const {next}: Props = $props()
|
||||
const {next, step, totalSteps}: Props = $props()
|
||||
|
||||
const email = getKey<string>("signup.email")
|
||||
|
||||
@@ -61,6 +64,9 @@
|
||||
above.
|
||||
</p>
|
||||
</ModalBody>
|
||||
{#if step && totalSteps}
|
||||
<ProgressBar current={step} total={totalSteps} />
|
||||
{/if}
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back} disabled={loading}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
type Props = {
|
||||
next: () => void
|
||||
step?: number
|
||||
totalSteps?: number
|
||||
}
|
||||
|
||||
const {next}: Props = $props()
|
||||
const {next, step, totalSteps}: Props = $props()
|
||||
|
||||
const secret = getKey<string>("signup.secret")!
|
||||
</script>
|
||||
|
||||
<KeyDownload {secret} {next} />
|
||||
<KeyDownload {secret} {next} {step} {totalSteps} />
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
import {getKey, setKey} from "@lib/implicit"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Modal from "@lib/components/Modal.svelte"
|
||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||
import ProfileEditForm from "@app/components/ProfileEditForm.svelte"
|
||||
import ProgressBar from "@app/components/ProgressBar.svelte"
|
||||
|
||||
type Props = {
|
||||
next: () => void
|
||||
step?: number
|
||||
totalSteps?: number
|
||||
}
|
||||
|
||||
const {next}: Props = $props()
|
||||
const {next, step, totalSteps}: Props = $props()
|
||||
|
||||
const profile = getKey<Profile>("signup.profile")!
|
||||
|
||||
@@ -27,19 +28,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal>
|
||||
<ModalBody>
|
||||
<ProfileEditForm isSignup {initialValues} {onsubmit}>
|
||||
{#snippet footer()}
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button class="btn btn-primary" type="submit">
|
||||
Create Account
|
||||
<Icon icon={AltArrowRight} />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</ProfileEditForm>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
<ProfileEditForm isSignup {initialValues} {onsubmit}>
|
||||
{#snippet footer()}
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button class="btn btn-primary" type="submit">
|
||||
Create Account
|
||||
<Icon icon={AltArrowRight} />
|
||||
</Button>
|
||||
{/snippet}
|
||||
{#snippet progressBar()}
|
||||
{#if step && totalSteps}
|
||||
<ProgressBar current={step} total={totalSteps} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ProfileEditForm>
|
||||
|
||||
@@ -25,6 +25,16 @@
|
||||
const {url} = $props()
|
||||
|
||||
const authError = deriveRelayAuthError(url)
|
||||
let networkError = $state(false)
|
||||
const isExplicitAuthError = $derived(
|
||||
$authError &&
|
||||
!(
|
||||
$authError.toLowerCase().includes("failed") ||
|
||||
$authError.toLowerCase().includes("timeout") ||
|
||||
$authError.toLowerCase().includes("network")
|
||||
),
|
||||
)
|
||||
const isGenericError = $derived(networkError || ($authError && !isExplicitAuthError))
|
||||
|
||||
const back = () => history.back()
|
||||
const copyInvite = () => clip(invite)
|
||||
@@ -70,8 +80,14 @@
|
||||
])
|
||||
|
||||
claim = getTagValue("claim", event?.tags || []) || ""
|
||||
} catch {
|
||||
} catch (err) {
|
||||
claim = ""
|
||||
if (
|
||||
(err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) ||
|
||||
!navigator.onLine
|
||||
) {
|
||||
networkError = true
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
@@ -92,7 +108,11 @@
|
||||
<p class="center">
|
||||
<Spinner {loading}>Requesting an invite link...</Spinner>
|
||||
</p>
|
||||
{:else if $authError}
|
||||
{:else if isGenericError}
|
||||
<p class="center text-center">
|
||||
Unable to reach the relay. Please check your connection and try again.
|
||||
</p>
|
||||
{:else if isExplicitAuthError}
|
||||
<p class="center">Oops! It looks like you're not a member of this relay.</p>
|
||||
{:else}
|
||||
<div class="flex flex-col items-center gap-6">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {derived} from "svelte/store"
|
||||
import {displayRelayUrl, EVENT_TIME, ZAP_GOAL, THREAD, CLASSIFIED, POLL} from "@welshman/util"
|
||||
import {deriveRelay, deriveRelayDisplay, createSearch, pubkey} from "@welshman/app"
|
||||
import {deriveRelay, createSearch, pubkey} from "@welshman/app"
|
||||
import {fly} from "@lib/transition"
|
||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
|
||||
@@ -65,7 +65,6 @@
|
||||
const {url} = $props()
|
||||
|
||||
const relay = deriveRelay(url)
|
||||
const display = deriveRelayDisplay(url)
|
||||
const chatPath = makeSpacePath(url, "chat")
|
||||
const goalsPath = makeSpacePath(url, "goals")
|
||||
const threadsPath = makeSpacePath(url, "threads")
|
||||
@@ -144,9 +143,7 @@
|
||||
class="relative flex w-full flex-col rounded-xl p-3 transition-all hover:bg-base-100"
|
||||
onclick={openMenu}>
|
||||
<div class="flex items-center justify-between">
|
||||
<strong
|
||||
class="flex items-center gap-1 relative tooltip tooltip-right"
|
||||
data-tip={$display}>
|
||||
<strong class="flex items-center gap-1 relative">
|
||||
<RelayName {url} class="ellipsize" />
|
||||
<div
|
||||
class="absolute -right-3 top-0 h-2 w-2 rounded-full bg-primary transition-all opacity-0"
|
||||
|
||||
@@ -26,27 +26,27 @@
|
||||
{@const {pubkey, software, version, supported_nips, limitation} = $relay}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#if pubkey}
|
||||
<div class="badge badge-neutral">
|
||||
<div class="badge badge-neutral text-wrap h-auto">
|
||||
<span class="ellipsize">Administrator: <ProfileLink unstyled {pubkey} /></span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $relay?.contact}
|
||||
<div class="badge badge-neutral">
|
||||
<div class="badge badge-neutral text-wrap h-auto">
|
||||
<span class="ellipsize">Contact: {$relay.contact}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if software}
|
||||
<div class="badge badge-neutral">
|
||||
<div class="badge badge-neutral text-wrap h-auto">
|
||||
<span class="ellipsize">Software: {software}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if version}
|
||||
<div class="badge badge-neutral">
|
||||
<div class="badge badge-neutral text-wrap h-auto">
|
||||
<span class="ellipsize">Version: {version}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if Array.isArray(supported_nips)}
|
||||
<p class="badge badge-neutral">
|
||||
<p class="badge badge-neutral text-wrap h-auto">
|
||||
<span class="ellipsize">Supported NIPs: {supported_nips.join(", ")}</span>
|
||||
</p>
|
||||
{/if}
|
||||
@@ -61,7 +61,7 @@
|
||||
</p>
|
||||
{/if}
|
||||
{#if limitation?.min_pow_difficulty}
|
||||
<p class="badge badge-warning">
|
||||
<p class="badge badge-warning text-wrap h-auto">
|
||||
<span class="ellipsize">Min PoW: {limitation?.min_pow_difficulty}</span>
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
import {tick} from "svelte"
|
||||
import {debounce} from "throttle-debounce"
|
||||
import {request} from "@welshman/net"
|
||||
import {formatTimestampAsDate, groupBy, now, MINUTE, HOUR, DAY, WEEK} from "@welshman/lib"
|
||||
import {repository, tracker} from "@welshman/app"
|
||||
import {formatTimestampAsDate, groupBy, uniqBy, now, MINUTE, HOUR, DAY, WEEK} from "@welshman/lib"
|
||||
import type {TrustedEvent, Filter} from "@welshman/util"
|
||||
import {sortEventsDesc} from "@welshman/util"
|
||||
import {MESSAGE, sortEventsDesc} from "@welshman/util"
|
||||
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||
import {fly} from "@lib/transition"
|
||||
@@ -53,8 +54,11 @@
|
||||
|
||||
const getFilter = (searchTerm: string): Filter =>
|
||||
h
|
||||
? {kinds: CONTENT_KINDS, "#h": [h], search: searchTerm}
|
||||
: {kinds: CONTENT_KINDS, search: searchTerm}
|
||||
? {kinds: [MESSAGE, ...CONTENT_KINDS], "#h": [h], search: searchTerm}
|
||||
: {kinds: [MESSAGE, ...CONTENT_KINDS], search: searchTerm}
|
||||
|
||||
const getLocalResults = (filter: Filter) =>
|
||||
repository.query([filter]).filter(event => tracker.getRelays(event.id).has(url))
|
||||
|
||||
const search = debounce(300, async (searchTerm: string) => {
|
||||
controller?.abort()
|
||||
@@ -68,18 +72,23 @@
|
||||
controller = new AbortController()
|
||||
loading = true
|
||||
|
||||
const filter = getFilter(searchTerm.trim())
|
||||
const localResults = getLocalResults(filter)
|
||||
|
||||
results = sortEventsDesc(localResults)
|
||||
|
||||
try {
|
||||
const events = await request({
|
||||
relays: getRelayUrls(),
|
||||
autoClose: true,
|
||||
signal: controller.signal,
|
||||
filters: [getFilter(searchTerm.trim())],
|
||||
filters: [filter],
|
||||
})
|
||||
|
||||
results = sortEventsDesc(events)
|
||||
results = sortEventsDesc(uniqBy((e: TrustedEvent) => e.id, [...events, ...localResults]))
|
||||
} catch (error) {
|
||||
if (!(error instanceof DOMException && error.name === "AbortError")) {
|
||||
results = []
|
||||
results = sortEventsDesc(localResults)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import VideoCallTile from "@app/components/VideoCallTile.svelte"
|
||||
import VoiceWidget from "@app/components/VoiceWidget.svelte"
|
||||
import VoiceParticipantMediaBadges from "@app/components/VoiceParticipantMediaBadges.svelte"
|
||||
import {get} from "svelte/store"
|
||||
import {
|
||||
VideoCallLayout,
|
||||
@@ -18,7 +19,12 @@
|
||||
ViewportSize,
|
||||
videoPrimaryTileKey,
|
||||
} from "@app/call/video"
|
||||
import {currentVoiceSession, currentVoiceRoom, pubkeyFromLiveKitIdentity} from "@app/call/stores"
|
||||
import {
|
||||
currentVoiceSession,
|
||||
currentVoiceRoom,
|
||||
mediaStateByIdentity,
|
||||
pubkeyFromLiveKitIdentity,
|
||||
} from "@app/call/stores"
|
||||
|
||||
type Props = {
|
||||
layout: VideoCallLayout
|
||||
@@ -123,6 +129,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
const tiledIdentities = new Set(videoTiles.map(t => t.identity))
|
||||
if (!tiledIdentities.has(user.identity)) {
|
||||
videoTiles.push({
|
||||
identity: user.identity,
|
||||
isLocal: true,
|
||||
trackSid: "local-avatar",
|
||||
track: undefined,
|
||||
source: Track.Source.Camera,
|
||||
})
|
||||
}
|
||||
for (const rp of room.remoteParticipants.values()) {
|
||||
if (!tiledIdentities.has(rp.identity)) {
|
||||
videoTiles.push({
|
||||
identity: rp.identity,
|
||||
isLocal: false,
|
||||
trackSid: `avatar-${rp.identity}`,
|
||||
track: undefined,
|
||||
source: Track.Source.Camera,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return videoTiles
|
||||
})
|
||||
|
||||
@@ -144,6 +172,9 @@
|
||||
|
||||
const useSpotlightLayout = $derived(primaryTile !== undefined)
|
||||
const useMultiGrid = $derived(!useSpotlightLayout && videoTiles.length > 2)
|
||||
const multiGridClass = $derived(
|
||||
layout === VideoCallLayout.Split ? "grid-cols-1" : "grid-cols-1 sm:grid-cols-2",
|
||||
)
|
||||
|
||||
$effect(() => {
|
||||
const k = $videoPrimaryTileKey
|
||||
@@ -184,6 +215,7 @@
|
||||
</script>
|
||||
|
||||
{#snippet videoTile(tile: VideoTileData, layout: TileLayout)}
|
||||
{@const media = $mediaStateByIdentity(tile.identity)}
|
||||
<div
|
||||
class={cx(
|
||||
"relative isolate overflow-hidden rounded-box shadow-sm",
|
||||
@@ -203,6 +235,15 @@
|
||||
<ProfileCircle pubkey={pubkeyFromLiveKitIdentity(tile.identity)} {url} size={14} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if tile.track}
|
||||
<div class="pointer-events-none absolute left-1 top-1 z-10">
|
||||
<VoiceParticipantMediaBadges
|
||||
muted={media.muted}
|
||||
cameraOn={media.cameraOn}
|
||||
showCamera={tile.source === Track.Source.Camera}
|
||||
size={3} />
|
||||
</div>
|
||||
{/if}
|
||||
<span
|
||||
class="pointer-events-none absolute bottom-1 left-1 max-w-[calc(100%-0.5rem)] truncate rounded bg-base-100/80 px-1.5 py-0.5 text-xs">
|
||||
{labelFor(tile.identity, tile.source)}{tile.isLocal ? " (you)" : ""}
|
||||
@@ -238,8 +279,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{:else if useMultiGrid}
|
||||
<div
|
||||
class="grid min-h-0 flex-1 grid-cols-1 content-start gap-2 overflow-y-auto sm:grid-cols-2">
|
||||
<div class={cx("grid min-h-0 flex-1 content-start gap-2 overflow-y-auto", multiGridClass)}>
|
||||
{#each videoTiles as tile (tileKey(tile))}
|
||||
{@render videoTile(tile, "default")}
|
||||
{/each}
|
||||
@@ -254,8 +294,10 @@
|
||||
{:else}
|
||||
<div
|
||||
class="flex min-h-[12rem] flex-1 flex-col items-center justify-center gap-2 rounded-box bg-base-200/50 p-4 text-center text-sm opacity-80">
|
||||
<p>No camera or screen share yet.</p>
|
||||
<p class="text-xs">Use the camera or screen share control to share video.</p>
|
||||
<p>No one is sharing video yet.</p>
|
||||
<p class="text-xs">
|
||||
Participants appear here when they turn on their camera or share their screen.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import cx from "classnames"
|
||||
import MicrophoneOff from "@assets/icons/microphone-off.svg?dataurl"
|
||||
import VideocameraOff from "@assets/icons/videocamera-off.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
|
||||
type Props = {
|
||||
muted: boolean
|
||||
cameraOn: boolean
|
||||
showCamera?: boolean
|
||||
size?: number
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {muted, cameraOn, showCamera = true, size = 3, class: className = ""}: Props = $props()
|
||||
|
||||
const badgeClass =
|
||||
"inline-flex size-4 shrink-0 items-center justify-center rounded bg-base-100/80 p-0.5 text-error"
|
||||
</script>
|
||||
|
||||
{#if muted || (showCamera && !cameraOn)}
|
||||
<div class={cx("flex items-center gap-1", className)}>
|
||||
{#if muted}
|
||||
<span class={badgeClass} aria-label="Muted">
|
||||
<Icon icon={MicrophoneOff} {size} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if showCamera && !cameraOn}
|
||||
<span class={badgeClass} aria-label="Camera off">
|
||||
<Icon icon={VideocameraOff} {size} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -9,11 +9,13 @@
|
||||
import {makeRoomPath} from "@app/util/routes"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import VoiceRoomJoinDialog from "@app/components/VoiceRoomJoinDialog.svelte"
|
||||
import VoiceParticipantMediaBadges from "@app/components/VoiceParticipantMediaBadges.svelte"
|
||||
import {makeRoomId} from "@app/core/state"
|
||||
import {
|
||||
VoiceState,
|
||||
currentVoiceRoom,
|
||||
isParticipantSpeaking,
|
||||
mediaStateByIdentity,
|
||||
participantKey,
|
||||
voiceState,
|
||||
type VoiceParticipant,
|
||||
@@ -83,9 +85,17 @@
|
||||
)}>
|
||||
<ProfileCircle pubkey={p.pubkey} size={5} class="h-5 w-5" />
|
||||
</div>
|
||||
<span class="ellipsize text-xs opacity-70">
|
||||
<span class="ellipsize min-w-0 flex-1 text-xs opacity-70">
|
||||
{p.pubkey ? displayProfileByPubkey(p.pubkey) : "Unknown"}
|
||||
</span>
|
||||
{#if isActive}
|
||||
{@const media = $mediaStateByIdentity(p.identity)}
|
||||
<VoiceParticipantMediaBadges
|
||||
muted={media.muted}
|
||||
cameraOn={media.cameraOn}
|
||||
size={3}
|
||||
class="shrink-0" />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import cx from "classnames"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import Microphone from "@assets/icons/microphone.svg?dataurl"
|
||||
import Videocamera from "@assets/icons/videocamera.svg?dataurl"
|
||||
import VideocameraOff from "@assets/icons/videocamera-off.svg?dataurl"
|
||||
import VideocameraRecord from "@assets/icons/videocamera-record.svg?dataurl"
|
||||
import Monitor from "@assets/icons/monitor.svg?dataurl"
|
||||
import PhoneRounded from "@assets/icons/phone-rounded.svg?dataurl"
|
||||
@@ -41,8 +41,8 @@
|
||||
VoiceState,
|
||||
currentVoiceSession,
|
||||
currentVoiceRoom,
|
||||
voiceMicMuted,
|
||||
voiceState,
|
||||
isLocalSpeaking,
|
||||
} from "@app/call/stores"
|
||||
import {cancelJoinVoiceRoom, leaveVoiceRoom, toggleMute} from "@app/call/voice"
|
||||
|
||||
@@ -183,18 +183,17 @@
|
||||
</Button>
|
||||
{:else if $voiceState === VoiceState.Connected && $currentVoiceSession}
|
||||
<Button
|
||||
data-tip={$currentVoiceSession.muted ? "Unmute" : "Mute"}
|
||||
data-tip={$voiceMicMuted ? "Unmute" : "Mute"}
|
||||
class={cx(
|
||||
mediaToggleClass,
|
||||
"overflow-visible",
|
||||
!$currentVoiceSession.muted && $isLocalSpeaking && "text-primary",
|
||||
$currentVoiceSession.muted &&
|
||||
"text-error ring-1 ring-error/50 ring-offset-0 ring-offset-base-100",
|
||||
!$voiceMicMuted && "text-primary",
|
||||
$voiceMicMuted && "text-error ring-1 ring-error/50 ring-offset-0 ring-offset-base-100",
|
||||
)}
|
||||
onclick={toggleMute}>
|
||||
<span class="relative inline-flex items-center justify-center overflow-visible">
|
||||
<Icon icon={Microphone} size={4} />
|
||||
{#if $currentVoiceSession.muted}
|
||||
{#if $voiceMicMuted}
|
||||
<span
|
||||
class="pointer-events-none absolute inset-0 flex items-center justify-center overflow-visible"
|
||||
aria-hidden="true">
|
||||
@@ -207,9 +206,17 @@
|
||||
</Button>
|
||||
<Button
|
||||
data-tip={$currentVoiceSession.cameraOn ? "Turn off camera" : "Turn on camera"}
|
||||
class={cx(mediaToggleClass, $currentVoiceSession.cameraOn && "text-primary")}
|
||||
class={cx(
|
||||
mediaToggleClass,
|
||||
"overflow-visible",
|
||||
$currentVoiceSession.cameraOn && "text-primary",
|
||||
!$currentVoiceSession.cameraOn &&
|
||||
"text-error ring-1 ring-error/50 ring-offset-0 ring-offset-base-100",
|
||||
)}
|
||||
onclick={toggleCamera}>
|
||||
<Icon icon={$currentVoiceSession.cameraOn ? VideocameraRecord : Videocamera} size={4} />
|
||||
<Icon
|
||||
icon={$currentVoiceSession.cameraOn ? VideocameraRecord : VideocameraOff}
|
||||
size={4} />
|
||||
</Button>
|
||||
{#if !Capacitor.isNativePlatform()}
|
||||
<Button
|
||||
|
||||
@@ -73,7 +73,6 @@ import {
|
||||
waitForThunkError,
|
||||
getPubkeyRelays,
|
||||
userBlossomServerList,
|
||||
getThunkError,
|
||||
addRoomMember,
|
||||
manageRelay,
|
||||
getRelay,
|
||||
@@ -264,16 +263,12 @@ export const attemptRelayAccess = async (url: string, claim = "") => {
|
||||
return stripPrefix(error)
|
||||
}
|
||||
|
||||
export const deriveRelayAuthError = (url: string, claim = "") => {
|
||||
// Kick off the auth process
|
||||
export const deriveRelayAuthError = (url: string) => {
|
||||
Pool.get().get(url).auth.attemptAuth(sign)
|
||||
|
||||
// Attempt to join the relay
|
||||
const thunk = publishJoinRequest({url, claim})
|
||||
|
||||
return derived(
|
||||
[thunk, relaysMostlyRestricted, deriveSocket(url)],
|
||||
([$thunk, $relaysMostlyRestricted, $socket]) => {
|
||||
[relaysMostlyRestricted, deriveSocket(url)],
|
||||
([$relaysMostlyRestricted, $socket]) => {
|
||||
if ($socket.auth.status === AuthStatus.Forbidden && $socket.auth.details) {
|
||||
return stripPrefix($socket.auth.details)
|
||||
}
|
||||
@@ -281,16 +276,6 @@ export const deriveRelayAuthError = (url: string, claim = "") => {
|
||||
if ($relaysMostlyRestricted[url]) {
|
||||
return stripPrefix($relaysMostlyRestricted[url])
|
||||
}
|
||||
|
||||
const error = getThunkError($thunk)
|
||||
|
||||
if (error) {
|
||||
const isEmptyInvite = !claim && error.includes("invite code")
|
||||
|
||||
if (!shouldIgnoreError(error) && !isEmptyInvite) {
|
||||
return stripPrefix(error) || "join request rejected"
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -560,11 +560,7 @@ export const chatsById = call(() => {
|
||||
})
|
||||
})
|
||||
|
||||
export const deriveChat = call(() => {
|
||||
const _deriveChat = makeDeriveItem(chatsById)
|
||||
|
||||
return (pubkeys: string[]) => _deriveChat(makeChatId(pubkeys))
|
||||
})
|
||||
export const deriveChat = makeDeriveItem(chatsById)
|
||||
|
||||
export const chatSearch = derived(throttled(1500, chatsById), $chatsByPubkey => {
|
||||
return createSearch(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {App} from "@capacitor/app"
|
||||
import {Capacitor} from "@capacitor/core"
|
||||
import {Keyboard} from "@capacitor/keyboard"
|
||||
import {noop} from "@welshman/lib"
|
||||
@@ -13,9 +14,16 @@ export const syncKeyboard = () => {
|
||||
document.body.classList.remove("keyboard-open")
|
||||
})
|
||||
|
||||
// On Android, system-dismissing the IME during pause doesn't fire keyboardWillHide,
|
||||
// so on resume we force a hide to re-sync native insets and clear our CSS state.
|
||||
const resumeListener = App.addListener("appStateChange", ({isActive}) => {
|
||||
if (isActive) Keyboard.hide()
|
||||
})
|
||||
|
||||
return () => {
|
||||
showListener.then(listener => listener.remove())
|
||||
hideListener.then(listener => listener.remove())
|
||||
resumeListener.then(listener => listener.remove())
|
||||
document.body.classList.remove("keyboard-open")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {derived} from "svelte/store"
|
||||
import {derived, get, writable} from "svelte/store"
|
||||
import {Badge} from "@capawesome/capacitor-badge"
|
||||
import {synced, throttled, withGetter} from "@welshman/store"
|
||||
import {pubkey, tracker, repository, relaysByUrl} from "@welshman/app"
|
||||
@@ -36,6 +36,9 @@ export const deriveChecked = (key: string) => derived(checked, prop<number>(key)
|
||||
|
||||
export const setChecked = (key: string) => checked.update(assoc(key, now()))
|
||||
|
||||
/** Room path while video call UI hides chat; checked + badge stay active until chat is shown. */
|
||||
export const deferredRoomPath = writable<string | undefined>(undefined)
|
||||
|
||||
export const syncChecked = () => {
|
||||
let prev = ""
|
||||
|
||||
@@ -57,8 +60,11 @@ export const syncChecked = () => {
|
||||
|
||||
// Set checked when we visit a given page - but delay it a tad
|
||||
setTimeout(() => {
|
||||
const defer = get(deferredRoomPath)
|
||||
|
||||
checked.update($checked => {
|
||||
for (const path of getPaths($page.url.pathname)) {
|
||||
if (defer && path === defer) continue
|
||||
$checked[path] = now()
|
||||
}
|
||||
|
||||
@@ -151,9 +157,17 @@ export const allNotifications = derived(
|
||||
},
|
||||
)
|
||||
|
||||
export const notifications = derived([page, allNotifications], ([$page, $allNotifications]) => {
|
||||
return new Set([...$allNotifications].filter(p => !$page.url.pathname.startsWith(p)))
|
||||
})
|
||||
export const notifications = derived(
|
||||
[page, allNotifications, deferredRoomPath],
|
||||
([$page, $allNotifications, $deferredRoomPath]) =>
|
||||
new Set(
|
||||
[...$allNotifications].filter(p => {
|
||||
if (!$page.url.pathname.startsWith(p)) return true
|
||||
if ($deferredRoomPath && p === $deferredRoomPath) return true
|
||||
return false
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
// Badges
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export const POMADE_INVALID_LOGIN_MESSAGE = "Invalid login information"
|
||||
export const POMADE_NETWORK_ERROR_MESSAGE = "Network error, please try again"
|
||||
|
||||
type PomadeMessage = {
|
||||
res?: unknown
|
||||
}
|
||||
|
||||
export const getPomadeLoginFailureMessage = (messages: PomadeMessage[]) =>
|
||||
messages.some(message => message.res !== undefined)
|
||||
? POMADE_INVALID_LOGIN_MESSAGE
|
||||
: POMADE_NETWORK_ERROR_MESSAGE
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 11.5C2 8.21252 2 6.56878 2.90796 5.46243C3.07418 5.25989 3.25989 5.07418 3.46243 4.90796C4.56878 4 6.21252 4 9.5 4C12.7875 4 14.4312 4 15.5376 4.90796C15.7401 5.07418 15.9258 5.25989 16.092 5.46243C17 6.56878 17 8.21252 17 11.5V12.5C17 15.7875 17 17.4312 16.092 18.5376C15.9258 18.7401 15.7401 18.9258 15.5376 19.092C14.4312 20 12.7875 20 9.5 20C6.21252 20 4.56878 20 3.46243 19.092C3.25989 18.9258 3.07418 18.7401 2.90796 18.5376C2 17.4312 2 15.7875 2 12.5V11.5Z" stroke="#000000" stroke-width="1.5"/>
|
||||
<path d="M17 9.50019L17.6584 9.17101C19.6042 8.19807 20.5772 7.7116 21.2886 8.15127C22 8.59094 22 9.67872 22 11.8543V12.1461C22 14.3217 22 15.4094 21.2886 15.8491C20.5772 16.2888 19.6042 15.8023 17.6584 14.8294L17 14.5002V9.50019Z" stroke="#000000" stroke-width="1.5"/>
|
||||
<path d="M22 2L2 22" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 970 B |
@@ -31,6 +31,7 @@
|
||||
<svelte:document onmousemove={onMouseMove} />
|
||||
|
||||
<Tippy
|
||||
class="flex"
|
||||
bind:popover
|
||||
component={EmojiPicker}
|
||||
props={{onClick}}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import "emoji-picker-element"
|
||||
import emojiDataUrl from "emoji-picker-element-data/en/emojibase/data.json?url"
|
||||
import type {Emoji} from "emoji-picker-element/shared"
|
||||
import {onMount} from "svelte"
|
||||
|
||||
@@ -26,4 +27,4 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<emoji-picker bind:this={element} class="m-auto"></emoji-picker>
|
||||
<emoji-picker bind:this={element} data-source={emojiDataUrl} class="m-auto"></emoji-picker>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
} = $props()
|
||||
</script>
|
||||
|
||||
<div class={cx("fixed bottom-20 right-4 z-nav hide-on-keyboard md:hidden", className)}>
|
||||
<div class={cx("fixed bottom-20 mb-sai right-4 z-nav hide-on-keyboard md:hidden", className)}>
|
||||
<Button
|
||||
class="btn btn-primary border-none shadow-xl hover:opacity-90 transition-all size-[50px] rounded-xl p-0"
|
||||
{onclick}>
|
||||
|
||||
@@ -9,16 +9,22 @@
|
||||
const {...props}: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 gap-2 lg:gap-6 lg:grid-cols-3 {props.class}">
|
||||
<label class="flex items-center gap-2 font-bold">
|
||||
{@render props.label?.()}
|
||||
</label>
|
||||
<div class="col-span-2 flex items-center gap-2">
|
||||
{@render props.input?.()}
|
||||
</div>
|
||||
<p class="flex-end text-sm opacity-50 lg:col-span-3">
|
||||
{#if props.info}
|
||||
{@render props.info?.()}
|
||||
<div class="flex flex-col gap-2 {props.class}">
|
||||
<div class="flex items-center justify-between w-full gap-2">
|
||||
{#if props.label}
|
||||
<label class="flex items-center gap-2 min-w-[30%] max-w-[80%] md:max-w-none">
|
||||
{@render props.label()}
|
||||
</label>
|
||||
{/if}
|
||||
</p>
|
||||
<div class="flex items-center gap-2 justify-end grow">
|
||||
{#if props.input}
|
||||
{@render props.input()}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if props.info}
|
||||
<p class="text-sm opacity-50">
|
||||
{@render props.info()}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,12 @@
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
let {value = $bindable(), addLabel, placeholder = "Enter text..."}: Props = $props()
|
||||
let {
|
||||
value = $bindable(),
|
||||
addLabel,
|
||||
placeholder = "Enter text...",
|
||||
allowAdd = true,
|
||||
}: Props & {allowAdd?: boolean} = $props()
|
||||
let draggedIndex: number | null = $state(null)
|
||||
|
||||
const onChange = (newValue: string[]) => {
|
||||
@@ -72,12 +77,14 @@
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<Button onclick={addItem} class="btn btn-link w-fit px-0">
|
||||
<Icon icon={AddCircle} size={5} />
|
||||
{#if addLabel}
|
||||
{@render addLabel?.()}
|
||||
{:else}
|
||||
Add Item
|
||||
{/if}
|
||||
</Button>
|
||||
{#if allowAdd}
|
||||
<Button onclick={addItem} class="btn btn-link w-fit px-0">
|
||||
<Icon icon={AddCircle} size={5} />
|
||||
{#if addLabel}
|
||||
{@render addLabel?.()}
|
||||
{:else}
|
||||
Add Item
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
const className = $derived(
|
||||
cx(
|
||||
props.class,
|
||||
"scroll-container z-feature flex min-h-0 w-full min-w-0 flex-1 flex-col overflow-y-auto overflow-x-hidden",
|
||||
"scroll-container z-feature flex min-h-0 w-full min-w-0 flex-col overflow-y-auto overflow-x-hidden pb-14 md:pb-0",
|
||||
),
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
cx(
|
||||
"flex h-full w-full cursor-pointer items-center justify-center rounded-full transition-colors hover:bg-base-300",
|
||||
restProps.class,
|
||||
{"bg-base-300 border border-solid border-base-content/20": active},
|
||||
),
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import {Badge} from "@capawesome/capacitor-badge"
|
||||
import Bell from "@assets/icons/bell.svg?dataurl"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
@@ -63,40 +64,64 @@
|
||||
<!-- pass -->
|
||||
{:then { isSupported }}
|
||||
{#if isSupported}
|
||||
<div class="flex justify-between">
|
||||
<p>Show badge for unread alerts</p>
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.badge} />
|
||||
</div>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Show badge for unread alerts</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.badge} />
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
{/if}
|
||||
{/await}
|
||||
{#if !Capacitor.isNativePlatform()}
|
||||
<div class="flex justify-between">
|
||||
<p>Play sound for new activity</p>
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.sound} />
|
||||
</div>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Play sound for new activity</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.sound} />
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
{/if}
|
||||
<div class="flex justify-between">
|
||||
<p>Enable push notifications</p>
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.push} />
|
||||
</div>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Enable push notifications</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.push} />
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
</div>
|
||||
<div
|
||||
class={cx("card2 bg-alt col-4 shadow-md", {
|
||||
"pointer-events-none opacity-50": !settings.badge && !settings.sound && !settings.push,
|
||||
})}>
|
||||
<strong class="text-lg">Alert Types</strong>
|
||||
<div class="flex justify-between">
|
||||
<p>Notify me about new activity</p>
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.spaces} />
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<p>Always notify me when mentioned</p>
|
||||
<input type="checkbox" class="toggle toggle-primary" checked={settings.mentions} />
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<p>Notify me about new messages</p>
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.messages} />
|
||||
</div>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Notify me about new activity</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.spaces} />
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Always notify me when mentioned</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input type="checkbox" class="toggle toggle-primary" checked={settings.mentions} />
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Notify me about new messages</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.messages} />
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
</div>
|
||||
<div
|
||||
class="card2 bg-alt sticky -bottom-3 shadow-md flex flex-row items-center justify-between gap-4">
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import {Router} from "@welshman/router"
|
||||
import {userMuteList, tagPubkey, publishThunk, userBlossomServerList} from "@welshman/app"
|
||||
import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl"
|
||||
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import Field from "@lib/components/Field.svelte"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
@@ -28,6 +29,10 @@
|
||||
blossomServers = getTagValues("server", getListTags($userBlossomServerList))
|
||||
}
|
||||
|
||||
const addServer = () => {
|
||||
blossomServers = [...blossomServers, ""]
|
||||
}
|
||||
|
||||
const onsubmit = preventDefault(async () => {
|
||||
await publishSettings($state.snapshot(settings))
|
||||
|
||||
@@ -104,7 +109,7 @@
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input
|
||||
class="range range-primary"
|
||||
class="range range-primary w-full"
|
||||
type="range"
|
||||
min="0.8"
|
||||
max="1.3"
|
||||
@@ -115,13 +120,13 @@
|
||||
</div>
|
||||
<div class="card2 bg-alt col-4 shadow-md">
|
||||
<strong class="text-lg">Editor Settings</strong>
|
||||
<FieldInline>
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Send Delay</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input
|
||||
class="range range-primary"
|
||||
class="range range-primary w-full"
|
||||
type="range"
|
||||
min="0"
|
||||
max="10000"
|
||||
@@ -134,17 +139,19 @@
|
||||
{settings.send_delay === 1000 ? "second" : "seconds"}.
|
||||
</p>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
</Field>
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Media Server</p>
|
||||
{/snippet}
|
||||
{#snippet secondary()}
|
||||
<Button class="link text-sm underline flex items-center gap-1" onclick={addServer}>
|
||||
<Icon icon={AddCircle} size={4} />
|
||||
Add Server
|
||||
</Button>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<InputList bind:value={blossomServers}>
|
||||
{#snippet addLabel()}
|
||||
Add Server
|
||||
{/snippet}
|
||||
</InputList>
|
||||
<InputList allowAdd={false} bind:value={blossomServers} />
|
||||
{/snippet}
|
||||
{#snippet info()}
|
||||
<p>Choose a media server type and url for files you upload to {PLATFORM_NAME}.</p>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import ShieldMinimalistic from "@assets/icons/shield-minimalistic.svg?dataurl"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
@@ -11,8 +12,10 @@
|
||||
settings = {...$userSettingsValues}
|
||||
}
|
||||
|
||||
const onAuthModeChange = (e: any) => {
|
||||
settings.auth_mode = e.target.checked ? RelayAuthMode.Aggressive : RelayAuthMode.Conservative
|
||||
const onAuthModeChange = (e: Event) => {
|
||||
const target = e.currentTarget as HTMLInputElement
|
||||
|
||||
settings.relay_auth = target.checked ? RelayAuthMode.Aggressive : RelayAuthMode.Conservative
|
||||
}
|
||||
|
||||
const onsubmit = preventDefault(async () => {
|
||||
@@ -30,31 +33,46 @@
|
||||
<Icon icon={ShieldMinimalistic} />
|
||||
Privacy Settings
|
||||
</strong>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<p>Authenticate with unknown relays?</p>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
onchange={onAuthModeChange}
|
||||
checked={settings.auth_mode === RelayAuthMode.Aggressive} />
|
||||
<p class="col-span-2 text-sm opacity-70">
|
||||
Controls whether {PLATFORM_NAME} will identify you to relays not in your lists.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<p>Report errors?</p>
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.report_errors} />
|
||||
<p class="col-span-2 text-sm opacity-70">
|
||||
Allow {PLATFORM_NAME} to send error reports to help improve the app.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<p>Report usage?</p>
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.report_usage} />
|
||||
<p class="col-span-2 text-sm opacity-70">
|
||||
Allow {PLATFORM_NAME} to collect anonymous usage data.
|
||||
</p>
|
||||
</div>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Authenticate with unknown relays?</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
onchange={onAuthModeChange}
|
||||
checked={settings.relay_auth === RelayAuthMode.Aggressive} />
|
||||
{/snippet}
|
||||
{#snippet info()}
|
||||
<p>Controls whether {PLATFORM_NAME} will identify you to relays not in your lists.</p>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Report errors?</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
bind:checked={settings.report_errors} />
|
||||
{/snippet}
|
||||
{#snippet info()}
|
||||
<p>Allow {PLATFORM_NAME} to send error reports to help improve the app.</p>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Report usage?</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.report_usage} />
|
||||
{/snippet}
|
||||
{#snippet info()}
|
||||
<p>Allow {PLATFORM_NAME} to collect anonymous usage data.</p>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
</div>
|
||||
<div
|
||||
class="card2 bg-alt sticky -bottom-3 shadow-md flex flex-row items-center justify-between gap-4">
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import PasswordReset from "@app/components/PasswordReset.svelte"
|
||||
import InfoKeys from "@app/components/InfoKeys.svelte"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {POMADE_NETWORK_ERROR_MESSAGE} from "@app/util/pomadeErrors"
|
||||
import {clip, pushToast} from "@app/util/toast"
|
||||
|
||||
const npub = nip19.npubEncode($pubkey!)
|
||||
@@ -48,13 +49,24 @@
|
||||
const {ok, peersByPrefix} = await Client.requestChallenge($session!.email)
|
||||
|
||||
if (!ok) {
|
||||
console.error("Pomade challenge request failed during password reset initiation")
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: "Failed to initiate password reset!",
|
||||
message: POMADE_NETWORK_ERROR_MESSAGE,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
pushModal(PasswordReset, {peersByPrefix})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
pushToast({
|
||||
theme: "error",
|
||||
message: POMADE_NETWORK_ERROR_MESSAGE,
|
||||
})
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
import {derived as _derived} from "svelte/store"
|
||||
import {dec, insertAt, removeAt, sleep} from "@welshman/lib"
|
||||
import type {RelayProfile} from "@welshman/util"
|
||||
import {ROOMS} from "@welshman/util"
|
||||
import {throttled} from "@welshman/store"
|
||||
import {relays, createSearch} from "@welshman/app"
|
||||
import {pull, relays, createSearch} from "@welshman/app"
|
||||
import {createScroller} from "@lib/html"
|
||||
import {fly} from "@lib/transition"
|
||||
import DragHandle from "@assets/icons/drag-handle.svg?dataurl"
|
||||
@@ -29,7 +30,9 @@
|
||||
userSpaceUrls,
|
||||
loadUserGroupList,
|
||||
PLATFORM_RELAYS,
|
||||
DEFAULT_RELAYS,
|
||||
groupListPubkeysByUrl,
|
||||
bootstrapPubkeys,
|
||||
parseInviteLink,
|
||||
} from "@app/core/state"
|
||||
import {setSpaceMembershipOrder} from "@app/core/commands"
|
||||
@@ -197,6 +200,11 @@
|
||||
},
|
||||
})
|
||||
|
||||
pull({
|
||||
filters: [{kinds: [ROOMS], authors: $bootstrapPubkeys}],
|
||||
relays: DEFAULT_RELAYS,
|
||||
})
|
||||
|
||||
return () => {
|
||||
scroller.stop()
|
||||
}
|
||||
@@ -205,41 +213,46 @@
|
||||
|
||||
<Page>
|
||||
<PageBar>
|
||||
{#if showSearch}
|
||||
<label class="input input-bordered input-sm flex flex-1 items-center gap-2" in:fly>
|
||||
<Icon icon={Magnifier} />
|
||||
<input
|
||||
bind:this={searchInput}
|
||||
bind:value={term}
|
||||
class="min-w-0 grow"
|
||||
type="text"
|
||||
placeholder="Search for spaces..." />
|
||||
<Button onclick={closeSearch} class="flex items-center">
|
||||
<Icon icon={CloseCircle} />
|
||||
</Button>
|
||||
</label>
|
||||
{:else}
|
||||
<div class="flex items-center justify-between gap-4" in:fly>
|
||||
<div class="ellipsize flex items-center gap-2 whitespace-nowrap">
|
||||
<Icon icon={Widget} size={6} />
|
||||
<strong>Spaces</strong>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="btn btn-neutral btn-sm btn-square"
|
||||
aria-label="Search"
|
||||
onclick={openSearch}>
|
||||
<Icon size={4} icon={Magnifier} />
|
||||
</button>
|
||||
{#if PLATFORM_RELAYS.length === 0}
|
||||
<Button class="btn btn-primary btn-sm" onclick={addSpace}>
|
||||
<Icon icon={AddCircle} />
|
||||
Add Space
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4" in:fly>
|
||||
<div class="ellipsize flex items-center gap-2 whitespace-nowrap">
|
||||
<Icon icon={Widget} size={6} />
|
||||
<strong>Spaces</strong>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="btn btn-neutral btn-sm btn-square" aria-label="Search" onclick={openSearch}>
|
||||
<Icon size={4} icon={Magnifier} />
|
||||
</button>
|
||||
{#if showSearch}
|
||||
<button class="fixed inset-0 z-feature" aria-label="Close search" onclick={closeSearch}
|
||||
></button>
|
||||
<div class="fixed top-sai right-sai left-content-full z-feature p-2">
|
||||
<div
|
||||
class="card2 card2-sm p-2! bg-alt flex flex-col shadow-md"
|
||||
transition:fly={{y: -40, duration: 150}}>
|
||||
<label class="input input-sm input-bordered flex w-full items-center gap-2">
|
||||
<Icon size={4} icon={Magnifier} />
|
||||
<input
|
||||
bind:this={searchInput}
|
||||
bind:value={term}
|
||||
class="min-w-0 grow"
|
||||
type="text"
|
||||
placeholder="Search for spaces..."
|
||||
onkeydown={e => e.key === "Escape" && closeSearch()} />
|
||||
<Button onclick={closeSearch} class="flex items-center">
|
||||
<Icon icon={CloseCircle} />
|
||||
</Button>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if PLATFORM_RELAYS.length === 0}
|
||||
<Button class="btn btn-primary btn-sm" onclick={addSpace}>
|
||||
<Icon icon={AddCircle} />
|
||||
Add Space
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</PageBar>
|
||||
<PageContent class="flex flex-col gap-2 p-2 pt-4">
|
||||
<div class="flex flex-col gap-2" bind:this={element}>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {onDestroy, onMount} from "svelte"
|
||||
import {readable} from "svelte/store"
|
||||
import {page} from "$app/stores"
|
||||
import {goto} from "$app/navigation"
|
||||
import type {Readable} from "svelte/store"
|
||||
import {debounce} from "throttle-debounce"
|
||||
import {pubkey, publishThunk, waitForThunkError, joinRoom, leaveRoom} from "@welshman/app"
|
||||
import {now, ifLet, int, formatTimestampAsDate, ago, MINUTE} from "@welshman/lib"
|
||||
import type {MakeNonOptional} from "@welshman/lib"
|
||||
@@ -14,7 +15,7 @@
|
||||
import InfoCircle from "@assets/icons/info-circle.svg?dataurl"
|
||||
import Login2 from "@assets/icons/login-3.svg?dataurl"
|
||||
import cx from "classnames"
|
||||
import {slide, fade, fly} from "@lib/transition"
|
||||
import {fade, fly} from "@lib/transition"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
@@ -48,7 +49,8 @@
|
||||
import {VideoCallLayout, videoCallLayout, videoTileCount} from "@app/call/video"
|
||||
import {makeFeed} from "@app/core/requests"
|
||||
import {popKey} from "@lib/implicit"
|
||||
import {checked} from "@app/util/notifications"
|
||||
import {checked, deferredRoomPath, setChecked} from "@app/util/notifications"
|
||||
import {makeRoomPath} from "@app/util/routes"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
|
||||
@@ -76,6 +78,21 @@
|
||||
voiceConnectedHere && $videoCallLayout === VideoCallLayout.Video,
|
||||
)
|
||||
|
||||
const roomPath = makeRoomPath(url, h)
|
||||
|
||||
const videoCallChatHidden = $derived(
|
||||
voiceConnectedHere && $videoCallLayout === VideoCallLayout.Video,
|
||||
)
|
||||
|
||||
$effect(() => {
|
||||
deferredRoomPath.set(videoCallChatHidden ? roomPath : undefined)
|
||||
if (voiceConnectedHere && !videoCallChatHidden) {
|
||||
setChecked(roomPath)
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => deferredRoomPath.set(undefined))
|
||||
|
||||
let prevVideoTileCount = $state(0)
|
||||
|
||||
$effect(() => {
|
||||
@@ -155,6 +172,10 @@
|
||||
}
|
||||
|
||||
const onSubmit = async ({content, tags}: EventContent) => {
|
||||
if (!content && !share) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
tags.push(["h", h])
|
||||
|
||||
@@ -244,6 +265,8 @@
|
||||
const onScroll = () => {
|
||||
if (!isProgrammaticScroll) {
|
||||
userHasScrolled = true
|
||||
isUserScrolling = true
|
||||
clearIsUserScrolling()
|
||||
manageScrollPosition()
|
||||
}
|
||||
|
||||
@@ -265,6 +288,7 @@
|
||||
let leaving = $state(false)
|
||||
let userHasScrolled = $state(false)
|
||||
let isProgrammaticScroll = $state(false)
|
||||
let isUserScrolling = $state(false)
|
||||
let loadingBackward = $state(true)
|
||||
let loadingForward = $state(true)
|
||||
let share = $state(popKey<TrustedEvent | undefined>("share"))
|
||||
@@ -278,6 +302,10 @@
|
||||
let compose: RoomCompose | undefined = $state()
|
||||
let eventToEdit: TrustedEvent | undefined = $state()
|
||||
|
||||
const clearIsUserScrolling = debounce(150, () => {
|
||||
isUserScrolling = false
|
||||
})
|
||||
|
||||
const elements = $derived.by(() => {
|
||||
const elements = []
|
||||
const seen = new Set()
|
||||
@@ -351,7 +379,7 @@
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (elements.length > 0) {
|
||||
if (elements.length > 0 && !isUserScrolling) {
|
||||
requestAnimationFrame(manageScrollPosition)
|
||||
}
|
||||
})
|
||||
@@ -396,7 +424,8 @@
|
||||
onMount(() => {
|
||||
start()
|
||||
|
||||
return cleanup
|
||||
// Wrap in a closure to avoid calling a stale cleanup function
|
||||
return () => cleanup?.()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -439,9 +468,8 @@
|
||||
bind:element
|
||||
onscroll={onScroll}
|
||||
class={cx(
|
||||
showMobileVideoPanel
|
||||
? "hidden flex-col-reverse pt-4 md:flex md:flex-col-reverse"
|
||||
: "flex flex-col-reverse pt-4",
|
||||
"flex-col-reverse pb-0! pt-4",
|
||||
showMobileVideoPanel ? "hidden md:flex md:flex-col-reverse" : "flex",
|
||||
pageContentHiddenDesktopVideoOnly && "md:hidden",
|
||||
)}>
|
||||
{#if $room.isPrivate && $membershipStatus !== MembershipStatus.Granted}
|
||||
@@ -488,16 +516,14 @@
|
||||
{#if event.kind === ROOM_ADD_MEMBER}
|
||||
<RoomItemAddMember {url} {event} />
|
||||
{:else}
|
||||
<div in:slide class="cv">
|
||||
<RoomItem
|
||||
{url}
|
||||
{event}
|
||||
{replyTo}
|
||||
{showPubkey}
|
||||
{addSpaceBelow}
|
||||
canEdit={canEditEvent}
|
||||
onEdit={onEditEvent} />
|
||||
</div>
|
||||
<RoomItem
|
||||
{url}
|
||||
{event}
|
||||
{replyTo}
|
||||
{showPubkey}
|
||||
{addSpaceBelow}
|
||||
canEdit={canEditEvent}
|
||||
onEdit={onEditEvent} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import {goto} from "$app/navigation"
|
||||
import type {Readable} from "svelte/store"
|
||||
import {readable} from "svelte/store"
|
||||
import {debounce} from "throttle-debounce"
|
||||
import {now, int, ifLet, formatTimestampAsDate, MINUTE, ago} from "@welshman/lib"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {makeEvent, MESSAGE, RELAY_ADD_MEMBER} from "@welshman/util"
|
||||
@@ -56,6 +57,10 @@
|
||||
}
|
||||
|
||||
const onSubmit = async ({content, tags}: EventContent) => {
|
||||
if (!content && !share) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let template: EventContent & {created_at?: number} = {content, tags}
|
||||
|
||||
@@ -139,6 +144,8 @@
|
||||
const onScroll = () => {
|
||||
if (!isProgrammaticScroll) {
|
||||
userHasScrolled = true
|
||||
isUserScrolling = true
|
||||
clearIsUserScrolling()
|
||||
manageScrollPosition()
|
||||
}
|
||||
|
||||
@@ -160,6 +167,7 @@
|
||||
let loadingForward = $state(true)
|
||||
let userHasScrolled = $state(false)
|
||||
let isProgrammaticScroll = $state(false)
|
||||
let isUserScrolling = $state(false)
|
||||
let share = $state(popKey<TrustedEvent | undefined>("share"))
|
||||
let parent: TrustedEvent | undefined = $state()
|
||||
let element: HTMLElement | undefined = $state()
|
||||
@@ -171,6 +179,10 @@
|
||||
let compose: RoomCompose | undefined = $state()
|
||||
let eventToEdit: TrustedEvent | undefined = $state()
|
||||
|
||||
const clearIsUserScrolling = debounce(150, () => {
|
||||
isUserScrolling = false
|
||||
})
|
||||
|
||||
const elements = $derived.by(() => {
|
||||
const elements = []
|
||||
const seen = new Set()
|
||||
@@ -244,7 +256,7 @@
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (elements.length > 0) {
|
||||
if (elements.length > 0 && !isUserScrolling) {
|
||||
requestAnimationFrame(manageScrollPosition)
|
||||
}
|
||||
})
|
||||
@@ -289,7 +301,8 @@
|
||||
onMount(() => {
|
||||
start()
|
||||
|
||||
return cleanup
|
||||
// Wrap in a closure to avoid calling a stale cleanup function
|
||||
return () => cleanup?.()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -303,7 +316,7 @@
|
||||
{/snippet}
|
||||
</SpaceBar>
|
||||
|
||||
<PageContent bind:element onscroll={onScroll} class="flex flex-col-reverse pt-4 mb-14 md:mb-0">
|
||||
<PageContent bind:element onscroll={onScroll} class="flex flex-col-reverse pt-4 pb-0!">
|
||||
{#if loadingForward}
|
||||
<p class="py-20 flex justify-center">
|
||||
<Spinner loading={loadingForward}>Looking for messages...</Spinner>
|
||||
@@ -326,16 +339,14 @@
|
||||
{#if event.kind === RELAY_ADD_MEMBER}
|
||||
<RoomItemAddMember {url} {event} />
|
||||
{:else}
|
||||
<div>
|
||||
<RoomItem
|
||||
{url}
|
||||
{event}
|
||||
{replyTo}
|
||||
{showPubkey}
|
||||
canEdit={canEditEvent}
|
||||
onEdit={onEditEvent}
|
||||
{addSpaceBelow} />
|
||||
</div>
|
||||
<RoomItem
|
||||
{url}
|
||||
{event}
|
||||
{replyTo}
|
||||
{showPubkey}
|
||||
canEdit={canEditEvent}
|
||||
onEdit={onEditEvent}
|
||||
{addSpaceBelow} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Server from "@assets/icons/server.svg?dataurl"
|
||||
import CloudCheck from "@assets/icons/cloud-check.svg?dataurl"
|
||||
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
|
||||
import ArrowRight from "@assets/icons/arrow-right.svg?dataurl"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
@@ -12,78 +13,91 @@
|
||||
<PageContent class="flex flex-col items-center gap-2 p-2 pt-4">
|
||||
<PageHeader>
|
||||
{#snippet title()}
|
||||
<div>Create your own Space</div>
|
||||
<div>Choose your Hosting Plan</div>
|
||||
{/snippet}
|
||||
{#snippet info()}
|
||||
<p>Get started with one of our trusted partners, or learn how to host your own space.</p>
|
||||
<p>
|
||||
Select how you want to deploy and manage your new Space. You can always migrate later.
|
||||
</p>
|
||||
{/snippet}
|
||||
</PageHeader>
|
||||
<div class="grid w-full max-w-lg grid-cols-1 gap-2 lg:max-w-4xl lg:grid-cols-2">
|
||||
<div class="card2 bg-alt flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<Icon icon={Server} />
|
||||
<h3 class="text-lg font-bold">Self-Host your Space</h3>
|
||||
<div class="flex w-full max-w-lg flex-col gap-4 lg:max-w-4xl">
|
||||
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2">
|
||||
<div class="card2 bg-alt flex flex-col gap-5">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="bg-primary/20 flex h-10 w-10 items-center justify-center rounded-md">
|
||||
<Icon icon={CloudCheck} class="text-primary" />
|
||||
</div>
|
||||
<div class="badge badge-neutral">Recommended</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-lg font-bold">Community</h3>
|
||||
<div class="text-xs font-semibold tracking-wider opacity-60">SELF-HOSTED</div>
|
||||
</div>
|
||||
<p class="text-sm opacity-70">
|
||||
For technical users who want full control. Deploy on your own infrastructure and
|
||||
manage your own updates and scaling.
|
||||
</p>
|
||||
</div>
|
||||
<ul class="flex list-inside list-disc flex-col gap-1 text-sm opacity-70">
|
||||
<li>Unlimited customization and control</li>
|
||||
<li>Free and open source software</li>
|
||||
<li>Full-featured admin dashboards available</li>
|
||||
<li>Requires some technical skills</li>
|
||||
<ul class="flex flex-col gap-2 text-sm">
|
||||
<li class="flex items-center gap-2">
|
||||
<Icon icon={CheckCircle} class="opacity-60" />
|
||||
Open source core
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<Icon icon={CheckCircle} class="opacity-60" />
|
||||
Community support
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<Icon icon={CheckCircle} class="opacity-60" />
|
||||
Bring your own infra
|
||||
</li>
|
||||
</ul>
|
||||
<Link
|
||||
external
|
||||
class="btn btn-neutral mt-auto"
|
||||
href="https://gitea.coracle.social/coracle/zooid">
|
||||
Get started
|
||||
<Icon icon={ArrowRight} />
|
||||
</Link>
|
||||
</div>
|
||||
<Link external class="btn btn-primary" href="https://github.com/coracle-social/zooid">
|
||||
Get Started
|
||||
<Icon icon={ArrowRight} />
|
||||
</Link>
|
||||
</div>
|
||||
<div class="card2 bg-alt flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<img alt="Coracle Logo" src="/coracle.png" class="h-7 w-7" />
|
||||
<div class="card2 bg-alt border-primary flex flex-col gap-5 border">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-start justify-between">
|
||||
<img alt="Coracle Logo" src="/coracle.png" class="h-10 w-10" />
|
||||
<div class="badge badge-primary">Recommended</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-lg font-bold">Coracle Hosting</h3>
|
||||
<div class="text-xs font-semibold tracking-wider opacity-60">FULLY MANAGED</div>
|
||||
</div>
|
||||
<div class="badge badge-neutral">Recommended</div>
|
||||
<p class="text-sm opacity-70">
|
||||
The premium experience. We handle the infrastructure, security updates, and scaling so
|
||||
you can focus on your community.
|
||||
</p>
|
||||
</div>
|
||||
<ul class="flex list-inside list-disc flex-col gap-1 text-sm opacity-70">
|
||||
<li>Simple setup, support included</li>
|
||||
<li>Free and open source software — no vendor lock-in</li>
|
||||
<li>Advanced access controls and relay policies</li>
|
||||
<li>Full-featured admin dashboard</li>
|
||||
<ul class="flex flex-col gap-2 text-sm">
|
||||
<li class="flex items-center gap-2">
|
||||
<Icon icon={CheckCircle} class="text-primary" />
|
||||
One-click deployment
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<Icon icon={CheckCircle} class="text-primary" />
|
||||
Automated backups & scaling
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<Icon icon={CheckCircle} class="text-primary" />
|
||||
Priority support
|
||||
</li>
|
||||
</ul>
|
||||
<Link external class="btn btn-primary mt-auto" href="https://hosting.coracle.social">
|
||||
Start for free
|
||||
<Icon icon={ArrowRight} />
|
||||
</Link>
|
||||
</div>
|
||||
<Link external class="btn btn-neutral" href="https://hosting.coracle.social">
|
||||
Get Started
|
||||
<Icon icon={ArrowRight} />
|
||||
</Link>
|
||||
</div>
|
||||
<div class="card2 bg-alt flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="self-start">
|
||||
<img
|
||||
alt="Relay Tools"
|
||||
src="https://relay.tools/17.svg"
|
||||
class="-my-20 -ml-2 hidden h-48 dark:block"
|
||||
style="filter: contrast(50%)" />
|
||||
<img
|
||||
alt="Relay Tools"
|
||||
src="https://relay.tools/19.svg"
|
||||
class="-my-20 -ml-2 h-48 dark:hidden"
|
||||
style="filter: contrast(50%)" />
|
||||
</div>
|
||||
<ul class="flex list-inside list-disc flex-col gap-1 text-sm opacity-70">
|
||||
<li>Independently run</li>
|
||||
<li>Customizable relay policies</li>
|
||||
<li>Simple management dashboard</li>
|
||||
<li>Support available</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Link external class="btn btn-neutral" href="https://relay.tools/signup">
|
||||
Get Started
|
||||
<div class="flex flex-col items-center justify-center gap-2 py-2 text-sm opacity-70">
|
||||
<span>Want to host on other servers?</span>
|
||||
<Link external class="link center gap-1" href="https://relay.tools/signup">
|
||||
Other hosting options
|
||||
<Icon icon={ArrowRight} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"relation": [
|
||||
"delegate_permission/common.handle_all_urls"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "social.flotilla",
|
||||
"sha256_cert_fingerprints": [
|
||||
"D0:2A:2E:82:75:92:4D:E2:13:E8:46:B8:EA:09:15:17:7F:46:7B:D1:49:E3:12:60:F0:01:D3:EF:42:9B:A2:DA",
|
||||
"6D:AF:68:3E:1C:A8:3A:4C:D8:85:73:E9:73:9E:2A:A9:44:C8:5D:56:15:4E:34:42:30:55:7C:FF:ED:4A:D7:8C"
|
||||
"6D:AF:68:3E:1C:A8:3A:4C:D8:85:73:E9:73:9E:2A:A9:44:C8:5D:56:15:4E:34:42:30:55:7C:FF:ED:4A:D7:8C",
|
||||
"8C:EE:37:F9:8A:08:02:A7:BB:55:2B:64:E5:A5:93:D8:58:73:14:26:66:71:DD:B0:4F:AB:9D:D5:4C:DF:FB:F7"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user