Compare commits

...
51 Commits
Author SHA1 Message Date
raucao 335ff1a9d8 1.26.2
CI / Lint (push) Successful in 52s
CI / Test (push) Successful in 1m2s
2026-08-12 13:26:37 -06:00
raucao 9dc080b86e Update dependencies
CI / Lint (push) Successful in 53s
CI / Test (push) Canceled after 1m3s
2026-08-12 13:24:12 -06:00
raucao a457e1521b 1.26.1
CI / Lint (push) Successful in 55s
CI / Test (push) Successful in 1m5s
2026-08-07 12:12:54 -06:00
raucao 39ab5e2cd1 Merge pull request 'Use our own relay as default Nostr Connect relay' (#72) from bugfix/default_signing_relay into master
CI / Lint (push) Successful in 53s
CI / Test (push) Successful in 1m4s
Reviewed-on: #72
2026-08-07 18:07:14 +00:00
raucao ce53d42581 Use our own relay as default Nostr Connect relay
CI / Lint (pull_request) Successful in 55s
CI / Test (pull_request) Successful in 1m5s
Release Drafter / Update release notes draft (pull_request) Successful in 5s
Fixes the current default connect relay being offline (and having been
down for weeks now)
2026-08-07 12:04:57 -06:00
raucao 387fd6efed 1.26.0
CI / Lint (push) Successful in 53s
CI / Test (push) Successful in 1m4s
2026-08-07 07:33:52 -06:00
raucao aaf0d8c3e9 Merge pull request 'Add alt text to place photos' (#71) from bugix/photo_alt_text into master
CI / Lint (push) Successful in 53s
CI / Test (push) Successful in 1m2s
Reviewed-on: #71
2026-08-07 13:30:39 +00:00
raucao 9807a2b822 Add alt text to photo uploads
CI / Lint (pull_request) Successful in 53s
CI / Test (pull_request) Successful in 1m6s
Release Drafter / Update release notes draft (pull_request) Successful in 5s
- Form field for optionally adding a description
- Publish as `imeta` `alt`
- Render as `<img>` alt when set

Fixes a placeholder being published instead. Also strips the current
placeholder if set as alt text (for existing uploads).
2026-08-07 07:25:53 -06:00
raucao ac9870240f Merge pull request 'Update dependencies' (#70) from chore/update_dependencies into master
CI / Lint (push) Successful in 1m7s
CI / Test (push) Successful in 59s
Reviewed-on: #70
2026-08-06 22:00:01 +00:00
raucao 1b185d1033 Upgrade lint and dev tooling to latest majors
CI / Test (pull_request) Successful in 1m4s
CI / Lint (pull_request) Successful in 1m12s
Release Drafter / Update release notes draft (pull_request) Successful in 21s
Bump eslint to ^10, @eslint/js to ^10, eslint-plugin-ember to ^13,
eslint-plugin-n to ^18, globals to ^17, stylelint to ^17,
stylelint-config-standard to ^40, sinon to ^22, and concurrently
to ^10.

Fix three new lint violations surfaced by the newer versions:
- Remove redundant padding-left after padding shorthand (stylelint 17)
- Remove useless null assignment before find() (eslint 10
  no-useless-assignment)
- Attach caught error as cause on rethrown Error (eslint 10
  preserve-caught-error)
2026-08-06 16:40:54 -05:00
raucao 4d4ed8c1ea Upgrade to oauth2-pkce 3
Bump oauth2-pkce from ^2.1.3 to ^3.0.0. In v3, getTokens() throws
ErrorAccessTokenExpired when the token has expired and no
onAccessTokenExpiry handler is configured. Add the handler to the
OSM auth client config so it automatically refreshes via the refresh
token instead of throwing
2026-08-06 16:35:11 -05:00
raucao 1613ebf0bf Upgrade to Ember 7.1
Bump ember-source from ~6.12.0 to ~7.1.0 and ember-cli from ^6.12.0
to ^7.1.0. Ember 7 removes APIs deprecated during the 6.x cycle
(import Ember, AMD bundles, inject from @ember/service) — none of
which were used in this codebase, so no source changes were needed.
2026-08-06 16:32:18 -05:00
raucao 4cb8c94c9c Upgrade to Babel 8, Vite 8, and babel-plugin-ember-template-compilation 4
Bump @babel/core, @babel/eslint-parser, @babel/plugin-transform-runtime,
and @babel/runtime to ^8.x. Remove useESModules and regenerator options
from @babel/plugin-transform-runtime config (both removed in Babel 8).
Switch @babel/eslint-parser import from the removed /experimental-worker
entry point to the main entry point.

Bump vite to ^8.0.0 (now using Rolldown/Oxc instead of esbuild/Rollup).
Bump @rollup/plugin-babel to ^7.1.0 and
babel-plugin-ember-template-compilation to ^4.0.0.

Patch ember-concurrency@5.2.0 to remove the api.assertVersion(7) call
that is incompatible with Babel 8 (ember-concurrency has not yet
released a Babel 8-compatible version).
2026-08-06 16:26:23 -05:00
raucao 1f87c802e1 Upgrade to Ember 6.12 stable
Move ember-source from ~6.11.0-alpha.6 to ~6.12.0 and ember-cli from
^6.10.0 to ^6.12.0. This is the final stable release of the 6.x
series and an LTS, clearing the way for a clean Ember 7 upgrade.
2026-08-06 16:19:14 -05:00
raucao a6576fcda8 Migrate to applesauce v6
Remove applesauce-factory (deleted in v6) and bump applesauce-core,
applesauce-relay, and applesauce-signers to ^6.2.x. Rewrite all
EventFactory usage from the removed constructor API
(`new EventFactory({ signer }).sign(template)`) to the new chainable
builder (`EventFactory.fromKind(k).content(...).sign(signer)`).

Remove `relay.eoseTimeout` overrides in nostr-auth — the eoseTimeout
property was removed in v6; NostrConnectSigner now uses persistent
subscriptions that don't rely on EOSE-based timeouts.

Update photo-gallery test mock to use 64-char hex pubkeys/ids/sigs
because v6's sign() validates event structure via validateEvent().

Also includes safe semver bumps to all other dependencies.
2026-08-06 16:13:24 -05:00
raucao 1822c4893f Update pinhead icon paths for 15.25.0 2026-08-06 16:12:37 -05:00
raucao 39dca446ac 1.25.1
CI / Lint (push) Successful in 46s
CI / Test (push) Successful in 1m10s
2026-06-30 23:12:42 +02:00
raucao 2e6827f0d0 Merge pull request 'Sort collections by createdAt' (#68) from ui/collection_sorting into master
CI / Lint (push) Successful in 33s
CI / Test (push) Successful in 56s
Reviewed-on: #68
2026-06-30 21:10:59 +00:00
raucao 8c3a805684 Sort collections by createdAt
CI / Lint (pull_request) Successful in 34s
CI / Test (pull_request) Successful in 57s
Release Drafter / Update release notes draft (pull_request) Successful in 8s
2026-06-30 23:07:53 +02:00
raucao cf80c1eea7 1.25.0
CI / Lint (push) Successful in 33s
CI / Test (push) Successful in 57s
2026-06-30 19:35:52 +02:00
raucao 126806fcd4 Merge pull request 'Fix some routing and click/nav UX issues' (#67) from bugfix/search_routing into master
CI / Lint (push) Successful in 33s
CI / Test (push) Successful in 56s
Reviewed-on: #67
2026-06-30 17:30:26 +00:00
raucao f48045c35d Fix flaky test by adding safe guard
CI / Lint (pull_request) Successful in 33s
CI / Test (pull_request) Successful in 57s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2026-06-30 19:28:01 +02:00
raucao 989173a2e6 Fix bottom drawer re-animating while open
CI / Lint (pull_request) Successful in 32s
CI / Test (pull_request) Failing after 57s
2026-06-30 19:18:29 +02:00
raucao 62407f5fa4 Sync search form query value
* Clear input when clearing search from anywhere
* Pre-fill input when opening search URL with query params
2026-06-30 19:12:38 +02:00
raucao 7f1c4b5f61 When search markers are visible, clear search on map click 2026-06-30 18:32:23 +02:00
raucao 59e334c499 Fix place route being active after closing place details
Depending on where a place was opened from, it now goes back to the
index or (active) search route
2026-06-30 18:06:12 +02:00
raucao 981502e293 Merge pull request 'Add Collections (place lists) to app menu' (#65) from feature/list-places-in-lists into master
CI / Lint (push) Successful in 32s
CI / Test (push) Successful in 57s
Reviewed-on: #65
2026-06-30 15:14:26 +00:00
raucao 9c5fbca72a Use new RS places module
CI / Lint (pull_request) Successful in 34s
CI / Test (pull_request) Successful in 57s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2026-06-30 17:11:08 +02:00
raucao fd8f04f902 Mock all map tile requests
CI / Lint (pull_request) Successful in 33s
CI / Test (pull_request) Successful in 58s
2026-06-30 17:05:04 +02:00
raucao 3e23ec161c Only hide sidebar for new searches 2026-06-30 16:56:15 +02:00
raucao f2e531c0f6 Make collection list loading async 2026-06-30 16:26:22 +02:00
raucao 96a5a6ac34 Show "Saved place" type for non-OSM places in lists 2026-06-30 15:11:53 +02:00
raucao 78996b6c61 Refactor sidebar header styles, center icon titles 2026-06-30 14:51:01 +02:00
raucao bb5b69711c Use list colors for list header icons 2026-06-30 14:05:25 +02:00
raucao ad9c489102 Refactor app menu, add place lists
Unify sidebar, make everything route-based
2026-06-30 13:28:48 +02:00
raucao c11882adfb Merge pull request 'Animate sidebar/bottom drawer sliding into view' (#64) from ui/animations into master
CI / Lint (push) Successful in 32s
CI / Test (push) Successful in 57s
Reviewed-on: #64
2026-06-29 16:37:16 +00:00
raucao 707f4ac11c Animate sidebar/bottom drawer sliding into view
CI / Lint (pull_request) Successful in 31s
CI / Test (pull_request) Successful in 57s
Release Drafter / Update release notes draft (pull_request) Successful in 5s
2026-06-29 18:21:17 +02:00
raucao 3bada05b63 Merge pull request 'Various UI improvements' (#63) from ui/various into master
CI / Lint (push) Successful in 32s
CI / Test (push) Successful in 55s
Reviewed-on: #63
2026-06-29 15:37:25 +00:00
raucao f01730fef5 Add icon and quick search results for tourist information
CI / Lint (pull_request) Successful in 33s
CI / Test (pull_request) Successful in 58s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2026-06-29 17:32:26 +02:00
raucao 448c51bab6 Add icons for city gates and historic towers 2026-06-29 17:28:57 +02:00
raucao 0bcbae374b Use "yes" tag values only as fallbacks if there isn't a more specific
OSM key
2026-06-29 17:28:00 +02:00
raucao c33fe3b268 Add icon for car repair shops 2026-06-29 17:08:55 +02:00
raucao 18bda60310 Add guest houses to hotel quick search 2026-06-29 17:02:43 +02:00
raucao 86d25dc6ba Add OSM links for custom saved places
Link to the OSM search route, so we get a pin when opening OSM from
Marco
2026-06-29 16:45:49 +02:00
raucao 5b8bec6a00 Cut off overlong sidebar link texts with ellipses 2026-06-29 16:06:55 +02:00
raucao f2c2eb1fdc Add icon for amenity=townhall 2026-06-29 15:55:28 +02:00
raucao b42c4881f6 Merge pull request 'Add missing icons, test for missing icons in rules' (#62) from bugfix/icons into master
CI / Lint (push) Successful in 34s
CI / Test (push) Successful in 58s
Reviewed-on: #62
2026-06-29 13:38:34 +00:00
raucao b18e299eca Add missing icons, test for missing icons in rules
CI / Lint (pull_request) Successful in 34s
CI / Test (pull_request) Successful in 59s
Release Drafter / Update release notes draft (pull_request) Successful in 5s
2026-06-29 15:32:48 +02:00
raucao 401ed41fcd Merge pull request 'Turn default relays into required relays' (#61) from nostr/required_relays into master
CI / Lint (push) Successful in 31s
CI / Test (push) Successful in 56s
Reviewed-on: #61
2026-06-07 12:30:38 +00:00
raucao 504e8fab94 Fix lint errors
CI / Lint (pull_request) Successful in 31s
CI / Test (pull_request) Successful in 56s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2026-06-07 16:28:09 +04:00
raucao 76897c9e69 Turn default relays into required relays
CI / Lint (pull_request) Failing after 31s
CI / Test (pull_request) Successful in 55s
2026-06-07 16:21:26 +04:00
75 changed files with 5957 additions and 2956 deletions

No files matched your search

+52 -3
View File
@@ -9,6 +9,7 @@ import SearchBox from '#components/search-box';
import CategoryChips from '#components/category-chips'; import CategoryChips from '#components/category-chips';
import { and } from 'ember-truth-helpers'; import { and } from 'ember-truth-helpers';
import cachedImage from '../modifiers/cached-image'; import cachedImage from '../modifiers/cached-image';
import { POI_CATEGORIES } from '../utils/poi-categories';
export default class AppHeaderComponent extends Component { export default class AppHeaderComponent extends Component {
@service storage; @service storage;
@@ -16,11 +17,59 @@ export default class AppHeaderComponent extends Component {
@service nostrAuth; @service nostrAuth;
@service nostrData; @service nostrData;
@service mapUi; @service mapUi;
@service router;
@tracked isUserMenuOpen = false; @tracked isUserMenuOpen = false;
@tracked searchQuery = ''; @tracked searchQuery = '';
get hasQuery() { constructor() {
return !!this.searchQuery; super(...arguments);
if (this.router && typeof this.router.on === 'function') {
this.router.on('routeDidChange', this.syncSearchQuery);
}
this.syncSearchQuery();
}
willDestroy() {
if (this.router && typeof this.router.off === 'function') {
this.router.off('routeDidChange', this.syncSearchQuery);
}
super.willDestroy(...arguments);
}
@action
syncSearchQuery() {
const qp =
this.mapUi.currentSearch || this.router?.currentRoute?.queryParams;
if (qp?.q) {
this.searchQuery = qp.q;
} else if (qp?.category) {
const category = POI_CATEGORIES.find((c) => c.id === qp.category);
this.searchQuery = category ? category.label : qp.category;
} else {
this.searchQuery = '';
}
}
get isSearching() {
// 1. If we are actively focusing/typing in the search box with a query, hide pills
if (this.mapUi.searchBoxHasFocus && this.searchQuery) {
return true;
}
// 2. If we are on the search route, check loading and results status
if (this.router?.currentRouteName === 'search') {
if (this.mapUi.loadingState) {
return false; // Keep pills visible while loading
}
return this.mapUi.searchResults && this.mapUi.searchResults.length > 0;
}
// 3. Fallback for integration tests (non-search route with a query)
if (this.router?.currentRouteName !== 'search' && this.searchQuery) {
return true;
}
return false;
} }
get showQuickSearch() { get showQuickSearch() {
@@ -61,7 +110,7 @@ export default class AppHeaderComponent extends Component {
</div> </div>
{{#if this.showQuickSearch}} {{#if this.showQuickSearch}}
<div class="header-center {{if this.hasQuery 'searching'}}"> <div class="header-center {{if this.isSearching 'searching'}}">
<CategoryChips @onSelect={{this.handleChipSelect}} /> <CategoryChips @onSelect={{this.handleChipSelect}} />
</div> </div>
{{/if}} {{/if}}
+2 -2
View File
@@ -3,11 +3,11 @@ import Icon from '#components/icon';
<template> <template>
{{! template-lint-disable no-nested-interactive }} {{! template-lint-disable no-nested-interactive }}
<div class="sidebar-header"> <div class="sidebar-header has-back-btn">
<button type="button" class="back-btn" {{on "click" @onBack}}> <button type="button" class="back-btn" {{on "click" @onBack}}>
<Icon @name="arrow-left" @size={{20}} @color="#333" /> <Icon @name="arrow-left" @size={{20}} @color="#333" />
</button> </button>
<h2>About</h2> <h2 class="sidebar-header-text-centered">About</h2>
<button type="button" class="close-btn" {{on "click" @onClose}}> <button type="button" class="close-btn" {{on "click" @onClose}}>
<Icon @name="x" @size={{20}} @color="#333" /> <Icon @name="x" @size={{20}} @color="#333" />
</button> </button>
+6
View File
@@ -19,6 +19,12 @@ import iconRounded from '../../icons/icon-rounded.svg?raw';
<div class="sidebar-content"> <div class="sidebar-content">
<ul class="app-menu"> <ul class="app-menu">
<li>
<button type="button" {{on "click" @onSavedPlaces}}>
<Icon @name="bookmark" @size={{20}} />
<span>Collections</span>
</button>
</li>
<li> <li>
<button type="button" {{on "click" (fn @onNavigate "settings")}}> <button type="button" {{on "click" (fn @onNavigate "settings")}}>
<Icon @name="settings" @size={{20}} /> <Icon @name="settings" @size={{20}} />
+12 -1
View File
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
import { action } from '@ember/object'; import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking'; import { tracked } from '@glimmer/tracking';
import { fn } from '@ember/helper'; import { fn } from '@ember/helper';
import { service } from '@ember/service';
import eq from 'ember-truth-helpers/helpers/eq'; import eq from 'ember-truth-helpers/helpers/eq';
import AppMenuHome from './home'; import AppMenuHome from './home';
@@ -9,6 +10,7 @@ import AppMenuSettings from './settings';
import AppMenuAbout from './about'; import AppMenuAbout from './about';
export default class AppMenu extends Component { export default class AppMenu extends Component {
@service router;
@tracked currentView = 'menu'; // 'menu', 'settings', 'about' @tracked currentView = 'menu'; // 'menu', 'settings', 'about'
@action @action
@@ -16,10 +18,19 @@ export default class AppMenu extends Component {
this.currentView = view; this.currentView = view;
} }
@action
goToSavedPlaces() {
this.router.transitionTo('lists.index');
}
<template> <template>
<div class="sidebar app-menu-pane"> <div class="sidebar app-menu-pane">
{{#if (eq this.currentView "menu")}} {{#if (eq this.currentView "menu")}}
<AppMenuHome @onNavigate={{this.setView}} @onClose={{@onClose}} /> <AppMenuHome
@onNavigate={{this.setView}}
@onClose={{@onClose}}
@onSavedPlaces={{this.goToSavedPlaces}}
/>
{{else if (eq this.currentView "settings")}} {{else if (eq this.currentView "settings")}}
<AppMenuSettings <AppMenuSettings
+2 -2
View File
@@ -21,11 +21,11 @@ export default class AppMenuSettings extends Component {
} }
<template> <template>
<div class="sidebar-header"> <div class="sidebar-header has-back-btn">
<button type="button" class="back-btn" {{on "click" @onBack}}> <button type="button" class="back-btn" {{on "click" @onBack}}>
<Icon @name="arrow-left" @size={{20}} @color="#333" /> <Icon @name="arrow-left" @size={{20}} @color="#333" />
</button> </button>
<h2>Settings</h2> <h2 class="sidebar-header-text-centered">Settings</h2>
<button type="button" class="close-btn" {{on "click" @onClose}}> <button type="button" class="close-btn" {{on "click" @onClose}}>
<Icon @name="x" @size={{20}} @color="#333" /> <Icon @name="x" @size={{20}} @color="#333" />
</button> </button>
+167 -25
View File
@@ -5,7 +5,11 @@ import { tracked } from '@glimmer/tracking';
import { service } from '@ember/service'; import { service } from '@ember/service';
import { fn } from '@ember/helper'; import { fn } from '@ember/helper';
import Icon from '#components/icon'; import Icon from '#components/icon';
import { normalizeRelayUrl } from '../../../utils/nostr'; import {
excludeRequiredRelays,
mergeRequiredRelays,
normalizeRelayUrl,
} from '../../../utils/nostr';
const stripProtocol = (url) => (url ? url.replace(/^wss?:\/\//, '') : ''); const stripProtocol = (url) => (url ? url.replace(/^wss?:\/\//, '') : '');
@@ -17,6 +21,74 @@ export default class AppMenuSettingsNostr extends Component {
@tracked newReadRelay = ''; @tracked newReadRelay = '';
@tracked newWriteRelay = ''; @tracked newWriteRelay = '';
get customReadRelays() {
return excludeRequiredRelays(
this.settings.nostrReadRelays || [],
this.nostrData.requiredReadRelays
);
}
get customWriteRelays() {
return excludeRequiredRelays(
this.settings.nostrWriteRelays || [],
this.nostrData.requiredWriteRelays
);
}
get readRelayExclusions() {
return this.settings.nostrReadRelayExclusions || [];
}
get writeRelayExclusions() {
return this.settings.nostrWriteRelayExclusions || [];
}
get requiredReadRelaySet() {
return new Set(this.nostrData.requiredReadRelays.filter(Boolean));
}
get requiredWriteRelaySet() {
return new Set(this.nostrData.requiredWriteRelays.filter(Boolean));
}
get mailboxReadRelaySet() {
return new Set(this.nostrData.mailboxReadRelays);
}
get mailboxWriteRelaySet() {
return new Set(this.nostrData.mailboxWriteRelays);
}
get hasReadOverrides() {
return (
this.customReadRelays.length > 0 || this.readRelayExclusions.length > 0
);
}
get hasWriteOverrides() {
return (
this.customWriteRelays.length > 0 || this.writeRelayExclusions.length > 0
);
}
get readRelaysForDisplay() {
return this.nostrData.activeReadRelays.map((url) => {
return {
url,
isRequired: this.requiredReadRelaySet.has(url),
};
});
}
get writeRelaysForDisplay() {
return this.nostrData.activeWriteRelays.map((url) => {
return {
url,
isRequired: this.requiredWriteRelaySet.has(url),
};
});
}
@action @action
updateNewReadRelay(event) { updateNewReadRelay(event) {
this.newReadRelay = event.target.value; this.newReadRelay = event.target.value;
@@ -32,19 +104,51 @@ export default class AppMenuSettingsNostr extends Component {
const url = normalizeRelayUrl(this.newReadRelay); const url = normalizeRelayUrl(this.newReadRelay);
if (!url) return; if (!url) return;
const current = const merged = mergeRequiredRelays(this.nostrData.requiredReadRelays, [
this.settings.nostrReadRelays || this.nostrData.defaultReadRelays; ...this.customReadRelays,
const set = new Set([...current, url]); url,
this.settings.update('nostrReadRelays', Array.from(set)); ]);
const custom = excludeRequiredRelays(
merged,
this.nostrData.requiredReadRelays
);
const readExclusions = this.readRelayExclusions.filter((relay) => {
return normalizeRelayUrl(relay) !== url;
});
this.settings.update('nostrReadRelays', custom.length > 0 ? custom : null);
this.settings.update(
'nostrReadRelayExclusions',
readExclusions.length > 0 ? readExclusions : null
);
this.newReadRelay = ''; this.newReadRelay = '';
} }
@action @action
removeReadRelay(url) { removeReadRelay(url) {
const current = if (this.requiredReadRelaySet.has(url)) {
this.settings.nostrReadRelays || this.nostrData.defaultReadRelays; return;
const filtered = current.filter((r) => r !== url); }
this.settings.update('nostrReadRelays', filtered);
const normalizedUrl = normalizeRelayUrl(url);
const remainingCustom = this.customReadRelays.filter((relay) => {
return normalizeRelayUrl(relay) !== normalizedUrl;
});
const nextExclusions = this.mailboxReadRelaySet.has(normalizedUrl)
? Array.from(new Set([...this.readRelayExclusions, normalizedUrl]))
: this.readRelayExclusions;
this.settings.update(
'nostrReadRelays',
remainingCustom.length > 0 ? remainingCustom : null
);
this.settings.update(
'nostrReadRelayExclusions',
nextExclusions.length > 0 ? nextExclusions : null
);
} }
@action @action
@@ -64,6 +168,7 @@ export default class AppMenuSettingsNostr extends Component {
@action @action
resetReadRelays() { resetReadRelays() {
this.settings.update('nostrReadRelays', null); this.settings.update('nostrReadRelays', null);
this.settings.update('nostrReadRelayExclusions', null);
} }
@action @action
@@ -71,24 +176,57 @@ export default class AppMenuSettingsNostr extends Component {
const url = normalizeRelayUrl(this.newWriteRelay); const url = normalizeRelayUrl(this.newWriteRelay);
if (!url) return; if (!url) return;
const current = const merged = mergeRequiredRelays(this.nostrData.requiredWriteRelays, [
this.settings.nostrWriteRelays || this.nostrData.defaultWriteRelays; ...this.customWriteRelays,
const set = new Set([...current, url]); url,
this.settings.update('nostrWriteRelays', Array.from(set)); ]);
const custom = excludeRequiredRelays(
merged,
this.nostrData.requiredWriteRelays
);
const writeExclusions = this.writeRelayExclusions.filter((relay) => {
return normalizeRelayUrl(relay) !== url;
});
this.settings.update('nostrWriteRelays', custom.length > 0 ? custom : null);
this.settings.update(
'nostrWriteRelayExclusions',
writeExclusions.length > 0 ? writeExclusions : null
);
this.newWriteRelay = ''; this.newWriteRelay = '';
} }
@action @action
removeWriteRelay(url) { removeWriteRelay(url) {
const current = if (this.requiredWriteRelaySet.has(url)) {
this.settings.nostrWriteRelays || this.nostrData.defaultWriteRelays; return;
const filtered = current.filter((r) => r !== url); }
this.settings.update('nostrWriteRelays', filtered);
const normalizedUrl = normalizeRelayUrl(url);
const remainingCustom = this.customWriteRelays.filter((relay) => {
return normalizeRelayUrl(relay) !== normalizedUrl;
});
const nextExclusions = this.mailboxWriteRelaySet.has(normalizedUrl)
? Array.from(new Set([...this.writeRelayExclusions, normalizedUrl]))
: this.writeRelayExclusions;
this.settings.update(
'nostrWriteRelays',
remainingCustom.length > 0 ? remainingCustom : null
);
this.settings.update(
'nostrWriteRelayExclusions',
nextExclusions.length > 0 ? nextExclusions : null
);
} }
@action @action
resetWriteRelays() { resetWriteRelays() {
this.settings.update('nostrWriteRelays', null); this.settings.update('nostrWriteRelays', null);
this.settings.update('nostrWriteRelayExclusions', null);
} }
@action @action
@@ -112,18 +250,20 @@ export default class AppMenuSettingsNostr extends Component {
<div class="form-group"> <div class="form-group">
<label for="new-read-relay">Read Relays</label> <label for="new-read-relay">Read Relays</label>
<ul class="relay-list"> <ul class="relay-list">
{{#each this.nostrData.activeReadRelays as |relay|}} {{#each this.readRelaysForDisplay as |relay|}}
<li> <li>
<span>{{stripProtocol relay}}</span> <span>{{stripProtocol relay.url}}</span>
{{#unless relay.isRequired}}
<button <button
type="button" type="button"
class="btn-remove-relay" class="btn-remove-relay"
title="Remove relay" title="Remove relay"
aria-label="Remove" aria-label="Remove"
{{on "click" (fn this.removeReadRelay relay)}} {{on "click" (fn this.removeReadRelay relay.url)}}
> >
<Icon @name="x" @size={{14}} @color="currentColor" /> <Icon @name="x" @size={{14}} @color="currentColor" />
</button> </button>
{{/unless}}
</li> </li>
{{/each}} {{/each}}
</ul> </ul>
@@ -143,7 +283,7 @@ export default class AppMenuSettingsNostr extends Component {
{{on "click" this.addReadRelay}} {{on "click" this.addReadRelay}}
>Add</button> >Add</button>
</div> </div>
{{#if this.settings.nostrReadRelays}} {{#if this.hasReadOverrides}}
<button <button
type="button" type="button"
class="btn-link reset-relays" class="btn-link reset-relays"
@@ -157,18 +297,20 @@ export default class AppMenuSettingsNostr extends Component {
<div class="form-group"> <div class="form-group">
<label for="new-write-relay">Write Relays</label> <label for="new-write-relay">Write Relays</label>
<ul class="relay-list"> <ul class="relay-list">
{{#each this.nostrData.activeWriteRelays as |relay|}} {{#each this.writeRelaysForDisplay as |relay|}}
<li> <li>
<span>{{stripProtocol relay}}</span> <span>{{stripProtocol relay.url}}</span>
{{#unless relay.isRequired}}
<button <button
type="button" type="button"
class="btn-remove-relay" class="btn-remove-relay"
title="Remove relay" title="Remove relay"
aria-label="Remove" aria-label="Remove"
{{on "click" (fn this.removeWriteRelay relay)}} {{on "click" (fn this.removeWriteRelay relay.url)}}
> >
<Icon @name="x" @size={{14}} @color="currentColor" /> <Icon @name="x" @size={{14}} @color="currentColor" />
</button> </button>
{{/unless}}
</li> </li>
{{/each}} {{/each}}
</ul> </ul>
@@ -188,7 +330,7 @@ export default class AppMenuSettingsNostr extends Component {
{{on "click" this.addWriteRelay}} {{on "click" this.addWriteRelay}}
>Add</button> >Add</button>
</div> </div>
{{#if this.settings.nostrWriteRelays}} {{#if this.hasWriteOverrides}}
<button <button
type="button" type="button"
class="btn-link reset-relays" class="btn-link reset-relays"
+7
View File
@@ -1088,6 +1088,7 @@ export default class MapComponent extends Component {
const bbox = { minLat, minLon, maxLat, maxLon }; const bbox = { minLat, minLon, maxLat, maxLon };
this.mapUi.updateBounds(bbox); this.mapUi.updateBounds(bbox);
await this.storage.loadPlacesInBounds(bbox); await this.storage.loadPlacesInBounds(bbox);
if (this.isDestroying || this.isDestroyed) return;
this.nostrData.loadPlacesInBounds(bbox); this.nostrData.loadPlacesInBounds(bbox);
this.loadBookmarks(this.storage.placesInView); this.loadBookmarks(this.storage.placesInView);
@@ -1191,6 +1192,12 @@ export default class MapComponent extends Component {
return; return;
} }
if (this.mapUi.searchResults && this.mapUi.searchResults.length > 0) {
console.debug('Clearing active search and markers on map click');
this.router.transitionTo('index');
return;
}
// Require Zoom >= 17 for generic map searches // Require Zoom >= 17 for generic map searches
// This prevents accidental searches when interacting with the map at a high level // This prevents accidental searches when interacting with the map at a high level
const currentZoom = this.mapInstance.getView().getZoom(); const currentZoom = this.mapInstance.getView().getZoom();
+4 -4
View File
@@ -237,7 +237,7 @@ export default class PhotoCarousel extends Component {
data-src={{photo.url}} data-src={{photo.url}}
class="place-header-photo class="place-header-photo
{{if photo.isLandscape 'landscape' 'portrait'}}" {{if photo.isLandscape 'landscape' 'portrait'}}"
alt={{@name}} alt={{photo.alt}}
{{fadeInImage photo.url}} {{fadeInImage photo.url}}
/> />
{{else if this.isGalleryThumbnails}} {{else if this.isGalleryThumbnails}}
@@ -245,7 +245,7 @@ export default class PhotoCarousel extends Component {
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}} data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
class="place-header-photo class="place-header-photo
{{if photo.isLandscape 'landscape' 'portrait'}}" {{if photo.isLandscape 'landscape' 'portrait'}}"
alt={{@name}} alt={{photo.alt}}
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}} {{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
/> />
{{else}} {{else}}
@@ -260,7 +260,7 @@ export default class PhotoCarousel extends Component {
<img <img
data-src={{photo.url}} data-src={{photo.url}}
class="place-header-photo landscape" class="place-header-photo landscape"
alt={{@name}} alt={{photo.alt}}
{{fadeInImage photo.url}} {{fadeInImage photo.url}}
/> />
</picture> </picture>
@@ -269,7 +269,7 @@ export default class PhotoCarousel extends Component {
<img <img
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}} data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
class="place-header-photo portrait" class="place-header-photo portrait"
alt={{@name}} alt={{photo.alt}}
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}} {{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
/> />
{{/if}} {{/if}}
+6 -10
View File
@@ -6,7 +6,7 @@ import { fn } from '@ember/helper';
import { service } from '@ember/service'; import { service } from '@ember/service';
import { modifier } from 'ember-modifier'; import { modifier } from 'ember-modifier';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import { EventFactory } from 'applesauce-factory'; import { EventFactory } from 'applesauce-core';
import config from 'marco/config/environment'; import config from 'marco/config/environment';
import DropdownMenu from './dropdown-menu'; import DropdownMenu from './dropdown-menu';
import PhotoCarousel from './photo-carousel'; import PhotoCarousel from './photo-carousel';
@@ -201,21 +201,17 @@ export default class PhotoGallery extends Component {
const eventId = this.currentPhoto.eventId; const eventId = this.currentPhoto.eventId;
// Publish Nostr kind: 5 deletion event first so we don't end up with dead blossom links on a failure // Publish Nostr kind: 5 deletion event first so we don't end up with dead blossom links on a failure
const factory = new EventFactory({ signer: this.nostrAuth.signer });
const tags = [['e', eventId]]; const tags = [['e', eventId]];
if (this.currentPhoto.placeIdentifier) { if (this.currentPhoto.placeIdentifier) {
tags.push(['i', this.currentPhoto.placeIdentifier]); tags.push(['i', this.currentPhoto.placeIdentifier]);
} }
const template = { const event = await EventFactory.fromKind(5)
kind: 5, .content('Deleted photo')
created_at: Math.floor(Date.now() / 1000), .modifyPublicTags(() => tags)
content: 'Deleted photo', .as(this.nostrAuth.signer)
tags, .sign();
};
const event = await factory.sign(template);
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event); await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
// Remove from local store by adding the kind 5 to it // Remove from local store by adding the kind 5 to it
+15 -2
View File
@@ -23,6 +23,7 @@ export default class PlaceDetails extends Component {
@service storage; @service storage;
@service nostrAuth; @service nostrAuth;
@service nostrData; @service nostrData;
@service mapUi;
@tracked isEditing = false; @tracked isEditing = false;
@tracked showLists = false; @tracked showLists = false;
@tracked isPhotoUploadActive = false; @tracked isPhotoUploadActive = false;
@@ -345,11 +346,23 @@ export default class PlaceDetails extends Component {
get osmUrl() { get osmUrl() {
const id = this.place.osmId; const id = this.place.osmId;
if (!id) return null; if (id) {
const type = this.place.osmType || 'node'; const type = this.place.osmType || 'node';
return `https://www.openstreetmap.org/${type}/${id}`; return `https://www.openstreetmap.org/${type}/${id}`;
} }
const lat = this.place.lat;
const lon = this.place.lon;
if (!lat || !lon) return null;
const viewLat = this.mapUi.currentCenter?.lat ?? lat;
const viewLon = this.mapUi.currentCenter?.lon ?? lon;
const zoom = this.mapUi.currentZoom ?? 17;
const roundedZoom = Math.round(zoom);
return `https://www.openstreetmap.org/search?lat=${lat}&lon=${lon}&zoom=${roundedZoom}#map=${roundedZoom}/${Number(viewLat).toFixed(5)}/${Number(viewLon).toFixed(5)}`;
}
get gmapsUrl() { get gmapsUrl() {
const id = this.place.gmapsId || this.place.osmId; const id = this.place.gmapsId || this.place.osmId;
if (!id) return null; if (!id) return null;
@@ -591,7 +604,7 @@ export default class PlaceDetails extends Component {
</div> </div>
{{#if this.osmUrl}} {{#if this.place.osmId}}
<div class="meta-info"> <div class="meta-info">
<p class="content-with-icon"> <p class="content-with-icon">
<Icon @name="feather-camera" /> <Icon @name="feather-camera" />
+2 -2
View File
@@ -103,7 +103,7 @@ export default class PlaceListsManager extends Component {
checked={{this.isSaved}} checked={{this.isSaved}}
{{on "change" this.toggleSaved}} {{on "change" this.toggleSaved}}
/> />
<span class="list-color"></span> <span class="list-color-dot"></span>
<span class="list-name">Saved places</span> <span class="list-name">Saved places</span>
</label> </label>
</div> </div>
@@ -122,7 +122,7 @@ export default class PlaceListsManager extends Component {
/> />
{{! template-lint-disable no-inline-styles }} {{! template-lint-disable no-inline-styles }}
<span <span
class="list-color" class="list-color-dot"
style={{this.styleFor list.color}} style={{this.styleFor list.color}}
></span> ></span>
<span class="list-name">{{list.title}}</span> <span class="list-name">{{list.title}}</span>
+30 -16
View File
@@ -27,6 +27,7 @@ export default class PlacePhotoUpload extends Component {
@tracked isPublishing = false; @tracked isPublishing = false;
@tracked isDragging = false; @tracked isDragging = false;
@tracked selectedTags = []; @tracked selectedTags = [];
@tracked altText = '';
get place() { get place() {
return this.args.place || {}; return this.args.place || {};
@@ -103,6 +104,7 @@ export default class PlacePhotoUpload extends Component {
this.file = null; this.file = null;
this.uploadedPhoto = null; this.uploadedPhoto = null;
this.selectedTags = []; this.selectedTags = [];
this.altText = '';
if (this.args.onUploadStateChange) { if (this.args.onUploadStateChange) {
this.args.onUploadStateChange(false); this.args.onUploadStateChange(false);
} }
@@ -118,6 +120,11 @@ export default class PlacePhotoUpload extends Component {
this.selectedTags = [tag]; this.selectedTags = [tag];
} }
@action
updateAltText(event) {
this.altText = event.target.value;
}
deletePhotoTask = task(async (photoData) => { deletePhotoTask = task(async (photoData) => {
try { try {
if (photoData.hash) { if (photoData.hash) {
@@ -155,8 +162,6 @@ export default class PlacePhotoUpload extends Component {
this.isPublishing = true; this.isPublishing = true;
try { try {
const factory = new EventFactory({ signer: this.nostrAuth.signer });
const tags = [['i', `osm:${osmType}:${osmId}`]]; const tags = [['i', `osm:${osmType}:${osmId}`]];
for (const tag of this.selectedTags) { for (const tag of this.selectedTags) {
@@ -179,7 +184,10 @@ export default class PlacePhotoUpload extends Component {
imeta.push(`dim ${photo.dim}`); imeta.push(`dim ${photo.dim}`);
} }
imeta.push('alt A photo of a place'); const alt = this.altText.trim();
if (alt) {
imeta.push(`alt ${alt}`);
}
if (photo.fallbackUrls && photo.fallbackUrls.length > 0) { if (photo.fallbackUrls && photo.fallbackUrls.length > 0) {
for (const fallbackUrl of photo.fallbackUrls) { for (const fallbackUrl of photo.fallbackUrls) {
@@ -198,17 +206,11 @@ export default class PlacePhotoUpload extends Component {
tags.push(imeta); tags.push(imeta);
// NIP-XX draft Place Photo event // NIP-XX draft Place Photo event
const template = { const event = await EventFactory.fromKind(360)
kind: 360, .content('')
content: '', .modifyPublicTags(() => tags)
tags, .as(this.nostrAuth.signer)
}; .sign();
if (!template.created_at) {
template.created_at = Math.floor(Date.now() / 1000);
}
const event = await factory.sign(template);
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event); await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
this.nostrData.store.add(event); this.nostrData.store.add(event);
@@ -217,6 +219,7 @@ export default class PlacePhotoUpload extends Component {
// Clear out the file so user can upload more or be done // Clear out the file so user can upload more or be done
this.file = null; this.file = null;
this.uploadedPhoto = null; this.uploadedPhoto = null;
this.altText = '';
if (this.args.onUploadStateChange) { if (this.args.onUploadStateChange) {
this.args.onUploadStateChange(false); this.args.onUploadStateChange(false);
@@ -243,13 +246,11 @@ export default class PlacePhotoUpload extends Component {
{{/if}} {{/if}}
{{#if this.file}} {{#if this.file}}
<div class="photo-grid">
<PlacePhotoUploadItem <PlacePhotoUploadItem
@file={{this.file}} @file={{this.file}}
@onSuccess={{this.handleUploadSuccess}} @onSuccess={{this.handleUploadSuccess}}
@onRemove={{this.removeFile}} @onRemove={{this.removeFile}}
/> />
</div>
{{#if this.suggestedTags.length}} {{#if this.suggestedTags.length}}
<div class="photo-tag-suggestions"> <div class="photo-tag-suggestions">
@@ -271,6 +272,19 @@ export default class PlacePhotoUpload extends Component {
</div> </div>
{{/if}} {{/if}}
<div class="form-group">
<label for="photo-alt-input">Description (optional):</label>
<input
id="photo-alt-input"
type="text"
class="form-control"
placeholder="Describe this photo"
value={{this.altText}}
disabled={{this.isPublishing}}
{{on "input" this.updateAltText}}
/>
</div>
<button <button
type="button" type="button"
class="btn btn-primary btn-publish" class="btn btn-primary btn-publish"
+40 -8
View File
@@ -5,10 +5,13 @@ import { on } from '@ember/modifier';
import { fn } from '@ember/helper'; import { fn } from '@ember/helper';
import or from 'ember-truth-helpers/helpers/or'; import or from 'ember-truth-helpers/helpers/or';
import eq from 'ember-truth-helpers/helpers/eq'; import eq from 'ember-truth-helpers/helpers/eq';
import and from 'ember-truth-helpers/helpers/and';
import not from 'ember-truth-helpers/helpers/not';
import PlaceDetails from './place-details'; import PlaceDetails from './place-details';
import Icon from './icon'; import Icon from './icon';
import humanizeOsmTag from '../helpers/humanize-osm-tag'; import humanizeOsmTag from '../helpers/humanize-osm-tag';
import { getLocalizedName, getPlaceType } from '../utils/osm'; import { getLocalizedName, getPlaceType } from '../utils/osm';
import restoreScroll from '../modifiers/restore-scroll';
export default class PlacesSidebar extends Component { export default class PlacesSidebar extends Component {
@service storage; @service storage;
@@ -160,19 +163,44 @@ export default class PlacesSidebar extends Component {
<template> <template>
<div class="sidebar"> <div class="sidebar">
<div class="sidebar-header {{if this.hasHeaderPhoto 'no-border'}}"> <div
class="sidebar-header
{{if this.hasHeaderPhoto 'no-border'}}
{{if (and (not @selectedPlace) @onBack) 'has-back-btn'}}"
>
{{#if @selectedPlace}} {{#if @selectedPlace}}
<button <button
type="button" type="button"
class="back-btn" class="back-btn"
{{on "click" this.clearSelection}} {{on "click" this.clearSelection}}
><Icon @name="arrow-left" @size={{20}} @color="#333" /></button> ><Icon @name="arrow-left" @size={{20}} @color="#333" /></button>
{{else}}
{{#if @onBack}}
<button type="button" class="back-btn" {{on "click" @onBack}}><Icon
@name="arrow-left"
@size={{20}}
@color="#333"
/></button>
{{/if}}
{{#if @onBack}}
<h2 class="sidebar-header-text-centered">
<span class="sidebar-header-icon-wrapper">
<Icon
@name="bookmark"
@size={{20}}
@color={{or @color "#898989"}}
/>
</span>
{{@title}}
</h2>
{{else}} {{else}}
{{#if this.isNearbySearch}} {{#if this.isNearbySearch}}
<h2><Icon @name="target" @size={{20}} @color="#ea4335" /> <h2><Icon @name="target" @size={{20}} @color="#ea4335" />
Nearby</h2> Nearby</h2>
{{else}} {{else}}
<h2><Icon @name="search" @size={{20}} @color="#333" /> Results</h2> <h2><Icon @name="search" @size={{20}} @color="#333" />
Results</h2>
{{/if}}
{{/if}} {{/if}}
{{/if}} {{/if}}
<button type="button" class="close-btn" {{on "click" @onClose}}><Icon <button type="button" class="close-btn" {{on "click" @onClose}}><Icon
@@ -182,13 +210,18 @@ export default class PlacesSidebar extends Component {
/></button> /></button>
</div> </div>
<div class="sidebar-content"> <div class="sidebar-content" {{restoreScroll @scrollTop}}>
{{#if @selectedPlace}} {{#if @selectedPlace}}
<PlaceDetails <PlaceDetails
@place={{@selectedPlace}} @place={{@selectedPlace}}
@onToggleSave={{this.toggleSave}} @onToggleSave={{this.toggleSave}}
@onSave={{this.updateBookmark}} @onSave={{this.updateBookmark}}
/> />
{{else}}
{{#if @isLoading}}
<div class="sidebar-loading">
<Icon @name="loading-ring" @size={{24}} @color="#898989" />
</div>
{{else}} {{else}}
{{#if @places}} {{#if @places}}
<ul class="places-list"> <ul class="places-list">
@@ -210,12 +243,10 @@ export default class PlacesSidebar extends Component {
{{humanizeOsmTag place.type}} {{humanizeOsmTag place.type}}
{{else if (eq place.source "photon")}} {{else if (eq place.source "photon")}}
{{place.description}} {{place.description}}
{{else if (getPlaceType place.osmTags)}}
{{getPlaceType place.osmTags}}
{{else}} {{else}}
{{#if place.osmTags}} Saved place
{{humanizeOsmTag (getPlaceType place.osmTags)}}
{{else if place.description}}
{{place.description}}
{{/if}}
{{/if}} {{/if}}
</div> </div>
</button> </button>
@@ -239,6 +270,7 @@ export default class PlacesSidebar extends Component {
Create new place Create new place
</button> </button>
{{/if}} {{/if}}
{{/if}}
</div> </div>
</div> </div>
</template> </template>
+118
View File
@@ -0,0 +1,118 @@
import Controller from '@ember/controller';
import { service } from '@ember/service';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency';
function getPlaceTime(place) {
const dateVal = place.createdAt;
if (!dateVal) return 0;
if (typeof dateVal === 'number') {
return dateVal;
}
const parsed = Date.parse(dateVal);
return isNaN(parsed) ? 0 : parsed;
}
export default class ListsListController extends Controller {
@service router;
@service mapUi;
@service storage;
@tracked model;
@tracked loadedPlaces = [];
get listId() {
return this.model?.list_id;
}
loadPlacesTask = task({ restartable: true }, async (listId) => {
this.loadedPlaces = []; // Clear previous elements immediately to show fresh loader
try {
this.loadedPlaces = await this.storage.getPlacesInList(listId);
} catch (e) {
console.error('Failed to load places in list', listId, e);
this.loadedPlaces = [];
}
});
get scrollTop() {
return this.mapUi.getScrollPosition(`list-${this.listId}`);
}
get listColor() {
const list = this.storage.lists.find((l) => l.id === this.listId);
if (list && list.color) {
return list.color;
}
return getComputedStyle(document.documentElement)
.getPropertyValue('--default-list-color')
.trim();
}
get listTitle() {
const list = this.storage.lists.find((l) => l.id === this.listId);
return list ? list.title : 'Collections';
}
get places() {
const currentList = this.storage.lists.find((l) => l.id === this.listId);
const placeRefsIds = new Set(
currentList?.placeRefs?.map((ref) => ref.id) || []
);
// Filter live tracked savedPlaces that are in this list
const livePlaces = this.storage.savedPlaces.filter((p) =>
placeRefsIds.has(p.id)
);
const merged = [];
const seen = new Set();
// Process live state first to reflect deletions/edits immediately
livePlaces.forEach((p) => {
merged.push(p);
seen.add(p.id);
});
// Supplement with any background-fetched places that are still valid but not in live state yet
this.loadedPlaces.forEach((p) => {
if (placeRefsIds.has(p.id) && !seen.has(p.id)) {
merged.push(p);
seen.add(p.id);
}
});
return merged.sort((a, b) => getPlaceTime(b) - getPlaceTime(a));
}
@action
selectPlace(place) {
if (place) {
const sidebarContent = document.querySelector('.sidebar-content');
if (sidebarContent) {
this.mapUi.saveScrollPosition(
`list-${this.listId}`,
sidebarContent.scrollTop
);
}
this.mapUi.returnToRoute = {
name: 'lists.list',
model: this.listId,
};
this.mapUi.showSidebar();
this.mapUi.preventNextZoom = true;
this.router.transitionTo('place', place);
}
}
@action
close() {
this.router.transitionTo('index');
}
@action
backToLists() {
this.router.transitionTo('lists.index');
}
}
+21 -4
View File
@@ -20,7 +20,26 @@ export default class SearchController extends Controller {
category = null; category = null;
fetchResultsTask = task({ restartable: true }, async (params) => { fetchResultsTask = task({ restartable: true }, async (params) => {
// Hide sidebar and clear previous results immediately to signal a new search // 1. Check if the incoming parameters match our currently loaded search
const isSameSearch =
this.mapUi.currentSearch &&
params.q === this.mapUi.currentSearch.q &&
params.category === this.mapUi.currentSearch.category &&
params.lat === this.mapUi.currentSearch.lat &&
params.lon === this.mapUi.currentSearch.lon;
const hasResults =
this.mapUi.searchResults && this.mapUi.searchResults.length > 0;
// 2. If it's a back navigation to the exact same search, resolve instantly with no animation
if (isSameSearch && hasResults) {
if (this.mapUi.isSidebarVisible) {
this.mapUi.showSidebar();
}
return;
}
// 3. Otherwise, this is a brand new search: hide the sidebar and clear previous results immediately to signal a new search
this.mapUi.hideSidebar(); this.mapUi.hideSidebar();
this.mapUi.clearSearchResults(); this.mapUi.clearSearchResults();
@@ -160,10 +179,8 @@ export default class SearchController extends Controller {
const targetName = params.selected || params.q; const targetName = params.selected || params.q;
if (targetName && pois.length > 0) { if (targetName && pois.length > 0) {
let matchedPlace = null;
// 1. Exact Name Match // 1. Exact Name Match
matchedPlace = pois.find( let matchedPlace = pois.find(
(p) => (p) =>
p.osmTags && p.osmTags &&
(p.osmTags.name === targetName || p.osmTags['name:en'] === targetName) (p.osmTags.name === targetName || p.osmTags['name:en'] === targetName)
+10
View File
@@ -0,0 +1,10 @@
import { modifier } from 'ember-modifier';
export default modifier((element, [scrollTop]) => {
if (element && typeof scrollTop === 'number' && scrollTop > 0) {
// Restore inside requestAnimationFrame to guarantee layout rendering is ready
requestAnimationFrame(() => {
element.scrollTop = scrollTop;
});
}
});
+4
View File
@@ -10,6 +10,10 @@ Router.map(function () {
this.route('place', { path: '/place/:place_id' }); this.route('place', { path: '/place/:place_id' });
this.route('place.new', { path: '/place/new' }); this.route('place.new', { path: '/place/new' });
this.route('search'); this.route('search');
this.route('menu');
this.route('lists', function () {
this.route('list', { path: '/:list_id' });
});
this.route('oauth', function () { this.route('oauth', function () {
this.route('osm-callback', { path: '/osm/callback' }); this.route('osm-callback', { path: '/osm/callback' });
}); });
+1
View File
@@ -6,5 +6,6 @@ export default class IndexRoute extends Route {
activate() { activate() {
this.mapUi.clearSearchResults(); this.mapUi.clearSearchResults();
this.mapUi.hideSidebar();
} }
} }
+10
View File
@@ -0,0 +1,10 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
export default class ListsRoute extends Route {
@service mapUi;
activate() {
this.mapUi.showSidebar();
}
}
+3
View File
@@ -0,0 +1,3 @@
import Route from '@ember/routing/route';
export default class ListsIndexRoute extends Route {}
+26
View File
@@ -0,0 +1,26 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
export default class ListsListRoute extends Route {
@service storage;
model(params) {
// Resolve instantly so transition happens in 0ms!
return { list_id: params.list_id };
}
setupController(controller, model) {
console.debug('DEBUG: setupController controller is:', controller);
console.debug(
'DEBUG: controller.loadPlacesTask is:',
controller?.loadPlacesTask
);
controller.model = model;
super.setupController(controller, model);
if (controller && controller.loadPlacesTask) {
controller.loadPlacesTask.perform(model.list_id);
} else {
console.error('DEBUG: ERROR! controller.loadPlacesTask is undefined!');
}
}
}
+10
View File
@@ -0,0 +1,10 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
export default class MenuRoute extends Route {
@service mapUi;
activate() {
this.mapUi.showSidebar();
}
}
+1
View File
@@ -108,6 +108,7 @@ export default class PlaceRoute extends Route {
this.mapUi.clearSelection(); this.mapUi.clearSelection();
// Reset the "return to search" flag so it doesn't persist to subsequent navigations // Reset the "return to search" flag so it doesn't persist to subsequent navigations
this.mapUi.returnToSearch = false; this.mapUi.returnToSearch = false;
this.mapUi.returnToRoute = null;
} }
async loadOsmPlace(id, type = null) { async loadOsmPlace(id, type = null) {
+7 -10
View File
@@ -35,23 +35,20 @@ export default class BlossomService extends Service {
} }
async _getAuthHeader(action, hash, serverUrl) { async _getAuthHeader(action, hash, serverUrl) {
const factory = new EventFactory({ signer: this.nostrAuth.signer });
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const serverHostname = new URL(serverUrl).hostname; const serverHostname = new URL(serverUrl).hostname;
const authTemplate = { const authEvent = await EventFactory.fromKind(24242)
kind: 24242, .content(action === 'upload' ? 'Upload photo for place' : 'Delete photo')
created_at: now, .modifyPublicTags(() => [
content: action === 'upload' ? 'Upload photo for place' : 'Delete photo',
tags: [
['t', action], ['t', action],
['x', hash], ['x', hash],
['expiration', String(now + 3600)], ['expiration', String(now + 3600)],
['server', serverHostname], ['server', serverHostname],
], ])
}; .created(now)
.as(this.nostrAuth.signer)
const authEvent = await factory.sign(authTemplate); .sign();
const base64 = btoa(JSON.stringify(authEvent)); const base64 = btoa(JSON.stringify(authEvent));
const base64url = base64 const base64url = base64
.replace(/\+/g, '-') .replace(/\+/g, '-')
+1 -1
View File
@@ -114,7 +114,7 @@ export default class ImageProcessorService extends Service {
}); });
}); });
} catch (e) { } catch (e) {
throw new Error(`Failed to process image: ${e.message}`); throw new Error(`Failed to process image: ${e.message}`, { cause: e });
} }
} }
+22
View File
@@ -1,5 +1,6 @@
import Service, { service } from '@ember/service'; import Service, { service } from '@ember/service';
import { tracked } from '@glimmer/tracking'; import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class MapUiService extends Service { export default class MapUiService extends Service {
@service nostrData; @service nostrData;
@@ -9,6 +10,7 @@ export default class MapUiService extends Service {
@tracked isCreating = false; @tracked isCreating = false;
@tracked creationCoordinates = null; @tracked creationCoordinates = null;
@tracked returnToSearch = false; @tracked returnToSearch = false;
@tracked returnToRoute = null;
@tracked currentCenter = null; @tracked currentCenter = null;
@tracked currentBounds = null; @tracked currentBounds = null;
@tracked currentZoom = null; @tracked currentZoom = null;
@@ -19,13 +21,33 @@ export default class MapUiService extends Service {
@tracked currentSearch = null; @tracked currentSearch = null;
@tracked loadingState = null; @tracked loadingState = null;
@tracked isSidebarVisible = false; @tracked isSidebarVisible = false;
@tracked isSidebarOpening = false;
scrollPositions = {};
@action
saveScrollPosition(key, value) {
this.scrollPositions[key] = value;
}
@action
getScrollPosition(key) {
return this.scrollPositions[key] || 0;
}
showSidebar() { showSidebar() {
if (!this.isSidebarVisible) {
this.isSidebarVisible = true; this.isSidebarVisible = true;
this.isSidebarOpening = true;
setTimeout(() => {
this.isSidebarOpening = false;
}, 250);
}
} }
hideSidebar() { hideSidebar() {
this.isSidebarVisible = false; this.isSidebarVisible = false;
this.isSidebarOpening = false;
} }
selectPlace(place, options = {}) { selectPlace(place, options = {}) {
+1 -7
View File
@@ -12,7 +12,7 @@ const STORAGE_KEY_CONNECT_LOCAL_KEY = 'marco:nostr_connect_local_key';
const STORAGE_KEY_CONNECT_REMOTE_PUBKEY = 'marco:nostr_connect_remote_pubkey'; const STORAGE_KEY_CONNECT_REMOTE_PUBKEY = 'marco:nostr_connect_remote_pubkey';
const STORAGE_KEY_CONNECT_RELAY = 'marco:nostr_connect_relay'; const STORAGE_KEY_CONNECT_RELAY = 'marco:nostr_connect_relay';
const DEFAULT_CONNECT_RELAY = 'wss://relay.nsec.app'; const DEFAULT_CONNECT_RELAY = 'wss://nostr.kosmos.org';
import { isMobile } from '../utils/device'; import { isMobile } from '../utils/device';
@@ -150,9 +150,6 @@ export default class NostrAuthService extends Service {
const relay = DEFAULT_CONNECT_RELAY; const relay = DEFAULT_CONNECT_RELAY;
localStorage.setItem(STORAGE_KEY_CONNECT_RELAY, relay); localStorage.setItem(STORAGE_KEY_CONNECT_RELAY, relay);
// Override aggressive 10s EOSE timeout to allow time for QR scanning
this.nostrRelay.pool.relay(relay).eoseTimeout = 180000; // 3 minutes
this._signerInstance = new NostrConnectSigner({ this._signerInstance = new NostrConnectSigner({
pool: this.nostrRelay.pool, pool: this.nostrRelay.pool,
relays: [relay], relays: [relay],
@@ -235,9 +232,6 @@ export default class NostrAuthService extends Service {
const localSigner = this._getLocalSigner(); const localSigner = this._getLocalSigner();
// Override aggressive 10s EOSE timeout to allow time for QR scanning
this.nostrRelay.pool.relay(relay).eoseTimeout = 180000; // 3 minutes
this._signerInstance = new NostrConnectSigner({ this._signerInstance = new NostrConnectSigner({
pool: this.nostrRelay.pool, pool: this.nostrRelay.pool,
relays: [relay], relays: [relay],
+50 -26
View File
@@ -6,7 +6,12 @@ import { MailboxesModel } from 'applesauce-core/models/mailboxes';
import { npubEncode } from 'applesauce-core/helpers/pointers'; import { npubEncode } from 'applesauce-core/helpers/pointers';
import { persistEventsToCache } from 'applesauce-core/helpers/event-cache'; import { persistEventsToCache } from 'applesauce-core/helpers/event-cache';
import { NostrIDB, openDB } from 'nostr-idb'; import { NostrIDB, openDB } from 'nostr-idb';
import { normalizeRelayUrl } from '../utils/nostr'; import {
excludeRequiredRelays,
mergeRequiredRelays,
normalizeRelayUrl,
uniqNormalizedRelays,
} from '../utils/nostr';
import { getGeohashPrefixesInBbox } from '../utils/geohash-coverage'; import { getGeohashPrefixesInBbox } from '../utils/geohash-coverage';
const DIRECTORY_RELAYS = [ const DIRECTORY_RELAYS = [
@@ -83,44 +88,63 @@ export default class NostrDataService extends Service {
}); });
} }
get defaultReadRelays() { get requiredReadRelays() {
const mailboxes = (this.mailboxes?.inboxes || []) return DEFAULT_READ_RELAYS;
.map(normalizeRelayUrl)
.filter(Boolean);
const defaults = DEFAULT_READ_RELAYS.map(normalizeRelayUrl).filter(Boolean);
return Array.from(new Set([...defaults, ...mailboxes]));
} }
get defaultWriteRelays() { get requiredWriteRelays() {
const mailboxes = (this.mailboxes?.outboxes || []) return DEFAULT_WRITE_RELAYS;
}
get mailboxReadRelays() {
return (this.mailboxes?.inboxes || [])
.map(normalizeRelayUrl) .map(normalizeRelayUrl)
.filter(Boolean); .filter(Boolean);
const defaults = }
DEFAULT_WRITE_RELAYS.map(normalizeRelayUrl).filter(Boolean);
return Array.from(new Set([...defaults, ...mailboxes])); get mailboxWriteRelays() {
return (this.mailboxes?.outboxes || [])
.map(normalizeRelayUrl)
.filter(Boolean);
}
get configuredReadRelays() {
const configured = uniqNormalizedRelays([
...this.mailboxReadRelays,
...(this.settings.nostrReadRelays || []),
]);
return excludeRequiredRelays(
configured,
this.settings.nostrReadRelayExclusions || []
);
}
get configuredWriteRelays() {
const configured = uniqNormalizedRelays([
...this.mailboxWriteRelays,
...(this.settings.nostrWriteRelays || []),
]);
return excludeRequiredRelays(
configured,
this.settings.nostrWriteRelayExclusions || []
);
} }
get activeReadRelays() { get activeReadRelays() {
if (this.settings.nostrReadRelays) { return mergeRequiredRelays(
return Array.from( this.requiredReadRelays,
new Set( this.configuredReadRelays
this.settings.nostrReadRelays.map(normalizeRelayUrl).filter(Boolean)
)
); );
} }
return this.defaultReadRelays;
}
get activeWriteRelays() { get activeWriteRelays() {
if (this.settings.nostrWriteRelays) { return mergeRequiredRelays(
return Array.from( this.requiredWriteRelays,
new Set( this.configuredWriteRelays
this.settings.nostrWriteRelays.map(normalizeRelayUrl).filter(Boolean)
)
); );
} }
return this.defaultWriteRelays;
}
async loadPlacesInBounds(bbox) { async loadPlacesInBounds(bbox) {
const requiredPrefixes = getGeohashPrefixesInBbox(bbox); const requiredPrefixes = getGeohashPrefixesInBbox(bbox);
+3
View File
@@ -45,6 +45,9 @@ export default class OsmAuthService extends Service {
clientId: clientId, clientId: clientId,
redirectUrl: redirectUrl, redirectUrl: redirectUrl,
storeRefreshToken: true, storeRefreshToken: true,
onAccessTokenExpiry() {
return this.exchangeRefreshTokenForAccessToken();
},
}, },
new MarcoOsmAuthStorage() new MarcoOsmAuthStorage()
); );
+9
View File
@@ -9,6 +9,8 @@ const DEFAULT_SETTINGS = {
nostrPhotoFallbackUploads: false, nostrPhotoFallbackUploads: false,
nostrReadRelays: null, nostrReadRelays: null,
nostrWriteRelays: null, nostrWriteRelays: null,
nostrReadRelayExclusions: null,
nostrWriteRelayExclusions: null,
experimentalEnablePhotoDeletion: false, experimentalEnablePhotoDeletion: false,
}; };
@@ -21,6 +23,9 @@ export default class SettingsService extends Service {
DEFAULT_SETTINGS.nostrPhotoFallbackUploads; DEFAULT_SETTINGS.nostrPhotoFallbackUploads;
@tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays; @tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays;
@tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays; @tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays;
@tracked nostrReadRelayExclusions = DEFAULT_SETTINGS.nostrReadRelayExclusions;
@tracked nostrWriteRelayExclusions =
DEFAULT_SETTINGS.nostrWriteRelayExclusions;
@tracked experimentalEnablePhotoDeletion = @tracked experimentalEnablePhotoDeletion =
DEFAULT_SETTINGS.experimentalEnablePhotoDeletion; DEFAULT_SETTINGS.experimentalEnablePhotoDeletion;
@@ -111,6 +116,8 @@ export default class SettingsService extends Service {
this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads; this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads;
this.nostrReadRelays = finalSettings.nostrReadRelays; this.nostrReadRelays = finalSettings.nostrReadRelays;
this.nostrWriteRelays = finalSettings.nostrWriteRelays; this.nostrWriteRelays = finalSettings.nostrWriteRelays;
this.nostrReadRelayExclusions = finalSettings.nostrReadRelayExclusions;
this.nostrWriteRelayExclusions = finalSettings.nostrWriteRelayExclusions;
this.experimentalEnablePhotoDeletion = this.experimentalEnablePhotoDeletion =
finalSettings.experimentalEnablePhotoDeletion; finalSettings.experimentalEnablePhotoDeletion;
@@ -127,6 +134,8 @@ export default class SettingsService extends Service {
nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads, nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads,
nostrReadRelays: this.nostrReadRelays, nostrReadRelays: this.nostrReadRelays,
nostrWriteRelays: this.nostrWriteRelays, nostrWriteRelays: this.nostrWriteRelays,
nostrReadRelayExclusions: this.nostrReadRelayExclusions,
nostrWriteRelayExclusions: this.nostrWriteRelayExclusions,
experimentalEnablePhotoDeletion: this.experimentalEnablePhotoDeletion, experimentalEnablePhotoDeletion: this.experimentalEnablePhotoDeletion,
}; };
localStorage.setItem('marco:settings', JSON.stringify(settings)); localStorage.setItem('marco:settings', JSON.stringify(settings));
+5
View File
@@ -270,6 +270,11 @@ export default class StorageService extends Service {
} }
} }
async getPlacesInList(listId) {
if (!this.places || !this.places.lists) return [];
return this.places.lists.getPlaces(listId);
}
async loadPlacesInBounds(bbox) { async loadPlacesInBounds(bbox) {
// 1. Calculate required prefixes // 1. Calculate required prefixes
const requiredPrefixes = getGeohashPrefixesInBbox(bbox); const requiredPrefixes = getGeohashPrefixesInBbox(bbox);
+184 -49
View File
@@ -1,8 +1,10 @@
/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */ /* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */
:root { :root {
--default-list-color: #fc3; --body-text-color: #333;
--primary-background-color: #fff;
--hover-bg: #f8f9fa; --hover-bg: #f8f9fa;
--divider-color: #eee;
--sidebar-width: 350px; --sidebar-width: 350px;
--link-color: #2a7fff; --link-color: #2a7fff;
--link-color-visited: #6a4fbf; --link-color-visited: #6a4fbf;
@@ -10,6 +12,7 @@
--marker-color-dark: #b31412; --marker-color-dark: #b31412;
--danger-color: var(--marker-color-primary); --danger-color: var(--marker-color-primary);
--danger-color-dark: var(--marker-color-dark); --danger-color-dark: var(--marker-color-dark);
--default-list-color: #fc3;
} }
html, html,
@@ -31,7 +34,7 @@ body {
font-family: 'Noto Sans', sans-serif; font-family: 'Noto Sans', sans-serif;
font-size: 16px; font-size: 16px;
font-weight: normal; font-weight: normal;
color: #333; color: var(--body-text-color);
} }
#root, #root,
@@ -112,8 +115,6 @@ body {
display: flex; display: flex;
align-items: center; align-items: center;
grid-area: search; grid-area: search;
/* Ensure it sits at the start of its grid area */
justify-self: start; justify-self: start;
width: 100%; width: 100%;
} }
@@ -127,7 +128,6 @@ body {
@media (width > 768px) { @media (width > 768px) {
.header-left { .header-left {
/* Desktop: Ensure minimum width for search box so it's not squeezed */
min-width: 300px; min-width: 300px;
max-width: 350px; max-width: 350px;
} }
@@ -140,8 +140,6 @@ body {
.header-center { .header-center {
grid-area: chips; grid-area: chips;
/* Desktop: Center the chips block in the available space */
display: flex; display: flex;
justify-content: center; justify-content: center;
min-width: 0; /* Allow shrinking */ min-width: 0; /* Allow shrinking */
@@ -156,7 +154,6 @@ body {
} }
@media (width <= 768px) { @media (width <= 768px) {
/* No need to reset min-width/max-width since they are only set in media query above */
.header-center { .header-center {
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
@@ -248,13 +245,6 @@ body {
display: none; display: none;
} }
.photo-grid {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 20px;
}
.photo-upload-item { .photo-upload-item {
position: relative; position: relative;
aspect-ratio: 4 / 3; aspect-ratio: 4 / 3;
@@ -262,6 +252,7 @@ body {
overflow: hidden; overflow: hidden;
background: #1e262e; background: #1e262e;
width: 100%; width: 100%;
margin-bottom: 1.25rem;
} }
.photo-upload-item img { .photo-upload-item img {
@@ -383,7 +374,7 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 1rem 0; padding: 1rem 0;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--divider-color);
} }
.account-item:last-child { .account-item:last-child {
@@ -460,6 +451,11 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; /* Ensure flex children are contained */ overflow: hidden; /* Ensure flex children are contained */
will-change: transform;
}
.sidebar-opening .sidebar {
animation: sidebar-slide-in-left 0.18s cubic-bezier(0.16, 1, 0.3, 1) forwards;
} }
.sidebar.app-menu-pane { .sidebar.app-menu-pane {
@@ -478,11 +474,13 @@ body {
} }
.sidebar-header { .sidebar-header {
padding: 1rem; height: 56px; /* Strictly enforce identical vertical height */
border-bottom: 1px solid #eee; padding: 0 1rem; /* Keep horizontal padding, remove vertical padding */
border-bottom: 1px solid var(--divider-color);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
box-sizing: border-box; /* Guarantee strict height boundaries */
} }
.sidebar-header.no-border { .sidebar-header.no-border {
@@ -491,7 +489,7 @@ body {
.sidebar-header h2 { .sidebar-header h2 {
margin: 0; margin: 0;
font-size: 1.2rem; font-size: 1.1rem;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
@@ -526,7 +524,7 @@ body {
padding-left: 1.4rem; padding-left: 1.4rem;
background: none; background: none;
border: none; border: none;
color: #333; color: var(--body-text-color);
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
font-size: 0.95rem; font-size: 0.95rem;
@@ -557,7 +555,7 @@ body {
padding-left: 1.4rem; padding-left: 1.4rem;
cursor: pointer; cursor: pointer;
font-size: 0.95rem; font-size: 0.95rem;
color: #333; color: var(--body-text-color);
transition: background-color 0.2s; transition: background-color 0.2s;
} }
@@ -628,7 +626,7 @@ body {
width: 24px; width: 24px;
height: 24px; height: 24px;
border-radius: 50%; border-radius: 50%;
background-color: #fff; background-color: var(--primary-background-color);
border: 1px solid var(--danger-color); border: 1px solid var(--danger-color);
color: var(--danger-color); color: var(--danger-color);
cursor: pointer; cursor: pointer;
@@ -644,7 +642,7 @@ body {
.btn-remove-relay:hover, .btn-remove-relay:hover,
.btn-remove-relay:active { .btn-remove-relay:active {
background-color: var(--danger-color); background-color: var(--danger-color);
color: #fff; color: var(--primary-background-color);
} }
.add-relay-input { .add-relay-input {
@@ -674,7 +672,7 @@ body {
margin-bottom: 1rem; margin-bottom: 1rem;
background: var(--hover-bg); background: var(--hover-bg);
padding: 1rem; padding: 1rem;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--divider-color);
} }
.form-group { .form-group {
@@ -696,8 +694,8 @@ body {
font-family: inherit; font-family: inherit;
font-size: 1rem; font-size: 1rem;
box-sizing: border-box; /* Ensure padding doesn't overflow width */ box-sizing: border-box; /* Ensure padding doesn't overflow width */
color: #333; color: var(--body-text-color);
background-color: #fff; background-color: var(--primary-background-color);
} }
.form-control:focus { .form-control:focus {
@@ -708,7 +706,7 @@ body {
select.form-control { select.form-control {
appearance: none; appearance: none;
background-color: #fff; background-color: var(--primary-background-color);
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 0.75rem center; background-position: right 0.75rem center;
@@ -784,7 +782,7 @@ select.form-control {
.meta-info p:first-child { .meta-info p:first-child {
margin-top: 1.2rem; margin-top: 1.2rem;
padding-top: 1.2rem; padding-top: 1.2rem;
border-top: 1px solid #eee; border-top: 1px solid var(--divider-color);
} }
.meta-info a, .meta-info a,
@@ -843,9 +841,9 @@ abbr[title] {
width: 100%; width: 100%;
text-align: left; text-align: left;
border: none; border: none;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--divider-color);
background: #fff; background: var(--primary-background-color);
color: #333; color: var(--body-text-color);
padding: 1rem; padding: 1rem;
cursor: pointer; cursor: pointer;
transition: background 0.2s; transition: background 0.2s;
@@ -1016,7 +1014,7 @@ abbr[title] {
.photo-carousel.inline .photo-carousel-track { .photo-carousel.inline .photo-carousel-track {
scroll-snap-type: none; scroll-snap-type: none;
gap: 2px; gap: 2px;
background-color: #fff; background-color: var(--primary-background-color);
} }
.photo-carousel.inline .carousel-slide { .photo-carousel.inline .carousel-slide {
@@ -1095,7 +1093,7 @@ abbr[title] {
.btn-outline { .btn-outline {
background: transparent; background: transparent;
color: #333; color: var(--body-text-color);
border: 1px solid #ccc; border: 1px solid #ccc;
} }
@@ -1104,7 +1102,7 @@ abbr[title] {
} }
.btn-secondary { .btn-secondary {
color: #333; color: var(--body-text-color);
border: 1px solid rgb(255 204 51 / 20%); border: 1px solid rgb(255 204 51 / 20%);
background: rgb(255 204 51 / 30%); background: rgb(255 204 51 / 30%);
} }
@@ -1266,6 +1264,20 @@ span.icon {
gap: 0.5rem; gap: 0.5rem;
} }
.content-with-icon > span:not(.icon) {
min-width: 0;
flex: 1;
}
.content-with-icon > span:not(.icon) a {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
}
.content-with-icon .icon { .content-with-icon .icon {
margin-top: 0.15rem; margin-top: 0.15rem;
} }
@@ -1347,7 +1359,7 @@ span.icon {
width: 24px; width: 24px;
height: 24px; height: 24px;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
color: #333; color: var(--body-text-color);
pointer-events: none; pointer-events: none;
z-index: 2000; z-index: 2000;
display: none; display: none;
@@ -1408,6 +1420,11 @@ button.create-place {
inset: auto 0 0; inset: auto 0 0;
} }
.sidebar-opening .sidebar {
animation: sidebar-slide-up-bottom 0.18s cubic-bezier(0.16, 1, 0.3, 1)
forwards;
}
.sidebar-content { .sidebar-content {
overflow-y: auto; overflow-y: auto;
@@ -1481,7 +1498,7 @@ button.create-place {
min-width: 0; min-width: 0;
height: 100%; height: 100%;
font-size: 1rem; font-size: 1rem;
color: #333; color: var(--body-text-color);
outline: none; outline: none;
width: 100%; width: 100%;
padding: 0 4px; padding: 0 4px;
@@ -1507,12 +1524,11 @@ button.create-place {
color: #5f6368; color: #5f6368;
border-radius: 50%; border-radius: 50%;
margin-left: 4px; margin-left: 4px;
padding-left: 8px;
} }
.search-submit-btn:hover { .search-submit-btn:hover {
background: rgb(0 0 0 / 5%); background: rgb(0 0 0 / 5%);
color: #333; color: var(--body-text-color);
} }
.search-clear-btn { .search-clear-btn {
@@ -1530,7 +1546,7 @@ button.create-place {
.search-clear-btn:hover { .search-clear-btn:hover {
background: rgb(0 0 0 / 5%); background: rgb(0 0 0 / 5%);
color: #333; color: var(--body-text-color);
} }
/* Search Results Popover */ /* Search Results Popover */
@@ -1599,7 +1615,7 @@ button.create-place {
.result-title { .result-title {
font-weight: 500; font-weight: 500;
color: #333; color: var(--body-text-color);
font-size: 0.95rem; font-size: 0.95rem;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -1651,7 +1667,7 @@ button.create-place {
cursor: pointer; cursor: pointer;
margin: 0; margin: 0;
font-size: 0.95rem; font-size: 0.95rem;
color: #333; color: var(--body-text-color);
} }
.place-lists-manager input[type='checkbox'] { .place-lists-manager input[type='checkbox'] {
@@ -1661,7 +1677,8 @@ button.create-place {
cursor: pointer; cursor: pointer;
} }
.place-lists-manager .list-color { /* Shared List Color Dot */
.list-color-dot {
width: 12px; width: 12px;
height: 12px; height: 12px;
background-color: var(--default-list-color); background-color: var(--default-list-color);
@@ -1672,7 +1689,7 @@ button.create-place {
.place-lists-manager .divider { .place-lists-manager .divider {
height: 1px; height: 1px;
background: #eee; background: var(--divider-color);
margin: 0.5rem 0; margin: 0.5rem 0;
} }
@@ -1715,7 +1732,7 @@ button.create-place {
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 16px; /* Pill shape */ border-radius: 16px; /* Pill shape */
font-size: 0.9rem; font-size: 0.9rem;
color: #333; color: var(--body-text-color);
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
box-shadow: 0 1px 3px rgb(0 0 0 / 10%); box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
@@ -1727,7 +1744,7 @@ button.create-place {
} }
.category-chip:active { .category-chip:active {
background: #eee; background: var(--divider-color);
} }
.category-chip:disabled { .category-chip:disabled {
@@ -1843,8 +1860,12 @@ button.create-place {
font-size: 1.2rem; font-size: 1.2rem;
} }
.place-photo-upload .form-group {
margin-bottom: 1.5rem;
}
.photo-tag-suggestions { .photo-tag-suggestions {
margin: 1rem 0 1.5rem; margin-bottom: 1.25rem;
} }
.photo-tag-suggestions-title { .photo-tag-suggestions-title {
@@ -1862,7 +1883,7 @@ button.create-place {
.photo-tag-chip { .photo-tag-chip {
background: #f8f9fa; background: #f8f9fa;
color: #333; color: var(--body-text-color);
border: none; border: none;
border-radius: 16px; border-radius: 16px;
padding: 6px 12px; padding: 6px 12px;
@@ -1872,7 +1893,7 @@ button.create-place {
} }
.photo-tag-chip:hover { .photo-tag-chip:hover {
background: #eee; background: var(--divider-color);
} }
.photo-tag-chip.is-selected { .photo-tag-chip.is-selected {
@@ -2103,7 +2124,7 @@ button.create-place {
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
font-size: 0.95rem; font-size: 0.95rem;
color: #333; color: var(--body-text-color);
white-space: nowrap; white-space: nowrap;
} }
@@ -2123,3 +2144,117 @@ button.create-place {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
/* Snappy slide-in from left (Desktop) */
@keyframes sidebar-slide-in-left {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
/* Snappy slide-up from bottom (Mobile) */
@keyframes sidebar-slide-up-bottom {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
/* Lists Index Sidebar Menu */
.lists-index-item {
width: 100%;
text-align: left;
border: none;
border-bottom: 1px solid var(--divider-color);
background: var(--primary-background-color);
color: var(--body-text-color);
padding: 1rem;
cursor: pointer;
transition: background 0.2s;
font-family: inherit;
display: flex;
align-items: center;
justify-content: space-between;
}
.lists-index-item:hover {
background: var(--hover-bg);
}
.lists-index-item-left {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.lists-index-name {
font-size: 0.95rem;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.lists-index-count {
color: #666;
font-size: 0.85rem;
margin: 0;
white-space: nowrap;
flex-shrink: 0;
}
/* Centered layout when back button is present */
.sidebar-header.has-back-btn {
position: relative;
justify-content: center; /* Center horizontally */
}
/* Absolute positioning for buttons in centered header */
.sidebar-header.has-back-btn .back-btn {
position: absolute;
left: 1rem;
z-index: 10;
}
.sidebar-header.has-back-btn .close-btn {
position: absolute;
right: 1rem;
z-index: 10;
}
/* Centralized Title text */
.sidebar-header-text-centered {
position: relative;
margin: 0;
font-size: 1.2rem;
font-weight: bold;
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
max-width: 60%;
}
.sidebar-header-icon-wrapper {
position: absolute;
right: 100%;
margin-right: 0.5rem;
display: flex;
align-items: center;
}
/* Sidebar Loading State */
.sidebar-loading {
display: flex;
align-items: center;
justify-content: center;
padding: 4rem 1rem;
}
+31 -24
View File
@@ -2,12 +2,9 @@ import Component from '@glimmer/component';
import { pageTitle } from 'ember-page-title'; import { pageTitle } from 'ember-page-title';
import Map from '#components/map'; import Map from '#components/map';
import AppHeader from '#components/app-header'; import AppHeader from '#components/app-header';
import AppMenu from '#components/app-menu/index';
import Toast from '#components/toast'; import Toast from '#components/toast';
import { service } from '@ember/service'; import { service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object'; import { action } from '@ember/object';
import { or } from 'ember-truth-helpers';
import { on } from '@ember/modifier'; import { on } from '@ember/modifier';
export default class ApplicationComponent extends Component { export default class ApplicationComponent extends Component {
@@ -15,16 +12,17 @@ export default class ApplicationComponent extends Component {
@service mapUi; @service mapUi;
@service router; @service router;
@tracked isAppMenuOpen = false;
get isSidebarOpen() { get isSidebarOpen() {
// We consider the sidebar "open" if we are in search or place routes AND it's visible. // We consider the sidebar "open" if we are in search, menu, lists or place routes AND it's visible.
// This helps the map know if it should shift the center or adjust view. // This helps the map know if it should shift the center or adjust view.
const name = this.router.currentRouteName;
return ( return (
this.mapUi.isSidebarVisible && this.mapUi.isSidebarVisible &&
(this.router.currentRouteName === 'place' || (name === 'place' ||
this.router.currentRouteName === 'place.new' || name === 'place.new' ||
this.router.currentRouteName === 'search') name === 'search' ||
name === 'menu' ||
name.startsWith('lists'))
); );
} }
@@ -37,24 +35,35 @@ export default class ApplicationComponent extends Component {
@action @action
toggleAppMenu() { toggleAppMenu() {
this.isAppMenuOpen = !this.isAppMenuOpen; if (this.router.currentRouteName === 'menu') {
this.router.transitionTo('index');
} else {
this.router.transitionTo('menu');
} }
@action
closeAppMenu() {
this.isAppMenuOpen = false;
} }
@action @action
handleOutsideClick() { handleOutsideClick() {
if (this.isAppMenuOpen) { const name = this.router.currentRouteName;
this.closeAppMenu(); if (
} else if ( name === 'search' ||
this.router.currentRouteName === 'search' || name === 'place' ||
this.router.currentRouteName === 'place' name === 'menu' ||
name.startsWith('lists')
) { ) {
this.mapUi.clearSelection(); this.mapUi.clearSelection();
this.mapUi.hideSidebar(); this.mapUi.hideSidebar();
if (name === 'menu' || name.startsWith('lists')) {
this.router.transitionTo('index');
} else if (name === 'place') {
if (this.mapUi.returnToSearch && this.mapUi.currentSearch) {
this.router.transitionTo('search', {
queryParams: this.mapUi.currentSearch,
});
} else {
this.router.transitionTo('index');
}
}
} }
} }
@@ -66,6 +75,7 @@ export default class ApplicationComponent extends Component {
<template> <template>
{{pageTitle "Marco"}} {{pageTitle "Marco"}}
<div class={{if this.mapUi.isSidebarOpening "sidebar-opening"}}>
<AppHeader @onToggleMenu={{this.toggleAppMenu}} /> <AppHeader @onToggleMenu={{this.toggleAppMenu}} />
<div <div
@@ -82,16 +92,13 @@ export default class ApplicationComponent extends Component {
{{/if}} {{/if}}
<Map <Map
@isSidebarOpen={{or this.isSidebarOpen this.isAppMenuOpen}} @isSidebarOpen={{this.isSidebarOpen}}
@onOutsideClick={{this.handleOutsideClick}} @onOutsideClick={{this.handleOutsideClick}}
/> />
{{#if this.isAppMenuOpen}}
<AppMenu @onClose={{this.closeAppMenu}} />
{{/if}}
<Toast /> <Toast />
{{outlet}} {{outlet}}
</div>
</template> </template>
} }
+1
View File
@@ -0,0 +1 @@
<template>{{outlet}}</template>
+89
View File
@@ -0,0 +1,89 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { action } from '@ember/object';
import { fn } from '@ember/helper';
import { on } from '@ember/modifier';
import Icon from '#components/icon';
import { htmlSafe } from '@ember/template';
export default class ListsIndexTemplate extends Component {
@service storage;
@service router;
@service mapUi;
styleFor(color) {
const finalColor =
color ||
getComputedStyle(document.documentElement)
.getPropertyValue('--default-list-color')
.trim();
return htmlSafe(`background-color: ${finalColor}`);
}
@action
selectList(listId) {
this.router.transitionTo('lists.list', listId);
}
@action
close() {
this.router.transitionTo('index');
}
@action
backToMenu() {
this.router.transitionTo('menu');
}
<template>
{{#if this.mapUi.isSidebarVisible}}
<div class="sidebar">
<div class="sidebar-header has-back-btn">
<button type="button" class="back-btn" {{on "click" this.backToMenu}}>
<Icon @name="arrow-left" @size={{20}} @color="#333" />
</button>
<h2 class="sidebar-header-text-centered">
<span class="sidebar-header-icon-wrapper">
<Icon @name="bookmark" @size={{20}} @color="#898989" />
</span>
Collections
</h2>
<button type="button" class="close-btn" {{on "click" this.close}}>
<Icon @name="x" @size={{20}} @color="#333" />
</button>
</div>
<div class="sidebar-content">
<ul class="places-list">
{{#each this.storage.lists as |list|}}
<li>
<button
type="button"
class="lists-index-item"
{{on "click" (fn this.selectList list.id)}}
>
<div class="lists-index-item-left">
{{! template-lint-disable no-inline-styles }}
<span
class="list-color-dot"
style={{this.styleFor list.color}}
></span>
<div class="lists-index-name">{{list.title}}</div>
</div>
<div class="lists-index-count">
{{#if list.placeRefs.length}}
{{list.placeRefs.length}}
places
{{else}}
empty
{{/if}}
</div>
</button>
</li>
{{/each}}
</ul>
</div>
</div>
{{/if}}
</template>
}
+16
View File
@@ -0,0 +1,16 @@
import PlacesSidebar from '#components/places-sidebar';
<template>
{{#if @controller.mapUi.isSidebarVisible}}
<PlacesSidebar
@places={{@controller.places}}
@title={{@controller.listTitle}}
@color={{@controller.listColor}}
@scrollTop={{@controller.scrollTop}}
@isLoading={{@controller.loadPlacesTask.isRunning}}
@onSelect={{@controller.selectPlace}}
@onClose={{@controller.close}}
@onBack={{@controller.backToLists}}
/>
{{/if}}
</template>
+15
View File
@@ -0,0 +1,15 @@
import Component from '@glimmer/component';
import AppMenu from '#components/app-menu/index';
import { service } from '@ember/service';
import { action } from '@ember/object';
export default class MenuTemplate extends Component {
@service router;
@action
close() {
this.router.transitionTo('index');
}
<template><AppMenu @onClose={{this.close}} /></template>
}
+14 -1
View File
@@ -77,8 +77,14 @@ export default class PlaceTemplate extends Component {
navigateBack(place) { navigateBack(place) {
// The sidebar calls this with null when "Back" is clicked. // The sidebar calls this with null when "Back" is clicked.
if (place === null) { if (place === null) {
// If we have an active route context (e.g. lists), return to it
if (this.mapUi.returnToRoute) {
this.mapUi.showSidebar();
const { name, model } = this.mapUi.returnToRoute;
this.router.transitionTo(name, model);
}
// If we have an active search context, return to it (UP navigation) // If we have an active search context, return to it (UP navigation)
if (this.mapUi.returnToSearch && this.mapUi.currentSearch) { else if (this.mapUi.returnToSearch && this.mapUi.currentSearch) {
this.mapUi.showSidebar(); this.mapUi.showSidebar();
this.router.transitionTo('search', { this.router.transitionTo('search', {
queryParams: this.mapUi.currentSearch, queryParams: this.mapUi.currentSearch,
@@ -98,6 +104,13 @@ export default class PlaceTemplate extends Component {
close() { close() {
this.mapUi.clearSelection(); this.mapUi.clearSelection();
this.mapUi.hideSidebar(); this.mapUi.hideSidebar();
if (this.mapUi.returnToSearch && this.mapUi.currentSearch) {
this.router.transitionTo('search', {
queryParams: this.mapUi.currentSearch,
});
} else {
this.router.transitionTo('index');
}
} }
<template> <template>
+5
View File
@@ -10,6 +10,10 @@ export default class SearchTemplate extends Component {
@action @action
selectPlace(place) { selectPlace(place) {
if (place) { if (place) {
const sidebarContent = document.querySelector('.sidebar-content');
if (sidebarContent) {
this.mapUi.saveScrollPosition('search', sidebarContent.scrollTop);
}
this.mapUi.returnToSearch = true; this.mapUi.returnToSearch = true;
this.mapUi.showSidebar(); this.mapUi.showSidebar();
this.mapUi.preventNextZoom = true; this.mapUi.preventNextZoom = true;
@@ -28,6 +32,7 @@ export default class SearchTemplate extends Component {
{{#if this.mapUi.isSidebarVisible}} {{#if this.mapUi.isSidebarVisible}}
<PlacesSidebar <PlacesSidebar
@places={{this.mapUi.searchResults}} @places={{this.mapUi.searchResults}}
@scrollTop={{this.mapUi.getScrollPosition "search"}}
@onSelect={{this.selectPlace}} @onSelect={{this.selectPlace}}
@onClose={{this.close}} @onClose={{this.close}}
/> />
+29 -2
View File
@@ -1,4 +1,7 @@
// AGENT: Keep imports sorted alphabetically, grouped by feather-icons → pinhead → custom/local // AGENT: Keep imports sorted alphabetically, grouped by feather-icons → pinhead → custom/local
/*
* Feather icons
*/
import activity from 'feather-icons/dist/icons/activity.svg?raw'; import activity from 'feather-icons/dist/icons/activity.svg?raw';
import arrowLeft from 'feather-icons/dist/icons/arrow-left.svg?raw'; import arrowLeft from 'feather-icons/dist/icons/arrow-left.svg?raw';
import bookmark from 'feather-icons/dist/icons/bookmark.svg?raw'; import bookmark from 'feather-icons/dist/icons/bookmark.svg?raw';
@@ -40,6 +43,9 @@ import check from 'feather-icons/dist/icons/check.svg?raw';
import alertCircle from 'feather-icons/dist/icons/alert-circle.svg?raw'; import alertCircle from 'feather-icons/dist/icons/alert-circle.svg?raw';
import zap from 'feather-icons/dist/icons/zap.svg?raw'; import zap from 'feather-icons/dist/icons/zap.svg?raw';
/*
* Pinhead icons
*/
import angelfish from '@waysidemapping/pinhead/dist/icons/angelfish.svg?raw'; import angelfish from '@waysidemapping/pinhead/dist/icons/angelfish.svg?raw';
import barbell from '@waysidemapping/pinhead/dist/icons/barbell.svg?raw'; import barbell from '@waysidemapping/pinhead/dist/icons/barbell.svg?raw';
import climbingWall from '@waysidemapping/pinhead/dist/icons/climbing_wall.svg?raw'; import climbingWall from '@waysidemapping/pinhead/dist/icons/climbing_wall.svg?raw';
@@ -53,8 +59,12 @@ import bus from '@waysidemapping/pinhead/dist/icons/bus.svg?raw';
import camera from '@waysidemapping/pinhead/dist/icons/camera.svg?raw'; import camera from '@waysidemapping/pinhead/dist/icons/camera.svg?raw';
import boxingGloveUp from '@waysidemapping/pinhead/dist/icons/boxing_glove_up.svg?raw'; import boxingGloveUp from '@waysidemapping/pinhead/dist/icons/boxing_glove_up.svg?raw';
import car from '@waysidemapping/pinhead/dist/icons/car.svg?raw'; import car from '@waysidemapping/pinhead/dist/icons/car.svg?raw';
import carAndWrench from '@waysidemapping/pinhead/dist/icons/car_and_wrench.svg?raw';
import castleKeep from '@waysidemapping/pinhead/dist/icons/castle_keep.svg?raw';
import cigaretteWithSmokeCurl from '@waysidemapping/pinhead/dist/icons/cigarette_with_smoke_curl.svg?raw'; import cigaretteWithSmokeCurl from '@waysidemapping/pinhead/dist/icons/cigarette_with_smoke_curl.svg?raw';
import cityGate from '@waysidemapping/pinhead/dist/icons/city_gate.svg?raw';
import classicalBuilding from '@waysidemapping/pinhead/dist/icons/classical_building.svg?raw'; import classicalBuilding from '@waysidemapping/pinhead/dist/icons/classical_building.svg?raw';
import classicalBuildingWithClock from '@waysidemapping/pinhead/dist/icons/classical_building_with_clock.svg?raw';
import classicalBuildingWithDomeAndFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_dome_and_flag.svg?raw'; import classicalBuildingWithDomeAndFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_dome_and_flag.svg?raw';
import classicalBuildingWithFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_flag.svg?raw'; import classicalBuildingWithFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_flag.svg?raw';
import commercialBuilding from '@waysidemapping/pinhead/dist/icons/commercial_building.svg?raw'; import commercialBuilding from '@waysidemapping/pinhead/dist/icons/commercial_building.svg?raw';
@@ -82,12 +92,17 @@ import grecianVase from '@waysidemapping/pinhead/dist/icons/grecian_vase.svg?raw
import greekCross from '@waysidemapping/pinhead/dist/icons/greek_cross.svg?raw'; import greekCross from '@waysidemapping/pinhead/dist/icons/greek_cross.svg?raw';
import iceCreamOnCone from '@waysidemapping/pinhead/dist/icons/ice_cream_on_cone.svg?raw'; import iceCreamOnCone from '@waysidemapping/pinhead/dist/icons/ice_cream_on_cone.svg?raw';
import industrialBuilding from '@waysidemapping/pinhead/dist/icons/industrial_building.svg?raw'; import industrialBuilding from '@waysidemapping/pinhead/dist/icons/industrial_building.svg?raw';
import infoI from '@waysidemapping/pinhead/dist/icons/info_i.svg?raw';
import jewel from '@waysidemapping/pinhead/dist/icons/jewel.svg?raw'; import jewel from '@waysidemapping/pinhead/dist/icons/jewel.svg?raw';
import lowriseBuilding from '@waysidemapping/pinhead/dist/icons/lowrise_building.svg?raw'; import lowriseBuilding from '@waysidemapping/pinhead/dist/icons/lowrise_building.svg?raw';
import marketStall from '@waysidemapping/pinhead/dist/icons/market_stall.svg?raw';
import memorialStoneWithInscription from '@waysidemapping/pinhead/dist/icons/memorial_stone_with_inscription.svg?raw';
import mobilePhoneWithKeypadAndAntenna from '@waysidemapping/pinhead/dist/icons/mobile_phone_with_keypad_and_antenna.svg?raw';
import molarTooth from '@waysidemapping/pinhead/dist/icons/molar_tooth.svg?raw';
import needleAndSpoolOfThread from '@waysidemapping/pinhead/dist/icons/needle_and_spool_of_thread.svg?raw'; import needleAndSpoolOfThread from '@waysidemapping/pinhead/dist/icons/needle_and_spool_of_thread.svg?raw';
import openBook from '@waysidemapping/pinhead/dist/icons/open_book.svg?raw'; import openBook from '@waysidemapping/pinhead/dist/icons/open_book.svg?raw';
import palace from '@waysidemapping/pinhead/dist/icons/palace.svg?raw'; import palace from '@waysidemapping/pinhead/dist/icons/palace.svg?raw';
import parkingP from '@waysidemapping/pinhead/dist/icons/parking_p.svg?raw'; import parkingP from '@waysidemapping/pinhead/dist/icons/p_wide.svg?raw';
import personCricketBattingAtCricketBall from '@waysidemapping/pinhead/dist/icons/person_cricket_batting_at_cricket_ball.svg?raw'; import personCricketBattingAtCricketBall from '@waysidemapping/pinhead/dist/icons/person_cricket_batting_at_cricket_ball.svg?raw';
import personBoardingTramWithDestinationDisplayAndPantographOnTramTrack from '@waysidemapping/pinhead/dist/icons/person_boarding_tram_with_destination_display_and_pantograph_on_tram_track.svg?raw'; import personBoardingTramWithDestinationDisplayAndPantographOnTramTrack from '@waysidemapping/pinhead/dist/icons/person_boarding_tram_with_destination_display_and_pantograph_on_tram_track.svg?raw';
import personJockeyingRacehorse from '@waysidemapping/pinhead/dist/icons/person_jockeying_racehorse.svg?raw'; import personJockeyingRacehorse from '@waysidemapping/pinhead/dist/icons/person_jockeying_racehorse.svg?raw';
@@ -103,7 +118,7 @@ import policeOfficerWithStopArm from '@waysidemapping/pinhead/dist/icons/police_
import planeTopRight from '@waysidemapping/pinhead/dist/icons/plane_top_right.svg?raw'; import planeTopRight from '@waysidemapping/pinhead/dist/icons/plane_top_right.svg?raw';
import roundStructureWithFlag from '@waysidemapping/pinhead/dist/icons/round_structure_with_flag.svg?raw'; import roundStructureWithFlag from '@waysidemapping/pinhead/dist/icons/round_structure_with_flag.svg?raw';
import sailingShipInWater from '@waysidemapping/pinhead/dist/icons/sailing_ship_in_water.svg?raw'; import sailingShipInWater from '@waysidemapping/pinhead/dist/icons/sailing_ship_in_water.svg?raw';
import scissorsOpen from '@waysidemapping/pinhead/dist/icons/scissors_open.svg?raw'; import scissorsOpen from '@waysidemapping/pinhead/dist/icons/open_scissors.svg?raw';
import shipwreckInWater from '@waysidemapping/pinhead/dist/icons/shipwreck_in_water.svg?raw'; import shipwreckInWater from '@waysidemapping/pinhead/dist/icons/shipwreck_in_water.svg?raw';
import steamTrainOnRailwayTrack from '@waysidemapping/pinhead/dist/icons/steam_train_on_railway_track.svg?raw'; import steamTrainOnRailwayTrack from '@waysidemapping/pinhead/dist/icons/steam_train_on_railway_track.svg?raw';
import shoppingBag from '@waysidemapping/pinhead/dist/icons/shopping_bag.svg?raw'; import shoppingBag from '@waysidemapping/pinhead/dist/icons/shopping_bag.svg?raw';
@@ -118,6 +133,9 @@ import wallHangingWithMountainsAndSun from '@waysidemapping/pinhead/dist/icons/w
import windingWayWide from '@waysidemapping/pinhead/dist/icons/winding_way_wide.svg?raw'; import windingWayWide from '@waysidemapping/pinhead/dist/icons/winding_way_wide.svg?raw';
import womensAndMensRestroomSymbol from '@waysidemapping/pinhead/dist/icons/womens_and_mens_restroom_symbol.svg?raw'; import womensAndMensRestroomSymbol from '@waysidemapping/pinhead/dist/icons/womens_and_mens_restroom_symbol.svg?raw';
/*
* Custom/local icons
*/
import loadingRing from '../icons/270-ring.svg?raw'; import loadingRing from '../icons/270-ring.svg?raw';
import nostrich from '../icons/nostrich-2.svg?raw'; import nostrich from '../icons/nostrich-2.svg?raw';
import remotestorage from '../icons/remotestorage.svg?raw'; import remotestorage from '../icons/remotestorage.svg?raw';
@@ -144,11 +162,13 @@ const ICONS = {
'chevron-left': chevronLeft, 'chevron-left': chevronLeft,
'chevron-right': chevronRight, 'chevron-right': chevronRight,
'cigarette-with-smoke-curl': cigaretteWithSmokeCurl, 'cigarette-with-smoke-curl': cigaretteWithSmokeCurl,
'city-gate': cityGate,
climbing_wall: climbingWall, climbing_wall: climbingWall,
check, check,
'alert-circle': alertCircle, 'alert-circle': alertCircle,
'alert-triangle': alertTriangle, 'alert-triangle': alertTriangle,
'classical-building': classicalBuilding, 'classical-building': classicalBuilding,
'classical-building-with-clock': classicalBuildingWithClock,
'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag, 'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag,
'classical-building-with-flag': classicalBuildingWithFlag, 'classical-building-with-flag': classicalBuildingWithFlag,
'commercial-building': commercialBuilding, 'commercial-building': commercialBuilding,
@@ -185,6 +205,7 @@ const ICONS = {
'ice-cream-on-cone': iceCreamOnCone, 'ice-cream-on-cone': iceCreamOnCone,
'industrial-building': industrialBuilding, 'industrial-building': industrialBuilding,
info, info,
'info-i': infoI,
instagram, instagram,
jewel, jewel,
'log-in': logIn, 'log-in': logIn,
@@ -193,7 +214,11 @@ const ICONS = {
mail, mail,
map, map,
'map-pin': mapPin, 'map-pin': mapPin,
'market-stall': marketStall,
'memorial-stone-with-inscription': memorialStoneWithInscription,
menu, menu,
'mobile-phone-with-keypad-and-antenna': mobilePhoneWithKeypadAndAntenna,
'molar-tooth': molarTooth,
'more-horizontal': moreHorizontal, 'more-horizontal': moreHorizontal,
'more-vertical': moreVertical, 'more-vertical': moreVertical,
navigation, navigation,
@@ -245,6 +270,8 @@ const ICONS = {
winding_way_wide: windingWayWide, winding_way_wide: windingWayWide,
parking_p: parkingP, parking_p: parkingP,
car, car,
'car-and-wrench': carAndWrench,
'castle-keep': castleKeep,
x, x,
zap, zap,
'loading-ring': loadingRing, 'loading-ring': loadingRing,
+30
View File
@@ -14,6 +14,30 @@ export function normalizeRelayUrl(url) {
return normalized; return normalized;
} }
export function uniqNormalizedRelays(relays = []) {
return Array.from(new Set(relays.map(normalizeRelayUrl).filter(Boolean)));
}
export function mergeRequiredRelays(requiredRelays = [], customRelays = []) {
const requiredSet = new Set(requiredRelays.filter(Boolean));
const merged = [...requiredRelays.filter(Boolean)];
for (const relay of uniqNormalizedRelays(customRelays)) {
if (!requiredSet.has(relay)) {
merged.push(relay);
}
}
return merged;
}
export function excludeRequiredRelays(customRelays = [], requiredRelays = []) {
const requiredSet = new Set(requiredRelays.filter(Boolean));
return uniqNormalizedRelays(customRelays).filter((relay) => {
return !requiredSet.has(relay);
});
}
/** /**
* Extracts and normalizes photo data from NIP-360 (Place Photos) events. * Extracts and normalizes photo data from NIP-360 (Place Photos) events.
* Sorts chronologically and guarantees the first landscape photo (or first portrait) is at index 0. * Sorts chronologically and guarantees the first landscape photo (or first portrait) is at index 0.
@@ -43,6 +67,7 @@ export function parsePlacePhotos(events) {
let blurhash = null; let blurhash = null;
let isLandscape = false; let isLandscape = false;
let aspectRatio = 16 / 9; // default let aspectRatio = 16 / 9; // default
let altText = null;
let placeIdentifier = event.tags.find((t) => t[0] === 'i')?.[1]; let placeIdentifier = event.tags.find((t) => t[0] === 'i')?.[1];
for (const tag of imeta.slice(1)) { for (const tag of imeta.slice(1)) {
@@ -61,6 +86,10 @@ export function parsePlacePhotos(events) {
isLandscape = true; isLandscape = true;
} }
} }
} else if (tag.startsWith('alt ')) {
const alt = tag.substring(4).trim();
// Strip the legacy placeholder we used to write on every event
altText = alt === 'A photo of a place' ? null : alt || null;
} }
} }
@@ -76,6 +105,7 @@ export function parsePlacePhotos(events) {
aspectRatio, aspectRatio,
placeIdentifier, placeIdentifier,
tags: eventTagValues, tags: eventTagValues,
alt: altText,
}); });
} }
} }
+6
View File
@@ -27,6 +27,8 @@ export const POI_ICON_RULES = [
{ tags: { amenity: 'bank' }, icon: 'banknote' }, { tags: { amenity: 'bank' }, icon: 'banknote' },
{ tags: { amenity: 'place_of_worship' }, icon: 'place-of-worship-building' }, { tags: { amenity: 'place_of_worship' }, icon: 'place-of-worship-building' },
{ tags: { amenity: 'townhall' }, icon: 'classical-building-with-clock' },
{ tags: { building: 'townhall' }, icon: 'classical-building-with-clock' },
{ tags: { amenity: 'fire_station' }, icon: 'badge-shield-with-fire' }, { tags: { amenity: 'fire_station' }, icon: 'badge-shield-with-fire' },
{ tags: { amenity: 'police' }, icon: 'police-officer-with-stop-arm' }, { tags: { amenity: 'police' }, icon: 'police-officer-with-stop-arm' },
{ tags: { amenity: 'toilets' }, icon: 'womens-and-mens-restroom-symbol' }, { tags: { amenity: 'toilets' }, icon: 'womens-and-mens-restroom-symbol' },
@@ -72,6 +74,7 @@ export const POI_ICON_RULES = [
tags: { shop: 'beauty' }, tags: { shop: 'beauty' },
icon: 'fancy-mirror-with-reflection-and-stars', icon: 'fancy-mirror-with-reflection-and-stars',
}, },
{ tags: { shop: 'car_repair' }, icon: 'car-and-wrench' },
{ tags: { craft: 'tailor' }, icon: 'needle-and-spool-of-thread' }, { tags: { craft: 'tailor' }, icon: 'needle-and-spool-of-thread' },
{ tags: { office: 'estate_agent' }, icon: 'village-buildings' }, { tags: { office: 'estate_agent' }, icon: 'village-buildings' },
{ tags: { office: true }, icon: 'commercial-building' }, { tags: { office: true }, icon: 'commercial-building' },
@@ -103,6 +106,7 @@ export const POI_ICON_RULES = [
{ tags: { tourism: 'viewpoint' }, icon: 'camera' }, { tags: { tourism: 'viewpoint' }, icon: 'camera' },
{ tags: { tourism: 'zoo' }, icon: 'camera' }, { tags: { tourism: 'zoo' }, icon: 'camera' },
{ tags: { tourism: 'artwork' }, icon: 'camera' }, { tags: { tourism: 'artwork' }, icon: 'camera' },
{ tags: { tourism: 'information' }, icon: 'info-i' },
{ tags: { amenity: 'cinema' }, icon: 'film' }, { tags: { amenity: 'cinema' }, icon: 'film' },
{ tags: { amenity: 'theatre' }, icon: 'camera' }, { tags: { amenity: 'theatre' }, icon: 'camera' },
{ tags: { amenity: 'arts_centre' }, icon: 'comedy-mask-and-tragedy-mask' }, { tags: { amenity: 'arts_centre' }, icon: 'comedy-mask-and-tragedy-mask' },
@@ -113,7 +117,9 @@ export const POI_ICON_RULES = [
{ tags: { historic: 'bridge' }, icon: 'bridge' }, { tags: { historic: 'bridge' }, icon: 'bridge' },
{ tags: { historic: 'bridge_site' }, icon: 'bridge' }, { tags: { historic: 'bridge_site' }, icon: 'bridge' },
{ tags: { historic: 'fort' }, icon: 'fort' }, { tags: { historic: 'fort' }, icon: 'fort' },
{ tags: { historic: 'city_gate' }, icon: 'city-gate' },
{ tags: { historic: 'castle' }, icon: 'palace' }, { tags: { historic: 'castle' }, icon: 'palace' },
{ tags: { building: 'tower', historic: 'yes' }, icon: 'castle-keep' },
{ tags: { historic: 'building' }, icon: 'classical-building-with-flag' }, { tags: { historic: 'building' }, icon: 'classical-building-with-flag' },
{ tags: { historic: 'archaeological_site' }, icon: 'grecian-vase' }, { tags: { historic: 'archaeological_site' }, icon: 'grecian-vase' },
{ tags: { historic: 'memorial' }, icon: 'memorial-stone-with-inscription' }, { tags: { historic: 'memorial' }, icon: 'memorial-stone-with-inscription' },
+10 -1
View File
@@ -56,15 +56,24 @@ const PLACE_TYPE_KEYS = [
export function getPlaceType(tags) { export function getPlaceType(tags) {
if (!tags) return null; if (!tags) return null;
let fallbackKey = null;
for (const key of PLACE_TYPE_KEYS) { for (const key of PLACE_TYPE_KEYS) {
const value = tags[key]; const value = tags[key];
if (value) { if (value) {
if (value === 'yes') { if (value === 'yes') {
return humanizeOsmTag(key); if (!fallbackKey) {
fallbackKey = key;
}
continue;
} }
return humanizeOsmTag(value); return humanizeOsmTag(value);
} }
} }
if (fallbackKey) {
return humanizeOsmTag(fallbackKey);
}
return null; return null;
} }
+2 -2
View File
@@ -43,7 +43,7 @@ export const POI_CATEGORIES = [
label: 'Things to do', label: 'Things to do',
icon: 'feather-camera', icon: 'feather-camera',
filter: [ filter: [
'["tourism"~"^(museum|gallery|attraction|viewpoint|zoo|theme_park|aquarium|artwork)$"]', '["tourism"~"^(museum|gallery|attraction|viewpoint|zoo|theme_park|aquarium|artwork|information)$"]',
'["amenity"~"^(cinema|theatre|arts_centre|planetarium)$"]', '["amenity"~"^(cinema|theatre|arts_centre|planetarium)$"]',
'["leisure"~"^(sports_centre|stadium|water_park)$"]', '["leisure"~"^(sports_centre|stadium|water_park)$"]',
'["historic"]', '["historic"]',
@@ -55,7 +55,7 @@ export const POI_CATEGORIES = [
id: 'accommodation', id: 'accommodation',
label: 'Hotels', label: 'Hotels',
icon: 'person-sleeping-in-bed', icon: 'person-sleeping-in-bed',
filter: ['["tourism"~"^(hotel|hostel|motel|chalet)$"]'], filter: ['["tourism"~"^(hotel|hostel|motel|chalet|guest_house)$"]'],
types: ['node', 'way', 'relation'], types: ['node', 'way', 'relation'],
}, },
]; ];
-2
View File
@@ -37,8 +37,6 @@ export default {
'@babel/plugin-transform-runtime', '@babel/plugin-transform-runtime',
{ {
absoluteRuntime: dirname(fileURLToPath(import.meta.url)), absoluteRuntime: dirname(fileURLToPath(import.meta.url)),
useESModules: true,
regenerator: false,
}, },
], ],
...macros.babelMacros, ...macros.babelMacros,
+1 -1
View File
@@ -22,7 +22,7 @@ import eslintConfigPrettier from 'eslint-config-prettier';
import qunit from 'eslint-plugin-qunit'; import qunit from 'eslint-plugin-qunit';
import n from 'eslint-plugin-n'; import n from 'eslint-plugin-n';
import babelParser from '@babel/eslint-parser/experimental-worker'; import babelParser from '@babel/eslint-parser';
const esmParserOptions = { const esmParserOptions = {
ecmaFeatures: { modules: true }, ecmaFeatures: { modules: true },
+54 -55
View File
@@ -1,6 +1,6 @@
{ {
"name": "marco", "name": "marco",
"version": "1.24.0", "version": "1.26.2",
"private": true, "private": true,
"description": "Unhosted maps app", "description": "Unhosted maps app",
"repository": { "repository": {
@@ -39,61 +39,61 @@
"version": "pnpm build && git add release/" "version": "pnpm build && git add release/"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.28.5", "@babel/core": "^8.0.1",
"@babel/eslint-parser": "^7.28.5", "@babel/eslint-parser": "^8.0.1",
"@babel/plugin-transform-runtime": "^7.28.5", "@babel/plugin-transform-runtime": "^8.0.1",
"@babel/runtime": "^7.28.4", "@babel/runtime": "^8.0.0",
"@ember/test-helpers": "^5.4.1", "@ember/test-helpers": "^5.4.3",
"@ember/test-waiters": "^4.1.1", "@ember/test-waiters": "^4.1.2",
"@embroider/core": "^4.4.2", "@embroider/core": "^4.6.3",
"@embroider/legacy-inspector-support": "^0.1.3", "@embroider/legacy-inspector-support": "^0.1.3",
"@embroider/macros": "^1.19.6", "@embroider/macros": "^1.20.6",
"@embroider/router": "^3.0.6", "@embroider/router": "^3.0.6",
"@embroider/vite": "^1.5.0", "@embroider/vite": "^1.7.9",
"@eslint/js": "^9.39.2", "@eslint/js": "^10.0.1",
"@glimmer/component": "^2.0.0", "@glimmer/component": "^2.1.1",
"@remotestorage/module-places": "~1.2.1", "@remotestorage/module-places": "~1.3.0",
"@rollup/plugin-babel": "^6.1.0", "@rollup/plugin-babel": "^7.1.0",
"@warp-drive/core": "~5.8.0", "@warp-drive/core": "~5.8.2",
"@warp-drive/ember": "~5.8.0", "@warp-drive/ember": "~5.8.2",
"@warp-drive/json-api": "~5.8.0", "@warp-drive/json-api": "~5.8.2",
"@warp-drive/legacy": "~5.8.0", "@warp-drive/legacy": "~5.8.2",
"@warp-drive/utilities": "~5.8.0", "@warp-drive/utilities": "~5.8.2",
"babel-plugin-ember-template-compilation": "^3.0.1", "babel-plugin-ember-template-compilation": "^4.0.0",
"concurrently": "^9.2.1", "concurrently": "^10.0.4",
"decorator-transforms": "^2.3.1", "decorator-transforms": "^2.4.0",
"ember-cli": "^6.10.0", "ember-cli": "^7.1.0",
"ember-cli-deprecation-workflow": "^4.0.0", "ember-cli-deprecation-workflow": "^4.0.1",
"ember-modifier": "^4.2.2", "ember-modifier": "^4.3.0",
"ember-page-title": "^9.0.3", "ember-page-title": "^9.0.3",
"ember-qunit": "^9.0.4", "ember-qunit": "^9.1.0",
"ember-resolver": "^13.1.1", "ember-resolver": "^13.2.0",
"ember-source": "~6.11.0-alpha.6", "ember-source": "~7.2.0",
"ember-template-lint": "^7.9.3", "ember-template-lint": "^7.9.3",
"ember-truth-helpers": "^5.0.0", "ember-truth-helpers": "^5.0.0",
"ember-welcome-page": "^8.0.4", "ember-welcome-page": "^8.0.4",
"eslint": "^9.39.2", "eslint": "^10.8.1",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-ember": "^12.7.5", "eslint-plugin-ember": "^13.5.0",
"eslint-plugin-n": "^17.23.1", "eslint-plugin-n": "^18.3.0",
"eslint-plugin-qunit": "^8.2.5", "eslint-plugin-qunit": "^8.2.6",
"eslint-plugin-warp-drive": "^5.8.0", "eslint-plugin-warp-drive": "^5.8.2",
"feather-icons": "^4.29.2", "feather-icons": "^4.29.2",
"globals": "^16.5.0", "globals": "^17.11.0",
"latlon-geohash": "^2.0.0", "latlon-geohash": "^2.0.0",
"ol": "^10.7.0", "ol": "^10.10.0",
"ol-mapbox-style": "^13.2.0", "ol-mapbox-style": "^13.4.2",
"prettier": "^3.7.4", "prettier": "^3.9.6",
"prettier-plugin-ember-template-tag": "^2.1.2", "prettier-plugin-ember-template-tag": "^2.1.7",
"qunit": "^2.25.0", "qunit": "^2.26.0",
"qunit-dom": "^3.5.0", "qunit-dom": "^3.6.0",
"remotestorage-widget": "^1.8.1", "remotestorage-widget": "^1.8.1",
"remotestoragejs": "2.0.0-beta.9", "remotestoragejs": "2.0.0-beta.10",
"sinon": "^21.0.1", "sinon": "^22.1.0",
"stylelint": "^16.26.1", "stylelint": "^17.14.1",
"stylelint-config-standard": "^38.0.0", "stylelint-config-standard": "^40.0.0",
"testem": "^3.17.0", "testem": "^3.20.1",
"vite": "^7.3.0" "vite": "^8.0.0"
}, },
"engines": { "engines": {
"node": ">= 22" "node": ">= 22"
@@ -102,17 +102,16 @@
"edition": "octane" "edition": "octane"
}, },
"dependencies": { "dependencies": {
"@noble/hashes": "^2.2.0", "@noble/hashes": "^2.3.0",
"@waysidemapping/pinhead": "^15.20.0", "@waysidemapping/pinhead": "^15.25.0",
"applesauce-core": "^5.2.0", "applesauce-core": "^6.2.0",
"applesauce-factory": "^4.0.0", "applesauce-relay": "^6.2.1",
"applesauce-relay": "^5.2.0", "applesauce-signers": "^6.2.2",
"applesauce-signers": "^5.2.0",
"blurhash": "^2.0.5", "blurhash": "^2.0.5",
"ember-concurrency": "^5.2.0", "ember-concurrency": "^5.2.0",
"ember-lifeline": "^7.0.0", "ember-lifeline": "^7.1.0",
"nostr-idb": "^5.0.0", "nostr-idb": "^5.1.0",
"oauth2-pkce": "^2.1.3", "oauth2-pkce": "^3.0.0",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"rxjs": "^7.8.2" "rxjs": "^7.8.2"
} }
+13
View File
@@ -0,0 +1,13 @@
diff --git a/async-arrow-task-transform.js b/async-arrow-task-transform.js
index 4eb856a2a7753ef583e10e07f2b602c0c1c0c21d..6a808d035834567dcfc491ecaa7f59e77d344887 100644
--- a/async-arrow-task-transform.js
+++ b/async-arrow-task-transform.js
@@ -331,7 +331,7 @@ function cleanupUnusedTaskFactoryImports(programPath, usedTaskFactories) {
}
module.exports = declare((api) => {
- api.assertVersion(7);
+ // api.assertVersion(7);
return {
name: 'transform-ember-concurrency-async-function-tasks',
+3723 -2591
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -0,0 +1,2 @@
patchedDependencies:
ember-concurrency@5.2.0: patches/ember-concurrency@5.2.0.patch
@@ -1 +0,0 @@
!function(){"use strict";var t=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","#","$","%","*","+",",","-",".",":",";","=","?","@","[","]","^","_","{","|","}","~"],e=(e,a)=>{var o="";for(let r=1;r<=a;r++){let h=Math.floor(e)/Math.pow(83,a-r)%83;o+=t[Math.floor(h)]}return o},a=t=>{let e=t/255;return e<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4)},o=t=>{let e=Math.max(0,Math.min(1,t));return e<=.0031308?Math.trunc(12.92*e*255+.5):Math.trunc(255*(1.055*Math.pow(e,.4166666666666667)-.055)+.5)},r=(t,e)=>(t=>t<0?-1:1)(t)*Math.pow(Math.abs(t),e),h=class extends Error{constructor(t){super(t),this.name="ValidationError",this.message=t}},i=(t,e,o,r)=>{let h=0,i=0,n=0,s=4*e;for(let g=0;g<e;g++){let e=4*g;for(let l=0;l<o;l++){let o=e+l*s,c=r(g,l);h+=c*a(t[o]),i+=c*a(t[o+1]),n+=c*a(t[o+2])}}let l=1/(e*o);return[h*l,i*l,n*l]};self.onmessage=async t=>{if("PROCESS_IMAGE"!==t.data?.type)return;const{id:a,file:n,targetWidth:s,targetHeight:l,quality:g,computeBlurhash:c}=t.data;try{let t,M;try{const e=await createImageBitmap(n,{resizeWidth:s,resizeHeight:l,resizeQuality:"high"});if(t=new OffscreenCanvas(s,l),M=t.getContext("2d"),!M)throw new Error("Failed to get 2d context from OffscreenCanvas");M.drawImage(e,0,0,s,l),e.close()}catch(f){console.warn("Hardware resize failed, falling back to stepped software scaling:",f);const e=await n.arrayBuffer(),a=new Blob([e],{type:n.type}),o=await createImageBitmap(a);let r=o.width,h=o.height,i=new OffscreenCanvas(r,h),g=i.getContext("2d");for(g.imageSmoothingEnabled=!0,g.imageSmoothingQuality="high",g.drawImage(o,0,0);.5*i.width>s&&.5*i.height>l;){const t=new OffscreenCanvas(Math.floor(.5*i.width),Math.floor(.5*i.height)),e=t.getContext("2d");e.imageSmoothingEnabled=!0,e.imageSmoothingQuality="high",e.drawImage(i,0,0,t.width,t.height),i=t}t=new OffscreenCanvas(s,l),M=t.getContext("2d"),M.imageSmoothingEnabled=!0,M.imageSmoothingQuality="high",M.drawImage(i,0,0,s,l),o.close()}let d=null;if(c)try{d=((t,a,n)=>{if(a*n*4!==t.length)throw new h("Width and height must match the pixels array");let s=[];for(let e=0;e<3;e++)for(let o=0;o<4;o++){let r=0==o&&0==e?1:2,h=i(t,a,n,(t,h)=>r*Math.cos(Math.PI*o*t/a)*Math.cos(Math.PI*e*h/n));s.push(h)}let l,g=s[0],c=s.slice(1),f="";if(f+=e(21,1),c.length>0){let t=Math.max(...c.map(t=>Math.max(...t))),a=Math.floor(Math.max(0,Math.min(82,Math.floor(166*t-.5))));l=(a+1)/166,f+=e(a,1)}else l=1,f+=e(0,1);return f+=e((t=>(o(t[0])<<16)+(o(t[1])<<8)+o(t[2]))(g),4),c.forEach(t=>{f+=e(((t,e)=>19*Math.floor(Math.max(0,Math.min(18,Math.floor(9*r(t[0]/e,.5)+9.5))))*19+19*Math.floor(Math.max(0,Math.min(18,Math.floor(9*r(t[1]/e,.5)+9.5))))+Math.floor(Math.max(0,Math.min(18,Math.floor(9*r(t[2]/e,.5)+9.5)))))(t,l),2)}),f})(M.getImageData(0,0,s,l).data,s,l)}catch(m){console.warn("Could not generate blurhash (possible canvas fingerprinting protection):",m)}const u=await t.convertToBlob({type:"image/jpeg",quality:g}),w=`${s}x${l}`;self.postMessage({id:a,success:!0,blob:u,dim:w,blurhash:d})}catch(M){self.postMessage({id:a,success:!1,error:M.message})}}}();
+182
View File
@@ -0,0 +1,182 @@
var q = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"#",
"$",
"%",
"*",
"+",
",",
"-",
".",
":",
";",
"=",
"?",
"@",
"[",
"]",
"^",
"_",
"{",
"|",
"}",
"~"
], p = (t, e) => {
var a = "";
for (let o = 1; o <= e; o++) {
let h = Math.floor(t) / Math.pow(83, e - o) % 83;
a += q[Math.floor(h)];
}
return a;
}, f = (t) => {
let e = t / 255;
return e <= .04045 ? e / 12.92 : Math.pow((e + .055) / 1.055, 2.4);
}, h = (t) => {
let e = Math.max(0, Math.min(1, t));
return e <= .0031308 ? Math.trunc(12.92 * e * 255 + .5) : Math.trunc(255 * (1.055 * Math.pow(e, .4166666666666667) - .055) + .5);
}, M = (t, e) => ((t) => t < 0 ? -1 : 1)(t) * Math.pow(Math.abs(t), e), d = class extends Error {
constructor(t) {
super(t), this.name = "ValidationError", this.message = t;
}
}, D = (t, e, a, o) => {
let h = 0, r = 0, n = 0, i = 4 * e;
for (let l = 0; l < e; l++) {
let e = 4 * l;
for (let s = 0; s < a; s++) {
let a = e + s * i, g = o(l, s);
h += g * f(t[a]), r += g * f(t[a + 1]), n += g * f(t[a + 2]);
}
}
let s = 1 / (e * a);
return [
h * s,
r * s,
n * s
];
}, S = (t, e, a, o, r) => {
if (o < 1 || o > 9 || r < 1 || r > 9) throw new d("BlurHash must have between 1 and 9 components");
if (e * a * 4 !== t.length) throw new d("Width and height must match the pixels array");
let n = [];
for (let h = 0; h < r; h++) for (let r = 0; r < o; r++) {
let o = 0 == r && 0 == h ? 1 : 2, i = D(t, e, a, (t, n) => o * Math.cos(Math.PI * r * t / e) * Math.cos(Math.PI * h * n / a));
n.push(i);
}
let i, s = n[0], l = n.slice(1), g = "";
if (g += p(o - 1 + 9 * (r - 1), 1), l.length > 0) {
let t = Math.max(...l.map((t) => Math.max(...t))), e = Math.floor(Math.max(0, Math.min(82, Math.floor(166 * t - .5))));
i = (e + 1) / 166, g += p(e, 1);
} else i = 1, g += p(0, 1);
return g += p(((t) => (h(t[0]) << 16) + (h(t[1]) << 8) + h(t[2]))(s), 4), l.forEach((t) => {
g += p(((t, e) => 19 * Math.floor(Math.max(0, Math.min(18, Math.floor(9 * M(t[0] / e, .5) + 9.5)))) * 19 + 19 * Math.floor(Math.max(0, Math.min(18, Math.floor(9 * M(t[1] / e, .5) + 9.5)))) + Math.floor(Math.max(0, Math.min(18, Math.floor(9 * M(t[2] / e, .5) + 9.5)))))(t, i), 2);
}), g;
};
self.onmessage = async (t) => {
if ("PROCESS_IMAGE" !== t.data?.type) return;
const { id: e, file: a, targetWidth: o, targetHeight: h, quality: r, computeBlurhash: n } = t.data;
try {
let t, l;
try {
const e = await createImageBitmap(a, {
resizeWidth: o,
resizeHeight: h,
resizeQuality: "high"
});
if (t = new OffscreenCanvas(o, h), l = t.getContext("2d"), !l) throw new Error("Failed to get 2d context from OffscreenCanvas");
l.drawImage(e, 0, 0, o, h), e.close();
} catch (i) {
console.warn("Hardware resize failed, falling back to stepped software scaling:", i);
const e = await a.arrayBuffer(), r = new Blob([e], { type: a.type }), n = await createImageBitmap(r);
let s = n.width, g = n.height, f = new OffscreenCanvas(s, g), c = f.getContext("2d");
for (c.imageSmoothingEnabled = !0, c.imageSmoothingQuality = "high", c.drawImage(n, 0, 0); .5 * f.width > o && .5 * f.height > h;) {
const t = new OffscreenCanvas(Math.floor(.5 * f.width), Math.floor(.5 * f.height)), e = t.getContext("2d");
e.imageSmoothingEnabled = !0, e.imageSmoothingQuality = "high", e.drawImage(f, 0, 0, t.width, t.height), f = t;
}
t = new OffscreenCanvas(o, h), l = t.getContext("2d"), l.imageSmoothingEnabled = !0, l.imageSmoothingQuality = "high", l.drawImage(f, 0, 0, o, h), n.close();
}
let g = null;
if (n) try {
const t = l.getImageData(0, 0, o, h);
g = S(t.data, o, h, 4, 3);
} catch (s) {
console.warn("Could not generate blurhash (possible canvas fingerprinting protection):", s);
}
const f = await t.convertToBlob({
type: "image/jpeg",
quality: r
}), c = `${o}x${h}`;
self.postMessage({
id: e,
success: !0,
blob: f,
dim: c,
blurhash: g
});
} catch (l) {
self.postMessage({
id: e,
success: !1,
error: l.message
});
}
};
+1
View File
@@ -0,0 +1 @@
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),a=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:"Module"}),r},l=(o,a,l)=>(l=null!=o?e(i(o)):{},((e,i,o,a)=>{if(i&&"object"==typeof i||"function"==typeof i)for(var l,c=r(i),u=0,h=c.length;u<h;u++)l=c[u],s.call(e,l)||void 0===l||t(e,l,{get:(e=>i[e]).bind(null,l),enumerable:!(a=n(i,l))||a.enumerable});return e})(!a&&o&&o.__esModule&&s.call(o,"default")?l:t(l,"default",{value:o,enumerable:!0}),o));!function(){const e=document.createElement("link").relList;if(!(e&&e.supports&&e.supports("modulepreload"))){for(const e of document.querySelectorAll('link[rel="modulepreload"]'))t(e);new MutationObserver(e=>{for(const n of e)if("childList"===n.type)for(const e of n.addedNodes)"LINK"===e.tagName&&"modulepreload"===e.rel&&t(e)}).observe(document,{childList:!0,subtree:!0})}function t(e){if(e.ep)return;e.ep=!0;const t=function(e){const t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),"use-credentials"===e.crossOrigin?t.credentials="include":"anonymous"===e.crossOrigin?t.credentials="omit":t.credentials="same-origin",t}(e);fetch(e.href,t)}}();var c,u=Object.prototype,h=Symbol("undefined"),d=function(e){return e[e.ADD=0]="ADD",e[e.ONCE=1]="ONCE",e[e.REMOVE=2]="REMOVE",e}(d||{}),p=1,f=class{_descriptors;_mixins;_isInit;_lazyChains;_values;_revisions;source;proto;_parent;_listeners;_listenersVersion=1;_inheritedEnd=-1;_flattenedVersion=0;constructor(e){this._parent=void 0,this._descriptors=void 0,this._mixins=void 0,this._lazyChains=void 0,this._values=void 0,this._revisions=void 0,this._isInit=!1,this.source=e,this.proto=void 0===e.constructor?void 0:e.constructor.prototype,this._listeners=void 0}get parent(){let e=this._parent;if(void 0===e){let t=g(this.source);this._parent=e=null===t||t===u?null:b(t)}return e}setInitializing(){this._isInit=!0}unsetInitializing(){this._isInit=!1}isInitializing(){return this._isInit}isPrototypeMeta(e){return this.proto===this.source&&this.source===e}_getOrCreateOwnMap(e){return this[e]||(this[e]=Object.create(null))}_getOrCreateOwnSet(e){return this[e]||(this[e]=new Set)}_findInheritedMap(e,t){let n=this;for(;null!==n;){let r=n[e];if(void 0!==r){let e=r.get(t);if(void 0!==e)return e}n=n.parent}}_hasInInheritedSet(e,t){let n=this;for(;null!==n;){let r=n[e];if(void 0!==r&&r.has(t))return!0;n=n.parent}return!1}valueFor(e){let t=this._values;return void 0!==t?t[e]:void 0}setValueFor(e,t){this._getOrCreateOwnMap("_values")[e]=t}revisionFor(e){let t=this._revisions;return void 0!==t?t[e]:void 0}setRevisionFor(e,t){this._getOrCreateOwnMap("_revisions")[e]=t}writableLazyChainsFor(e){let t=this._getOrCreateOwnMap("_lazyChains"),n=t[e];return void 0===n&&(n=t[e]=[]),n}readableLazyChainsFor(e){let t=this._lazyChains;if(void 0!==t)return t[e]}addMixin(e){this._getOrCreateOwnSet("_mixins").add(e)}hasMixin(e){return this._hasInInheritedSet("_mixins",e)}forEachMixins(e){let t,n=this;for(;null!==n;){let r=n._mixins;void 0!==r&&(t=void 0===t?new Set:t,r.forEach(n=>{t.has(n)||(t.add(n),e(n))})),n=n.parent}}writeDescriptors(e,t){(this._descriptors||(this._descriptors=new Map)).set(e,t)}peekDescriptors(e){let t=this._findInheritedMap("_descriptors",e);return t===h?void 0:t}removeDescriptors(e){this.writeDescriptors(e,h)}forEachDescriptors(e){let t,n=this;for(;null!==n;){let r=n._descriptors;void 0!==r&&(t=void 0===t?new Set:t,r.forEach((n,r)=>{t.has(r)||(t.add(r),n!==h&&e(r,n))})),n=n.parent}}addToListeners(e,t,n,r,i){this.pushListener(e,t,n,r?d.ONCE:d.ADD,i)}removeFromListeners(e,t,n){this.pushListener(e,t,n,d.REMOVE)}pushListener(e,t,n,r,i=!1){let s=this.writableListeners(),o=w(s,e,t,n);if(-1!==o&&o<this._inheritedEnd&&(s.splice(o,1),this._inheritedEnd--,o=-1),-1===o)s.push({event:e,target:t,method:n,kind:r,sync:i});else{let e=s[o];r===d.REMOVE&&e.kind!==d.REMOVE?s.splice(o,1):(e.kind=r,e.sync=i)}}writableListeners(){return this._flattenedVersion!==p||this.source!==this.proto&&-1!==this._inheritedEnd||p++,-1===this._inheritedEnd&&(this._inheritedEnd=0,this._listeners=[]),this._listeners}flattenedListeners(){if(this._flattenedVersion<p){let e=this.parent;if(null!==e){let t=e.flattenedListeners();if(void 0!==t)if(void 0===this._listeners)this._listeners=t;else{let e=this._listeners;this._inheritedEnd>0&&(e.splice(0,this._inheritedEnd),this._inheritedEnd=0);for(let n of t)-1===w(e,n.event,n.target,n.method)&&(e.unshift(n),this._inheritedEnd++)}}this._flattenedVersion=p}return this._listeners}matchingListeners(e){let t,n=this.flattenedListeners();if(void 0!==n)for(let r of n)r.event!==e||r.kind!==d.ADD&&r.kind!==d.ONCE||(void 0===t&&(t=[]),t.push(r.target,r.method,r.kind===d.ONCE));return t}observerEvents(){let e,t=this.flattenedListeners();if(void 0!==t)for(let n of t)nLine truncated
+1
View File
@@ -0,0 +1 @@
:root{--body-text-color:#333;--primary-background-color:#fff;--hover-bg:#f8f9fa;--divider-color:#eee;--sidebar-width:350px;--link-color:#2a7fff;--link-color-visited:#6a4fbf;--marker-color-primary:#ea4335;--marker-color-dark:#b31412;--danger-color:var(--marker-color-primary);--danger-color-dark:var(--marker-color-dark);--default-list-color:#fc3}html,body{overscroll-behavior:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent;height:100%}button{-webkit-tap-highlight-color:transparent}body{color:var(--body-text-color);margin:0;font-family:Noto Sans,sans-serif;font-size:16px;font-weight:400}#root,#ember-testing{height:100%}.map-container{-webkit-tap-highlight-color:transparent;background:#f8f9fa;outline:none;position:absolute;inset:0}#rs-widget-container{z-index:4000;display:none;position:absolute;top:60px;right:10px}#rs-widget-container.visible{display:block}.rs-backdrop{z-index:3999;position:fixed;inset:0}.app-header{z-index:3000;pointer-events:none;grid-template-columns:1fr auto 1fr;grid-template-areas:"search chips user";align-items:center;gap:1rem;height:60px;padding:.5rem 1rem;display:grid;position:absolute;top:0;left:0;right:0}@media (width<=768px){.app-header{grid-template-columns:1fr auto;grid-template-areas:"search user""chips chips";row-gap:8px;height:auto;padding:.5rem .5rem 0}}.header-left,.header-right,.header-center{pointer-events:auto}.header-left{grid-area:search;justify-self:start;align-items:center;width:100%;display:flex}@media (width>768px){.header-left{min-width:300px;max-width:350px}}.header-right{grid-area:user;justify-self:end}.header-center{grid-area:chips;justify-content:center;min-width:0;display:flex}@media (width>768px){.header-center .category-chips-scroll{width:auto;max-width:100%}}@media (width<=768px){.header-center{justify-content:start;width:100%;overflow:hidden}.header-center.searching{display:none}}.btn-press{transition:transform .1s}.btn-press:active{transform:scale(.95)}.user-btn{cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:0;display:flex}.user-avatar-placeholder{background:#2a3743;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;width:40px;height:40px;display:flex}.user-avatar{object-fit:cover;border-radius:50%;flex-shrink:0;width:40px;height:40px;display:block}.photo-preview-img{max-width:100%;height:auto}.dropzone{text-align:center;cursor:pointer;aspect-ratio:4/3;background-color:#ffffff05;border:2px dashed #ccc;border-radius:8px;margin:1.5rem 0 1rem;transition:all .2s}.dropzone.is-dragging{background-color:#61afef0d;border-color:#61afef}.dropzone-label{cursor:pointer;color:#898989;flex-direction:column;justify-content:center;align-items:center;gap:1rem;width:100%;height:100%;display:flex}.dropzone-label p{margin:0}.file-input-hidden{display:none}.photo-upload-item{aspect-ratio:4/3;background:#1e262e;border-radius:6px;width:100%;margin-bottom:1.25rem;position:relative;overflow:hidden}.photo-upload-item img{object-fit:contain;z-index:1;width:100%;height:100%;display:block;position:absolute;top:0;left:0}.photo-upload-item .overlay,.photo-upload-item .btn-remove-photo{z-index:2}.photo-upload-item .overlay{background:#0009;flex-direction:column;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.upload-status-text{color:#fff;text-shadow:0 1px 3px #000c;text-align:center;margin-top:1rem;padding:0 1rem;font-size:.9rem}.photo-upload-item .error-overlay{cursor:pointer;background:#e06c75cc;border:none;width:100%;margin:0;padding:0}.photo-upload-item .btn-remove-photo{cursor:pointer;color:#fff;background:#000000b3;border:none;border-radius:50%;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:flex;position:absolute;top:4px;right:4px}.photo-upload-item .btn-remove-photo:hover{background:var(--danger-color)}.spin-animation{animation:1s linear infinite spin}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.btn-publish{width:100%}.user-menu-container{position:relative}.user-menu-popover{z-index:3001;background:#fff;border-radius:8px;width:280px;margin-top:10px;padding:1rem;position:absolute;top:100%;right:0;box-shadow:0 4px 12px #00000026}.menu-backdrop{z-index:3000;position:fixed;inset:0}.account-list{margin:0;padding:0;list-style:none}.account-item{border-bottom:1px solid var(--divider-color);flex-direction:column;padding:1rem 0;display:flex}.account-item:last-child{border-bottom:none;padding-bottom:0}.account-item:first-child{padding-top:0}.account-header{justify-content:space-between;align-items:center;display:flex}.account-status{color:#898989;white-space:nowrap;text-overflow:ellipsis;margin-top:.35rem;margin-left:calc(18px + .75rem);font-size:.85rem;overflow:hidden}.account-status strong{font-weight:600}.account-item.disabled{opacity:.5;pointer-events:none}.account-info{align-items:center;gap:.75rem;display:flex}.btn-text{cursor:pointLine truncated
-25
View File
@@ -1,25 +0,0 @@
const amdModule0=Object.freeze(Object.defineProperty({__proto__:null,get default(){return App}},Symbol.toStringTag,{value:"Module"}));!function(){const e=document.createElement("link").relList;if(!(e&&e.supports&&e.supports("modulepreload"))){for(const e of document.querySelectorAll('link[rel="modulepreload"]'))t(e);new MutationObserver(e=>{for(const r of e)if("childList"===r.type)for(const e of r.addedNodes)"LINK"===e.tagName&&"modulepreload"===e.rel&&t(e)}).observe(document,{childList:!0,subtree:!0})}function t(e){if(e.ep)return;e.ep=!0;const t=function(e){const t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),"use-credentials"===e.crossOrigin?t.credentials="include":"anonymous"===e.crossOrigin?t.credentials="omit":t.credentials="same-origin",t}(e);fetch(e.href,t)}}();const hasDOM="object"==typeof self&&null!==self&&self.Object===Object&&"undefined"!=typeof Window&&self.constructor===Window&&"object"==typeof document&&null!==document&&self.document===document&&"object"==typeof location&&null!==location&&self.location===location&&"object"==typeof history&&null!==history&&self.history===history&&"object"==typeof navigator&&null!==navigator&&self.navigator===navigator&&"string"==typeof navigator.userAgent,window$1=hasDOM?self:null,location$1=hasDOM?self.location:null,history$1=hasDOM?self.history:null,userAgent=hasDOM?self.navigator.userAgent:"Lynx (textmode)",isChrome=!(!hasDOM||"object"!=typeof chrome||"object"==typeof opera),isFirefox=!!hasDOM&&/Firefox|FxiOS/.test(userAgent),environment=Object.freeze(Object.defineProperty({__proto__:null,hasDOM:hasDOM,history:history$1,isChrome:isChrome,isFirefox:isFirefox,location:location$1,userAgent:userAgent,window:window$1},Symbol.toStringTag,{value:"Module"}));function checkGlobal(e){return e&&e.Object===Object?e:void 0}function checkElementIdShadowing(e){return e&&void 0===e.nodeType?e:void 0}const global$1=checkGlobal(checkElementIdShadowing("object"==typeof global&&global))||checkGlobal("object"==typeof self&&self)||checkGlobal("object"==typeof window&&window)||"undefined"!=typeof mainContext&&mainContext||new Function("return this")(),context$1=function(e,t){return void 0===t?{imports:e,exports:e,lookup:e}:{imports:t.imports||e,exports:t.exports||e,lookup:t.lookup||e}}(global$1,global$1.Ember);function getLookup(){return context$1.lookup}function setLookup(e){context$1.lookup=e}const ENV$1={ENABLE_OPTIONAL_FEATURES:!1,EXTEND_PROTOTYPES:{Array:!1},LOG_STACKTRACE_ON_DEPRECATION:!0,LOG_VERSION:!0,RAISE_ON_DEPRECATION:!1,STRUCTURED_PROFILE:!1,_DEBUG_RENDER_TREE:!1,_ALL_DEPRECATIONS_ENABLED:!1,_OVERRIDE_DEPRECATION_VERSION:null,_DEFAULT_ASYNC_OBSERVERS:!1,_RERENDER_LOOP_LIMIT:1e3,EMBER_LOAD_HOOKS:{},FEATURES:{}};function getENV(){return ENV$1}(e=>{if("object"!=typeof e||null===e)return;for(let n in e){if(!Object.prototype.hasOwnProperty.call(e,n)||"EXTEND_PROTOTYPES"===n||"EMBER_LOAD_HOOKS"===n)continue;let t=ENV$1[n];ENV$1[n]=!0===t?!1!==e[n]:!1===t?!0===e[n]:e[n]}let{EMBER_LOAD_HOOKS:t}=e;if("object"==typeof t&&null!==t)for(let n in t){if(!Object.prototype.hasOwnProperty.call(t,n))continue;let e=t[n];Array.isArray(e)&&(ENV$1.EMBER_LOAD_HOOKS[n]=e.filter(e=>"function"==typeof e))}let{FEATURES:r}=e;if("object"==typeof r&&null!==r)for(let n in r)Object.prototype.hasOwnProperty.call(r,n)&&(ENV$1.FEATURES[n]=!0===r[n])})(global$1.EmberENV);let assert$3=()=>{},registerHandler$1=()=>{},deprecate$1=()=>{};const defaultDeprecate=deprecate$1;let testing=!1;function isTesting(){return testing}function setTesting(e){testing=Boolean(e)}let registerHandler=()=>{};const{toString:objectToString$1}=Object.prototype,{toString:functionToString}=Function.prototype,{isArray:isArray$6}=Array,{keys:objectKeys}=Object,{stringify:stringify}=JSON,LIST_LIMIT=100,DEPTH_LIMIT=4,SAFE_KEY=/^[\w$]+$/;function inspect(e){return"number"==typeof e&&2===arguments.length?this:inspectValue(e,0)}function inspectValue(e,t,r){let n=!1;switch(typeof e){case"undefined":return"undefined";case"object":if(null===e)return"null";if(isArray$6(e)){n=!0;break}if(e.toString===objectToString$1||void 0===e.toString)break;return e.toString();case"function":return e.toString===functionToString?e.name?`[Function:${e.name}]`:"[Function]":e.toString();case"string":return stringify(e);default:return e.toString()}if(void 0===r)r=new WeakSet;else if(r.has(e))return"[Circular]";return r.add(e),n?inspectArray(e,t+1,r):inspectObject(e,t+1,r)}function inspectKey(e){return SAFE_KEY.test(e)?e:stringify(e)}function inspectObject(e,t,r){if(t>DEPTH_LIMIT)return"[Object]";let n="{",i=objectKeys(e);for(let o=0;o<i.length;o++){if(n+=0===o?" ":", ",o>=LIST_LIMIT){n+=`... ${i.length-LIST_LIMIT} more keys`;break}let s=i[o];n+=`${inspectKey(String(s))}: ${inspectValue(e[s],t,r)}`}return n+=" }",n}function inspectArray(e,t,r){if(t>DEPTH_LIMIT)return"[Array]";let n="[";for(let i=0;i<e.length;i++){if(n+=0===i?" ":", ",i>=LIST_LIMIT){n+=`... ${e.length-LIST_LIMIT} more items`;break}n+=insLine truncated
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */if(!isLE$3)throw new Error("Non little-endian hardware is not supported");function utf8ToBytes$2(e){if("string"!=typeof e)throw new Error("utf8ToBytes expected string, got "+typeof e);return new Uint8Array((new TextEncoder).encode(e))}function toBytes$1(e){if("string"==typeof e&&(e=utf8ToBytes$2(e)),!u8a$2(e))throw new Error("expected Uint8Array, got "+typeof e);return e}function concatBytes$3(...e){const t=new Uint8Array(e.reduce((e,t)=>e+t.length,0));let r=0;return e.forEach(e=>{if(!u8a$2(e))throw new Error("Uint8Array expected");t.set(e,r),r+=e.length}),t}let Hash$1=class{clone(){return this._cloneInto()}};function wrapConstructor$1(e){const t=t=>e().update(toBytes$1(t)).digest(),r=e();return t.outputLen=r.outputLen,t.blockLen=r.blockLen,t.create=()=>e(),t}function randomBytes$2(e=32){if(crypto$2&&"function"==typeof crypto$2.getRandomValues)return crypto$2.getRandomValues(new Uint8Array(e));throw new Error("crypto.getRandomValues must be defined")}function setBigUint64$1(e,t,r,n){if("function"==typeof e.setBigUint64)return e.setBigUint64(t,r,n);const i=BigInt(32),o=BigInt(4294967295),s=Number(r>>i&o),a=Number(r&o),l=n?4:0,c=n?0:4;e.setUint32(t+l,s,n),e.setUint32(t+c,a,n)}let SHA2$1=class extends Hash$1{constructor(e,t,r,n){super(),this.blockLen=e,this.outputLen=t,this.padOffset=r,this.isLE=n,this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.buffer=new Uint8Array(e),this.view=createView$3(this.buffer)}update(e){exists$1(this);const{view:t,buffer:r,blockLen:n}=this,i=(e=toBytes$1(e)).length;for(let o=0;o<i;){const s=Math.min(n-this.pos,i-o);if(s===n){const t=createView$3(e);for(;n<=i-o;o+=n)this.process(t,o);continue}r.set(e.subarray(o,o+s),this.pos),this.pos+=s,o+=s,this.pos===n&&(this.process(t,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){exists$1(this),output$1(e,this),this.finished=!0;const{buffer:t,view:r,blockLen:n,isLE:i}=this;let{pos:o}=this;t[o++]=128,this.buffer.subarray(o).fill(0),this.padOffset>n-o&&(this.process(r,0),o=0);for(let u=o;u<n;u++)t[u]=0;setBigUint64$1(r,n-8,BigInt(8*this.length),i),this.process(r,0);const s=createView$3(e),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const l=a/4,c=this.get();if(l>c.length)throw new Error("_sha2: outputLen bigger than state");for(let u=0;u<l;u++)s.setUint32(4*u,c[u],i)}digest(){const{buffer:e,outputLen:t}=this;this.digestInto(e);const r=e.slice(0,t);return this.destroy(),r}_cloneInto(e){e||(e=new this.constructor),e.set(...this.get());const{blockLen:t,buffer:r,length:n,finished:i,destroyed:o,pos:s}=this;return e.length=n,e.pos=s,e.finished=i,e.destroyed=o,n%t&&e.buffer.set(r),e}};const Chi$3=(e,t,r)=>e&t^~e&r,Maj$3=(e,t,r)=>e&t^e&r^t&r,SHA256_K$3=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),IV$1=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),SHA256_W$3=new Uint32Array(64);let SHA256$1=class extends SHA2$1{constructor(){super(64,32,8,!1),this.A=0|IV$1[0],this.B=0|IV$1[1],this.C=0|IV$1[2],this.D=0|IV$1[3],this.E=0|IV$1[4],this.F=0|IV$1[5],this.G=0|IV$1[6],this.H=0|IV$1[7]}get(){const{A:e,B:t,C:r,D:n,E:i,F:o,G:s,H:a}=this;return[e,t,r,n,i,o,s,a]}set(e,t,r,n,i,o,s,a){this.A=0|e,this.B=0|t,this.C=0|r,this.D=0|n,this.E=0|i,this.F=0|o,this.G=0|s,this.H=0|a}process(e,t){for(let u=0;u<16;u++,t+=4)SHA256_W$3[u]=e.getUint32(t,!1);for(let u=16;u<64;u++){const e=SHA256_W$3[u-15],t=SHA256_W$3[u-2],r=rotr$3(e,7)^rotr$3(e,18)^e>>>3,n=rotr$3(t,17)^rotr$3(t,19)^t>>>10;SHA256_W$3[u]=n+SHA256_W$3[u-7]+r+SHA256_W$3[u-16]|0}let{A:r,B:n,C:i,D:o,E:s,F:a,G:l,H:c}=this;for(let u=0;u<64;u++){const e=c+(rotr$3(s,6)^rotr$3(s,11)^rotr$3(s,25))+Chi$3(s,a,l)+SHA256_K$3[u]+SHA256_W$3[u]|0,t=(rotr$3(r,2)^rotr$3(r,13)^rotr$3(r,22))+Maj$3(r,n,i)|0;c=l,l=a,a=s,s=o+e|0,o=i,i=n,n=r,r=e+t|0}r=r+this.A|0,n=n+this.B|0,i=i+this.C|0,o=o+this.D|0,s=s+this.E|0,a=a+this.F|0,l=l+this.G|0,c=c+this.H|0,this.set(r,n,i,o,s,a,l,c)}roundClean(){SHA256_W$3.fill(0)}destroy(){this.set(0,0,0,0,0,0,0,0),this.buffer.fill(0)}};const sha256$3=wrapConstructor$1(()=>new SHA256$1),_0n$a=BigInt(0),_1n$9=BigInt(1),_2n$6=BigInt(2),u8a$1=e=>e instanceof Uint8Array,hexes$4=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function bytesToHex$4(e){if(!u8a$1(e))throw new Error("Uint8Array expected");let t="";for(let r=0;r<e.length;r++)t+=hexes$4[e[r]];return t}function numberToHexUnpadded$2(e){const t=e.toString(16);return 1&t.length?`0${t}`:t}function hexToNumber$2(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);return BigInt(""===e?"0":`0x${e}`)}function hexToBytes$4(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);const t=e.length;if(t%2)throw new Error("padded hex string expected, got unpadded hex of length "+t);const r=new Uint8Array(t/2);for(let n=0;n<r.length;n++){const t=2*n,i=e.slice(t,t+2),o=Number.parseInt(i,16);if(Number.isNaN(o)||o<0)throw new Error("Invalid byte sequence");r[n]=o}return r}function bytesToNumberBE$1(e){return hexToNumber$2(bytesToHex$4(e))}function bytesToNumberLE$1(e){if(!u8a$1(e))throw new Error("Uint8Array expected");return hexToNumber$2(bytesToHex$4(Uint8Array.from(e).reverse()))}function numberToBytesBE$1(e,t){return hexToBytes$4(e.toString(16).padStart(2*t,"0"))}function numberToBytesLE$1(e,t){return numberToBytesBE$1(e,t).reverse()}function numberToVarBytesBE(e){return hexToBytes$4(numberToHexUnpadded$2(e))}function ensureBytes$1(e,t,r){let n;if("string"==typeof t)try{n=hexToBytes$4(t)}catch(o){throw new Error(`${e} must be valid hex string, got "${t}". Cause: ${o}`)}else{if(!u8a$1(t))throw new Error(`${e} must be hex string or Uint8Array`);n=Uint8Array.from(t)}const i=n.length;if("number"==typeof r&&i!==r)throw new Error(`${e} expected ${r} bytes, got ${i}`);return n}function concatBytes$2(...e){const t=new Uint8Array(e.reduce((e,t)=>e+t.length,0));let r=0;return e.forEach(e=>{if(!u8a$1(e))throw new Error("Uint8Array expected");t.set(e,r),r+=e.length}),t}function equalBytes$2(e,t){if(e.length!==t.length)return!1;for(let r=0;r<e.length;r++)if(e[r]!==t[r])return!1;return!0}function utf8ToBytes$1(e){if("string"!=typeof e)throw new Error("utf8ToBytes expected string, got "+typeof e);return new Uint8Array((new TextEncoder).encode(e))}function bitLen$1(e){let t;for(t=0;e>_0n$a;e>>=_1n$9,t+=1);return t}function bitGet(e,t){return e>>BigInt(t)&_1n$9}const bitSet=(e,t,r)=>e|(r?_1n$9:_0n$a)<<BigInt(t),bitMask$1=e=>(_2n$6<<BigInt(e-1))-_1n$9,u8n=e=>new Uint8Array(e),u8fr=e=>Uint8Array.from(e);function createHmacDrbg$1(e,t,r){if("number"!=typeof e||e<2)throw new Error("hashLen must be a number");if("number"!=typeof t||t<2)throw new Error("qByteLen must be a number");if("function"!=typeof r)throw new Error("hmacFn must be a function");let n=u8n(e),i=u8n(e),o=0;const s=()=>{n.fill(1),i.fill(0),o=0},a=(...e)=>r(i,n,...e),l=(e=u8n())=>{i=a(u8fr([0]),e),n=a(),0!==e.length&&(i=a(u8fr([1]),e),n=a())},c=()=>{if(o++>=1e3)throw new Error("drbg: tried 1000 values");let e=0;const r=[];for(;e<t;){n=a();const t=n.slice();r.push(t),e+=n.length}return concatBytes$2(...r)};return(e,t)=>{let r;for(s(),l(e);!(r=t(c()));)l();return s(),r}}const validatorFns={bigint:e=>"bigint"==typeof e,function:e=>"function"==typeof e,boolean:e=>"boolean"==typeof e,string:e=>"string"==typeof e,stringOrUint8Array:e=>"string"==typeof e||e instanceof Uint8Array,isSafeInteger:e=>Number.isSafeInteger(e),array:e=>Array.isArray(e),field:(e,t)=>t.Fp.isValid(e),hash:e=>"function"==typeof e&&Number.isSafeInteger(e.outputLen)};function validateObject$1(e,t,r={}){const n=(t,r,n)=>{const i=validatorFns[r];if("function"!=typeof i)throw new Error(`Invalid validator "${r}", expected function`);const o=e[t];if(!(n&&void 0===o||i(o,e)))throw new Error(`Invalid param ${String(t)}=${o} (${typeof o}), expected ${r}`)};for(const[i,o]of Object.entries(t))n(i,o,!1);for(const[i,o]of Object.entries(r))n(i,o,!0);return e}const ut=Object.freeze(Object.defineProperty({__proto__:null,bitGet:bitGet,bitLen:bitLen$1,bitMask:bitMask$1,bitSet:bitSet,bytesToHex:bytesToHex$4,bytesToNumberBE:bytesToNumberBE$1,bytesToNumberLE:bytesToNumberLE$1,concatBytes:concatBytes$2,createHmacDrbg:createHmacDrbg$1,ensureBytes:ensureBytes$1,equalBytes:equalBytes$2,hexToBytes:hexToBytes$4,hexToNumber:hexToNumber$2,numberToBytesBE:numberToBytesBE$1,numberToBytesLE:numberToBytesLE$1,numberToHexUnpadded:numberToHexUnpadded$2,numberToVarBytesBE:numberToVarBytesBE,utf8ToBytes:utf8ToBytes$1,validateObject:validateObject$1},Symbol.toStringTag,{value:"Module"})),_0n$9=BigInt(0),_1n$8=BigInt(1),_2n$5=BigInt(2),_3n$4=BigInt(3),_4n$2=BigInt(4),_5n$1=BigInt(5),_8n$2=BigInt(8);
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function mod$2(e,t){const r=e%t;return r>=_0n$9?r:t+r}function pow(e,t,r){if(r<=_0n$9||t<_0n$9)throw new Error("Expected power/modulo > 0");if(r===_1n$8)return _0n$9;let n=_1n$8;for(;t>_0n$9;)t&_1n$8&&(n=n*e%r),e=e*e%r,t>>=_1n$8;return n}function pow2$2(e,t,r){let n=e;for(;t-- >_0n$9;)n*=n,n%=r;return n}function invert$2(e,t){if(e===_0n$9||t<=_0n$9)throw new Error(`invert: expected positive integers, got n=${e} mod=${t}`);let r=mod$2(e,t),n=t,i=_0n$9,o=_1n$8;for(;r!==_0n$9;){const e=n%r,t=i-o*(n/r);n=r,r=e,i=o,o=t}if(n!==_1n$8)throw new Error("invert: does not exist");return mod$2(i,t)}function tonelliShanks$1(e){const t=(e-_1n$8)/_2n$5;let r,n,i;for(r=e-_1n$8,n=0;r%_2n$5===_0n$9;r/=_2n$5,n++);for(i=_2n$5;i<e&&pow(i,t,e)!==e-_1n$8;i++);if(1===n){const t=(e+_1n$8)/_4n$2;return function(e,r){const n=e.pow(r,t);if(!e.eql(e.sqr(n),r))throw new Error("Cannot find square root");return n}}const o=(r+_1n$8)/_2n$5;return function(e,s){if(e.pow(s,t)===e.neg(e.ONE))throw new Error("Cannot find square root");let a=n,l=e.pow(e.mul(e.ONE,i),r),c=e.pow(s,o),u=e.pow(s,r);for(;!e.eql(u,e.ONE);){if(e.eql(u,e.ZERO))return e.ZERO;let t=1;for(let n=e.sqr(u);t<a&&!e.eql(n,e.ONE);t++)n=e.sqr(n);const r=e.pow(l,_1n$8<<BigInt(a-t-1));l=e.sqr(r),c=e.mul(c,r),u=e.mul(u,l),a=t}return c}}function FpSqrt$1(e){if(e%_4n$2===_3n$4){const t=(e+_1n$8)/_4n$2;return function(e,r){const n=e.pow(r,t);if(!e.eql(e.sqr(n),r))throw new Error("Cannot find square root");return n}}if(e%_8n$2===_5n$1){const t=(e-_5n$1)/_8n$2;return function(e,r){const n=e.mul(r,_2n$5),i=e.pow(n,t),o=e.mul(r,i),s=e.mul(e.mul(o,_2n$5),i),a=e.mul(o,e.sub(s,e.ONE));if(!e.eql(e.sqr(a),r))throw new Error("Cannot find square root");return a}}return tonelliShanks$1(e)}BigInt(9),BigInt(16);const FIELD_FIELDS$1=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"];function validateField$1(e){const t=FIELD_FIELDS$1.reduce((e,t)=>(e[t]="function",e),{ORDER:"bigint",MASK:"bigint",BYTES:"isSafeInteger",BITS:"isSafeInteger"});return validateObject$1(e,t)}function FpPow$1(e,t,r){if(r<_0n$9)throw new Error("Expected power > 0");if(r===_0n$9)return e.ONE;if(r===_1n$8)return t;let n=e.ONE,i=t;for(;r>_0n$9;)r&_1n$8&&(n=e.mul(n,i)),i=e.sqr(i),r>>=_1n$8;return n}function FpInvertBatch$1(e,t){const r=new Array(t.length),n=t.reduce((t,n,i)=>e.is0(n)?t:(r[i]=t,e.mul(t,n)),e.ONE),i=e.inv(n);return t.reduceRight((t,n,i)=>e.is0(n)?t:(r[i]=e.mul(t,r[i]),e.mul(t,n)),i),r}function nLength$1(e,t){const r=void 0!==t?t:e.toString(2).length;return{nBitLength:r,nByteLength:Math.ceil(r/8)}}function Field$1(e,t,r=!1,n={}){if(e<=_0n$9)throw new Error(`Expected Field ORDER > 0, got ${e}`);const{nBitLength:i,nByteLength:o}=nLength$1(e,t);if(o>2048)throw new Error("Field lengths over 2048 bytes are not supported");const s=FpSqrt$1(e),a=Object.freeze({ORDER:e,BITS:i,BYTES:o,MASK:bitMask$1(i),ZERO:_0n$9,ONE:_1n$8,create:t=>mod$2(t,e),isValid:t=>{if("bigint"!=typeof t)throw new Error("Invalid field element: expected bigint, got "+typeof t);return _0n$9<=t&&t<e},is0:e=>e===_0n$9,isOdd:e=>(e&_1n$8)===_1n$8,neg:t=>mod$2(-t,e),eql:(e,t)=>e===t,sqr:t=>mod$2(t*t,e),add:(t,r)=>mod$2(t+r,e),sub:(t,r)=>mod$2(t-r,e),mul:(t,r)=>mod$2(t*r,e),pow:(e,t)=>FpPow$1(a,e,t),div:(t,r)=>mod$2(t*invert$2(r,e),e),sqrN:e=>e*e,addN:(e,t)=>e+t,subN:(e,t)=>e-t,mulN:(e,t)=>e*t,inv:t=>invert$2(t,e),sqrt:n.sqrt||(e=>s(a,e)),invertBatch:e=>FpInvertBatch$1(a,e),cmov:(e,t,r)=>r?t:e,toBytes:e=>r?numberToBytesLE$1(e,o):numberToBytesBE$1(e,o),fromBytes:e=>{if(e.length!==o)throw new Error(`Fp.fromBytes: expected ${o}, got ${e.length}`);return r?bytesToNumberLE$1(e):bytesToNumberBE$1(e)}});return Object.freeze(a)}function getFieldBytesLength$1(e){if("bigint"!=typeof e)throw new Error("field order must be bigint");const t=e.toString(2).length;return Math.ceil(t/8)}function getMinHashLength$1(e){const t=getFieldBytesLength$1(e);return t+Math.ceil(t/2)}function mapHashToField$1(e,t,r=!1){const n=e.length,i=getFieldBytesLength$1(t),o=getMinHashLength$1(t);if(n<16||n<o||n>1024)throw new Error(`expected ${o}-1024 bytes of input, got ${n}`);const s=mod$2(r?bytesToNumberBE$1(e):bytesToNumberLE$1(e),t-_1n$8)+_1n$8;return r?numberToBytesLE$1(s,i):numberToBytesBE$1(s,i)}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const _0n$8=BigInt(0),_1n$7=BigInt(1);function wNAF$1(e,t){const r=(e,t)=>{const r=t.negate();return e?r:t},n=e=>({windows:Math.ceil(t/e)+1,windowSize:2**(e-1)});return{constTimeNegate:r,unsafeLadder(t,r){let n=e.ZERO,i=t;for(;r>_0n$8;)r&_1n$7&&(n=n.add(i)),i=i.double(),r>>=_1n$7;return n},precomputeWindow(e,t){const{windows:r,windowSize:i}=n(t),o=[];let s=e,a=s;for(let n=0;n<r;n++){a=s,o.push(a);for(let e=1;e<i;e++)a=a.add(s),o.push(a);s=a.double()}return o},wNAF(t,i,o){const{windows:s,windowSize:a}=n(t);let l=e.ZERO,c=e.BASE;const u=BigInt(2**t-1),h=2**t,d=BigInt(t);for(let e=0;e<s;e++){const t=e*a;let n=Number(o&u);o>>=d,n>a&&(n-=h,o+=_1n$7);const s=t,p=t+Math.abs(n)-1,f=e%2!=0,g=n<0;0===n?c=c.add(r(f,i[s])):l=l.add(r(g,i[p]))}return{p:l,f:c}},wNAFCached(e,t,r,n){const i=e._WINDOW_SIZE||1;let o=t.get(e);return o||(o=this.precomputeWindow(e,i),1!==i&&t.set(e,n(o))),this.wNAF(i,o,r)}}}function validateBasic(e){return validateField$1(e.Fp),validateObject$1(e,{n:"bigint",h:"bigint",Gx:"field",Gy:"field"},{nBitLength:"isSafeInteger",nByteLength:"isSafeInteger"}),Object.freeze({...nLength$1(e.n,e.nBitLength),...e,p:e.Fp.ORDER})}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function validatePointOpts(e){const t=validateBasic(e);validateObject$1(t,{a:"field",b:"field"},{allowedPrivateKeyLengths:"array",wrapPrivateKey:"boolean",isTorsionFree:"function",clearCofactor:"function",allowInfinityPoint:"boolean",fromBytes:"function",toBytes:"function"});const{endo:r,Fp:n,a:i}=t;if(r){if(!n.eql(i,n.ZERO))throw new Error("Endomorphism can only be defined for Koblitz curves that have a=0");if("object"!=typeof r||"bigint"!=typeof r.beta||"function"!=typeof r.splitScalar)throw new Error("Expected endomorphism with beta: bigint and splitScalar: function")}return Object.freeze({...t})}const{bytesToNumberBE:b2n,hexToBytes:h2b}=ut,DER$1={Err:class extends Error{constructor(e=""){super(e)}},_parseInt(e){const{Err:t}=DER$1;if(e.length<2||2!==e[0])throw new t("Invalid signature integer tag");const r=e[1],n=e.subarray(2,r+2);if(!r||n.length!==r)throw new t("Invalid signature integer: wrong length");if(128&n[0])throw new t("Invalid signature integer: negative");if(0===n[0]&&!(128&n[1]))throw new t("Invalid signature integer: unnecessary leading zero");return{d:b2n(n),l:e.subarray(r+2)}},toSig(e){const{Err:t}=DER$1,r="string"==typeof e?h2b(e):e;if(!(r instanceof Uint8Array))throw new Error("ui8a expected");let n=r.length;if(n<2||48!=r[0])throw new t("Invalid signature tag");if(r[1]!==n-2)throw new t("Invalid signature: incorrect length");const{d:i,l:o}=DER$1._parseInt(r.subarray(2)),{d:s,l:a}=DER$1._parseInt(o);if(a.length)throw new t("Invalid signature: left bytes after parsing");return{r:i,s:s}},hexFromSig(e){const t=e=>8&Number.parseInt(e[0],16)?"00"+e:e,r=e=>{const t=e.toString(16);return 1&t.length?`0${t}`:t},n=t(r(e.s)),i=t(r(e.r)),o=n.length/2,s=i.length/2,a=r(o),l=r(s);return`30${r(s+o+4)}02${l}${i}02${a}${n}`}},_0n$7=BigInt(0),_1n$6=BigInt(1);BigInt(2);const _3n$3=BigInt(3);function weierstrassPoints(e){const t=validatePointOpts(e),{Fp:r}=t,n=t.toBytes||((e,t)=>{const n=t.toAffine();return concatBytes$2(Uint8Array.from([4]),r.toBytes(n.x),r.toBytes(n.y))}),i=t.fromBytes||(e=>{const t=e.subarray(1);return{x:r.fromBytes(t.subarray(0,r.BYTES)),y:r.fromBytes(t.subarray(r.BYTES,2*r.BYTES))}});function o(e){const{a:n,b:i}=t,o=r.sqr(e),s=r.mul(o,e);return r.add(r.add(s,r.mul(e,n)),i)}if(!r.eql(r.sqr(t.Gy),o(t.Gx)))throw new Error("bad generator point: equation left != right");function s(e){return"bigint"==typeof e&&_0n$7<e&&e<t.n}function a(e){if(!s(e))throw new Error("Expected valid bigint: 0 < bigint < curve.n")}function l(e){const{allowedPrivateKeyLengths:r,nByteLength:n,wrapPrivateKey:i,n:o}=t;if(r&&"bigint"!=typeof e){if(e instanceof Uint8Array&&(e=bytesToHex$4(e)),"string"!=typeof e||!r.includes(e.length))throw new Error("Invalid key");e=e.padStart(2*n,"0")}let s;try{s="bigint"==typeof e?e:bytesToNumberBE$1(ensureBytes$1("private key",e,n))}catch(error){throw new Error(`private key must be ${n} bytes, hex or bigint, not ${typeof e}`)}return i&&(s=mod$2(s,o)),a(s),s}const c=new Map;function u(e){if(!(e instanceof h))throw new Error("ProjectivePoint expected")}class h{constructor(e,t,n){if(this.px=e,this.py=t,this.pz=n,null==e||!r.isValid(e))throw new Error("x required");if(null==t||!r.isValid(t))throw new Error("y required");if(null==n||!r.isValid(n))throw new Error("z required")}static fromAffine(e){const{x:t,y:n}=e||{};if(!e||!r.isValid(t)||!r.isValid(n))throw new Error("invalid affine point");if(e instanceof h)throw new Error("projective point not allowed");const i=e=>r.eql(e,r.ZERO);return i(t)&&i(n)?h.ZERO:new h(t,n,r.ONE)}get x(){return this.toAffine().x}get y(){return this.toAffine().y}static normalizeZ(e){const t=r.invertBatch(e.map(e=>e.pz));return e.map((e,r)=>e.toAffine(t[r])).map(h.fromAffine)}static fromHex(e){const t=h.fromAffine(i(ensureBytes$1("pointHex",e)));return t.assertValidity(),t}static fromPrivateKey(e){return h.BASE.multiply(l(e))}_setWindowSize(e){this._WINDOW_SIZE=e,c.delete(this)}assertValidity(){if(this.is0()){if(t.allowInfinityPoint&&!r.is0(this.py))return;throw new Error("bad point: ZERO")}const{x:e,y:n}=this.toAffine();if(!r.isValid(e)||!r.isValid(n))throw new Error("bad point: x or y not FE");const i=r.sqr(n),s=o(e);if(!r.eql(i,s))throw new Error("bad point: equation left != right");if(!this.isTorsionFree())throw new Error("bad point: not in prime-order subgroup")}hasEvenY(){const{y:e}=this.toAffine();if(r.isOdd)return!r.isOdd(e);throw new Error("Field doesn't support isOdd")}equals(e){u(e);const{px:t,py:n,pz:i}=this,{px:o,py:s,pz:a}=e,l=r.eql(r.mul(t,a),r.mul(o,i)),c=r.eql(r.mul(n,a),r.mul(s,i));return l&&c}negate(){return new h(this.px,r.neg(this.py),this.pz)}double(){const{a:e,b:n}=t,i=r.mul(n,_3n$3),{px:o,py:s,pz:a}=this;let l=r.ZERO,c=r.ZERO,u=r.ZERO,d=r.mul(o,o),p=r.mul(s,s),f=r.mul(a,a),g=r.mul(o,s);return g=r.add(g,g),u=r.mul(o,a),u=r.add(u,u),l=r.mul(e,u),c=r.mul(i,f),c=r.add(l,c),l=r.sub(p,c),c=r.add(p,c),c=r.mul(l,c),l=r.mul(g,l),u=r.mul(i,u),f=r.mul(e,f),g=r.sub(d,f),g=r.mul(e,g)Line truncated
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
function getHash(e){return{hash:e,hmac:(t,...r)=>hmac$2(e,t,concatBytes$3(...r)),randomBytes:randomBytes$2}}function createCurve(e,t){const r=t=>weierstrass$2({...e,...getHash(t)});return Object.freeze({...r(t),create:r})}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */hmac$2.create=(e,t)=>new HMAC$1(e,t);const secp256k1P=BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),secp256k1N=BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),_1n$5=BigInt(1),_2n$4=BigInt(2),divNearest$2=(e,t)=>(e+t/_2n$4)/t;function sqrtMod$2(e){const t=secp256k1P,r=BigInt(3),n=BigInt(6),i=BigInt(11),o=BigInt(22),s=BigInt(23),a=BigInt(44),l=BigInt(88),c=e*e*e%t,u=c*c*e%t,h=pow2$2(u,r,t)*u%t,d=pow2$2(h,r,t)*u%t,p=pow2$2(d,_2n$4,t)*c%t,f=pow2$2(p,i,t)*p%t,g=pow2$2(f,o,t)*f%t,m=pow2$2(g,a,t)*g%t,y=pow2$2(m,l,t)*m%t,b=pow2$2(y,a,t)*g%t,v=pow2$2(b,r,t)*u%t,_=pow2$2(v,s,t)*f%t,E=pow2$2(_,n,t)*c%t,w=pow2$2(E,_2n$4,t);if(!Fp.eql(Fp.sqr(w),e))throw new Error("Cannot find square root");return w}const Fp=Field$1(secp256k1P,void 0,void 0,{sqrt:sqrtMod$2}),secp256k1$1=createCurve({a:BigInt(0),b:BigInt(7),Fp:Fp,n:secp256k1N,Gx:BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"),Gy:BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"),h:BigInt(1),lowS:!0,endo:{beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),splitScalar:e=>{const t=secp256k1N,r=BigInt("0x3086d221a7d46bcde86c90e49284eb15"),n=-_1n$5*BigInt("0xe4437ed6010e88286f547fa90abfe4c3"),i=BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),o=r,s=BigInt("0x100000000000000000000000000000000"),a=divNearest$2(o*e,t),l=divNearest$2(-n*e,t);let c=mod$2(e-a*r-l*i,t),u=mod$2(-a*n-l*o,t);const h=c>s,d=u>s;if(h&&(c=t-c),d&&(u=t-u),c>s||u>s)throw new Error("splitScalar: Endomorphism failed, k="+e);return{k1neg:h,k1:c,k2neg:d,k2:u}}}},sha256$3),_0n$6=BigInt(0),fe=e=>"bigint"==typeof e&&_0n$6<e&&e<secp256k1P,ge=e=>"bigint"==typeof e&&_0n$6<e&&e<secp256k1N,TAGGED_HASH_PREFIXES$1={};function taggedHash$1(e,...t){let r=TAGGED_HASH_PREFIXES$1[e];if(void 0===r){const t=sha256$3(Uint8Array.from(e,e=>e.charCodeAt(0)));r=concatBytes$2(t,t),TAGGED_HASH_PREFIXES$1[e]=r}return sha256$3(concatBytes$2(r,...t))}const pointToBytes$1=e=>e.toRawBytes(!0).slice(1),numTo32b=e=>numberToBytesBE$1(e,32),modP=e=>mod$2(e,secp256k1P),modN=e=>mod$2(e,secp256k1N),Point$2=secp256k1$1.ProjectivePoint,GmulAdd=(e,t,r)=>Point$2.BASE.multiplyAndAddUnsafe(e,t,r);function schnorrGetExtPubKey$1(e){let t=secp256k1$1.utils.normPrivateKeyToScalar(e),r=Point$2.fromPrivateKey(t);return{scalar:r.hasEvenY()?t:modN(-t),bytes:pointToBytes$1(r)}}function lift_x$1(e){if(!fe(e))throw new Error("bad x: need 0 < x < p");const t=modP(e*e);let r=sqrtMod$2(modP(t*e+BigInt(7)));r%_2n$4!==_0n$6&&(r=modP(-r));const n=new Point$2(e,r,_1n$5);return n.assertValidity(),n}function challenge$1(...e){return modN(bytesToNumberBE$1(taggedHash$1("BIP0340/challenge",...e)))}function schnorrGetPublicKey$1(e){return schnorrGetExtPubKey$1(e).bytes}function schnorrSign$1(e,t,r=randomBytes$2(32)){const n=ensureBytes$1("message",e),{bytes:i,scalar:o}=schnorrGetExtPubKey$1(t),s=ensureBytes$1("auxRand",r,32),a=numTo32b(o^bytesToNumberBE$1(taggedHash$1("BIP0340/aux",s))),l=taggedHash$1("BIP0340/nonce",a,i,n),c=modN(bytesToNumberBE$1(l));if(c===_0n$6)throw new Error("sign failed: k is zero");const{bytes:u,scalar:h}=schnorrGetExtPubKey$1(c),d=challenge$1(u,i,n),p=new Uint8Array(64);if(p.set(u,0),p.set(numTo32b(modN(h+d*o)),32),!schnorrVerify$1(p,n,i))throw new Error("sign: Invalid signature produced");return p}function schnorrVerify$1(e,t,r){const n=ensureBytes$1("signature",e,64),i=ensureBytes$1("message",t),o=ensureBytes$1("publicKey",r,32);try{const e=lift_x$1(bytesToNumberBE$1(o)),t=bytesToNumberBE$1(n.subarray(0,32));if(!fe(t))return!1;const r=bytesToNumberBE$1(n.subarray(32,64));if(!ge(r))return!1;const s=challenge$1(numTo32b(t),pointToBytes$1(e),i),a=GmulAdd(e,r,modN(-s));return!(!a||!a.hasEvenY()||a.toAffine().x!==t)}catch(error){return!1}}const schnorr$1=(()=>({getPublicKey:schnorrGetPublicKey$1,sign:schnorrSign$1,verify:schnorrVerify$1,utils:{randomPrivateKey:secp256k1$1.utils.randomPrivateKey,lift_x:lift_x$1,pointToBytes:pointToBytes$1,numberToBytesBE:numberToBytesBE$1,bytesToNumberBE:bytesToNumberBE$1,taggedHash:taggedHash$1,mod:mod$2}}))(),crypto$1="object"==typeof globalThis&&"crypto"in globalThis?globalThis.crypto:void 0,u8a=e=>e instanceof Uint8Array,createView$2=e=>new DataView(e.buffer,e.byteOffset,e.byteLength),rotr$2=(e,t)=>e<<32-t|e>>>t,isLE$2=68===new Uint8Array(new Uint32Array([287454020]).buffer)[0];if(!isLE$2)throw new Error("Non little-endian hardware is not supported");const hexes$3=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function bytesToHex$3(e){if(!u8a(e))throw new Error("Uint8Array expected");let t="";for(let r=0;r<e.length;r++)t+=hexes$3[e[r]];return t}function hexToBytes$3(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);const t=e.length;if(t%2)throw new Error("padded hex string expected, got unpadded hex of length "+t);const r=new Uint8Array(t/2);Line truncated
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
function assertNumber(e){if(!Number.isSafeInteger(e))throw new Error(`Wrong integer: ${e}`)}function chain$1(...e){const t=(e,t)=>r=>e(t(r)),r=Array.from(e).reverse().reduce((e,r)=>e?t(e,r.encode):r.encode,void 0),n=e.reduce((e,r)=>e?t(e,r.decode):r.decode,void 0);return{encode:r,decode:n}}function alphabet$1(e){return{encode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("alphabet.encode input should be an array of numbers");return t.map(t=>{if(assertNumber(t),t<0||t>=e.length)throw new Error(`Digit index outside alphabet: ${t} (alphabet: ${e.length})`);return e[t]})},decode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("alphabet.decode input should be array of strings");return t.map(t=>{if("string"!=typeof t)throw new Error(`alphabet.decode: not string element=${t}`);const r=e.indexOf(t);if(-1===r)throw new Error(`Unknown letter: "${t}". Allowed: ${e}`);return r})}}}function join$1(e=""){if("string"!=typeof e)throw new Error("join separator should be string");return{encode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("join.encode input should be array of strings");for(let e of t)if("string"!=typeof e)throw new Error(`join.encode: non-string input=${e}`);return t.join(e)},decode:t=>{if("string"!=typeof t)throw new Error("join.decode input should be string");return t.split(e)}}}function padding$1(e,t="="){if(assertNumber(e),"string"!=typeof t)throw new Error("padding chr should be string");return{encode(r){if(!Array.isArray(r)||r.length&&"string"!=typeof r[0])throw new Error("padding.encode input should be array of strings");for(let e of r)if("string"!=typeof e)throw new Error(`padding.encode: non-string input=${e}`);for(;r.length*e%8;)r.push(t);return r},decode(r){if(!Array.isArray(r)||r.length&&"string"!=typeof r[0])throw new Error("padding.encode input should be array of strings");for(let e of r)if("string"!=typeof e)throw new Error(`padding.decode: non-string input=${e}`);let n=r.length;if(n*e%8)throw new Error("Invalid padding: string should have whole number of bytes");for(;n>0&&r[n-1]===t;n--)if(!((n-1)*e%8))throw new Error("Invalid padding: string has too much padding");return r.slice(0,n)}}}function normalize(e){if("function"!=typeof e)throw new Error("normalize fn should be function");return{encode:e=>e,decode:t=>e(t)}}function convertRadix(e,t,r){if(t<2)throw new Error(`convertRadix: wrong from=${t}, base cannot be less than 2`);if(r<2)throw new Error(`convertRadix: wrong to=${r}, base cannot be less than 2`);if(!Array.isArray(e))throw new Error("convertRadix: data should be array");if(!e.length)return[];let n=0;const i=[],o=Array.from(e);for(o.forEach(e=>{if(assertNumber(e),e<0||e>=t)throw new Error(`Wrong integer: ${e}`)});;){let e=0,s=!0;for(let i=n;i<o.length;i++){const a=o[i],l=t*e+a;if(!Number.isSafeInteger(l)||t*e/t!==e||l-a!==t*e)throw new Error("convertRadix: carry overflow");if(e=l%r,o[i]=Math.floor(l/r),!Number.isSafeInteger(o[i])||o[i]*r+e!==l)throw new Error("convertRadix: carry overflow");s&&(o[i]?s=!1:n=i)}if(i.push(e),s)break}for(let s=0;s<e.length-1&&0===e[s];s++)i.push(0);return i.reverse()}wrapConstructor(()=>new SHA224);const gcd$1=(e,t)=>t?gcd$1(t,e%t):e,radix2carry$1=(e,t)=>e+(t-gcd$1(e,t));function convertRadix2$1(e,t,r,n){if(!Array.isArray(e))throw new Error("convertRadix2: data should be array");if(t<=0||t>32)throw new Error(`convertRadix2: wrong from=${t}`);if(r<=0||r>32)throw new Error(`convertRadix2: wrong to=${r}`);if(radix2carry$1(t,r)>32)throw new Error(`convertRadix2: carry overflow from=${t} to=${r} carryBits=${radix2carry$1(t,r)}`);let i=0,o=0;const s=2**r-1,a=[];for(const l of e){if(assertNumber(l),l>=2**t)throw new Error(`convertRadix2: invalid data word=${l} from=${t}`);if(i=i<<t|l,o+t>32)throw new Error(`convertRadix2: carry overflow pos=${o} from=${t}`);for(o+=t;o>=r;o-=r)a.push((i>>o-r&s)>>>0);i&=2**o-1}if(i=i<<r-o&s,!n&&o>=t)throw new Error("Excess padding");if(!n&&i)throw new Error(`Non-zero padding: ${i}`);return n&&o>0&&a.push(i>>>0),a}function radix(e){return assertNumber(e),{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix.encode input should be Uint8Array");return convertRadix(Array.from(t),256,e)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix.decode input should be array of strings");return Uint8Array.from(convertRadix(t,e,256))}}}function radix2$1(e,t=!1){if(assertNumber(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(radix2carry$1(8,e)>32||radix2carry$1(e,8)>32)throw new Error("radix2: carry overflow");return{encode:r=>{if(!(r instanceof Uint8Array))throw new Error("radix2.encode input should be Uint8Array");return convertRadix2$1(Array.from(r),8,e,!t)},decode:r=>{if(!Array.isArray(r)||r.length&&"number"!=typeof r[0])throw new Error("radix2.decode input should be array of strings");return Uint8Array.from(convertRadix2$1(r,e,8,t))}}}function unsafeWrapper$1(e){if("function"!=typeof e)thrLine truncated
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */Object.keys(CODERS).join(", ");const u32$1=e=>new Uint32Array(e.buffer,e.byteOffset,Math.floor(e.byteLength/4)),isLE$1=68===new Uint8Array(new Uint32Array([287454020]).buffer)[0];if(!isLE$1)throw new Error("Non little-endian hardware is not supported");const hexes$2=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function bytesToHex$2(e){bytes(e);let t="";for(let r=0;r<e.length;r++)t+=hexes$2[e[r]];return t}const asciis$2={_0:48,_9:57,_A:65,_F:70,_a:97,_f:102};function asciiToBase16$2(e){return e>=asciis$2._0&&e<=asciis$2._9?e-asciis$2._0:e>=asciis$2._A&&e<=asciis$2._F?e-(asciis$2._A-10):e>=asciis$2._a&&e<=asciis$2._f?e-(asciis$2._a-10):void 0}function hexToBytes$2(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);const t=e.length,r=t/2;if(t%2)throw new Error("padded hex string expected, got unpadded hex of length "+t);const n=new Uint8Array(r);for(let i=0,o=0;i<r;i++,o+=2){const t=asciiToBase16$2(e.charCodeAt(o)),r=asciiToBase16$2(e.charCodeAt(o+1));if(void 0===t||void 0===r){const t=e[o]+e[o+1];throw new Error('hex string expected, got non-hex character "'+t+'" at index '+o)}n[i]=16*t+r}return n}function checkOpts$1(e,t){if(null==t||"object"!=typeof t)throw new Error("options must be defined");return Object.assign(e,t)}function equalBytes$1(e,t){if(e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e[n]^t[n];return 0===r}const wrapCipher$1=(e,t)=>(Object.assign(t,e),t),BLOCK_SIZE$1=16,POLY$1=283;function mul2$1(e){return e<<1^POLY$1&-(e>>7)}function mul$1(e,t){let r=0;for(;t>0;t>>=1)r^=e&-(1&t),e=mul2$1(e);return r}const sbox$1=(()=>{let e=new Uint8Array(256);for(let r=0,n=1;r<256;r++,n^=mul2$1(n))e[r]=n;const t=new Uint8Array(256);t[0]=99;for(let r=0;r<255;r++){let n=e[255-r];n|=n<<8,t[e[r]]=255&(n^n>>4^n>>5^n>>6^n>>7^99)}return t})(),invSbox$1=sbox$1.map((e,t)=>sbox$1.indexOf(t)),rotr32_8$1=e=>e<<24|e>>>8,rotl32_8$1=e=>e<<8|e>>>24;function genTtable$1(e,t){if(256!==e.length)throw new Error("Wrong sbox length");const r=new Uint32Array(256).map((r,n)=>t(e[n])),n=r.map(rotl32_8$1),i=n.map(rotl32_8$1),o=i.map(rotl32_8$1),s=new Uint32Array(65536),a=new Uint32Array(65536),l=new Uint16Array(65536);for(let c=0;c<256;c++)for(let t=0;t<256;t++){const u=256*c+t;s[u]=r[c]^n[t],a[u]=i[c]^o[t],l[u]=e[c]<<8|e[t]}return{sbox:e,sbox2:l,T0:r,T1:n,T2:i,T3:o,T01:s,T23:a}}const tableEncoding$1=genTtable$1(sbox$1,e=>mul$1(e,3)<<24|e<<16|e<<8|mul$1(e,2)),tableDecoding$1=genTtable$1(invSbox$1,e=>mul$1(e,11)<<24|mul$1(e,13)<<16|mul$1(e,9)<<8|mul$1(e,14)),xPowers$1=(()=>{const e=new Uint8Array(16);for(let t=0,r=1;t<16;t++,r=mul2$1(r))e[t]=r;return e})();function expandKeyLE$1(e){bytes(e);const t=e.length;if(![16,24,32].includes(t))throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${t}`);const{sbox2:r}=tableEncoding$1,n=u32$1(e),i=n.length,o=e=>applySbox$1(r,e,e,e,e),s=new Uint32Array(t+28);s.set(n);for(let a=i;a<s.length;a++){let e=s[a-1];a%i===0?e=o(rotr32_8$1(e))^xPowers$1[a/i-1]:i>6&&a%i===4&&(e=o(e)),s[a]=s[a-i]^e}return s}function expandKeyDecLE$1(e){const t=expandKeyLE$1(e),r=t.slice(),n=t.length,{sbox2:i}=tableEncoding$1,{T0:o,T1:s,T2:a,T3:l}=tableDecoding$1;for(let c=0;c<n;c+=4)for(let e=0;e<4;e++)r[c+e]=t[n-c-4+e];t.fill(0);for(let c=4;c<n-4;c++){const e=r[c],t=applySbox$1(i,e,e,e,e);r[c]=o[255&t]^s[t>>>8&255]^a[t>>>16&255]^l[t>>>24]}return r}function apply0123$1(e,t,r,n,i,o){return e[r<<8&65280|n>>>8&255]^t[i>>>8&65280|o>>>24&255]}function applySbox$1(e,t,r,n,i){return e[255&t|65280&r]|e[n>>>16&255|i>>>16&65280]<<16}function encrypt$4(e,t,r,n,i){const{sbox2:o,T01:s,T23:a}=tableEncoding$1;let l=0;t^=e[l++],r^=e[l++],n^=e[l++],i^=e[l++];const c=e.length/4-2;for(let u=0;u<c;u++){const o=e[l++]^apply0123$1(s,a,t,r,n,i),c=e[l++]^apply0123$1(s,a,r,n,i,t),u=e[l++]^apply0123$1(s,a,n,i,t,r),h=e[l++]^apply0123$1(s,a,i,t,r,n);t=o,r=c,n=u,i=h}return{s0:e[l++]^applySbox$1(o,t,r,n,i),s1:e[l++]^applySbox$1(o,r,n,i,t),s2:e[l++]^applySbox$1(o,n,i,t,r),s3:e[l++]^applySbox$1(o,i,t,r,n)}}function decrypt$4(e,t,r,n,i){const{sbox2:o,T01:s,T23:a}=tableDecoding$1;let l=0;t^=e[l++],r^=e[l++],n^=e[l++],i^=e[l++];const c=e.length/4-2;for(let u=0;u<c;u++){const o=e[l++]^apply0123$1(s,a,t,i,n,r),c=e[l++]^apply0123$1(s,a,r,t,i,n),u=e[l++]^apply0123$1(s,a,n,r,t,i),h=e[l++]^apply0123$1(s,a,i,n,r,t);t=o,r=c,n=u,i=h}return{s0:e[l++]^applySbox$1(o,t,i,n,r),s1:e[l++]^applySbox$1(o,r,t,i,n),s2:e[l++]^applySbox$1(o,n,r,t,i),s3:e[l++]^applySbox$1(o,i,n,r,t)}}function getDst(e,t){if(!t)return new Uint8Array(e);if(bytes(t),t.length<e)throw new Error(`aes: wrong destination length, expected at least ${e}, got: ${t.length}`);return t}function validateBlockDecrypt$1(e){if(bytes(e),e.length%BLOCK_SIZE$1!==0)throw new Error(`aes/(cbc-ecb).decrypt ciphertext should consist of blocks with size ${BLOCK_SIZE$1}`)}function validateBlockEncrypt$1(e,t,r){let n=e.length;const i=n%BLOCK_SIZE$1;if(!t&&0!==i)throw new Error("aecLine truncated
/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */const _0n$5=BigInt(0),_1n$4=BigInt(1),_2n$3=BigInt(2),_3n$2=BigInt(3),_8n$1=BigInt(8),CURVE=Object.freeze({a:_0n$5,b:BigInt(7),P:BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),n:BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),h:_1n$4,Gx:BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"),Gy:BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"),beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee")}),divNearest$1=(e,t)=>(e+t/_2n$3)/t,endo={beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),splitScalar(e){const{n:t}=CURVE,r=BigInt("0x3086d221a7d46bcde86c90e49284eb15"),n=-_1n$4*BigInt("0xe4437ed6010e88286f547fa90abfe4c3"),i=BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),o=r,s=BigInt("0x100000000000000000000000000000000"),a=divNearest$1(o*e,t),l=divNearest$1(-n*e,t);let c=mod$1(e-a*r-l*i,t),u=mod$1(-a*n-l*o,t);const h=c>s,d=u>s;if(h&&(c=t-c),d&&(u=t-u),c>s||u>s)throw new Error("splitScalarEndo: Endomorphism failed, k="+e);return{k1neg:h,k1:c,k2neg:d,k2:u}}},fieldLen=32,groupLen=32,compressedLen=fieldLen+1,uncompressedLen=2*fieldLen+1;function weierstrass$1(e){const{a:t,b:r}=CURVE,n=mod$1(e*e),i=mod$1(n*e);return mod$1(i+t*e+r)}const USE_ENDOMORPHISM=CURVE.a===_0n$5;function assertJacPoint(e){if(!(e instanceof JacobianPoint))throw new TypeError("JacobianPoint expected")}class JacobianPoint{constructor(e,t,r){this.x=e,this.y=t,this.z=r}static fromAffine(e){if(!(e instanceof Point$1))throw new TypeError("JacobianPoint#fromAffine: expected Point");return e.equals(Point$1.ZERO)?JacobianPoint.ZERO:new JacobianPoint(e.x,e.y,_1n$4)}static toAffineBatch(e){const t=invertBatch(e.map(e=>e.z));return e.map((e,r)=>e.toAffine(t[r]))}static normalizeZ(e){return JacobianPoint.toAffineBatch(e).map(JacobianPoint.fromAffine)}equals(e){assertJacPoint(e);const{x:t,y:r,z:n}=this,{x:i,y:o,z:s}=e,a=mod$1(n*n),l=mod$1(s*s),c=mod$1(t*l),u=mod$1(i*a),h=mod$1(mod$1(r*s)*l),d=mod$1(mod$1(o*n)*a);return c===u&&h===d}negate(){return new JacobianPoint(this.x,mod$1(-this.y),this.z)}double(){const{x:e,y:t,z:r}=this,n=mod$1(e*e),i=mod$1(t*t),o=mod$1(i*i),s=e+i,a=mod$1(_2n$3*(mod$1(s*s)-n-o)),l=mod$1(_3n$2*n),c=mod$1(l*l),u=mod$1(c-_2n$3*a),h=mod$1(l*(a-u)-_8n$1*o),d=mod$1(_2n$3*t*r);return new JacobianPoint(u,h,d)}add(e){assertJacPoint(e);const{x:t,y:r,z:n}=this,{x:i,y:o,z:s}=e;if(i===_0n$5||o===_0n$5)return this;if(t===_0n$5||r===_0n$5)return e;const a=mod$1(n*n),l=mod$1(s*s),c=mod$1(t*l),u=mod$1(i*a),h=mod$1(mod$1(r*s)*l),d=mod$1(mod$1(o*n)*a),p=mod$1(u-c),f=mod$1(d-h);if(p===_0n$5)return f===_0n$5?this.double():JacobianPoint.ZERO;const g=mod$1(p*p),m=mod$1(p*g),y=mod$1(c*g),b=mod$1(f*f-m-_2n$3*y),v=mod$1(f*(y-b)-h*m),_=mod$1(n*s*p);return new JacobianPoint(b,v,_)}subtract(e){return this.add(e.negate())}multiplyUnsafe(e){const t=JacobianPoint.ZERO;if("bigint"==typeof e&&e===_0n$5)return t;let r=normalizeScalar(e);if(r===_1n$4)return this;if(!USE_ENDOMORPHISM){let e=t,n=this;for(;r>_0n$5;)r&_1n$4&&(e=e.add(n)),n=n.double(),r>>=_1n$4;return e}let{k1neg:n,k1:i,k2neg:o,k2:s}=endo.splitScalar(r),a=t,l=t,c=this;for(;i>_0n$5||s>_0n$5;)i&_1n$4&&(a=a.add(c)),s&_1n$4&&(l=l.add(c)),c=c.double(),i>>=_1n$4,s>>=_1n$4;return n&&(a=a.negate()),o&&(l=l.negate()),l=new JacobianPoint(mod$1(l.x*endo.beta),l.y,l.z),a.add(l)}precomputeWindow(e){const t=USE_ENDOMORPHISM?128/e+1:256/e+1,r=[];let n=this,i=n;for(let o=0;o<t;o++){i=n,r.push(i);for(let t=1;t<2**(e-1);t++)i=i.add(n),r.push(i);n=i.double()}return r}wNAF(e,t){!t&&this.equals(JacobianPoint.BASE)&&(t=Point$1.BASE);const r=t&&t._WINDOW_SIZE||1;if(256%r)throw new Error("Point#wNAF: Invalid precomputation window, must be power of 2");let n=t&&pointPrecomputes$1.get(t);n||(n=this.precomputeWindow(r),t&&1!==r&&(n=JacobianPoint.normalizeZ(n),pointPrecomputes$1.set(t,n)));let i=JacobianPoint.ZERO,o=JacobianPoint.BASE;const s=1+(USE_ENDOMORPHISM?128/r:256/r),a=2**(r-1),l=BigInt(2**r-1),c=2**r,u=BigInt(r);for(let h=0;h<s;h++){const t=h*a;let r=Number(e&l);e>>=u,r>a&&(r-=c,e+=_1n$4);const s=t,d=t+Math.abs(r)-1,p=h%2!=0,f=r<0;0===r?o=o.add(constTimeNegate(p,n[s])):i=i.add(constTimeNegate(f,n[d]))}return{p:i,f:o}}multiply(e,t){let r,n,i=normalizeScalar(e);if(USE_ENDOMORPHISM){const{k1neg:e,k1:o,k2neg:s,k2:a}=endo.splitScalar(i);let{p:l,f:c}=this.wNAF(o,t),{p:u,f:h}=this.wNAF(a,t);l=constTimeNegate(e,l),u=constTimeNegate(s,u),u=new JacobianPoint(mod$1(u.x*endo.beta),u.y,u.z),r=l.add(u),n=c.add(h)}else{const{p:e,f:o}=this.wNAF(i,t);r=e,n=o}return JacobianPoint.normalizeZ([r,n])[0]}toAffine(e){const{x:t,y:r,z:n}=this,i=this.equals(JacobianPoint.ZERO);null==e&&(e=i?_8n$1:invert$1(n));const o=e,s=mod$1(o*o),a=mod$1(s*o),l=mod$1(t*s),c=mod$1(r*a),u=mod$1(n*o);if(i)return Point$1.ZERO;if(u!==_1n$4)throw new Error("invZ was invalid");return new Point$1(l,c)}}function constTLine truncated
/*! For license information please see remotestorage.js.LICENSE.txt */function requireRemotestorage(){return hasRequiredRemotestorage||(hasRequiredRemotestorage=1,function(module,exports$1){var t;t=function(){return function(){var __webpack_modules__={548:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Access=void 0;class r{constructor(e){this.rs=e,this.reset(!1)}get scopes(){return Object.keys(this.scopeModeMap).map(e=>({name:e,mode:this.scopeModeMap[e]}))}get scopeParameter(){return this.scopes.map(e=>`${this._scopeNameForParameter(e)}:${e.mode}`).join(" ")}claim(e,t){if("string"!=typeof e||-1!==e.indexOf("/")||0===e.length)throw new Error("Scope should be a non-empty string without forward slashes");if("string"!=typeof t||!t.match(/^rw?$/))throw new Error("Mode should be either 'r' or 'rw'");this._adjustRootPaths(e),this.scopeModeMap[e]=t,this._notifyChange()}get(e){return this.scopeModeMap[e]}remove(e){const t={};for(const r in this.scopeModeMap)t[r]=this.scopeModeMap[r];this.reset(!1),delete t[e];for(const r in t)this._adjustRootPaths(r),this.scopeModeMap[r]=t[r];this._notifyChange()}checkPermission(e,t){const r=this.get(e);return r&&("r"===t||"rw"===r)}checkPathPermission(e,t){if(this.checkPermission("*",t))return!0;const r=this._getModuleName(e);return!!this.checkPermission(r,t)}reset(e=!0){this.rootPaths=[],this.scopeModeMap={},e&&this._notifyChange()}_getModuleName(e){if("/"!==e[0])throw new Error("Path should start with a slash");const t=e.replace(/^\/public/,"").match(/^\/([^/]*)\//);return t?t[1]:"*"}_adjustRootPaths(e){"*"in this.scopeModeMap||"*"===e?this.rootPaths=["/"]:e in this.scopeModeMap||(this.rootPaths.push("/"+e+"/"),this.rootPaths.push("/public/"+e+"/"))}_scopeNameForParameter(e){if("*"===e.name&&this.storageType){if("2012.04"===this.storageType)return"";if(this.storageType.match(/remotestorage-0[01]/))return"root"}return e.name}setStorageType(e){this.storageType=e}_notifyChange(){this.rs&&"function"==typeof this.rs._checkScopeChange&&this.rs._checkScopeChange()}static _rs_init(){}}t.Access=r,t.default=r},275:function(e,t,r){var n=this&&this.__awaiter||function(e,t,r,n){return new(r||(r=Promise))(function(i,o){function s(e){try{l(n.next(e))}catch(e){o(e)}}function a(e){try{l(n.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof r?t:new r(function(e){e(t)})).then(s,a)}l((n=n.apply(e,t||[])).next())})},i=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Authorize=void 0;const o=i(r(28)),s=i(r(334)),a=r(746),l=i(r(503)),c=r(368);let u;function h(e){const t=e||d.getLocation().href,r={};for(const[o,s]of new URL(t).searchParams)r[o]=s;const n=t.indexOf("#");if(-1===n)return r;const i=t.substring(n+1);return i.includes("=")?i.split("&").reduce(function(e,t){const r=t.split("=");if("state"===r[0]&&r[1].match(/rsDiscovery/)){let t=decodeURIComponent(r[1]);const n=t.substr(t.indexOf("rsDiscovery=")).split("&")[0].split("=")[1];e.rsDiscovery=JSON.parse(atob(n)),t=t.replace(new RegExp("&?rsDiscovery="+n),""),t.length>0&&(e.state=t)}else e[decodeURIComponent(r[0])]=decodeURIComponent(r[1]);return e},r):r}class d{static authorize(e,t){if((0,s.default)("[Authorize] authURL = ",t.authURL,"scope = ",t.scope,"redirectUri = ",t.redirectUri,"clientId = ",t.clientId,"response_type =",t.response_type),!t.scope)throw new Error("Cannot authorize due to undefined or empty scope; did you forget to access.claim()?");if("function"==typeof e._rememberPendingScope&&e._rememberPendingScope(t.scope),!(0,a.localStorageAvailable)()&&"remotestorage"===e.backend){t.redirectUri+=t.redirectUri.indexOf("#")>0?"&":"#";const r={userAddress:e.remote.userAddress,href:e.remote.href,storageApi:e.remote.storageApi,properties:e.remote.properties};t.redirectUri+="rsDiscovery="+(0,a.toBase64)(JSON.stringify(r))}const r=function(e){const t=new URL(e.redirectUri);e.state||(e.state=t.hash?t.hash.substring(1):""),e.response_type||(e.response_type="token");const r=new URL(e.authURL);r.searchParams.set("redirect_uri",e.redirectUri.replace(/#.*$/,"")),r.searchParams.set("scope",e.scope),r.searchParams.set("client_id",e.clientId);for(const n of["state","response_type","code_challenge","code_challenge_method","token_access_type"]){const t=e[n];t&&r.searchParams.set(n,t)}return r.href}(t);a.globalContext.cordova&&(d.getLocation().href.startsWith("file:")||"string"==typeof o.default.cordovaRedirectUri&&t.redirectUri===o.default.cordovaRedirectUri)?d.openWindow(r,t.redirectUri,"location=yes,clearsessioncache=yes,clearcache=yes").then(r=>{e.remote.configure({token:r.access_token}),"function"==typeof e._completeAuthorization&&e._completeAuthorization(r.scope||t.scope)}):d.setLocation(r)}static refreshAccessToken(e,t,r){return n(this,void 0,void 0,function*(){var e,n,i;yield t.configure({token:null,tokenType:null});const o=new URLSearchParams({grant_type:"refresh_token",client_id:t.clientId,refresh_token:r}),a=yield(Line truncated
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
Object.defineProperty(e,"__esModule",{value:!0}),e.bytes=e.stringToBytes=e.str=e.bytesToString=e.hex=e.utf8=e.bech32m=e.bech32=e.base58check=e.base58xmr=e.base58xrp=e.base58flickr=e.base58=e.base64url=e.base64=e.base32crockford=e.base32hex=e.base32=e.base16=e.utils=e.assertNumber=void 0,e.assertNumber=t;const l=(e,t)=>t?l(t,e%t):e,c=(e,t)=>e+(t-l(e,t));function u(e,r,n,i){if(!Array.isArray(e))throw new Error("convertRadix2: data should be array");if(r<=0||r>32)throw new Error(`convertRadix2: wrong from=${r}`);if(n<=0||n>32)throw new Error(`convertRadix2: wrong to=${n}`);if(c(r,n)>32)throw new Error(`convertRadix2: carry overflow from=${r} to=${n} carryBits=${c(r,n)}`);let o=0,s=0;const a=2**n-1,l=[];for(const c of e){if(t(c),c>=2**r)throw new Error(`convertRadix2: invalid data word=${c} from=${r}`);if(o=o<<r|c,s+r>32)throw new Error(`convertRadix2: carry overflow pos=${s} from=${r}`);for(s+=r;s>=n;s-=n)l.push((o>>s-n&a)>>>0);o&=2**s-1}if(o=o<<n-s&a,!i&&s>=r)throw new Error("Excess padding");if(!i&&o)throw new Error(`Non-zero padding: ${o}`);return i&&s>0&&l.push(o>>>0),l}function h(e){return t(e),{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix.encode input should be Uint8Array");return a(Array.from(t),256,e)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix.decode input should be array of strings");return Uint8Array.from(a(t,e,256))}}}function d(e,r=!1){if(t(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(c(8,e)>32||c(e,8)>32)throw new Error("radix2: carry overflow");return{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix2.encode input should be Uint8Array");return u(Array.from(t),8,e,!r)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix2.decode input should be array of strings");return Uint8Array.from(u(t,e,8,r))}}}function p(e){if("function"!=typeof e)throw new Error("unsafeWrapper fn should be function");return function(...t){try{return e.apply(null,t)}catch(r){}}}function f(e,r){if(t(e),"function"!=typeof r)throw new Error("checksum fn should be function");return{encode(t){if(!(t instanceof Uint8Array))throw new Error("checksum.encode: input should be Uint8Array");const n=r(t).slice(0,e),i=new Uint8Array(t.length+e);return i.set(t),i.set(n,t.length),i},decode(t){if(!(t instanceof Uint8Array))throw new Error("checksum.decode: input should be Uint8Array");const n=t.slice(0,-e),i=r(n).slice(0,e),o=t.slice(-e);for(let r=0;r<e;r++)if(i[r]!==o[r])throw new Error("Invalid checksum");return n}}}e.utils={alphabet:n,chain:r,checksum:f,radix:h,radix2:d,join:i,padding:o},e.base16=r(d(4),n("0123456789ABCDEF"),i("")),e.base32=r(d(5),n("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"),o(5),i("")),e.base32hex=r(d(5),n("0123456789ABCDEFGHIJKLMNOPQRSTUV"),o(5),i("")),e.base32crockford=r(d(5),n("0123456789ABCDEFGHJKMNPQRSTVWXYZ"),i(""),s(e=>e.toUpperCase().replace(/O/g,"0").replace(/[IL]/g,"1"))),e.base64=r(d(6),n("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),o(6),i("")),e.base64url=r(d(6),n("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"),o(6),i(""));const g=e=>r(h(58),n(e),i(""));e.base58=g("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"),e.base58flickr=g("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"),e.base58xrp=g("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz");const m=[0,2,3,5,6,7,9,10,11];e.base58xmr={encode(t){let r="";for(let n=0;n<t.length;n+=8){const i=t.subarray(n,n+8);r+=e.base58.encode(i).padStart(m[i.length],"1")}return r},decode(t){let r=[];for(let n=0;n<t.length;n+=11){const i=t.slice(n,n+11),o=m.indexOf(i.length),s=e.base58.decode(i);for(let e=0;e<s.length-o;e++)if(0!==s[e])throw new Error("base58xmr: wrong padding");r=r.concat(Array.from(s.slice(s.length-o)))}return Uint8Array.from(r)}},e.base58check=t=>r(f(4,e=>t(t(e))),e.base58);const y=r(n("qpzry9x8gf2tvdw0s3jn54khce6mua7l"),i("")),b=[996825010,642813549,513874426,1027748829,705979059];function v(e){const t=e>>25;let r=(33554431&e)<<5;for(let n=0;n<b.length;n++)1==(t>>n&1)&&(r^=b[n]);return r}function _(e,t,r=1){const n=e.length;let i=1;for(let o=0;o<n;o++){const t=e.charCodeAt(o);if(t<33||t>126)throw new Error(`Invalid prefix (${e})`);i=v(i)^t>>5}i=v(i);for(let o=0;o<n;o++)i=v(i)^31&e.charCodeAt(o);for(let o of t)i=v(i)^o;for(let o=0;o<6;o++)i=v(i);return i^=r,y.encode(u([i%2**30],30,5,!1))}function E(e){const t="bech32"===e?1:734539939,r=d(5),n=r.decode,i=r.encode,o=p(n);function s(e,r=90){if("string"!=typeof e)throw new Error("bech32.decode input should be string, not "+typeof e);if(e.length<8||!1!==r&&e.length>r)throw new TypeError(`Wrong string length: ${e.length} (${e}). Expected (8..${r})`);const n=e.toLowerCase();if(e!==n&&e!==e.toUpperCase())throw new Error("String must be lowercase or uppercase");const i=(e=n).lastIndexOf("1");if(0===i||-1===i)throw new Error('Letter "1" must be present between prefix and data only');const o=e.slice(0,i),s=e.slLine truncated
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */function isBytes$2(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&"Uint8Array"===e.constructor.name}function anumber$2(e,t=""){if(!Number.isSafeInteger(e)||e<0)throw new Error(`${t&&`"${t}" `}expected integer >= 0, got ${e}`)}function abytes$2(e,t,r=""){const n=isBytes$2(e),i=e?.length,o=void 0!==t;if(!n||o&&i!==t)throw new Error((r&&`"${r}" `)+"expected Uint8Array"+(o?` of length ${t}`:"")+", got "+(n?`length=${i}`:"type="+typeof e));return e}function ahash(e){if("function"!=typeof e||"function"!=typeof e.create)throw new Error("Hash must wrapped by utils.createHasher");anumber$2(e.outputLen),anumber$2(e.blockLen)}function aexists(e,t=!0){if(e.destroyed)throw new Error("Hash instance has been destroyed");if(t&&e.finished)throw new Error("Hash#digest() has already been called")}function aoutput(e,t){abytes$2(e,void 0,"digestInto() output");const r=t.outputLen;if(e.length<r)throw new Error('"digestInto() output" expected to be of length >='+r)}function clean$1(...e){for(let t=0;t<e.length;t++)e[t].fill(0)}function createView(e){return new DataView(e.buffer,e.byteOffset,e.byteLength)}function rotr(e,t){return e<<32-t|e>>>t}const hasHexBuiltin=(()=>"function"==typeof Uint8Array.from([]).toHex&&"function"==typeof Uint8Array.fromHex)(),hexes=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function bytesToHex(e){if(abytes$2(e),hasHexBuiltin)return e.toHex();let t="";for(let r=0;r<e.length;r++)t+=hexes[e[r]];return t}const asciis={_0:48,_9:57,A:65,F:70,a:97,f:102};function asciiToBase16(e){return e>=asciis._0&&e<=asciis._9?e-asciis._0:e>=asciis.A&&e<=asciis.F?e-(asciis.A-10):e>=asciis.a&&e<=asciis.f?e-(asciis.a-10):void 0}function hexToBytes(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);if(hasHexBuiltin)return Uint8Array.fromHex(e);const t=e.length,r=t/2;if(t%2)throw new Error("hex string expected, got unpadded hex of length "+t);const n=new Uint8Array(r);for(let i=0,o=0;i<r;i++,o+=2){const t=asciiToBase16(e.charCodeAt(o)),r=asciiToBase16(e.charCodeAt(o+1));if(void 0===t||void 0===r){const t=e[o]+e[o+1];throw new Error('hex string expected, got non-hex character "'+t+'" at index '+o)}n[i]=16*t+r}return n}function concatBytes(...e){let t=0;for(let n=0;n<e.length;n++){const r=e[n];abytes$2(r),t+=r.length}const r=new Uint8Array(t);for(let n=0,i=0;n<e.length;n++){const t=e[n];r.set(t,i),i+=t.length}return r}function createHasher(e,t={}){const r=(t,r)=>e(r).update(t).digest(),n=e(void 0);return r.outputLen=n.outputLen,r.blockLen=n.blockLen,r.create=t=>e(t),Object.assign(r,t),Object.freeze(r)}function randomBytes(e=32){const t="object"==typeof globalThis?globalThis.crypto:null;if("function"!=typeof t?.getRandomValues)throw new Error("crypto.getRandomValues must be defined");return t.getRandomValues(new Uint8Array(e))}const oidNist=e=>({oid:Uint8Array.from([6,9,96,134,72,1,101,3,4,2,e])});function Chi(e,t,r){return e&t^~e&r}function Maj(e,t,r){return e&t^e&r^t&r}class HashMD{blockLen;outputLen;padOffset;isLE;buffer;view;finished=!1;length=0;pos=0;destroyed=!1;constructor(e,t,r,n){this.blockLen=e,this.outputLen=t,this.padOffset=r,this.isLE=n,this.buffer=new Uint8Array(e),this.view=createView(this.buffer)}update(e){aexists(this),abytes$2(e);const{view:t,buffer:r,blockLen:n}=this,i=e.length;for(let o=0;o<i;){const s=Math.min(n-this.pos,i-o);if(s===n){const t=createView(e);for(;n<=i-o;o+=n)this.process(t,o);continue}r.set(e.subarray(o,o+s),this.pos),this.pos+=s,o+=s,this.pos===n&&(this.process(t,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){aexists(this),aoutput(e,this),this.finished=!0;const{buffer:t,view:r,blockLen:n,isLE:i}=this;let{pos:o}=this;t[o++]=128,clean$1(this.buffer.subarray(o)),this.padOffset>n-o&&(this.process(r,0),o=0);for(let u=o;u<n;u++)t[u]=0;r.setBigUint64(n-8,BigInt(8*this.length),i),this.process(r,0);const s=createView(e),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen must be aligned to 32bit");const l=a/4,c=this.get();if(l>c.length)throw new Error("_sha2: outputLen bigger than state");for(let u=0;u<l;u++)s.setUint32(4*u,c[u],i)}digest(){const{buffer:e,outputLen:t}=this;this.digestInto(e);const r=e.slice(0,t);return this.destroy(),r}_cloneInto(e){e||=new this.constructor,e.set(...this.get());const{blockLen:t,buffer:r,length:n,finished:i,destroyed:o,pos:s}=this;return e.destroyed=o,e.finished=i,e.length=n,e.pos=s,n%t&&e.buffer.set(r),e}clone(){return this._cloneInto()}}const SHA256_IV=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),SHA256_K=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,358452871Line truncated
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function abool$1(e,t=""){if("boolean"!=typeof e)throw new Error((t&&`"${t}" `)+"expected boolean, got type="+typeof e);return e}function abignumber(e){if("bigint"==typeof e){if(!isPosBig(e))throw new Error("positive bigint expected, got "+e)}else anumber$2(e);return e}function numberToHexUnpadded(e){const t=abignumber(e).toString(16);return 1&t.length?"0"+t:t}function hexToNumber(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);return""===e?_0n$4:BigInt("0x"+e)}function bytesToNumberBE(e){return hexToNumber(bytesToHex(e))}function bytesToNumberLE(e){return hexToNumber(bytesToHex(copyBytes$1(abytes$2(e)).reverse()))}function numberToBytesBE(e,t){anumber$2(t);const r=hexToBytes((e=abignumber(e)).toString(16).padStart(2*t,"0"));if(r.length!==t)throw new Error("number too large");return r}function numberToBytesLE(e,t){return numberToBytesBE(e,t).reverse()}function copyBytes$1(e){return Uint8Array.from(e)}function asciiToBytes(e){return Uint8Array.from(e,(t,r)=>{const n=t.charCodeAt(0);if(1!==t.length||n>127)throw new Error(`string contains non-ASCII character "${e[r]}" with code ${n} at position ${r}`);return n})}const isPosBig=e=>"bigint"==typeof e&&_0n$4<=e;function inRange(e,t,r){return isPosBig(e)&&isPosBig(t)&&isPosBig(r)&&t<=e&&e<r}function aInRange(e,t,r,n){if(!inRange(t,r,n))throw new Error("expected valid "+e+": "+r+" <= n < "+n+", got "+t)}function bitLen(e){let t;for(t=0;e>_0n$4;e>>=_1n$3,t+=1);return t}const bitMask=e=>(_1n$3<<BigInt(e))-_1n$3;function createHmacDrbg(e,t,r){if(anumber$2(e,"hashLen"),anumber$2(t,"qByteLen"),"function"!=typeof r)throw new Error("hmacFn must be a function");const n=e=>new Uint8Array(e),i=Uint8Array.of(),o=Uint8Array.of(0),s=Uint8Array.of(1);let a=n(e),l=n(e),c=0;const u=()=>{a.fill(1),l.fill(0),c=0},h=(...e)=>r(l,concatBytes(a,...e)),d=(e=i)=>{l=h(o,e),a=h(),0!==e.length&&(l=h(s,e),a=h())},p=()=>{if(c++>=1e3)throw new Error("drbg: tried max amount of iterations");let e=0;const r=[];for(;e<t;){a=h();const t=a.slice();r.push(t),e+=a.length}return concatBytes(...r)};return(e,t)=>{let r;for(u(),d(e);!(r=t(p()));)d();return u(),r}}function validateObject(e,t={},r={}){if(!e||"object"!=typeof e)throw new Error("expected valid options object");const n=(t,r)=>Object.entries(t).forEach(([t,n])=>function(t,r,n){const i=e[t];if(n&&void 0===i)return;const o=typeof i;if(o!==r||null===i)throw new Error(`param "${t}" is invalid: expected ${r}, got ${o}`)}(t,n,r));n(t,!1),n(r,!0)}function memoized(e){const t=new WeakMap;return(r,...n)=>{const i=t.get(r);if(void 0!==i)return i;const o=e(r,...n);return t.set(r,o),o}}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const _0n$3=BigInt(0),_1n$2=BigInt(1),_2n$2=BigInt(2),_3n$1=BigInt(3),_4n$1=BigInt(4),_5n=BigInt(5),_7n=BigInt(7),_8n=BigInt(8),_9n=BigInt(9),_16n=BigInt(16);function mod(e,t){const r=e%t;return r>=_0n$3?r:t+r}function pow2(e,t,r){let n=e;for(;t-- >_0n$3;)n*=n,n%=r;return n}function invert(e,t){if(e===_0n$3)throw new Error("invert: expected non-zero number");if(t<=_0n$3)throw new Error("invert: expected positive modulus, got "+t);let r=mod(e,t),n=t,i=_0n$3,o=_1n$2;for(;r!==_0n$3;){const e=n%r,t=i-o*(n/r);n=r,r=e,i=o,o=t}if(n!==_1n$2)throw new Error("invert: does not exist");return mod(i,t)}function assertIsSquare(e,t,r){if(!e.eql(e.sqr(t),r))throw new Error("Cannot find square root")}function sqrt3mod4(e,t){const r=(e.ORDER+_1n$2)/_4n$1,n=e.pow(t,r);return assertIsSquare(e,n,t),n}function sqrt5mod8(e,t){const r=(e.ORDER-_5n)/_8n,n=e.mul(t,_2n$2),i=e.pow(n,r),o=e.mul(t,i),s=e.mul(e.mul(o,_2n$2),i),a=e.mul(o,e.sub(s,e.ONE));return assertIsSquare(e,a,t),a}function sqrt9mod16(e){const t=Field(e),r=tonelliShanks(e),n=r(t,t.neg(t.ONE)),i=r(t,n),o=r(t,t.neg(n)),s=(e+_7n)/_16n;return(e,t)=>{let r=e.pow(t,s),a=e.mul(r,n);const l=e.mul(r,i),c=e.mul(r,o),u=e.eql(e.sqr(a),t),h=e.eql(e.sqr(l),t);r=e.cmov(r,a,u),a=e.cmov(c,l,h);const d=e.eql(e.sqr(a),t),p=e.cmov(r,a,d);return assertIsSquare(e,p,t),p}}function tonelliShanks(e){if(e<_3n$1)throw new Error("sqrt is not defined for small field");let t=e-_1n$2,r=0;for(;t%_2n$2===_0n$3;)t/=_2n$2,r++;let n=_2n$2;const i=Field(e);for(;1===FpLegendre(i,n);)if(n++>1e3)throw new Error("Cannot find square root: probably non-prime P");if(1===r)return sqrt3mod4;let o=i.pow(n,t);const s=(t+_1n$2)/_2n$2;return function(e,n){if(e.is0(n))return n;if(1!==FpLegendre(e,n))throw new Error("Cannot find square root");let i=r,a=e.mul(e.ONE,o),l=e.pow(n,t),c=e.pow(n,s);for(;!e.eql(l,e.ONE);){if(e.is0(l))return e.ZERO;let t=1,r=e.sqr(l);for(;!e.eql(r,e.ONE);)if(t++,r=e.sqr(r),t===i)throw new Error("Cannot find square root");const n=_1n$2<<BigInt(i-t-1),o=e.pow(a,n);i=t,a=e.sqr(o),l=e.mul(l,a),c=e.mul(c,o)}return c}}function FpSqrt(e){return e%_4n$1===_3n$1?sqrt3mod4:e%_8n===_5n?sqrt5mod8:e%_16n===_9n?sqrt9mod16(e):tonelliShanks(e)}const FIELD_FIELDS=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"];function validateField(e){const t=FIELD_FIELDS.reduce((e,t)=>(e[t]="function",e),{ORDER:"bigint",BYTES:"number",BITS:"number"});return validateObject(e,t),e}function FpPow(e,t,r){if(r<_0n$3)throw new Error("invalid exponent, negatives unsupported");if(r===_0n$3)return e.ONE;if(r===_1n$2)return t;let n=e.ONE,i=t;for(;r>_0n$3;)r&_1n$2&&(n=e.mul(n,i)),i=e.sqr(i),r>>=_1n$2;return n}function FpInvertBatch(e,t,r=!1){const n=new Array(t.length).fill(r?e.ZERO:void 0),i=t.reduce((t,r,i)=>e.is0(r)?t:(n[i]=t,e.mul(t,r)),e.ONE),o=e.inv(i);return t.reduceRight((t,r,i)=>e.is0(r)?t:(n[i]=e.mul(t,n[i]),e.mul(t,r)),o),n}function FpLegendre(e,t){const r=(e.ORDER-_1n$2)/_2n$2,n=e.pow(t,r),i=e.eql(n,e.ONE),o=e.eql(n,e.ZERO),s=e.eql(n,e.neg(e.ONE));if(!i&&!o&&!s)throw new Error("invalid Legendre symbol result");return i?1:o?0:-1}function nLength(e,t){void 0!==t&&anumber$2(t);const r=void 0!==t?t:e.toString(2).length;return{nBitLength:r,nByteLength:Math.ceil(r/8)}}class _Field{ORDER;BITS;BYTES;isLE;ZERO=_0n$3;ONE=_1n$2;_lengths;_sqrt;_mod;constructor(e,t={}){if(e<=_0n$3)throw new Error("invalid field: expected ORDER > 0, got "+e);let r;this.isLE=!1,null!=t&&"object"==typeof t&&("number"==typeof t.BITS&&(r=t.BITS),"function"==typeof t.sqrt&&(this.sqrt=t.sqrt),"boolean"==typeof t.isLE&&(this.isLE=t.isLE),t.allowedLengths&&(this._lengths=t.allowedLengths?.slice()),"boolean"==typeof t.modFromBytes&&(this._mod=t.modFromBytes));const{nBitLength:n,nByteLength:i}=nLength(e,r);if(i>2048)throw new Error("invalid field: expected ORDER of <= 2048 bytes");this.ORDER=e,this.BITS=n,this.BYTES=i,this._sqrt=void 0,Object.preventExtensions(this)}create(e){return mod(e,this.ORDER)}isValid(e){if("bigint"!=typeof e)throw new Error("invalid field element: expected bigint, got "+typeof e);return _0n$3<=e&&e<this.ORDER}is0(e){return e===_0n$3}isValidNot0(e){return!this.is0(e)&&this.isValid(e)}isOdd(e){return(e&_1n$2)===_1n$2}neg(e){return mod(-e,this.ORDER)}eql(e,t){return e===t}sqr(e){return mod(e*e,this.ORDER)}add(e,t){return mod(e+t,this.ORDER)}sub(e,t){return mod(e-t,this.ORDER)}mul(e,t){return mod(e*t,this.ORDER)}pow(e,t){return FpPow(this,e,t)}div(e,t){return mod(e*invert(t,this.ORDER),this.ORDER)}sqrN(e){return e*e}addN(e,t){return e+t}subN(e,t){return e-t}mulN(e,t){return e*t}inv(e){return invert(e,this.ORDER)}sqrt(e){return this._sqrt||(this._sqrt=FpSqrt(this.ORDER)),this._sqrt(this,e)}toBytes(e){return this.isLE?numberToBytesLE(e,this.BYTES):numberToBytesBE(e,this.BYTES)}fromBytes(e,t=!1){abytes$2(e);const{_lengths:r,BYTES:n,isLE:i,ORDER:o,_mod:s}=this;if(r){if(!r.includes(e.length)||e.length>n)throw new Line truncated
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const _0n$2=BigInt(0),_1n$1=BigInt(1);function negateCt(e,t){const r=t.negate();return e?r:t}function normalizeZ(e,t){const r=FpInvertBatch(e.Fp,t.map(e=>e.Z));return t.map((t,n)=>e.fromAffine(t.toAffine(r[n])))}function validateW(e,t){if(!Number.isSafeInteger(e)||e<=0||e>t)throw new Error("invalid window size, expected [1.."+t+"], got W="+e)}function calcWOpts(e,t){validateW(e,t);const r=2**e;return{windows:Math.ceil(t/e)+1,windowSize:2**(e-1),mask:bitMask(e),maxNumber:r,shiftBy:BigInt(e)}}function calcOffsets(e,t,r){const{windowSize:n,mask:i,maxNumber:o,shiftBy:s}=r;let a=Number(e&i),l=e>>s;a>n&&(a-=o,l+=_1n$1);const c=t*n;return{nextN:l,offset:c+Math.abs(a)-1,isZero:0===a,isNeg:a<0,isNegF:t%2!=0,offsetF:c}}const pointPrecomputes=new WeakMap,pointWindowSizes=new WeakMap;function getW(e){return pointWindowSizes.get(e)||1}function assert0(e){if(e!==_0n$2)throw new Error("invalid wNAF")}class wNAF{BASE;ZERO;Fn;bits;constructor(e,t){this.BASE=e.BASE,this.ZERO=e.ZERO,this.Fn=e.Fn,this.bits=t}_unsafeLadder(e,t,r=this.ZERO){let n=e;for(;t>_0n$2;)t&_1n$1&&(r=r.add(n)),n=n.double(),t>>=_1n$1;return r}precomputeWindow(e,t){const{windows:r,windowSize:n}=calcWOpts(t,this.bits),i=[];let o=e,s=o;for(let a=0;a<r;a++){s=o,i.push(s);for(let e=1;e<n;e++)s=s.add(o),i.push(s);o=s.double()}return i}wNAF(e,t,r){if(!this.Fn.isValid(r))throw new Error("invalid scalar");let n=this.ZERO,i=this.BASE;const o=calcWOpts(e,this.bits);for(let s=0;s<o.windows;s++){const{nextN:e,offset:a,isZero:l,isNeg:c,isNegF:u,offsetF:h}=calcOffsets(r,s,o);r=e,l?i=i.add(negateCt(u,t[h])):n=n.add(negateCt(c,t[a]))}return assert0(r),{p:n,f:i}}wNAFUnsafe(e,t,r,n=this.ZERO){const i=calcWOpts(e,this.bits);for(let o=0;o<i.windows&&r!==_0n$2;o++){const{nextN:e,offset:s,isZero:a,isNeg:l}=calcOffsets(r,o,i);if(r=e,!a){const e=t[s];n=n.add(l?e.negate():e)}}return assert0(r),n}getPrecomputes(e,t,r){let n=pointPrecomputes.get(t);return n||(n=this.precomputeWindow(t,e),1!==e&&("function"==typeof r&&(n=r(n)),pointPrecomputes.set(t,n))),n}cached(e,t,r){const n=getW(e);return this.wNAF(n,this.getPrecomputes(n,e,r),t)}unsafe(e,t,r,n){const i=getW(e);return 1===i?this._unsafeLadder(e,t,n):this.wNAFUnsafe(i,this.getPrecomputes(i,e,r),t,n)}createCache(e,t){validateW(t,this.bits),pointWindowSizes.set(e,t),pointPrecomputes.delete(e)}hasCache(e){return 1!==getW(e)}}function mulEndoUnsafe(e,t,r,n){let i=t,o=e.ZERO,s=e.ZERO;for(;r>_0n$2||n>_0n$2;)r&_1n$1&&(o=o.add(i)),n&_1n$1&&(s=s.add(i)),i=i.double(),r>>=_1n$1,n>>=_1n$1;return{p1:o,p2:s}}function createField(e,t,r){if(t){if(t.ORDER!==e)throw new Error("Field.ORDER must match order: Fp == p, Fn == n");return validateField(t),t}return Field(e,{isLE:r})}function createCurveFields(e,t,r={},n){if(void 0===n&&(n="edwards"===e),!t||"object"!=typeof t)throw new Error(`expected valid ${e} CURVE object`);for(const a of["p","n","h"]){const e=t[a];if(!("bigint"==typeof e&&e>_0n$2))throw new Error(`CURVE.${a} must be positive bigint`)}const i=createField(t.p,r.Fp,n),o=createField(t.n,r.Fn,n),s=["Gx","Gy","a","b"];for(const a of s)if(!i.isValid(t[a]))throw new Error(`CURVE.${a} must be valid field element of CURVE.Fp`);return{CURVE:t=Object.freeze(Object.assign({},t)),Fp:i,Fn:o}}function createKeygen(e,t){return function(r){const n=e(r);return{secretKey:n,publicKey:t(n)}}}class _HMAC{oHash;iHash;blockLen;outputLen;finished=!1;destroyed=!1;constructor(e,t){if(ahash(e),abytes$2(t,void 0,"key"),this.iHash=e.create(),"function"!=typeof this.iHash.update)throw new Error("Expected instance of class which extends utils.Hash");this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;const r=this.blockLen,n=new Uint8Array(r);n.set(t.length>r?e.create().update(t).digest():t);for(let i=0;i<n.length;i++)n[i]^=54;this.iHash.update(n),this.oHash=e.create();for(let i=0;i<n.length;i++)n[i]^=106;this.oHash.update(n),clean$1(n)}update(e){return aexists(this),this.iHash.update(e),this}digestInto(e){aexists(this),abytes$2(e,this.outputLen,"output"),this.finished=!0,this.iHash.digestInto(e),this.oHash.update(e),this.oHash.digestInto(e),this.destroy()}digest(){const e=new Uint8Array(this.oHash.outputLen);return this.digestInto(e),e}_cloneInto(e){e||=Object.create(Object.getPrototypeOf(this),{});const{oHash:t,iHash:r,finished:n,destroyed:i,blockLen:o,outputLen:s}=this;return e.finished=n,e.destroyed=i,e.blockLen=o,e.outputLen=s,e.oHash=t._cloneInto(e.oHash),e.iHash=r._cloneInto(e.iHash),e}clone(){return this._cloneInto()}destroy(){this.destroyed=!0,this.oHash.destroy(),this.iHash.destroy()}}const hmac=(e,t,r)=>new _HMAC(e,t).update(r).digest();hmac.create=(e,t)=>new _HMAC(e,t);
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
const divNearest=(e,t)=>(e+(e>=0?t:-t)/_2n$1)/t;function _splitEndoScalar(e,t,r){const[[n,i],[o,s]]=t,a=divNearest(s*e,r),l=divNearest(-i*e,r);let c=e-a*n-l*o,u=-a*i-l*s;const h=c<_0n$1,d=u<_0n$1;h&&(c=-c),d&&(u=-u);const p=bitMask(Math.ceil(bitLen(r)/2))+_1n;if(c<_0n$1||c>=p||u<_0n$1||u>=p)throw new Error("splitScalar (endomorphism): failed, k="+e);return{k1neg:h,k1:c,k2neg:d,k2:u}}function validateSigFormat(e){if(!["compact","recovered","der"].includes(e))throw new Error('Signature format must be "compact", "recovered", or "der"');return e}function validateSigOpts(e,t){const r={};for(let n of Object.keys(t))r[n]=void 0===e[n]?t[n]:e[n];return abool$1(r.lowS,"lowS"),abool$1(r.prehash,"prehash"),void 0!==r.format&&validateSigFormat(r.format),r}class DERErr extends Error{constructor(e=""){super(e)}}const DER={Err:DERErr,_tlv:{encode:(e,t)=>{const{Err:r}=DER;if(e<0||e>256)throw new r("tlv.encode: wrong tag");if(1&t.length)throw new r("tlv.encode: unpadded data");const n=t.length/2,i=numberToHexUnpadded(n);if(i.length/2&128)throw new r("tlv.encode: long form length too big");const o=n>127?numberToHexUnpadded(i.length/2|128):"";return numberToHexUnpadded(e)+o+i+t},decode(e,t){const{Err:r}=DER;let n=0;if(e<0||e>256)throw new r("tlv.encode: wrong tag");if(t.length<2||t[n++]!==e)throw new r("tlv.decode: wrong tlv");const i=t[n++];let o=0;if(128&i){const e=127&i;if(!e)throw new r("tlv.decode(long): indefinite length not supported");if(e>4)throw new r("tlv.decode(long): byte length is too big");const s=t.subarray(n,n+e);if(s.length!==e)throw new r("tlv.decode: length bytes not complete");if(0===s[0])throw new r("tlv.decode(long): zero leftmost byte");for(const t of s)o=o<<8|t;if(n+=e,o<128)throw new r("tlv.decode(long): not minimal encoding")}else o=i;const s=t.subarray(n,n+o);if(s.length!==o)throw new r("tlv.decode: wrong value length");return{v:s,l:t.subarray(n+o)}}},_int:{encode(e){const{Err:t}=DER;if(e<_0n$1)throw new t("integer: negative integers are not allowed");let r=numberToHexUnpadded(e);if(8&Number.parseInt(r[0],16)&&(r="00"+r),1&r.length)throw new t("unexpected DER parsing assertion: unpadded hex");return r},decode(e){const{Err:t}=DER;if(128&e[0])throw new t("invalid signature integer: negative");if(0===e[0]&&!(128&e[1]))throw new t("invalid signature integer: unnecessary leading zero");return bytesToNumberBE(e)}},toSig(e){const{Err:t,_int:r,_tlv:n}=DER,i=abytes$2(e,void 0,"signature"),{v:o,l:s}=n.decode(48,i);if(s.length)throw new t("invalid signature: left bytes after parsing");const{v:a,l:l}=n.decode(2,o),{v:c,l:u}=n.decode(2,l);if(u.length)throw new t("invalid signature: left bytes after parsing");return{r:r.decode(a),s:r.decode(c)}},hexFromSig(e){const{_tlv:t,_int:r}=DER,n=t.encode(2,r.encode(e.r))+t.encode(2,r.encode(e.s));return t.encode(48,n)}},_0n$1=BigInt(0),_1n=BigInt(1),_2n$1=BigInt(2),_3n=BigInt(3),_4n=BigInt(4);function weierstrass(e,t={}){const r=createCurveFields("weierstrass",e,t),{Fp:n,Fn:i}=r;let o=r.CURVE;const{h:s,n:a}=o;validateObject(t,{},{allowInfinityPoint:"boolean",clearCofactor:"function",isTorsionFree:"function",fromBytes:"function",toBytes:"function",endo:"object"});const{endo:l}=t;if(l&&(!n.is0(o.a)||"bigint"!=typeof l.beta||!Array.isArray(l.basises)))throw new Error('invalid endo: expected "beta": bigint and "basises": array');const c=getWLengths(n,i);function u(){if(!n.isOdd)throw new Error("compression is not supported: Field does not have .isOdd()")}const h=t.toBytes||function(e,t,r){const{x:i,y:o}=t.toAffine(),s=n.toBytes(i);return abool$1(r,"isCompressed"),r?(u(),concatBytes(pprefix(!n.isOdd(o)),s)):concatBytes(Uint8Array.of(4),s,n.toBytes(o))},d=t.fromBytes||function(e){abytes$2(e,void 0,"Point");const{publicKey:t,publicKeyUncompressed:r}=c,i=e.length,o=e[0],s=e.subarray(1);if(i!==t||2!==o&&3!==o){if(i===r&&4===o){const e=n.BYTES,t=n.fromBytes(s.subarray(0,e)),r=n.fromBytes(s.subarray(e,2*e));if(!f(t,r))throw new Error("bad point: is not on curve");return{x:t,y:r}}throw new Error(`bad point: got length ${i}, expected compressed=${t} or uncompressed=${r}`)}{const e=n.fromBytes(s);if(!n.isValid(e))throw new Error("bad point: is not on curve, wrong x");const t=p(e);let r;try{r=n.sqrt(t)}catch(a){const e=a instanceof Error?": "+a.message:"";throw new Error("bad point: is not on curve, sqrt error"+e)}return u(),!(1&~o)!==n.isOdd(r)&&(r=n.neg(r)),{x:e,y:r}}};function p(e){const t=n.sqr(e),r=n.mul(t,e);return n.add(n.add(r,n.mul(e,o.a)),o.b)}function f(e,t){const r=n.sqr(t),i=p(e);return n.eql(r,i)}if(!f(o.Gx,o.Gy))throw new Error("bad curve params: generator point");const g=n.mul(n.pow(o.a,_3n),_4n),m=n.mul(n.sqr(o.b),BigInt(27));if(n.is0(n.add(g,m)))throw new Error("bad curve params: a or b");function y(e,t,r=!1){if(!n.isValid(t)||r&&n.is0(t))throw new Error(`bad point coordinate ${e}`);return t}function b(e){if(!(e instanceof C))throw new Error("Weierstrass Point expected")}function v(e){if(!l||!l.basises)throw new Error("no endo");return _splitEndoScalar(e,l.basises,i.OLine truncated
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const secp256k1_CURVE={p:BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),n:BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),h:BigInt(1),a:BigInt(0),b:BigInt(7),Gx:BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),Gy:BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")},secp256k1_ENDO={beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),basises:[[BigInt("0x3086d221a7d46bcde86c90e49284eb15"),-BigInt("0xe4437ed6010e88286f547fa90abfe4c3")],[BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),BigInt("0x3086d221a7d46bcde86c90e49284eb15")]]},_0n=BigInt(0),_2n=BigInt(2);function sqrtMod(e){const t=secp256k1_CURVE.p,r=BigInt(3),n=BigInt(6),i=BigInt(11),o=BigInt(22),s=BigInt(23),a=BigInt(44),l=BigInt(88),c=e*e*e%t,u=c*c*e%t,h=pow2(u,r,t)*u%t,d=pow2(h,r,t)*u%t,p=pow2(d,_2n,t)*c%t,f=pow2(p,i,t)*p%t,g=pow2(f,o,t)*f%t,m=pow2(g,a,t)*g%t,y=pow2(m,l,t)*m%t,b=pow2(y,a,t)*g%t,v=pow2(b,r,t)*u%t,_=pow2(v,s,t)*f%t,E=pow2(_,n,t)*c%t,w=pow2(E,_2n,t);if(!Fpk1.eql(Fpk1.sqr(w),e))throw new Error("Cannot find square root");return w}const Fpk1=Field(secp256k1_CURVE.p,{sqrt:sqrtMod}),Pointk1=weierstrass(secp256k1_CURVE,{Fp:Fpk1,endo:secp256k1_ENDO}),secp256k1=ecdsa(Pointk1,sha256),TAGGED_HASH_PREFIXES={};function taggedHash(e,...t){let r=TAGGED_HASH_PREFIXES[e];if(void 0===r){const t=sha256(asciiToBytes(e));r=concatBytes(t,t),TAGGED_HASH_PREFIXES[e]=r}return sha256(concatBytes(r,...t))}const pointToBytes=e=>e.toBytes(!0).slice(1),hasEven=e=>e%_2n===_0n;function schnorrGetExtPubKey(e){const{Fn:t,BASE:r}=Pointk1,n=t.fromBytes(e),i=r.multiply(n);return{scalar:hasEven(i.y)?n:t.neg(n),bytes:pointToBytes(i)}}function lift_x(e){const t=Fpk1;if(!t.isValidNot0(e))throw new Error("invalid x: Fail if x ≥ p");const r=t.create(e*e),n=t.create(r*e+BigInt(7));let i=t.sqrt(n);hasEven(i)||(i=t.neg(i));const o=Pointk1.fromAffine({x:e,y:i});return o.assertValidity(),o}const num=bytesToNumberBE;function challenge(...e){return Pointk1.Fn.create(num(taggedHash("BIP0340/challenge",...e)))}function schnorrGetPublicKey(e){return schnorrGetExtPubKey(e).bytes}function schnorrSign(e,t,r=randomBytes(32)){const{Fn:n}=Pointk1,i=abytes$2(e,void 0,"message"),{bytes:o,scalar:s}=schnorrGetExtPubKey(t),a=abytes$2(r,32,"auxRand"),l=n.toBytes(s^num(taggedHash("BIP0340/aux",a))),c=taggedHash("BIP0340/nonce",l,o,i),{bytes:u,scalar:h}=schnorrGetExtPubKey(c),d=challenge(u,o,i),p=new Uint8Array(64);if(p.set(u,0),p.set(n.toBytes(n.create(h+d*s)),32),!schnorrVerify(p,i,o))throw new Error("sign: Invalid signature produced");return p}function schnorrVerify(e,t,r){const{Fp:n,Fn:i,BASE:o}=Pointk1,s=abytes$2(e,64,"signature"),a=abytes$2(t,void 0,"message"),l=abytes$2(r,32,"publicKey");try{const e=lift_x(num(l)),t=num(s.subarray(0,32));if(!n.isValidNot0(t))return!1;const r=num(s.subarray(32,64));if(!i.isValidNot0(r))return!1;const c=challenge(i.toBytes(t),pointToBytes(e),a),u=o.multiplyUnsafe(r).add(e.multiplyUnsafe(i.neg(c))),{x:h,y:d}=u.toAffine();return!(u.is0()||!hasEven(d)||h!==t)}catch(error){return!1}}const schnorr=(()=>{const e=(e=randomBytes(48))=>mapHashToField(e,secp256k1_CURVE.n);return{keygen:createKeygen(e,schnorrGetPublicKey),getPublicKey:schnorrGetPublicKey,sign:schnorrSign,verify:schnorrVerify,Point:Pointk1,utils:{randomSecretKey:e,taggedHash:taggedHash,lift_x:lift_x,pointToBytes:pointToBytes},lengths:{secretKey:32,publicKey:32,publicKeyHasPrefix:!1,signature:64,seed:48}}})();
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */function isBytes$1(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&"Uint8Array"===e.constructor.name}function abytes$1(e){if(!isBytes$1(e))throw new Error("Uint8Array expected")}function isArrayOf(e,t){return!!Array.isArray(t)&&(0===t.length||(e?t.every(e=>"string"==typeof e):t.every(e=>Number.isSafeInteger(e))))}function afn(e){if("function"!=typeof e)throw new Error("function expected");return!0}function astr(e,t){if("string"!=typeof t)throw new Error(`${e}: string expected`);return!0}function anumber$1(e){if(!Number.isSafeInteger(e))throw new Error(`invalid integer: ${e}`)}function aArr(e){if(!Array.isArray(e))throw new Error("array expected")}function astrArr(e,t){if(!isArrayOf(!0,t))throw new Error(`${e}: array of strings expected`)}function anumArr(e,t){if(!isArrayOf(!1,t))throw new Error(`${e}: array of numbers expected`)}function chain(...e){const t=e=>e,r=(e,t)=>r=>e(t(r)),n=e.map(e=>e.encode).reduceRight(r,t),i=e.map(e=>e.decode).reduce(r,t);return{encode:n,decode:i}}function alphabet(e){const t="string"==typeof e?e.split(""):e,r=t.length;astrArr("alphabet",t);const n=new Map(t.map((e,t)=>[e,t]));return{encode:n=>(aArr(n),n.map(n=>{if(!Number.isSafeInteger(n)||n<0||n>=r)throw new Error(`alphabet.encode: digit index outside alphabet "${n}". Allowed: ${e}`);return t[n]})),decode:t=>(aArr(t),t.map(t=>{astr("alphabet.decode",t);const r=n.get(t);if(void 0===r)throw new Error(`Unknown letter: "${t}". Allowed: ${e}`);return r}))}}function join(e=""){return astr("join",e),{encode:t=>(astrArr("join.decode",t),t.join(e)),decode:t=>(astr("join.decode",t),t.split(e))}}function padding(e,t="="){return anumber$1(e),astr("padding",t),{encode(r){for(astrArr("padding.encode",r);r.length*e%8;)r.push(t);return r},decode(r){astrArr("padding.decode",r);let n=r.length;if(n*e%8)throw new Error("padding: invalid, string should have whole number of bytes");for(;n>0&&r[n-1]===t;n--)if((n-1)*e%8==0)throw new Error("padding: invalid, string has too much padding");return r.slice(0,n)}}}const gcd=(e,t)=>0===t?e:gcd(t,e%t),radix2carry=(e,t)=>e+(t-gcd(e,t)),powers=(()=>{let e=[];for(let t=0;t<40;t++)e.push(2**t);return e})();function convertRadix2(e,t,r,n){if(aArr(e),t<=0||t>32)throw new Error(`convertRadix2: wrong from=${t}`);if(r<=0||r>32)throw new Error(`convertRadix2: wrong to=${r}`);if(radix2carry(t,r)>32)throw new Error(`convertRadix2: carry overflow from=${t} to=${r} carryBits=${radix2carry(t,r)}`);let i=0,o=0;const s=powers[t],a=powers[r]-1,l=[];for(const c of e){if(anumber$1(c),c>=s)throw new Error(`convertRadix2: invalid data word=${c} from=${t}`);if(i=i<<t|c,o+t>32)throw new Error(`convertRadix2: carry overflow pos=${o} from=${t}`);for(o+=t;o>=r;o-=r)l.push((i>>o-r&a)>>>0);const e=powers[o];if(void 0===e)throw new Error("invalid carry");i&=e-1}if(i=i<<r-o&a,!n&&o>=t)throw new Error("Excess padding");if(!n&&i>0)throw new Error(`Non-zero padding: ${i}`);return n&&o>0&&l.push(i>>>0),l}function radix2(e,t=!1){if(anumber$1(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(radix2carry(8,e)>32||radix2carry(e,8)>32)throw new Error("radix2: carry overflow");return{encode:r=>{if(!isBytes$1(r))throw new Error("radix2.encode input should be Uint8Array");return convertRadix2(Array.from(r),8,e,!t)},decode:r=>(anumArr("radix2.decode",r),Uint8Array.from(convertRadix2(r,e,8,t)))}}function unsafeWrapper(e){return afn(e),function(...t){try{return e.apply(null,t)}catch(r){}}}const hasBase64Builtin=(()=>"function"==typeof Uint8Array.from([]).toBase64&&"function"==typeof Uint8Array.fromBase64)(),decodeBase64Builtin=e=>{if(astr("base64",e),e.length>0&&!/^[A-Za-z0-9=+/]+$/.test(e))throw new Error("invalid base64");return Uint8Array.fromBase64(e,{alphabet:"base64",lastChunkHandling:"strict"})},base64=hasBase64Builtin?{encode:e=>(abytes$1(e),e.toBase64()),decode:e=>decodeBase64Builtin(e)}:chain(radix2(6),alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),padding(6),join("")),BECH_ALPHABET=chain(alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"),join("")),POLYMOD_GENERATORS=[996825010,642813549,513874426,1027748829,705979059];function bech32Polymod(e){const t=e>>25;let r=(33554431&e)<<5;for(let n=0;n<POLYMOD_GENERATORS.length;n++)1==(t>>n&1)&&(r^=POLYMOD_GENERATORS[n]);return r}function bechChecksum(e,t,r=1){const n=e.length;let i=1;for(let o=0;o<n;o++){const t=e.charCodeAt(o);if(t<33||t>126)throw new Error(`Invalid prefix (${e})`);i=bech32Polymod(i)^t>>5}i=bech32Polymod(i);for(let o=0;o<n;o++)i=bech32Polymod(i)^31&e.charCodeAt(o);for(let o of t)i=bech32Polymod(i)^o;for(let o=0;o<6;o++)i=bech32Polymod(i);return i^=r,BECH_ALPHABET.encode(convertRadix2([i%powers[30]],30,5,!1))}function genBech32(e){const t="bech32"===e?1:734539939,r=radix2(5),n=r.decode,i=r.encode,o=unsafeWrapper(n);function s(e,r,n=90){astr("bech32.encode prefix",e),isBytes$1(r)&&(r=Array.from(r)),anumArr("bech32.encode",r);const i=e.length;if(0===i)throw new TyLine truncated
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */function isBytes(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&"Uint8Array"===e.constructor.name}function abool(e){if("boolean"!=typeof e)throw new Error(`boolean expected, not ${e}`)}function anumber(e){if(!Number.isSafeInteger(e)||e<0)throw new Error("positive integer expected, got "+e)}function abytes(e,t,r=""){const n=isBytes(e),i=e?.length,o=void 0!==t;if(!n||o&&i!==t)throw new Error((r&&`"${r}" `)+"expected Uint8Array"+(o?` of length ${t}`:"")+", got "+(n?`length=${i}`:"type="+typeof e));return e}function u32(e){return new Uint32Array(e.buffer,e.byteOffset,Math.floor(e.byteLength/4))}function clean(...e){for(let t=0;t<e.length;t++)e[t].fill(0)}const isLE=(()=>68===new Uint8Array(new Uint32Array([287454020]).buffer)[0])();function overlapBytes(e,t){return e.buffer===t.buffer&&e.byteOffset<t.byteOffset+t.byteLength&&t.byteOffset<e.byteOffset+e.byteLength}function complexOverlapBytes(e,t){if(overlapBytes(e,t)&&e.byteOffset<t.byteOffset)throw new Error("complex overlap of input and output is not supported")}function checkOpts(e,t){if(null==t||"object"!=typeof t)throw new Error("options must be defined");return Object.assign(e,t)}function equalBytes(e,t){if(e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e[n]^t[n];return 0===r}const wrapCipher=(e,t)=>{function r(r,...n){if(abytes(r,void 0,"key"),!isLE)throw new Error("Non little-endian hardware is not yet supported");void 0!==e.nonceLength&&abytes(n[0],e.varSizeNonce?void 0:e.nonceLength,"nonce");const i=e.tagLength;i&&void 0!==n[1]&&abytes(n[1],void 0,"AAD");const o=t(r,...n),s=(e,t)=>{if(void 0!==t){if(2!==e)throw new Error("cipher output not supported");abytes(t,void 0,"output")}};let a=!1;const l={encrypt(e,t){if(a)throw new Error("cannot encrypt() twice with same key + nonce");return a=!0,abytes(e),s(o.encrypt.length,t),o.encrypt(e,t)},decrypt(e,t){if(abytes(e),i&&e.length<i)throw new Error('"ciphertext" expected length bigger than tagLength='+i);return s(o.decrypt.length,t),o.decrypt(e,t)}};return l}return Object.assign(r,e),r};function getOutput(e,t,r=!0){if(void 0===t)return new Uint8Array(e);if(t.length!==e)throw new Error('"output" expected Uint8Array of length '+e+", got: "+t.length);if(r&&!isAligned32$1(t))throw new Error("invalid output, must be aligned");return t}function isAligned32$1(e){return e.byteOffset%4==0}function copyBytes(e){return Uint8Array.from(e)}const BLOCK_SIZE=16,POLY=283;function validateKeyLength(e){if(![16,24,32].includes(e.length))throw new Error('"aes key" expected Uint8Array of length 16/24/32, got length='+e.length)}function mul2(e){return e<<1^POLY&-(e>>7)}function mul(e,t){let r=0;for(;t>0;t>>=1)r^=e&-(1&t),e=mul2(e);return r}const sbox=(()=>{const e=new Uint8Array(256);for(let r=0,n=1;r<256;r++,n^=mul2(n))e[r]=n;const t=new Uint8Array(256);t[0]=99;for(let r=0;r<255;r++){let n=e[255-r];n|=n<<8,t[e[r]]=255&(n^n>>4^n>>5^n>>6^n>>7^99)}return clean(e),t})(),invSbox=sbox.map((e,t)=>sbox.indexOf(t)),rotr32_8=e=>e<<24|e>>>8,rotl32_8=e=>e<<8|e>>>24;function genTtable(e,t){if(256!==e.length)throw new Error("Wrong sbox length");const r=new Uint32Array(256).map((r,n)=>t(e[n])),n=r.map(rotl32_8),i=n.map(rotl32_8),o=i.map(rotl32_8),s=new Uint32Array(65536),a=new Uint32Array(65536),l=new Uint16Array(65536);for(let c=0;c<256;c++)for(let t=0;t<256;t++){const u=256*c+t;s[u]=r[c]^n[t],a[u]=i[c]^o[t],l[u]=e[c]<<8|e[t]}return{sbox:e,sbox2:l,T0:r,T1:n,T2:i,T3:o,T01:s,T23:a}}const tableEncoding=genTtable(sbox,e=>mul(e,3)<<24|e<<16|e<<8|mul(e,2)),tableDecoding=genTtable(invSbox,e=>mul(e,11)<<24|mul(e,13)<<16|mul(e,9)<<8|mul(e,14)),xPowers=(()=>{const e=new Uint8Array(16);for(let t=0,r=1;t<16;t++,r=mul2(r))e[t]=r;return e})();function expandKeyLE(e){abytes(e);const t=e.length;validateKeyLength(e);const{sbox2:r}=tableEncoding,n=[];isAligned32$1(e)||n.push(e=copyBytes(e));const i=u32(e),o=i.length,s=e=>applySbox(r,e,e,e,e),a=new Uint32Array(t+28);a.set(i);for(let l=o;l<a.length;l++){let e=a[l-1];l%o===0?e=s(rotr32_8(e))^xPowers[l/o-1]:o>6&&l%o===4&&(e=s(e)),a[l]=a[l-o]^e}return clean(...n),a}function expandKeyDecLE(e){const t=expandKeyLE(e),r=t.slice(),n=t.length,{sbox2:i}=tableEncoding,{T0:o,T1:s,T2:a,T3:l}=tableDecoding;for(let c=0;c<n;c+=4)for(let e=0;e<4;e++)r[c+e]=t[n-c-4+e];clean(t);for(let c=4;c<n-4;c++){const e=r[c],t=applySbox(i,e,e,e,e);r[c]=o[255&t]^s[t>>>8&255]^a[t>>>16&255]^l[t>>>24]}return r}function apply0123(e,t,r,n,i,o){return e[r<<8&65280|n>>>8&255]^t[i>>>8&65280|o>>>24&255]}function applySbox(e,t,r,n,i){return e[255&t|65280&r]|e[n>>>16&255|i>>>16&65280]<<16}function encrypt$1(e,t,r,n,i){const{sbox2:o,T01:s,T23:a}=tableEncoding;let l=0;t^=e[l++],r^=e[l++],n^=e[l++],i^=e[l++];const c=e.length/4-2;for(let u=0;u<c;u++){const o=e[l++]^apply0123(s,a,t,r,n,i),c=e[l++]^apply0123(s,a,r,n,i,t),u=e[l++]^apply0123(s,a,n,i,t,r),h=e[l++]^apply0123(s,a,i,t,r,n);t=o,r=c,n=u,i=h}return{s0:e[l++]^applySbox(o,t,r,n,i),s1:e[l++]^applySbox(o,rLine truncated
-1
View File
@@ -1 +0,0 @@
:root{--default-list-color: #fc3;--hover-bg: #f8f9fa;--sidebar-width: 350px;--link-color: #2a7fff;--link-color-visited: #6a4fbf;--marker-color-primary: #ea4335;--marker-color-dark: #b31412;--danger-color: var(--marker-color-primary);--danger-color-dark: var(--marker-color-dark)}html,body{height:100%;overscroll-behavior:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}button{-webkit-tap-highlight-color:transparent}body{margin:0;font-family:Noto Sans,sans-serif;font-size:16px;font-weight:400;color:#333}#root,#ember-testing{height:100%}.map-container{background:#f8f9fa;-webkit-tap-highlight-color:transparent;outline:none;position:absolute;inset:0}#rs-widget-container{position:absolute;top:60px;right:10px;z-index:4000;display:none}#rs-widget-container.visible{display:block}.rs-backdrop{position:fixed;inset:0;z-index:3999}.app-header{position:absolute;top:0;left:0;right:0;height:60px;padding:.5rem 1rem;z-index:3000;pointer-events:none;display:grid;grid-template-columns:1fr auto 1fr;grid-template-areas:"search chips user";align-items:center;gap:1rem}@media(max-width:768px){.app-header{padding:.5rem .5rem 0;height:auto;grid-template-columns:1fr auto;grid-template-areas:"search user" "chips chips";row-gap:8px}}.header-left,.header-right,.header-center{pointer-events:auto}.header-left{display:flex;align-items:center;grid-area:search;justify-self:start;width:100%}@media not (max-width:768px){.header-left{min-width:300px;max-width:400px}}@media not (max-width:768px){.header-left{min-width:300px;max-width:350px}}.header-right{grid-area:user;justify-self:end}.header-center{grid-area:chips;display:flex;justify-content:center;min-width:0}@media not (max-width:768px){.header-center .category-chips-scroll{width:auto;max-width:100%}}@media(max-width:768px){.header-center{width:100%;overflow:hidden;justify-content:start}.header-center.searching{display:none}}.btn-press{transition:transform .1s}.btn-press:active{transform:scale(.95)}.user-btn{background:none;border:none;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center}.user-avatar-placeholder{width:40px;height:40px;background:#2a3743;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0}.user-avatar{width:40px;height:40px;border-radius:50%;object-fit:cover;flex-shrink:0;display:block}.photo-preview-img{max-width:100%;height:auto}.dropzone{border:2px dashed #ccc;border-radius:8px;text-align:center;transition:all .2s ease;margin:1.5rem 0 1rem;background-color:#ffffff05;cursor:pointer;aspect-ratio:4 / 3}.dropzone.is-dragging{border-color:#61afef;background-color:#61afef0d}.dropzone-label{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;cursor:pointer;color:#898989;width:100%;height:100%}.dropzone-label p{margin:0}.file-input-hidden{display:none}.photo-grid{display:flex;flex-direction:column;gap:12px;margin-bottom:20px}.photo-upload-item{position:relative;aspect-ratio:4 / 3;border-radius:6px;overflow:hidden;background:#1e262e;width:100%}.photo-upload-item img{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain;display:block;z-index:1}.photo-upload-item .overlay,.photo-upload-item .btn-remove-photo{z-index:2}.photo-upload-item .overlay{position:absolute;inset:0;background:#0009;display:flex;flex-direction:column;align-items:center;justify-content:center}.upload-status-text{color:#fff;margin-top:1rem;font-size:.9rem;text-shadow:0 1px 3px rgb(0 0 0 / 80%);text-align:center;padding:0 1rem}.photo-upload-item .error-overlay{background:#e06c75cc;cursor:pointer;border:none;padding:0;margin:0;width:100%}.photo-upload-item .btn-remove-photo{position:absolute;top:4px;right:4px;background:#000000b3;border:none;border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#fff;padding:0}.photo-upload-item .btn-remove-photo:hover{background:var(--danger-color)}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.btn-publish{width:100%}.user-menu-container{position:relative}.user-menu-popover{position:absolute;top:100%;right:0;margin-top:10px;width:280px;background:#fff;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:1rem;z-index:3001}.menu-backdrop{position:fixed;inset:0;z-index:3000}.account-list{list-style:none;padding:0;margin:0}.account-item{display:flex;flex-direction:column;padding:1rem 0;border-bottom:1px solid #eee}.account-item:last-child{border-bottom:none;padding-bottom:0}.account-item:first-child{padding-top:0}.account-header{display:flex;justify-content:space-between;align-items:center}.account-status{font-size:.85rem;color:#898989;margin-top:.35rem;margin-left:calc(18px + .75rem);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.account-status strong{font-weight:600}.account-item.disabled{opacity:.5;pointer-events:none}.account-iLine truncated
+1
View File
@@ -0,0 +1 @@
import{$ as e,$n as t,$r as a,$t as n,A as s,An as r,Ar as i,At as o,B as l,Bn as g,Br as d,Bt as h,Cn as c,Cr as u,Ct as p,Dn as _,Dr as m,Dt as y,En as C,Er as b,Et as f,F as A,Fn as M,Fr as I,Ft as D,G as T,Gn as v,Gr as O,Gt as E,H as S,Hn as R,Hr as j,Ht as x,I as w,In as P,Ir as N,It as V,J as F,Jn as G,Jr as L,Jt as k,K as U,Kn as Y,Kr as B,Kt as H,L as $,Ln as z,Lr as W,Lt as q,M as Z,Mn as J,Mr as K,Mt as Q,N as X,Nn as ee,Nr as te,Nt as ae,On as ne,Or as se,Ot as re,P as ie,Pn as oe,Pr as le,Pt as ge,Q as de,Qn as he,Qr as ce,Qt as ue,R as pe,Rn as _e,Rr as me,Rt as ye,Sn as Ce,Sr as be,St as fe,Tn as Ae,Tr as Me,Tt as Ie,U as De,Un as Te,Ur as ve,Ut as Oe,V as Ee,Vn as Se,Vr as Re,Vt as je,W as xe,Wn as we,Wr as Pe,Wt as Ne,X as Ve,Xn as Fe,Xr as Ge,Xt as Le,Y as ke,Yn as Ue,Yr as Ye,Yt as Be,Z as He,Zn as $e,Zr as ze,Zt as We,_ as qe,_n as Ze,_r as Je,_t as Ke,a as Qe,ai as Xe,an as et,ar as tt,at,bn as nt,br as st,bt as rt,c as it,cn as ot,cr as lt,ct as gt,d as dt,dn as ht,dr as ct,dt as ut,ei as pt,en as _t,er as mt,et as yt,f as Ct,fn as bt,fr as ft,ft as At,g as Mt,gn as It,gr as Dt,gt as Tt,h as vt,hn as Ot,hr as Et,ht as St,i as Rt,ii as jt,in as xt,ir as wt,it as Pt,j as Nt,jn as Vt,jr as Ft,jt as Gt,k as Lt,kn as kt,kr as Ut,kt as Yt,l as Bt,ln as Ht,lr as $t,lt as zt,m as Wt,mn as qt,mr as Zt,mt as Jt,n as Kt,ni as Qt,nn as Xt,nr as ea,nt as ta,o as aa,oi as na,on as sa,or as ra,ot as ia,p as oa,pn as la,pr as ga,pt as da,q as ha,qn as ca,qr as ua,qt as pa,r as _a,ri as ma,rn as ya,rr as Ca,rt as ba,s as fa,si as Aa,sn as Ma,sr as Ia,st as Da,t as Ta,ti as va,tn as Oa,tr as Ea,tt as Sa,u as Ra,un as ja,ur as xa,ut as wa,v as Pa,vn as Na,vr as Va,vt as Fa,wn as Ga,wr as La,wt as ka,xn as Ua,xr as Ya,xt as Ba,yn as Ha,yr as $a,yt as za,z as Wa,zn as qa,zr as Za,zt as Ja}from"./main-CAv0oB3R.js";var Ka=Aa({default:()=>Ga}),Qa=Aa({default:()=>tn}),Xa={willChange:"_arrangedContentArrayWillChange",didChange:"_arrangedContentArrayDidChange"};function en(e,t){return"[]"===t?(e._revalidate(),e._arrTag):"length"===t?(e._revalidate(),e._lengthTag):B(e,t)}var tn=class extends Y{_objectsDirtyIndex=0;_objects=null;_lengthDirty=!0;_length=0;_arrangedContent=null;_arrangedContentIsUpdating=!1;_arrangedContentTag=null;_arrangedContentRevision=null;_lengthTag=null;_arrTag=null;init(e){super.init(e),W(this,en)}[b](){this._revalidate()}willDestroy(){this._removeArrangedContentArrayObserver()}objectAtContent(e){let t=be(this,"arrangedContent");return le(t,e)}replace(e,t,a){this.replaceContent(e,t,a)}replaceContent(e,t,a){let n=be(this,"content");$a(n,e,t,a)}objectAt(e){if(this._revalidate(),null===this._objects&&(this._objects=[]),-1!==this._objectsDirtyIndex&&e>=this._objectsDirtyIndex){let e=be(this,"arrangedContent");if(e){let t=this._objects.length=be(e,"length");for(let e=this._objectsDirtyIndex;e<t;e++)this._objects[e]=this.objectAtContent(e)}else this._objects.length=0;this._objectsDirtyIndex=-1}return this._objects[e]}get length(){if(this._revalidate(),this._lengthDirty){let e=be(this,"arrangedContent");this._length=e?be(e,"length"):0,this._lengthDirty=!1}return L(this._lengthTag),this._length}set length(e){let t,a=this.length-e;if(0===a)return;a<0&&(t=new Array(-a),a=0);let n=be(this,"content");n&&($a(n,e,a,t),this._invalidate())}_updateArrangedContentArray(e){let t=null===this._objects?0:this._objects.length,a=e?be(e,"length"):0;this._removeArrangedContentArrayObserver(),Je(this,0,t,a),this._invalidate(),Dt(this,0,t,a,!1),this._addArrangedContentArrayObserver(e)}_addArrangedContentArrayObserver(e){e&&!e.isDestroyed&&(Et(e,this,Xa),this._arrangedContent=e)}_removeArrangedContentArrayObserver(){this._arrangedContent&&Va(this._arrangedContent,this,Xa)}_arrangedContentArrayWillChange(){}_arrangedContentArrayDidChange(e,t,a,n){Je(this,t,a,n);let s=t;s<0&&(s+=be(this._arrangedContent,"length")+a-n),(-1===this._objectsDirtyIndex||this._objectsDirtyIndex>s)&&(this._objectsDirtyIndex=s),this._lengthDirty=!0,Dt(this,t,a,n,!1)}_invalidate(){this._objectsDirtyIndex=0,this._lengthDirty=!0}_revalidate(){if(!0!==this._arrangedContentIsUpdating&&(null===this._arrangedContentTag||!Ye(this._arrangedContentTag,this._arrangedContentRevision))){let e=this.get("arrangedContent");null===this._arrangedContentTag?this._addArrangedContentArrayObserver(e):(this._arrangedContentIsUpdating=!0,this._updateArrangedContentArray(e),this._arrangedContentIsUpdating=!1);let t=this._arrangedContentTag=B(this,"arrangedContent");this._arrangedContentRevision=Ge(this._arrangedContentTag),O(e)?(this._lengthTag=ua([t,I(e,"length")]),this._arrTag=ua([t,I(e,"[]")])):this._lengthTag=this._arrTag=t}}};tn.reopen(Ga,{arrangedContent:st("content")});var an=Aa({Meta:()=>va,UNDEFINED:()=>Qt,counters:()=>ma,meta:()=>jt,peekMeta:()=>Xe,setMeta:()=>na}),nn=Aa({ActionHandler:()=>Fe,Comparable:()=>C,ContainerProxyMixin:()=>Ua,MutableEnumerable:()=>_,RSVP:()=>wt,RegistryProxyMixin:()=>nt,TargetActionSupport:()=>Pa,_ProxyMixin:()=>Sa,_contentFor:()=>taLine truncated
-1
View File
@@ -1 +0,0 @@
import{c as e,g as a,a as s,s as t,A as r,C as o,b as n,D as i,L as l,N as b,d as c,P as p,e as d,S as m,T as u,_ as g,f as y,h as _,i as O,j as S,k as f,l as C,m as P,n as h,o as v,p as j,q as E,r as M,t as D,u as N,v as V,w as A,x as w,y as T,z as I,B as x,E as R,F as k,G as F,H as L,I as z,J as G,K as U,M as B,O as W,Q as Y,R as H,U as J,V as K,W as X,X as q,Y as Q,Z,$,a0 as ee,a1 as ae,a2 as se,a3 as te,a4 as re,a5 as oe,a6 as ne,a7 as ie,a8 as le,a9 as be,aa as pe,ab as de,ac as me,ad as ue,ae as ge,af as ye,ag as _e,ah as Oe,ai as Se,aj as fe,ak as Ce,al as Pe,am as he,an as ve,ao as je,ap as Ee,aq as Me,ar as De,as as Ne,at as Ve,au as Ae,av as we,aw as Te,ax as Ie,ay as xe,az as Re,aA as ke,aB as Fe,aC as Le,aD as ze,aE as Ge,aF as Ue,aG as Be,aH as We,aI as Ye,aJ as He,aK as Je,aL as Ke,aM as Xe,aN as qe,aO as Qe,aP as Ze,aQ as $e,aR as ea,aS as aa,aT as sa,aU as ta,aV as ra,aW as oa,aX as na,aY as ia,aZ as la,a_ as ba,a$ as pa,b0 as da,b1 as ma,b2 as ua,b3 as ga,b4 as ya,b5 as _a,b6 as Oa,b7 as Sa,b8 as fa,b9 as Ca,ba as Pa,bb as ha,bc as va,bd as ja,be as Ea,bf as Ma,bg as Da,bh as Na,bi as Va,bj as Aa,bk as wa,bl as Ta,bm as Ia,bn as xa,bo as Ra,bp as ka,bq as Fa,br as La,bs as za,bt as Ga,bu as Ua,bv as Ba,bw as Wa,bx as Ya,by as Ha,bz as Ja,bA as Ka,bB as Xa,bC as qa,bD as Qa,bE as Za,bF as $a,bG as es,bH as as,bI as ss,bJ as ts,bK as rs,bL as os,bM as ns,bN as is,bO as ls,bP as bs,bQ as cs,bR as ps,bS as ds,bT as ms,bU as us}from"./main-CLZV93ov.js";export{bV as Application,bW as ApplicationNamespace,bX as Array,bY as ArrayProxy,bZ as Controller,b_ as Debug,b$ as EmberDestroyable,c0 as EmberObject,c1 as EnumerableMutable,cc as GlimmerComponent,cd as GlimmerManager,ce as GlimmerReference,cf as GlimmerRuntime,cg as GlimmerUtil,ch as GlimmerValidator,c2 as Instrumentation,c0 as Object,c3 as ObjectCore,c4 as ObjectEvented,c5 as ObjectObservable,c6 as ObjectPromiseProxyMixin,c7 as ObjectProxy,ca as Owner,c8 as Runloop,c9 as Service,cb as VERSION}from"./main-CLZV93ov.js";const gs=new WeakMap,ys=Object.freeze(Object.defineProperty({__proto__:null,ASYNC_OBSERVERS:r,ComputedDescriptor:o,ComputedProperty:n,DEBUG_INJECTION_FUNCTIONS:i,Libraries:l,NAMESPACES:b,NAMESPACES_BY_ID:c,PROPERTY_DID_CHANGE:p,PROXY_CONTENT:d,SYNC_OBSERVERS:m,TrackedDescriptor:u,_getPath:g,_getProp:y,_setProp:_,activateObserver:O,addArrayObserver:S,addListener:f,addNamespace:C,addObserver:P,alias:h,arrayContentDidChange:v,arrayContentWillChange:j,autoComputed:E,beginPropertyChanges:M,cached:(...s)=>{const[t,r,o]=s,n=new WeakMap,i=o.get;o.get=function(){return n.has(this)||n.set(this,e(i.bind(this))),a(n.get(this))}},changeProperties:D,computed:N,createCache:e,defineDecorator:V,defineProperty:A,defineValue:w,deprecateProperty:function(e,a,r){Object.defineProperty(e,a,{configurable:!0,enumerable:!1,set(e){t(this,r,e)},get(){return s(this,r)}})},descriptorForDecorator:T,descriptorForProperty:I,eachProxyArrayDidChange:function(e,a,s,t){let r=gs.get(e);void 0!==r&&r.arrayDidChange(e,a,s,t)},eachProxyArrayWillChange:function(e,a,s,t){let r=gs.get(e);void 0!==r&&r.arrayWillChange(e,a,s,t)},endPropertyChanges:x,expandProperties:R,findNamespace:k,findNamespaces:F,flushAsyncObservers:L,get:s,getCachedValueFor:z,getProperties:G,getValue:a,hasListeners:U,hasUnknownProperty:B,inject:W,isClassicDecorator:Y,isComputed:H,isConst:J,isElementDescriptor:K,isNamespaceSearchDisabled:X,libraries:q,makeComputedDecorator:Q,markObjectAsDirty:Z,nativeDescDecorator:$,notifyPropertyChange:ee,objectAt:ae,on:se,processAllNamespaces:te,processNamespace:re,removeArrayObserver:oe,removeListener:ne,removeNamespace:ie,removeObserver:le,replace:be,replaceInNativeArray:pe,revalidateObservers:de,sendEvent:me,set:t,setClassicDecorator:ue,setNamespaceSearchDisabled:ge,setProperties:ye,setUnprocessedMixins:_e,tagForObject:Oe,tagForProperty:Se,tracked:fe,trySet:Ce},Symbol.toStringTag,{value:"Module"})),_s=Object.freeze(Object.defineProperty({__proto__:null,Input:Pe,Textarea:he,capabilities:ve,default:je,getComponentTemplate:Ee,setComponentManager:Me,setComponentTemplate:De},Symbol.toStringTag,{value:"Module"})),Os=Object.freeze(Object.defineProperty({__proto__:null,EventTarget:Ne,Promise:Ve,all:Ae,allSettled:we,asap:Te,async:Ie,cast:xe,configure:Re,default:ke,defer:Fe,denodeify:Le,filter:ze,hash:Ge,hashSettled:Ue,map:Be,off:We,on:Ye,race:He,reject:Je,resolve:Ke,rethrow:Xe},Symbol.toStringTag,{value:"Module"})),Ss=Object.freeze(Object.defineProperty({__proto__:null,cacheFor:z,guidFor:qe},Symbol.toStringTag,{value:"Module"})),fs=Object.freeze(Object.defineProperty({__proto__:null,default:Qe},Symbol.toStringTag,{value:"Module"})),Cs=Object.freeze(Object.defineProperty({__proto__:null,ENV:Ze,context:$e,getENV:ea,getLookup:aa,global:sa,setLookup:ta},Symbol.toStringTag,{value:"Module"})),Ps=Object.freeze(Object.defineProperty({__proto__:null,Meta:ra,UNDEFINED:oa,counters:na,meta:ia,peekMeta:la,setMeta:ba},Symbol.toStringTag,{value:"Module"})),hs=Object.freeze(Object.definePropLine truncated
-2
View File
@@ -1,2 +0,0 @@
import{ci as t,cj as e,ck as i,cl as r,cm as n,cn as s,co as o,cp as a}from"./main-CLZV93ov.js";var h=Array.isArray;function u(){for(var r,n=[],s=0;s<arguments.length;s++)n[s]=arguments[s];return 1===(n=1===(r=n).length&&h(r[0])?r[0]:r).length?t(n[0]):new e(function(e){return function(r){for(var n=[],s=function(s){n.push(t(e[s]).subscribe(i(r,function(t){if(n){for(var e=0;e<n.length;e++)e!==s&&n[e].unsubscribe();n=null}r.next(t)})))},o=0;n&&!r.closed&&o<e.length;o++)s(o)}}
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */(n))}function l(t,...e){if(!((i=t)instanceof Uint8Array||ArrayBuffer.isView(i)&&"Uint8Array"===i.constructor.name))throw new Error("Uint8Array expected");var i;if(e.length>0&&!e.includes(t.length))throw new Error("Uint8Array expected of length "+e+", got length="+t.length)}function c(t,e=!0){if(t.destroyed)throw new Error("Hash instance has been destroyed");if(e&&t.finished)throw new Error("Hash#digest() has already been called")}function d(...t){for(let e=0;e<t.length;e++)t[e].fill(0)}function f(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}function g(t,e){return t<<32-e|t>>>e}function p(t){return"string"==typeof t&&(t=function(t){if("string"!=typeof t)throw new Error("string expected");return new Uint8Array((new TextEncoder).encode(t))}(t)),l(t),t}class m{}function w(t){const e=e=>t().update(p(e)).digest(),i=t();return e.outputLen=i.outputLen,e.blockLen=i.blockLen,e.create=()=>t(),e}function b(t,e,i){return t&e^~t&i}function y(t,e,i){return t&e^t&i^e&i}class E extends m{constructor(t,e,i,r){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=t,this.outputLen=e,this.padOffset=i,this.isLE=r,this.buffer=new Uint8Array(t),this.view=f(this.buffer)}update(t){c(this),l(t=p(t));const{view:e,buffer:i,blockLen:r}=this,n=t.length;for(let s=0;s<n;){const o=Math.min(r-this.pos,n-s);if(o===r){const e=f(t);for(;r<=n-s;s+=r)this.process(e,s);continue}i.set(t.subarray(s,s+o),this.pos),this.pos+=o,s+=o,this.pos===r&&(this.process(e,0),this.pos=0)}return this.length+=t.length,this.roundClean(),this}digestInto(t){c(this),function(t,e){l(t);const i=e.outputLen;if(t.length<i)throw new Error("digestInto() expects output buffer of length at least "+i)}(t,this),this.finished=!0;const{buffer:e,view:i,blockLen:r,isLE:n}=this;let{pos:s}=this;e[s++]=128,d(this.buffer.subarray(s)),this.padOffset>r-s&&(this.process(i,0),s=0);for(let l=s;l<r;l++)e[l]=0;!function(t,e,i,r){if("function"==typeof t.setBigUint64)return t.setBigUint64(e,i,r);const n=BigInt(32),s=BigInt(4294967295),o=Number(i>>n&s),a=Number(i&s),h=r?4:0,u=r?0:4;t.setUint32(e+h,o,r),t.setUint32(e+u,a,r)}(i,r-8,BigInt(8*this.length),n),this.process(i,0);const o=f(t),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const h=a/4,u=this.get();if(h>u.length)throw new Error("_sha2: outputLen bigger than state");for(let l=0;l<h;l++)o.setUint32(4*l,u[l],n)}digest(){const{buffer:t,outputLen:e}=this;this.digestInto(t);const i=t.slice(0,e);return this.destroy(),i}_cloneInto(t){t||(t=new this.constructor),t.set(...this.get());const{blockLen:e,buffer:i,length:r,finished:n,destroyed:s,pos:o}=this;return t.destroyed=s,t.finished=n,t.length=r,t.pos=o,r%e&&t.buffer.set(i),t}clone(){return this._cloneInto()}}const _=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),x=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),A=new Uint32Array(64);class L extends E{constructor(t=32){super(64,t,8,!1),this.A=0|_[0],this.B=0|_[1],this.C=0|_[2],this.D=0|_[3],this.E=0|_[4],this.F=0|_[5],this.G=0|_[6],this.H=0|_[7]}get(){const{A:t,B:e,C:i,D:r,E:n,F:s,G:o,H:a}=this;return[t,e,i,r,n,s,o,a]}set(t,e,i,r,n,s,o,a){this.A=0|t,this.B=0|e,this.C=0|i,this.D=0|r,this.E=0|n,this.F=0|s,this.G=0|o,this.H=0|a}process(t,e){for(let l=0;l<16;l++,e+=4)A[l]=t.getUint32(e,!1);for(let l=16;l<64;l++){const t=A[l-15],e=A[l-2],i=g(t,7)^g(t,18)^t>>>3,r=g(e,17)^g(e,19)^e>>>10;A[l]=r+A[l-7]+i+A[l-16]|0}let{A:i,B:r,C:n,D:s,E:o,F:a,G:h,H:u}=this;for(let l=0;l<64;l++){const t=u+(g(o,6)^g(o,11)^g(o,25))+b(o,a,h)+x[l]+A[l]|0,e=(g(i,2)^g(i,13)^g(i,22))+y(i,r,n)|0;u=h,h=a,a=o,o=s+t|0,s=n,n=r,r=i,i=t+e|0}i=i+this.A|0,r=r+this.B|0,n=n+this.C|0,s=s+this.D|0,o=o+this.E|0,a=a+this.F|0,h=h+this.G|0,u=u+this.H|0,this.set(i,r,n,s,o,a,h,u)}roundClean(){d(A)}destroy(){this.set(0,0,0,0,0,0,0,0),d(this.buffer)}}const U=w(()=>new L),I=32;class B{_raw;length;constructor(t){this._raw=new Uint8Array(t||512),this.length=t?t.length:0}unwrap(){return this._raw.subarray(0,this.length)}get capacity(){return this._raw.byteLength}extend(t){if(t instanceof B&&(t=t.unwrap()),"number"!=typeof t.length)throw Error("bad length");const e=t.length+this.length;if(this.capacity<e){const t=this._raw,i=Math.max(2*this.capacity,e)Line truncated
+1
View File
@@ -0,0 +1 @@
import{C as t,D as e,E as i,O as r,S as n,T as s,b as a,w as o,x as h,y as u}from"./main-CAv0oB3R.js";function l(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];return 1===(t=n(t)).length?s(t[0]):new e(function(t){return function(e){for(var r=[],n=function(n){r.push(s(t[n]).subscribe(i(e,function(t){if(r){for(var i=0;i<r.length;i++)i!==n&&r[i].unsubscribe();r=null}e.next(t)})))},a=0;r&&!e.closed&&a<t.length;a++)n(a)}}(t))}var d=32,c=class t{_raw;length;constructor(t){this._raw=new Uint8Array(t||512),this.length=t?t.length:0}unwrap(){return this._raw.subarray(0,this.length)}get capacity(){return this._raw.byteLength}extend(e){if(e instanceof t&&(e=e.unwrap()),"number"!=typeof e.length)throw Error("bad length");const i=e.length+this.length;if(this.capacity<i){const t=this._raw,e=Math.max(2*this.capacity,i);this._raw=new Uint8Array(e),this._raw.set(t)}this._raw.set(e,this.length),this.length+=e.length}shift(){const t=this._raw[0];return this._raw=this._raw.subarray(1),this.length--,t}shiftN(t=1){const e=this._raw.subarray(0,t);return this._raw=this._raw.subarray(t),this.length-=t,e}};function f(t){let e=0;for(;;){if(0===t.length)throw Error("parse ends prematurely");let i=t.shift();if(e=e<<7|127&i,!(128&i))break}return e}function m(t){if(0===t)return new c([0]);let e=[];for(;0!==t;)e.push(127&t),t>>>=7;e.reverse();for(let i=0;i<e.length-1;i++)e[i]|=128;return new c(e)}function g(t,e){if(t.length<e)throw Error("parse ends prematurely");return t.shiftN(e)}var p=class{buf;sha256;constructor(){this.buf=new Uint8Array(d),this.sha256=async t=>u.create().update(t).digest()}setToZero(){this.buf=new Uint8Array(d)}add(t){let e=0,i=0,r=new DataView(this.buf.buffer),n=new DataView(t.buffer);for(let s=0;s<8;s++){let t=4*s,a=r.getUint32(t,!0);a+=e,a+=n.getUint32(t,!0),a>4294967295&&(i=1),r.setUint32(t,4294967295&a,!0),e=i,i=0}}negate(){let t=new DataView(this.buf.buffer);for(let i=0;i<8;i++){let e=4*i;t.setUint32(e,~t.getUint32(e,!0))}let e=new Uint8Array(d);e[0]=1,this.add(e)}async getFingerprint(t){let e=new c;return e.extend(this.buf),e.extend(m(t)),(await this.sha256(e.unwrap())).subarray(0,16)}},w=class{items;sealed;constructor(){this.items=[],this.sealed=!1}insert(t,e){if(this.sealed)throw Error("already sealed");if((e=y(e)).byteLength!==d)throw Error("bad id size for added item");this.items.push({timestamp:t,id:e})}seal(){if(this.sealed)throw Error("already sealed");this.sealed=!0,this.items.sort(A);for(let t=1;t<this.items.length;t++)if(0===A(this.items[t-1],this.items[t]))throw Error("duplicate item inserted")}unseal(){this.sealed=!1}size(){return this._checkSealed(),this.items.length}getItem(t){if(this._checkSealed(),t>=this.items.length)throw Error("out of range");return this.items[t]}iterate(t,e,i){this._checkSealed(),this._checkBounds(t,e);for(let r=t;r<e&&i(this.items[r],r);++r);}findLowerBound(t,e,i){return this._checkSealed(),this._checkBounds(t,e),this._binarySearch(this.items,t,e,t=>A(t,i)<0)}async fingerprint(t,e){let i=new p;return i.setToZero(),this.iterate(t,e,t=>(i.add(t.id),!0)),await i.getFingerprint(e-t)}_checkSealed(){if(!this.sealed)throw Error("not sealed")}_checkBounds(t,e){if(t>e||e>this.items.length)throw Error("bad range")}_binarySearch(t,e,i,r){let n=i-e;for(;n>0;){let i=e,s=Math.floor(n/2);i+=s,r(t[i])?(e=++i,n-=s+1):n=s}return e}},b=class{storage;frameSizeLimit;lastTimestampIn;lastTimestampOut;isInitiator;wantUint8ArrayOutput;constructor(t,e=0){if(0!==e&&e<4096)throw Error("frameSizeLimit too small");this.storage=t,this.frameSizeLimit=e,this.lastTimestampIn=0,this.lastTimestampOut=0}_bound(t,e){return{timestamp:t,id:e||new Uint8Array(0)}}async initiate(){if(this.isInitiator)throw Error("already initiated");this.isInitiator=!0;let t=new c;return t.extend([97]),await this.splitRange(0,this.storage.size(),this._bound(Number.MAX_VALUE),t),this._renderOutput(t)}setInitiator(){this.isInitiator=!0}async reconcile(t){let e=[],i=[],r=new c(y(t));this.lastTimestampIn=this.lastTimestampOut=0;let n=new c;n.extend([97]);let s=g(r,1)[0];if(s<96||s>111)throw Error("invalid negentropy protocol version byte");if(97!==s){if(this.isInitiator)throw Error("unsupported negentropy protocol version requested: "+(s-96));return[this._renderOutput(n),e,i]}let a=this.storage.size(),o=this._bound(0),h=0,u=!1;for(;0!==r.length;){let t=new c,s=()=>{u&&(u=!1,t.extend(this.encodeBound(o)),t.extend(m(0)))},l=this.decodeBound(r),p=f(r),w=h,b=this.storage.findLowerBound(h,a,l);if(0===p)u=!0;else if(1===p)0!==E(g(r,16),await this.storage.fingerprint(w,b))?(s(),await this.splitRange(w,b,l,t)):u=!0;else{if(2!==p)throw Error("unexpected mode");{let a=f(r),o={};for(let t=0;t<a;t++){let t=g(r,d);this.isInitiator&&(o[t]=t)}if(this.isInitiator){u=!0,this.storage.iterate(w,b,t=>{let i=t.id;return o[i]?delete o[i]:this.isInitiator&&e.push(this.wantUint8ArrayOutput?i:x(i)),!0});for(let t of Object.values(o))i.push(this.wantUint8ArrayOutput?t:x(t))}else{s();let e=new c,i=0,r=l;this.storage.iterate(w,b,(t,s)=>this.exceededFLine truncated
+2 -2
View File
@@ -39,8 +39,8 @@
<meta name="msapplication-TileColor" content="#F6E9A6"> <meta name="msapplication-TileColor" content="#F6E9A6">
<meta name="msapplication-TileImage" content="/icons/icon-144.png"> <meta name="msapplication-TileImage" content="/icons/icon-144.png">
<script type="module" crossorigin src="/assets/main-CLZV93ov.js"></script> <script type="module" crossorigin src="/assets/main-CAv0oB3R.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-COnSXoPt.css"> <link rel="stylesheet" crossorigin href="/assets/main-CIu0qcV7.css">
</head> </head>
<body> <body>
<div id="modal-portal"></div> <div id="modal-portal"></div>
+219
View File
@@ -0,0 +1,219 @@
import { module, test } from 'qunit';
import { visit, currentURL, click, waitFor } from '@ember/test-helpers';
import { setupApplicationTest } from 'marco/tests/helpers';
import Service from '@ember/service';
class MockOsmService extends Service {
async fetchOsmObject() {
return null;
}
}
class MockStorageService extends Service {
initialSyncDone = true;
savedPlaces = [
{
id: 'place-123',
title: 'Mountain Trail',
geohash: 'u33dc0',
osmTags: { name: 'Mountain Trail' },
},
];
lists = [
{
id: 'to-go',
title: 'Want to go',
color: '#2e9e4f',
placeRefs: [{ id: 'place-123', geohash: 'u33dc0' }],
},
{ id: 'to-do', title: 'To do', color: '#2a7fff', placeRefs: [] },
];
findPlaceById(id) {
if (id === 'place-123') {
return this.savedPlaces[0];
}
return null;
}
isPlaceSaved() {
return true;
}
loadPlacesInBounds() {
return [];
}
getPlacesInList(listId) {
if (listId === 'to-go') {
return Promise.resolve([this.savedPlaces[0]]);
}
return Promise.resolve([]);
}
rs = {
on: () => {},
};
}
module('Acceptance | collections navigation', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
this.owner.register('service:osm', MockOsmService);
this.owner.register('service:storage', MockStorageService);
});
test('navigating through the collections menu hierarchy, viewing list places, and going back', async function (assert) {
// 1. Visit Home Map
await visit('/');
assert.strictEqual(currentURL(), '/');
// 2. Open the App Menu overlay
await click('.menu-btn-integrated');
assert.dom('.sidebar.app-menu-pane').exists('App menu sidebar is open');
assert
.dom('.app-menu')
.includesText('Collections', 'Menu contains Collections link');
// 3. Transition to Collections Index (List of lists)
await click(document.querySelectorAll('.app-menu button')[0]); // Click "Collections"
assert.strictEqual(currentURL(), '/lists', 'Transitions to /lists index');
assert
.dom('.sidebar-header-text-centered')
.includesText('Collections', 'Header is centered and titled Collections');
assert
.dom('.lists-index-item')
.exists({ count: 2 }, 'Renders our 2 mocked list items');
// 4. Transition to a specific list (Want to go)
await click(document.querySelectorAll('.lists-index-item')[0]); // Click "Want to go"
assert.strictEqual(
currentURL(),
'/lists/to-go',
'Transitions instantly to /lists/to-go'
);
// 5. Verify background loading spinner shows up, then results populate
await waitFor('.places-list');
assert
.dom('.places-list .place-name')
.hasText('Mountain Trail', 'Renders the saved place from the list');
assert
.dom('.places-list .place-type')
.hasText('Saved place', 'Place type displays Saved place correctly');
// 6. Click back button in collection list header
await click('.sidebar-header .back-btn');
assert.strictEqual(currentURL(), '/lists', 'Goes back to /lists index');
// 7. Click back button in collections index header
await click('.sidebar-header .back-btn');
assert.strictEqual(currentURL(), '/menu', 'Goes back to main menu route');
// 8. Close sidebar
await click('.sidebar-header .close-btn');
assert.strictEqual(currentURL(), '/', 'Sidebar closed and returned home');
});
test('clicking a place inside a collection sets returnToRoute and returns gracefully on back click', async function (assert) {
await visit('/lists/to-go');
await waitFor('.places-list');
// Click on the place item to view details
await click('.place-item');
assert.ok(
currentURL().includes('/place/place-123'),
'Transitions to place details route'
);
// Click back from place details
await click('.back-btn');
assert.strictEqual(
currentURL(),
'/lists/to-go',
'Returns gracefully back to lists/to-go list view'
);
});
test('places inside a collection are sorted by createdAt descending', async function (assert) {
class SortedMockStorageService extends Service {
initialSyncDone = true;
savedPlaces = [
{
id: 'place-oldest',
title: 'Oldest Place',
geohash: 'u33dc0',
createdAt: '2023-01-01T12:00:00.000Z',
osmTags: { name: 'Oldest Place' },
},
{
id: 'place-newest',
title: 'Newest Place',
geohash: 'u33dc0',
createdAt: '2023-01-03T12:00:00.000Z',
osmTags: { name: 'Newest Place' },
},
{
id: 'place-middle',
title: 'Middle Place',
geohash: 'u33dc0',
createdAt: '2023-01-02T12:00:00.000Z',
updatedAt: '2023-01-04T12:00:00.000Z',
osmTags: { name: 'Middle Place' },
},
];
lists = [
{
id: 'to-go',
title: 'Want to go',
color: '#2e9e4f',
placeRefs: [
{ id: 'place-oldest', geohash: 'u33dc0' },
{ id: 'place-newest', geohash: 'u33dc0' },
{ id: 'place-middle', geohash: 'u33dc0' },
],
},
];
findPlaceById(id) {
return this.savedPlaces.find((p) => p.id === id) || null;
}
isPlaceSaved() {
return true;
}
loadPlacesInBounds() {
return [];
}
getPlacesInList(listId) {
if (listId === 'to-go') {
return Promise.resolve(this.savedPlaces);
}
return Promise.resolve([]);
}
rs = {
on: () => {},
};
}
this.owner.unregister('service:storage');
this.owner.register('service:storage', SortedMockStorageService);
await visit('/lists/to-go');
await waitFor('.places-list');
const placeNames = Array.from(
document.querySelectorAll('.places-list .place-name')
).map((el) => el.textContent.trim());
assert.deepEqual(
placeNames,
['Newest Place', 'Middle Place', 'Oldest Place'],
'Places are ordered by createdAt in descending order'
);
});
});
+2 -56
View File
@@ -2,7 +2,6 @@ import { module, test } from 'qunit';
import { visit, currentURL, waitFor, triggerEvent } from '@ember/test-helpers'; import { visit, currentURL, waitFor, triggerEvent } from '@ember/test-helpers';
import { setupApplicationTest } from 'marco/tests/helpers'; import { setupApplicationTest } from 'marco/tests/helpers';
import Service from '@ember/service'; import Service from '@ember/service';
import sinon from 'sinon';
module('Acceptance | map search reset', function (hooks) { module('Acceptance | map search reset', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
@@ -17,58 +16,10 @@ module('Acceptance | map search reset', function (hooks) {
'marco:map-view', 'marco:map-view',
JSON.stringify(highZoomState) JSON.stringify(highZoomState)
); );
// Stub window.fetch using Sinon
// We want to intercept map style requests and let everything else through
this.fetchStub = sinon.stub(window, 'fetch');
this.fetchStub.callsFake(async (input, init) => {
let url = input;
if (typeof input === 'object' && input !== null && 'url' in input) {
url = input.url;
}
if (
typeof url === 'string' &&
url.includes('tiles.openfreemap.org/styles/liberty')
) {
return {
ok: true,
status: 200,
json: async () => ({
version: 8,
name: 'Liberty',
sources: {
openmaptiles: {
type: 'vector',
url: 'https://tiles.openfreemap.org/planet',
},
},
layers: [
{
id: 'background',
type: 'background',
paint: {
'background-color': '#123456',
},
},
],
glyphs:
'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf',
sprite: 'https://tiles.openfreemap.org/sprites/liberty',
}),
};
}
// Pass through to the original implementation
return this.fetchStub.wrappedMethod(input, init);
});
}); });
hooks.afterEach(function () { hooks.afterEach(function () {
window.localStorage.removeItem('marco:map-view'); window.localStorage.removeItem('marco:map-view');
// Restore the original fetch
this.fetchStub.restore();
}); });
test('clicking the map clears the category search parameter', async function (assert) { test('clicking the map clears the category search parameter', async function (assert) {
@@ -150,7 +101,7 @@ module('Acceptance | map search reset', function (hooks) {
'Should have stayed on the search route with markers intact' 'Should have stayed on the search route with markers intact'
); );
// Second Click (Start new search) // Second Click (Clear search and markers)
// Click slightly differently to ensure fresh event // Click slightly differently to ensure fresh event
await triggerEvent(canvas, 'pointerdown', { await triggerEvent(canvas, 'pointerdown', {
clientX: 250, clientX: 250,
@@ -174,11 +125,6 @@ module('Acceptance | map search reset', function (hooks) {
// 3. Wait for transition // 3. Wait for transition
await new Promise((r) => setTimeout(r, 1000)); await new Promise((r) => setTimeout(r, 1000));
const newUrl = currentURL(); assert.strictEqual(currentURL(), '/', 'Should have transitioned to index');
assert.notOk(
newUrl.includes('category=coffee'),
`New URL ${newUrl} should not contain category param`
);
assert.ok(newUrl.includes('/search'), 'Should be on search route');
}); });
}); });
+17 -2
View File
@@ -85,7 +85,7 @@ module('Acceptance | navigation', function (hooks) {
); );
}); });
test('closing the sidebar resets the returnToSearch flag', async function (assert) { test('closing the sidebar transitions back to search route when opened from search results', async function (assert) {
const mapUi = this.owner.lookup('service:map-ui'); const mapUi = this.owner.lookup('service:map-ui');
await visit('/search?lat=1&lon=1'); await visit('/search?lat=1&lon=1');
@@ -97,7 +97,22 @@ module('Acceptance | navigation', function (hooks) {
await click('.close-btn'); await click('.close-btn');
assert.dom('.sidebar').doesNotExist('Sidebar should be closed'); assert.dom('.sidebar').doesNotExist('Sidebar should be closed');
assert.ok(currentURL().includes('/place/'), 'Remains on place route'); assert.strictEqual(
currentURL(),
'/search?lat=1&lon=1',
'Should transition back to search route'
);
});
test('closing the sidebar when visiting a place directly transitions to index', async function (assert) {
await visit('/place/osm:node:123');
assert.ok(currentURL().includes('/place/'), 'Visited place directly');
// Click the Close (X) button
await click('.close-btn');
assert.dom('.sidebar').doesNotExist('Sidebar should be closed');
assert.strictEqual(currentURL(), '/', 'Should transition back to index');
}); });
test('navigating directly to place and back closes sidebar', async function (assert) { test('navigating directly to place and back closes sidebar', async function (assert) {
+73
View File
@@ -169,4 +169,77 @@ module('Acceptance | search loading', function (hooks) {
// Verify we are back on index (or at least query is gone) // Verify we are back on index (or at least query is gone)
assert.strictEqual(currentURL(), '/', 'Navigated to index'); assert.strictEqual(currentURL(), '/', 'Navigated to index');
}); });
test('quick search pills visibility during category search transition', async function (assert) {
const mapUi = this.owner.lookup('service:map-ui');
mapUi.currentZoom = 15;
// Seed localStorage with a high zoom level to ensure quick search buttons show
const highZoomState = {
center: [13.4, 52.5],
zoom: 18,
};
window.localStorage.setItem(
'marco:map-view',
JSON.stringify(highZoomState)
);
try {
// Make sure quick search buttons setting is enabled
const settings = this.owner.lookup('service:settings');
settings.showQuickSearchButtons = true;
// 1. Visit slowly loading category search
const catPromise = visit('/search?category=slow_category&lat=1&lon=1');
await new Promise((r) => setTimeout(r, 10));
// Verify loading state is set and pills are visible (i.e. header-center does NOT have .searching)
assert.ok(mapUi.loadingState, 'Search is loading');
assert
.dom('.header-center')
.doesNotHaveClass(
'searching',
'Pills remain visible while search is loading'
);
// Resolve the promise with empty results
osmResolve();
await catPromise;
await settled();
// Verify search completed and since results are empty, pills are still visible
assert.strictEqual(mapUi.searchResults.length, 0, 'No results found');
assert
.dom('.header-center')
.doesNotHaveClass(
'searching',
'Pills remain visible after search completes with no results'
);
// 2. Now simulate a fast category search that returns results
const osmService = this.owner.lookup('service:osm');
osmService.getCategoryPois = async () => [
{
title: 'Latte Art Cafe',
lat: 1,
lon: 1,
osmId: '101',
osmType: 'N',
},
];
await visit('/search?category=coffee&lat=1&lon=1');
// Verify search completed with results, so pills are hidden
assert.ok(mapUi.searchResults.length > 0, 'Results found');
assert
.dom('.header-center')
.hasClass(
'searching',
'Pills are hidden after search completes with results'
);
} finally {
window.localStorage.removeItem('marco:map-view');
}
});
}); });
+72
View File
@@ -270,4 +270,76 @@ module('Acceptance | search', function (hooks) {
.hasText('Search request failed. Please try again.'); .hasText('Search request failed. Please try again.');
assert.dom('.places-sidebar').doesNotExist('Results panel should not open'); assert.dom('.places-sidebar').doesNotExist('Results panel should not open');
}); });
test('search box query synchronized with active route query parameters', async function (assert) {
// Mock Osm Service
class MockOsmService extends Service {
async getCategoryPois() {
return [];
}
}
this.owner.register('service:osm', MockOsmService);
// Mock Photon Service
class MockPhotonService extends Service {
async search() {
return [];
}
}
this.owner.register('service:photon', MockPhotonService);
// Mock Storage Service
class MockStorageService extends Service {
savedPlaces = [];
findPlaceById() {
return null;
}
isPlaceSaved() {
return false;
}
rs = { on: () => {} };
placesInView = [];
loadPlacesInBounds() {
return Promise.resolve();
}
}
this.owner.register('service:storage', MockStorageService);
// Mock Map Service
class MockMapService extends Service {
getBounds() {
return {
minLat: 52.5,
minLon: 13.4,
maxLat: 52.6,
maxLon: 13.5,
};
}
}
this.owner.register('service:map', MockMapService);
// 1. Visit a search URL directly
await visit('/search?q=Berlin');
assert
.dom('.search-input')
.hasValue(
'Berlin',
'Search input is populated with search term on direct load'
);
// 2. Visit a category search URL
await visit('/search?category=coffee&lat=52.52&lon=13.405');
assert
.dom('.search-input')
.hasValue(
'Coffee',
'Search input is populated with mapped category label'
);
// 3. Go back to index
await visit('/');
assert
.dom('.search-input')
.hasValue('', 'Search input is cleared on transitioning to index');
});
}); });
+87
View File
@@ -4,6 +4,92 @@ import {
setupTest as upstreamSetupTest, setupTest as upstreamSetupTest,
} from 'ember-qunit'; } from 'ember-qunit';
import { setupNostrMocks } from './mock-nostr'; import { setupNostrMocks } from './mock-nostr';
import sinon from 'sinon';
function setupMapStyleMocks(hooks) {
hooks.beforeEach(function () {
// Stub window.fetch to capture map-style assets before they hit the network
this.fetchStub = sinon.stub(window, 'fetch');
this.fetchStub.callsFake(async (input, init) => {
let url = input;
if (typeof input === 'object' && input !== null && 'url' in input) {
url = input.url;
}
if (typeof url === 'string' && url.includes('tiles.openfreemap.org')) {
// A. Mock Style Sheet
if (url.includes('/styles/liberty')) {
return {
ok: true,
status: 200,
json: async () => ({
version: 8,
name: 'Mock Style',
sources: {
openmaptiles: {
type: 'vector',
tiles: [], // Empty tiles list prevents any map tile fetching completely!
},
},
sprite: 'https://tiles.openfreemap.org/sprites/liberty',
glyphs:
'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf',
layers: [
{
id: 'background',
type: 'background',
paint: { 'background-color': '#f8f9fa' },
},
],
}),
};
}
// B. Mock Sprite Atlas JSON
if (url.endsWith('.json') && url.includes('/sprites/')) {
return {
ok: true,
status: 200,
json: async () => ({}), // Empty sprite dictionary
};
}
// C. Mock Sprite Atlas PNG (Returns a valid 1x1 transparent PNG)
if (url.endsWith('.png') && url.includes('/sprites/')) {
const bytes = new Uint8Array([
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0,
0, 1, 0, 0, 0, 1, 8, 6, 0, 0, 0, 31, 21, 196, 137, 0, 0, 0, 11, 73,
68, 65, 84, 120, 156, 99, 96, 0, 0, 0, 2, 0, 1, 226, 33, 188, 51, 0,
0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
]);
const blob = new Blob([bytes], { type: 'image/png' });
return {
ok: true,
status: 200,
blob: async () => blob,
};
}
// Catch-all mock for other openfreemap endpoints
return {
ok: true,
status: 200,
json: async () => ({}),
};
}
// Pass through to original fetch (e.g. Photon results, local mock APIs)
return this.fetchStub.wrappedMethod(input, init);
});
});
hooks.afterEach(function () {
if (this.fetchStub && typeof this.fetchStub.restore === 'function') {
this.fetchStub.restore();
}
});
}
// This file exists to provide wrappers around ember-qunit's // This file exists to provide wrappers around ember-qunit's
// test setup functions. This way, you can easily extend the setup that is // test setup functions. This way, you can easily extend the setup that is
@@ -12,6 +98,7 @@ import { setupNostrMocks } from './mock-nostr';
function setupApplicationTest(hooks, options) { function setupApplicationTest(hooks, options) {
upstreamSetupApplicationTest(hooks, options); upstreamSetupApplicationTest(hooks, options);
setupNostrMocks(hooks); setupNostrMocks(hooks);
setupMapStyleMocks(hooks);
// Additional setup for application tests can be done here. // Additional setup for application tests can be done here.
// //
+10 -2
View File
@@ -49,11 +49,19 @@ export class MockNostrDataService extends Service {
return []; return [];
} }
get defaultReadRelays() { get requiredReadRelays() {
return []; return [];
} }
get defaultWriteRelays() { get requiredWriteRelays() {
return [];
}
get mailboxReadRelays() {
return [];
}
get mailboxWriteRelays() {
return []; return [];
} }
@@ -0,0 +1,188 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'marco/tests/helpers';
import { click, fillIn, render } from '@ember/test-helpers';
import Service, { service } from '@ember/service';
import AppMenuSettingsNostr from 'marco/components/app-menu/settings/nostr';
import {
excludeRequiredRelays,
mergeRequiredRelays,
uniqNormalizedRelays,
} from 'marco/utils/nostr';
class MockNostrDataService extends Service {
@service settings;
requiredReadRelays = ['wss://nostr.kosmos.org'];
requiredWriteRelays = [];
mailboxReadRelays = ['wss://mailbox.example.com'];
mailboxWriteRelays = ['wss://mailbox-write.example.com'];
get configuredReadRelays() {
const configured = uniqNormalizedRelays([
...this.mailboxReadRelays,
...(this.settings.nostrReadRelays || []),
]);
return excludeRequiredRelays(
configured,
this.settings.nostrReadRelayExclusions || []
);
}
get configuredWriteRelays() {
const configured = uniqNormalizedRelays([
...this.mailboxWriteRelays,
...(this.settings.nostrWriteRelays || []),
]);
return excludeRequiredRelays(
configured,
this.settings.nostrWriteRelayExclusions || []
);
}
get activeReadRelays() {
return mergeRequiredRelays(
this.requiredReadRelays,
this.configuredReadRelays
);
}
get activeWriteRelays() {
return mergeRequiredRelays(
this.requiredWriteRelays,
this.configuredWriteRelays
);
}
async clearCache() {}
}
function readRows(element) {
const list = element.querySelectorAll('.relay-list')[0];
return [...list.querySelectorAll('li')];
}
function writeRows(element) {
const list = element.querySelectorAll('.relay-list')[1];
return [...list.querySelectorAll('li')];
}
function rowByText(rows, text) {
return rows.find((row) => row.textContent.includes(text));
}
module('Integration | Component | app-menu/settings/nostr', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
localStorage.removeItem('marco:settings');
this.owner.register('service:nostrData', MockNostrDataService);
this.settings = this.owner.lookup('service:settings');
this.onChange = () => {};
});
hooks.afterEach(function () {
localStorage.removeItem('marco:settings');
});
async function renderAndOpenDetails(context) {
await render(
<template><AppMenuSettingsNostr @onChange={{this.onChange}} /></template>
);
await click('summary');
return context.element;
}
test('required read relay is first and non-removable', async function (assert) {
const element = await renderAndOpenDetails(this);
const rows = readRows(element);
assert.dom(rows[0]).includesText('nostr.kosmos.org');
const requiredRow = rowByText(rows, 'nostr.kosmos.org');
const mailboxRow = rowByText(rows, 'mailbox.example.com');
assert.dom(requiredRow.querySelector('.btn-remove-relay')).doesNotExist();
assert.dom(mailboxRow.querySelector('.btn-remove-relay')).exists();
});
test('removing mailbox read relay stores exclusion override', async function (assert) {
const element = await renderAndOpenDetails(this);
const mailboxRow = rowByText(readRows(element), 'mailbox.example.com');
await click(mailboxRow.querySelector('.btn-remove-relay'));
assert.deepEqual(this.settings.nostrReadRelayExclusions, [
'wss://mailbox.example.com',
]);
assert.strictEqual(this.settings.nostrReadRelays, null);
});
test('removing custom read relay updates custom list without exclusions', async function (assert) {
this.settings.update('nostrReadRelays', ['wss://custom.example.com']);
const element = await renderAndOpenDetails(this);
const customRow = rowByText(readRows(element), 'custom.example.com');
await click(customRow.querySelector('.btn-remove-relay'));
assert.strictEqual(this.settings.nostrReadRelays, null);
assert.strictEqual(this.settings.nostrReadRelayExclusions, null);
});
test('adding read relay clears existing exclusion for same relay', async function (assert) {
this.settings.update('nostrReadRelayExclusions', [
'wss://mailbox.example.com',
]);
const element = await renderAndOpenDetails(this);
await fillIn('#new-read-relay', 'Mailbox.EXAMPLE.com/');
await click(element.querySelector('#new-read-relay').nextElementSibling);
assert.deepEqual(this.settings.nostrReadRelays, [
'wss://mailbox.example.com',
]);
assert.strictEqual(this.settings.nostrReadRelayExclusions, null);
});
test('reset read relays clears additions and exclusions', async function (assert) {
this.settings.update('nostrReadRelays', ['wss://custom.example.com']);
this.settings.update('nostrReadRelayExclusions', [
'wss://mailbox.example.com',
]);
const element = await renderAndOpenDetails(this);
await click(element.querySelectorAll('.reset-relays')[0]);
assert.strictEqual(this.settings.nostrReadRelays, null);
assert.strictEqual(this.settings.nostrReadRelayExclusions, null);
const requiredRow = rowByText(readRows(element), 'nostr.kosmos.org');
assert.dom(requiredRow).exists();
});
test('write relays are removable and mailbox delete stores exclusion', async function (assert) {
this.settings.update('nostrWriteRelays', [
'wss://custom-write.example.com',
]);
const element = await renderAndOpenDetails(this);
const rows = writeRows(element);
assert.true(
rows.every((row) => row.querySelector('.btn-remove-relay')),
'all write relays can be removed'
);
const mailboxRow = rowByText(rows, 'mailbox-write.example.com');
await click(mailboxRow.querySelector('.btn-remove-relay'));
assert.deepEqual(this.settings.nostrWriteRelayExclusions, [
'wss://mailbox-write.example.com',
]);
});
});
@@ -6,6 +6,9 @@ import PhotoGallery from 'marco/components/photo-gallery';
import { setupNostrMocks } from 'marco/tests/helpers/mock-nostr'; import { setupNostrMocks } from 'marco/tests/helpers/mock-nostr';
import sinon from 'sinon'; import sinon from 'sinon';
const USER_A = 'a'.repeat(64);
const USER_B = 'b'.repeat(64);
class MockBlossomService extends Service { class MockBlossomService extends Service {
async delete() { async delete() {
return true; return true;
@@ -34,7 +37,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
this.photos = [ this.photos = [
{ {
eventId: 'event1', eventId: 'event1',
pubkey: 'userA', pubkey: USER_A,
placeIdentifier: 'osm:node:12345', placeIdentifier: 'osm:node:12345',
url: 'https://example.com/a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1.jpg', url: 'https://example.com/a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1.jpg',
thumbUrl: thumbUrl:
@@ -42,7 +45,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}, },
{ {
eventId: 'event2', eventId: 'event2',
pubkey: 'userB', pubkey: USER_B,
placeIdentifier: 'osm:node:12345', placeIdentifier: 'osm:node:12345',
url: 'photo2.jpg', url: 'photo2.jpg',
}, },
@@ -55,7 +58,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it does not show delete button if user is not creator', async function (assert) { test('it does not show delete button if user is not creator', async function (assert) {
this.nostrAuth.pubkey = 'userB'; // Different from photo1's pubkey this.nostrAuth.pubkey = USER_B; // Different from photo1's pubkey
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
await render( await render(
@@ -80,7 +83,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it shows delete button if user is creator and setting is enabled', async function (assert) { test('it shows delete button if user is creator and setting is enabled', async function (assert) {
this.nostrAuth.pubkey = 'userA'; // Matches photo1's pubkey this.nostrAuth.pubkey = USER_A; // Matches photo1's pubkey
this.settings.update('experimentalEnablePhotoDeletion', true); // Enable the setting this.settings.update('experimentalEnablePhotoDeletion', true); // Enable the setting
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
@@ -106,7 +109,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it handles cancellation of deletion', async function (assert) { test('it handles cancellation of deletion', async function (assert) {
this.nostrAuth.pubkey = 'userA'; this.nostrAuth.pubkey = USER_A;
this.settings.update('experimentalEnablePhotoDeletion', true); this.settings.update('experimentalEnablePhotoDeletion', true);
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
@@ -133,7 +136,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it performs full deletion flow when confirmed', async function (assert) { test('it performs full deletion flow when confirmed', async function (assert) {
this.nostrAuth.pubkey = 'userA'; this.nostrAuth.pubkey = USER_A;
this.settings.update('experimentalEnablePhotoDeletion', true); this.settings.update('experimentalEnablePhotoDeletion', true);
// Override the mock's getter just for this test // Override the mock's getter just for this test
Object.defineProperty(this.nostrAuth, 'signer', { Object.defineProperty(this.nostrAuth, 'signer', {
@@ -141,11 +144,11 @@ module('Integration | Component | photo-gallery', function (hooks) {
get: () => ({ get: () => ({
signEvent: async (e) => ({ signEvent: async (e) => ({
...e, ...e,
id: 'signed-id', id: 'a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
sig: 'sig', sig: 'b3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
pubkey: 'userA', pubkey: USER_A,
}), }),
getPublicKey: async () => 'userA', getPublicKey: async () => USER_A,
}), }),
}); });
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
@@ -227,7 +230,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it copies event id to clipboard', async function (assert) { test('it copies event id to clipboard', async function (assert) {
this.nostrAuth.pubkey = 'userA'; this.nostrAuth.pubkey = USER_A;
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
const clipboardStub = sinon const clipboardStub = sinon
@@ -334,4 +334,54 @@ module('Integration | Component | place-details', function (hooks) {
assert.dom(links[0]).hasText('+44 987 654 321'); assert.dom(links[0]).hasText('+44 987 654 321');
assert.dom(links[1]).hasText('+1 234-567 8900'); assert.dom(links[1]).hasText('+1 234-567 8900');
}); });
test('it renders correct OpenStreetMap link for an OSM place', async function (assert) {
const place = {
title: 'OSM Place',
osmId: '12345',
osmType: 'node',
lat: 52.520008,
lon: 13.404954,
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const osmLink = this.element.querySelector(
'.meta-info a[href^="https://www.openstreetmap.org/node/12345"]'
);
assert.ok(osmLink, 'OpenStreetMap link is rendered');
assert.strictEqual(
osmLink.getAttribute('href'),
'https://www.openstreetmap.org/node/12345'
);
assert.dom('button.btn-link').hasText('Add a photo');
});
test('it renders correct search-based OpenStreetMap link for a custom saved place', async function (assert) {
class MockMapUi extends Service {
currentCenter = { lat: 52.5, lon: 13.4 };
currentZoom = 15.6;
}
this.owner.register('service:map-ui', MockMapUi);
const place = {
title: 'Custom Place',
lat: 52.520008,
lon: 13.404954,
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const osmLink = this.element.querySelector(
'.meta-info a[href^="https://www.openstreetmap.org/search"]'
);
assert.ok(osmLink, 'OpenStreetMap search link is rendered');
assert.strictEqual(
osmLink.getAttribute('href'),
'https://www.openstreetmap.org/search?lat=52.520008&lon=13.404954&zoom=16#map=16/52.50000/13.40000'
);
assert.dom('button.btn-link').doesNotExist();
});
}); });
@@ -97,4 +97,27 @@ module('Integration | Component | place-photo-upload', function (hooks) {
assert.dom('.photo-tag-suggestions').doesNotExist(); assert.dom('.photo-tag-suggestions').doesNotExist();
}); });
test('it renders an optional alt text input after upload selection', async function (assert) {
this.place = {
title: 'Cafe Alpha',
osmId: '123',
osmType: 'node',
osmTags: { amenity: 'cafe' },
};
await render(
<template><PlacePhotoUpload @place={{this.place}} /></template>
);
assert.dom('#photo-alt-input').doesNotExist();
const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
await selectFile(this.element, file);
assert.dom('#photo-alt-input').exists();
assert
.dom('#photo-alt-input')
.hasAttribute('placeholder', 'Describe this photo');
});
}); });
+124 -1
View File
@@ -1,5 +1,11 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { normalizeRelayUrl, parsePlacePhotos } from 'marco/utils/nostr'; import {
excludeRequiredRelays,
mergeRequiredRelays,
normalizeRelayUrl,
parsePlacePhotos,
uniqNormalizedRelays,
} from 'marco/utils/nostr';
module('Unit | Utility | nostr', function () { module('Unit | Utility | nostr', function () {
test('normalizeRelayUrl normalizes protocol, case, and slashes', function (assert) { test('normalizeRelayUrl normalizes protocol, case, and slashes', function (assert) {
@@ -141,4 +147,121 @@ module('Unit | Utility | nostr', function () {
assert.strictEqual(photos[0].placeIdentifier, 'osm:node:123'); assert.strictEqual(photos[0].placeIdentifier, 'osm:node:123');
assert.strictEqual(photos[1].placeIdentifier, 'osm:node:456'); assert.strictEqual(photos[1].placeIdentifier, 'osm:node:456');
}); });
test('parsePlacePhotos extracts alt text from imeta', function (assert) {
const events = [
{
id: 'event-1',
pubkey: 'pubkey-1',
created_at: 100,
tags: [
[
'imeta',
'url https://example.com/photo.jpg',
'dim 800x600',
'alt A sunny terrace overlooking the bay',
],
],
},
];
const photos = parsePlacePhotos(events);
assert.strictEqual(photos.length, 1);
assert.strictEqual(photos[0].alt, 'A sunny terrace overlooking the bay');
});
test('parsePlacePhotos strips the legacy placeholder alt text', function (assert) {
const events = [
{
id: 'event-1',
pubkey: 'pubkey-1',
created_at: 100,
tags: [
[
'imeta',
'url https://example.com/photo.jpg',
'dim 800x600',
'alt A photo of a place',
],
],
},
];
const photos = parsePlacePhotos(events);
assert.strictEqual(photos.length, 1);
assert.strictEqual(photos[0].alt, null);
});
test('parsePlacePhotos leaves alt null when not provided', function (assert) {
const events = [
{
id: 'event-1',
pubkey: 'pubkey-1',
created_at: 100,
tags: [['imeta', 'url https://example.com/photo.jpg', 'dim 800x600']],
},
];
const photos = parsePlacePhotos(events);
assert.strictEqual(photos.length, 1);
assert.strictEqual(photos[0].alt, null);
});
test('uniqNormalizedRelays returns normalized unique relays', function (assert) {
const relays = uniqNormalizedRelays([
'Relay.example.com',
'wss://relay.example.com/',
'wss://other.example.com',
]);
assert.deepEqual(relays, [
'wss://relay.example.com',
'wss://other.example.com',
]);
});
test('mergeRequiredRelays keeps required relays as-is and merges normalized custom relays', function (assert) {
const relays = mergeRequiredRelays(
['wss://required.example.com', 'required-2.example.com'],
['required-2.example.com/', 'wss://custom.example.com']
);
assert.deepEqual(relays, [
'wss://required.example.com',
'required-2.example.com',
'wss://required-2.example.com',
'wss://custom.example.com',
]);
});
test('excludeRequiredRelays removes required relays from normalized custom list', function (assert) {
const relays = excludeRequiredRelays(
[
'wss://required.example.com',
'custom.example.com',
'ws://custom2.example.com',
],
['wss://required.example.com']
);
assert.deepEqual(relays, [
'wss://custom.example.com',
'ws://custom2.example.com',
]);
});
test('excludeRequiredRelays trusts required list without normalizing it', function (assert) {
const relays = excludeRequiredRelays(
['Required.example.com', 'custom.example.com'],
['required.example.com']
);
assert.deepEqual(relays, [
'wss://required.example.com',
'wss://custom.example.com',
]);
});
}); });
+12 -1
View File
@@ -1,4 +1,5 @@
import { getIconNameForTags } from 'marco/utils/osm-icons'; import { getIconNameForTags, POI_ICON_RULES } from 'marco/utils/osm-icons';
import { getIcon } from 'marco/utils/icons';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
module('Unit | Utility | osm-icons', function () { module('Unit | Utility | osm-icons', function () {
@@ -36,4 +37,14 @@ module('Unit | Utility | osm-icons', function () {
let result = getIconNameForTags({ foo: 'bar' }); let result = getIconNameForTags({ foo: 'bar' });
assert.strictEqual(result, null); assert.strictEqual(result, null);
}); });
test('all icons used in POI_ICON_RULES exist in the icons utility', function (assert) {
for (let rule of POI_ICON_RULES) {
let icon = getIcon(rule.icon);
assert.ok(
icon,
`Icon "${rule.icon}" specified in POI_ICON_RULES should be imported and available in getIcon`
);
}
});
}); });
+6
View File
@@ -89,6 +89,12 @@ module('Unit | Utility | osm', function (hooks) {
assert.strictEqual(result, 'Building'); assert.strictEqual(result, 'Building');
}); });
test('getPlaceType ignores generic "yes" values if a more specific tag is present', function (assert) {
const tags = { historic: 'yes', building: 'tower' };
const result = getPlaceType(tags);
assert.strictEqual(result, 'Tower');
});
test('getPlaceType prioritizes order (amenity > shop > building)', function (assert) { test('getPlaceType prioritizes order (amenity > shop > building)', function (assert) {
// If something is both a shop and a building, it should be a shop // If something is both a shop and a building, it should be a shop
const tags = { building: 'yes', shop: 'supermarket' }; const tags = { building: 'yes', shop: 'supermarket' };