1
0
forked from Kieran/snort
This commit is contained in:
Kieran 2023-09-22 16:55:26 +01:00
parent e8519daa47
commit 8c7fbed191
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
18 changed files with 276 additions and 106 deletions

View File

@ -6,7 +6,8 @@
"scripts": {
"build": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app build",
"start": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app start",
"test": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/app test && yarn workspace @snort/system test"
"test": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/app test && yarn workspace @snort/system test",
"pre:commit": "yarn workspace @snort/app intl-extract && yarn workspace @snort/app intl-compile && yarn prettier --write ."
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230307.0",

View File

@ -14,6 +14,7 @@
"@snort/system-query": "workspace:*",
"@snort/system-react": "workspace:*",
"@szhsin/react-menu": "^3.3.1",
"@types/use-sync-external-store": "^0.0.4",
"@void-cat/api": "^1.0.4",
"debug": "^4.3.4",
"dexie": "^3.2.4",
@ -30,6 +31,7 @@
"react-textarea-autosize": "^8.4.0",
"react-twitter-embed": "^4.0.4",
"use-long-press": "^2.0.3",
"use-sync-external-store": "^1.2.0",
"uuid": "^9.0.0",
"workbox-core": "^6.4.2",
"workbox-precaching": "^7.0.0",

View File

@ -81,8 +81,8 @@ export default function CashuNuts({ token }: { token: string }) {
x2="7.11501"
y2="6.7213"
gradientUnits="userSpaceOnUse">
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient
id="paint1_linear_1976_19241"
@ -91,8 +91,8 @@ export default function CashuNuts({ token }: { token: string }) {
x2="7.11501"
y2="32.2311"
gradientUnits="userSpaceOnUse">
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient
id="paint2_linear_1976_19241"
@ -101,8 +101,8 @@ export default function CashuNuts({ token }: { token: string }) {
x2="22.2658"
y2="19.4576"
gradientUnits="userSpaceOnUse">
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>

View File

@ -1,5 +1,5 @@
import "./Modal.css";
import { ReactNode } from "react";
import { ReactNode, useEffect } from "react";
export interface ModalProps {
id: string;
@ -9,6 +9,11 @@ export interface ModalProps {
}
export default function Modal(props: ModalProps) {
useEffect(() => {
document.body.classList.add("scroll-lock");
return () => document.body.classList.remove("scroll-lock");
}, []);
return (
<div className={`modal${props.className ? ` ${props.className}` : ""}`} onClick={props.onClose}>
<div className="modal-body" onClick={e => e.stopPropagation()}>

View File

@ -52,7 +52,7 @@ export default function NoteFooter(props: NoteFooterProps) {
const author = useUserProfile(ev.pubkey);
const interactionCache = useInteractionCache(publicKey, ev.id);
const publisher = useEventPublisher();
const note = useNoteCreator();
const note = useNoteCreator(n => ({ show: n.show, replyTo: n.replyTo, update: n.update }));
const willRenderNoteCreator = note.show && note.replyTo?.id === ev.id;
const [tip, setTip] = useState(false);
const [zapping, setZapping] = useState(false);
@ -263,7 +263,7 @@ export default function NoteFooter(props: NoteFooterProps) {
{tipButton()}
{powIcon()}
</div>
{willRenderNoteCreator && <NoteCreator />}
{willRenderNoteCreator && <NoteCreator key={`note-creator-${ev.id}`} />}
<SendSats targets={getZapTarget()} onClose={() => setTip(false)} show={tip} note={ev.id} allocatePool={true} />
</div>
<ZapsSummary zaps={zaps} />

View File

@ -68,7 +68,10 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
}
getSessions() {
return [...this.#accounts.values()].map(v => unwrap(v.publicKey));
return [...this.#accounts.values()].map(v => ({
pubkey: unwrap(v.publicKey),
id: v.id,
}));
}
allSubscriptions() {

View File

@ -25,19 +25,12 @@ import { LoginUnlock } from "Element/PinPrompt";
export default function Layout() {
const location = useLocation();
const note = useNoteCreator();
const isReplyNoteCreatorShowing = note.replyTo && note.show;
const [pageClass, setPageClass] = useState("page");
useLoginFeed();
useTheme();
useLoginRelays();
const shouldHideNoteCreator = useMemo(() => {
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/e", "/subscribe"];
return isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a));
}, [location, isReplyNoteCreatorShowing]);
const shouldHideHeader = useMemo(() => {
const hideOn = ["/login", "/new"];
return hideOn.some(a => location.pathname.startsWith(a));
@ -63,21 +56,7 @@ export default function Layout() {
</header>
)}
<Outlet />
{!shouldHideNoteCreator && (
<>
<button
className="primary note-create-button"
onClick={() =>
note.update(v => {
v.replyTo = undefined;
v.show = true;
})
}>
<Icon name="plus" size={16} />
</button>
<NoteCreator />
</>
)}
<NoteCreatorButton />
<Toaster />
</div>
<LoginUnlock />
@ -85,6 +64,34 @@ export default function Layout() {
);
}
const NoteCreatorButton = () => {
const location = useLocation();
const { show, replyTo, update } = useNoteCreator(v => ({ show: v.show, replyTo: v.replyTo, update: v.update }));
const shouldHideNoteCreator = useMemo(() => {
const isReplyNoteCreatorShowing = replyTo && show;
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/e", "/subscribe"];
return isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a));
}, [location]);
if (shouldHideNoteCreator) return;
return (
<>
<button
className="primary note-create-button"
onClick={() =>
update(v => {
v.replyTo = undefined;
v.show = true;
})
}>
<Icon name="plus" size={16} />
</button>
<NoteCreator key="global-note-creator" />
</>
);
};
const AccountHeader = () => {
const navigate = useNavigate();
const { formatMessage } = useIntl();

View File

@ -1,57 +1,32 @@
import { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom";
import ProfilePreview from "Element/ProfilePreview";
import { LoginStore } from "Login";
import useLoginHandler from "Hooks/useLoginHandler";
import AsyncButton from "Element/AsyncButton";
import { getActiveSubscriptions } from "Subscription";
export default function AccountsPage() {
const { formatMessage } = useIntl();
const [key, setKey] = useState("");
const [error, setError] = useState("");
const loginHandler = useLoginHandler();
const logins = LoginStore.getSessions();
const sub = getActiveSubscriptions(LoginStore.allSubscriptions());
async function doLogin() {
try {
setError("");
await loginHandler.doLogin(key);
setKey("");
} catch (e) {
if (e instanceof Error) {
setError(e.message);
} else {
setError(
formatMessage({
defaultMessage: "Unknown login error",
}),
);
}
console.error(e);
}
}
return (
<>
<div className="flex-column g12">
<h3>
<FormattedMessage defaultMessage="Logins" />
</h3>
{logins.map(a => (
<div className="card flex" key={a}>
<div className="card flex" key={a.id}>
<ProfilePreview
pubkey={a}
pubkey={a.pubkey}
options={{
about: false,
}}
actions={
<div className="f-1">
<button className="mb10" onClick={() => LoginStore.switchAccount(a)}>
<button className="mb10" onClick={() => LoginStore.switchAccount(a.id)}>
<FormattedMessage defaultMessage="Switch" />
</button>
<button onClick={() => LoginStore.removeSession(a)}>
<button onClick={() => LoginStore.removeSession(a.id)}>
<FormattedMessage defaultMessage="Logout" />
</button>
</div>
@ -61,27 +36,12 @@ export default function AccountsPage() {
))}
{sub && (
<>
<h3>
<Link to={"/login"}>
<button type="button">
<FormattedMessage defaultMessage="Add Account" />
</h3>
<div className="flex">
<input
dir="auto"
type="text"
placeholder={formatMessage({
defaultMessage: "nsec, npub, nip-05, hex, mnemonic",
})}
className="f-grow mr10"
onChange={e => setKey(e.target.value)}
/>
<AsyncButton onClick={() => doLogin()}>
<FormattedMessage defaultMessage="Login" />
</AsyncButton>
</div>
</>
</button>
</Link>
)}
{error && <b className="error">{error}</b>}
</>
</div>
);
}

View File

@ -2,6 +2,7 @@ import { ExternalStore } from "@snort/shared";
import { NostrEvent, TaggedNostrEvent } from "@snort/system";
import { ZapTarget } from "Zapper";
import { useSyncExternalStore } from "react";
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
interface NoteCreatorDataSnapshot {
show: boolean;
@ -33,11 +34,11 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
advanced: false,
reset: () => {
this.#reset(this.#data);
this.notifyChange();
this.notifyChange(this.#data);
},
update: (fn: (v: NoteCreatorDataSnapshot) => void) => {
fn(this.#data);
this.notifyChange();
this.notifyChange(this.#data);
},
};
}
@ -65,8 +66,7 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
},
update: (fn: (v: NoteCreatorDataSnapshot) => void) => {
fn(this.#data);
console.debug(this.#data);
this.notifyChange();
this.notifyChange(this.#data);
},
} as NoteCreatorDataSnapshot;
return sn;
@ -75,9 +75,20 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
const NoteCreatorState = new NoteCreatorStore();
export function useNoteCreator() {
return useSyncExternalStore(
c => NoteCreatorState.hook(c),
() => NoteCreatorState.snapshot(),
);
export function useNoteCreator<T extends object = NoteCreatorDataSnapshot>(
selector?: (v: NoteCreatorDataSnapshot) => T,
) {
if (selector) {
return useSyncExternalStoreWithSelector<NoteCreatorDataSnapshot, T>(
c => NoteCreatorState.hook(c),
() => NoteCreatorState.snapshot(),
undefined,
selector,
);
} else {
return useSyncExternalStore<T>(
c => NoteCreatorState.hook(c),
() => NoteCreatorState.snapshot() as T,
);
}
}

View File

@ -38,7 +38,6 @@ import { preload, RelayMetrics, UserCache, UserRelays } from "Cache";
import { LoginStore } from "Login";
import { SnortDeckLayout } from "Pages/DeckLayout";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const WasmQueryOptimizer = {
expandFilter: (f: ReqFilter) => {
return expand_filter(f) as Array<FlatReqFilter>;
@ -61,7 +60,7 @@ export const System = new NostrSystem({
relayCache: UserRelays,
profileCache: UserCache,
relayMetrics: RelayMetrics,
//queryOptimizer: WasmQueryOptimizer,
queryOptimizer: WasmQueryOptimizer,
authHandler: async (c, r) => {
const { id } = LoginStore.snapshot();
const pub = LoginStore.getPublisher(id);

View File

@ -35,9 +35,7 @@ export abstract class ExternalStore<TSnapshot> {
protected notifyChange(sn?: TSnapshot) {
this.#changed = true;
if (this.#hooks.length > 0) {
queueMicrotask(() => {
this.#hooks.forEach(h => h.fn(sn));
});
this.#hooks.forEach(h => h.fn(sn));
}
}

View File

@ -24,6 +24,8 @@ fn criterion_benchmark(c: &mut Criterion) {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -39,6 +41,8 @@ fn criterion_benchmark(c: &mut Criterion) {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,

View File

@ -28,6 +28,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -42,6 +44,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -63,6 +67,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -78,6 +84,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -92,6 +100,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -111,6 +121,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -130,6 +142,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -144,6 +158,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -162,6 +178,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,

View File

@ -29,6 +29,10 @@ pub struct ReqFilter {
pub d_tag: Option<HashSet<String>>,
#[serde(rename = "#r", skip_serializing_if = "Option::is_none")]
pub r_tag: Option<HashSet<String>>,
#[serde(rename = "#a", skip_serializing_if = "Option::is_none")]
pub a_tag: Option<HashSet<String>>,
#[serde(rename = "#g", skip_serializing_if = "Option::is_none")]
pub g_tag: Option<HashSet<String>>,
#[serde(rename = "search", skip_serializing_if = "Option::is_none")]
pub search: Option<HashSet<String>>,
#[serde(rename = "since", skip_serializing_if = "Option::is_none")]
@ -64,6 +68,10 @@ pub struct FlatReqFilter {
pub d_tag: Option<String>,
#[serde(rename = "#r", skip_serializing_if = "Option::is_none")]
pub r_tag: Option<String>,
#[serde(rename = "#a", skip_serializing_if = "Option::is_none")]
pub a_tag: Option<String>,
#[serde(rename = "#g", skip_serializing_if = "Option::is_none")]
pub g_tag: Option<String>,
#[serde(rename = "search", skip_serializing_if = "Option::is_none")]
pub search: Option<String>,
#[serde(rename = "since", skip_serializing_if = "Option::is_none")]
@ -145,6 +153,8 @@ impl From<Vec<&FlatReqFilter>> for ReqFilter {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -159,6 +169,8 @@ impl From<Vec<&FlatReqFilter>> for ReqFilter {
array_prop_append(&x.t_tag, &mut acc.t_tag);
array_prop_append(&x.d_tag, &mut acc.d_tag);
array_prop_append(&x.r_tag, &mut acc.r_tag);
array_prop_append(&x.a_tag, &mut acc.a_tag);
array_prop_append(&x.g_tag, &mut acc.g_tag);
array_prop_append(&x.search, &mut acc.search);
acc.since = x.since;
acc.until = x.until;
@ -180,6 +192,8 @@ impl From<Vec<&ReqFilter>> for ReqFilter {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -194,6 +208,8 @@ impl From<Vec<&ReqFilter>> for ReqFilter {
array_prop_append_vec(&x.t_tag, &mut acc.t_tag);
array_prop_append_vec(&x.d_tag, &mut acc.d_tag);
array_prop_append_vec(&x.r_tag, &mut acc.r_tag);
array_prop_append_vec(&x.a_tag, &mut acc.a_tag);
array_prop_append_vec(&x.g_tag, &mut acc.g_tag);
array_prop_append_vec(&x.search, &mut acc.search);
acc.since = x.since;
acc.until = x.until;
@ -265,6 +281,20 @@ impl Into<Vec<FlatReqFilter>> for &ReqFilter {
.collect();
inputs.push(t_ids);
}
if let Some(a_tags) = &self.a_tag {
let t_ids = a_tags
.iter()
.map(|z| StringOrNumberEntry::String(("a_tag", z)))
.collect();
inputs.push(t_ids);
}
if let Some(g_tags) = &self.g_tag {
let t_ids = g_tags
.iter()
.map(|z| StringOrNumberEntry::String(("g_tag", z)))
.collect();
inputs.push(t_ids);
}
if let Some(search) = &self.search {
let t_ids = search
.iter()
@ -339,6 +369,22 @@ impl Into<Vec<FlatReqFilter>> for &ReqFilter {
}
None
}),
a_tag: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("a_tag") {
return Some((*v).to_string());
}
}
None
}),
g_tag: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("g_tag") {
return Some((*v).to_string());
}
}
None
}),
search: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("search") {
@ -355,6 +401,7 @@ impl Into<Vec<FlatReqFilter>> for &ReqFilter {
ret
}
}
impl Distance for ReqFilter {
fn distance(&self, b: &Self) -> u32 {
let mut ret = 0u32;
@ -367,6 +414,7 @@ impl Distance for ReqFilter {
ret += prop_dist_vec(&self.d_tag, &b.d_tag);
ret += prop_dist_vec(&self.r_tag, &b.r_tag);
ret += prop_dist_vec(&self.t_tag, &b.t_tag);
ret += prop_dist_vec(&self.a_tag, &b.a_tag);
ret += prop_dist_vec(&self.search, &b.search);
ret
@ -464,6 +512,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -471,7 +521,7 @@ mod tests {
e_tag: None,
};
let output : Vec<FlatReqFilter> = (&input).into();
let output: Vec<FlatReqFilter> = (&input).into();
let expected = vec![
FlatReqFilter {
author: Some("a".to_owned()),
@ -481,6 +531,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -495,6 +547,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -509,6 +563,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -523,6 +579,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -537,6 +595,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -551,6 +611,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -565,6 +627,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -579,6 +643,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -593,6 +659,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -607,6 +675,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -621,6 +691,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -635,6 +707,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -649,6 +723,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -663,6 +739,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -677,6 +755,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -691,6 +771,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -705,6 +787,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
@ -719,6 +803,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,

View File

@ -74,6 +74,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
authors: Some(HashSet::from([
"kieran".to_string(),
"snort".to_string(),
@ -94,6 +96,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -109,6 +113,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -122,6 +128,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,

View File

@ -1,9 +1,9 @@
use crate::filter::CanMerge;
pub fn merge<'a, T, Z>(all: Vec<&'a T>) -> Vec<Z>
where
T: CanMerge,
for<'b> Z: CanMerge + From<Vec<&'a T>> + From<Vec<&'b Z>>,
where
T: CanMerge,
for<'b> Z: CanMerge + From<Vec<&'a T>> + From<Vec<&'b Z>>,
{
let mut ret: Vec<Z> = merge_once(all);
loop {
@ -17,9 +17,9 @@ where
}
fn merge_once<'a, T, Z>(all: Vec<&'a T>) -> Vec<Z>
where
T: CanMerge,
Z: From<Vec<&'a T>>,
where
T: CanMerge,
Z: From<Vec<&'a T>>,
{
let mut ret: Vec<Z> = vec![];
if all.is_empty() {
@ -66,6 +66,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -80,6 +82,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -94,6 +98,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -108,6 +114,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -122,6 +130,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -144,6 +154,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -158,6 +170,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -173,6 +187,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -192,6 +208,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -206,6 +224,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -220,6 +240,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -241,6 +263,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -255,6 +279,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -269,6 +295,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -283,6 +311,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -297,6 +327,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -311,6 +343,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -325,6 +359,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -339,6 +375,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -353,6 +391,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -373,6 +413,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -387,6 +429,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -401,6 +445,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -415,6 +461,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
@ -429,6 +477,8 @@ mod tests {
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,

View File

@ -2698,6 +2698,7 @@ __metadata:
"@types/node": ^20.4.1
"@types/react": ^18.0.26
"@types/react-dom": ^18.0.10
"@types/use-sync-external-store": ^0.0.4
"@types/uuid": ^9.0.2
"@types/webscopeio__react-textarea-autocomplete": ^4.7.2
"@types/webtorrent": ^0.109.3
@ -2740,6 +2741,7 @@ __metadata:
ts-loader: ^9.4.4
typescript: ^5.2.2
use-long-press: ^2.0.3
use-sync-external-store: ^1.2.0
uuid: ^9.0.0
webpack: ^5.88.2
webpack-bundle-analyzer: ^4.8.0
@ -3568,6 +3570,13 @@ __metadata:
languageName: node
linkType: hard
"@types/use-sync-external-store@npm:^0.0.4":
version: 0.0.4
resolution: "@types/use-sync-external-store@npm:0.0.4"
checksum: f8bf56b14f28fda6d0215281d50623d5affd17c549ba46bcfcfbb97c6301a583066c0477856260ae6feadcaa714c46cd45678e76b74da0f6f8b364aec07bd854
languageName: node
linkType: hard
"@types/uuid@npm:^9.0.2":
version: 9.0.2
resolution: "@types/uuid@npm:9.0.2"
@ -13331,6 +13340,15 @@ __metadata:
languageName: node
linkType: hard
"use-sync-external-store@npm:^1.2.0":
version: 1.2.0
resolution: "use-sync-external-store@npm:1.2.0"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a
languageName: node
linkType: hard
"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
version: 1.0.2
resolution: "util-deprecate@npm:1.0.2"