zwischenstand

This commit is contained in:
2024-02-13 16:36:09 +00:00
parent 7d8a07e8c4
commit efb67be9fe
184 changed files with 15832 additions and 1359 deletions

50
vendor/svelte-routing/src/Link.svelte vendored Normal file
View File

@@ -0,0 +1,50 @@
<script>
import { createEventDispatcher, getContext } from "svelte";
import { HISTORY, LOCATION, ROUTER } from "./contexts.js";
import { resolve, shouldNavigate } from "./utils.js";
export let to = "#";
export let replace = false;
export let state = {};
export let getProps = () => ({});
export let preserveScroll = false;
const location = getContext(LOCATION);
const { base } = getContext(ROUTER);
const { navigate } = getContext(HISTORY);
const dispatch = createEventDispatcher();
let href, isPartiallyCurrent, isCurrent, props;
$: href = resolve(to, $base.uri);
$: isPartiallyCurrent = $location.pathname.startsWith(href);
$: isCurrent = href === $location.pathname;
$: ariaCurrent = isCurrent ? "page" : undefined;
$: props = getProps({
location: $location,
href,
isPartiallyCurrent,
isCurrent,
existingProps: $$restProps,
});
const onClick = (event) => {
dispatch("click", event);
if (shouldNavigate(event)) {
event.preventDefault();
// Don't push another entry to the history stack when the user
// clicks on a Link to the page they are currently on.
const shouldReplace = $location.pathname === href || replace;
navigate(href, { state, replace: shouldReplace, preserveScroll });
}
};
</script>
<a
{href}
aria-current={ariaCurrent}
on:click={onClick}
{...props}
{...$$restProps}
>
<slot active={!!ariaCurrent} />
</a>

59
vendor/svelte-routing/src/Route.svelte vendored Normal file
View File

@@ -0,0 +1,59 @@
<script>
import { getContext, onDestroy } from "svelte";
import { ROUTER } from "./contexts.js";
import { canUseDOM } from "./utils.js";
export let path = "";
export let component = null;
let routeParams = {};
let routeProps = {};
const { registerRoute, unregisterRoute, activeRoute } = getContext(ROUTER);
const route = {
path,
// If no path prop is given, this Route will act as the default Route
// that is rendered if no other Route in the Router is a match.
default: path === "",
};
$: if ($activeRoute && $activeRoute.route === route) {
routeParams = $activeRoute.params;
const { component: c, path, ...rest } = $$props;
routeProps = rest;
if (c) {
// goja gives object instead of class
if (
c.toString().startsWith("class ") ||
c.toString().includes("object")
)
component = c;
else component = c();
}
canUseDOM() && !$activeRoute.preserveScroll && window?.scrollTo(0, 0);
}
registerRoute(route);
onDestroy(() => {
unregisterRoute(route);
});
</script>
{#if $activeRoute && $activeRoute.route === route}
{#if component}
{#await component then resolvedComponent}
<svelte:component
this={resolvedComponent?.default || resolvedComponent}
{...routeParams}
{...routeProps}
/>
{/await}
{:else}
<slot params={routeParams} />
{/if}
{/if}

145
vendor/svelte-routing/src/Router.svelte vendored Normal file
View File

@@ -0,0 +1,145 @@
<script>
import { getContext, onMount, setContext } from "svelte";
import { derived, writable } from "svelte/store";
import { HISTORY, LOCATION, ROUTER } from "./contexts.js";
import { globalHistory } from "./history.js";
import { combinePaths, pick } from "./utils.js";
export let basepath = "/";
export let url = null;
export let viewtransition = null;
export let history = globalHistory;
const viewtransitionFn = (node, _, direction) => {
const vt = viewtransition(direction);
if (typeof vt?.fn === "function") return vt.fn(node, vt);
else return vt;
};
setContext(HISTORY, history);
const locationContext = getContext(LOCATION);
const routerContext = getContext(ROUTER);
const routes = writable([]);
const activeRoute = writable(null);
let hasActiveRoute = false; // Used in SSR to synchronously set that a Route is active.
// If locationContext is not set, this is the topmost Router in the tree.
// If the `url` prop is given we force the location to it.
const location =
locationContext || writable(url ? { pathname: url } : history.location);
// If routerContext is set, the routerBase of the parent Router
// will be the base for this Router's descendants.
// If routerContext is not set, the path and resolved uri will both
// have the value of the basepath prop.
const base = routerContext
? routerContext.routerBase
: writable({
path: basepath,
uri: basepath,
});
const routerBase = derived([base, activeRoute], ([base, activeRoute]) => {
// If there is no activeRoute, the routerBase will be identical to the base.
if (!activeRoute) return base;
const { path: basepath } = base;
const { route, uri } = activeRoute;
// Remove the potential /* or /*splatname from
// the end of the child Routes relative paths.
const path = route.default ? basepath : route.path.replace(/\*.*$/, "");
return { path, uri };
});
const registerRoute = (route) => {
const { path: basepath } = $base;
let { path } = route;
// We store the original path in the _path property so we can reuse
// it when the basepath changes. The only thing that matters is that
// the route reference is intact, so mutation is fine.
route._path = path;
route.path = combinePaths(basepath, path);
if (typeof window === "undefined") {
// In SSR we should set the activeRoute immediately if it is a match.
// If there are more Routes being registered after a match is found,
// we just skip them.
if (hasActiveRoute) return;
const matchingRoute = pick([route], $location.pathname);
if (matchingRoute) {
activeRoute.set(matchingRoute);
hasActiveRoute = true;
}
} else {
routes.update((rs) => [...rs, route]);
}
};
const unregisterRoute = (route) => {
routes.update((rs) => rs.filter((r) => r !== route));
};
let preserveScroll = false;
// This reactive statement will update all the Routes' path when
// the basepath changes.
$: {
const { path: basepath } = $base;
routes.update((rs) =>
rs.map((r) =>
Object.assign(r, { path: combinePaths(basepath, r._path) })
)
);
}
// This reactive statement will be run when the Router is created
// when there are no Routes and then again the following tick, so it
// will not find an active Route in SSR and in the browser it will only
// pick an active Route after all Routes have been registered.
$: {
const bestMatch = pick($routes, $location.pathname);
activeRoute.set(
bestMatch ? { ...bestMatch, preserveScroll } : bestMatch
);
}
if (!locationContext) {
// The topmost Router in the tree is responsible for updating
// the location store and supplying it through context.
onMount(() => {
const unlisten = history.listen((event) => {
preserveScroll = event.preserveScroll || false;
location.set(event.location);
});
return unlisten;
});
setContext(LOCATION, location);
}
setContext(ROUTER, {
activeRoute,
base,
routerBase,
registerRoute,
unregisterRoute,
});
</script>
{#if viewtransition}
{#key $location.pathname}
<div in:viewtransitionFn out:viewtransitionFn>
<slot
route={$activeRoute && $activeRoute.uri}
location={$location}
/>
</div>
{/key}
{:else}
<slot route={$activeRoute && $activeRoute.uri} location={$location} />
{/if}

87
vendor/svelte-routing/src/actions.js vendored Normal file
View File

@@ -0,0 +1,87 @@
import { navigate } from "./history.js";
import { hostMatches, shouldNavigate } from "./utils.js";
/**
* A link action that can be added to <a href=""> tags rather
* than using the <Link> component.
*
* Example:
* ```html
* <a href="/post/{postId}" use:link>{post.title}</a>
* ```
*/
const link = (node) => {
const onClick = (event) => {
const anchor = event.currentTarget;
if (
(anchor.target === "" || anchor.target === "_self") &&
hostMatches(anchor) &&
shouldNavigate(event)
) {
event.preventDefault();
navigate(anchor.pathname + anchor.search, {
replace: anchor.hasAttribute("replace"),
preserveScroll: anchor.hasAttribute("preserveScroll"),
});
}
};
node.addEventListener("click", onClick);
return {
destroy() {
node.removeEventListener("click", onClick);
},
};
};
/**
* An action to be added at a root element of your application to
* capture all relative links and push them onto the history stack.
*
* Example:
* ```html
* <div use:links>
* <Router>
* <Route path="/" component={Home} />
* <Route path="/p/:projectId/:docId?" component={ProjectScreen} />
* {#each projects as project}
* <a href="/p/{project.id}">{project.title}</a>
* {/each}
* </Router>
* </div>
* ```
*/
const links = (node) => {
const findClosest = (tagName, el) => {
while (el && el.tagName !== tagName) el = el.parentNode;
return el;
};
const onClick = (event) => {
const anchor = findClosest("A", event.target);
if (
anchor &&
(anchor.target === "" || anchor.target === "_self") &&
hostMatches(anchor) &&
shouldNavigate(event) &&
!anchor.hasAttribute("noroute")
) {
event.preventDefault();
navigate(anchor.pathname + anchor.search, {
replace: anchor.hasAttribute("replace"),
preserveScroll: anchor.hasAttribute("preserveScroll"),
});
}
};
node.addEventListener("click", onClick);
return {
destroy() {
node.removeEventListener("click", onClick);
},
};
};
export { link, links };

9
vendor/svelte-routing/src/contexts.js vendored Normal file
View File

@@ -0,0 +1,9 @@
import { getContext } from "svelte";
export const LOCATION = {};
export const ROUTER = {};
export const HISTORY = {};
export const useLocation = () => getContext(LOCATION);
export const useRouter = () => getContext(ROUTER);
export const useHistory = () => getContext(HISTORY);

109
vendor/svelte-routing/src/history.js vendored Normal file
View File

@@ -0,0 +1,109 @@
/**
* Adapted from https://github.com/reach/router/blob/b60e6dd781d5d3a4bdaaf4de665649c0f6a7e78d/src/lib/history.js
* https://github.com/reach/router/blob/master/LICENSE
*/
import { canUseDOM } from "./utils";
const getLocation = (source) => {
return {
...source.location,
state: source.history.state,
key: (source.history.state && source.history.state.key) || "initial",
};
};
const createHistory = (source) => {
const listeners = [];
let location = getLocation(source);
return {
get location() {
return location;
},
listen(listener) {
listeners.push(listener);
const popstateListener = () => {
location = getLocation(source);
listener({ location, action: "POP" });
};
source.addEventListener("popstate", popstateListener);
return () => {
source.removeEventListener("popstate", popstateListener);
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
},
navigate(
to,
{
state,
replace = false,
preserveScroll = false,
blurActiveElement = true,
} = {}
) {
state = { ...state, key: Date.now() + "" };
// try...catch iOS Safari limits to 100 pushState calls
try {
if (replace) source.history.replaceState(state, "", to);
else source.history.pushState(state, "", to);
} catch (e) {
source.location[replace ? "replace" : "assign"](to);
}
location = getLocation(source);
listeners.forEach((listener) =>
listener({ location, action: "PUSH", preserveScroll })
);
if (typeof document !== "undefined")
if (blurActiveElement) document.activeElement.blur();
},
};
};
// Stores history entries in memory for testing or other platforms like Native
const createMemorySource = (initialPathname = "/") => {
let index = 0;
const stack = [{ pathname: initialPathname, search: "" }];
const states = [];
return {
get location() {
return stack[index];
},
addEventListener(name, fn) {},
removeEventListener(name, fn) {},
history: {
get entries() {
return stack;
},
get index() {
return index;
},
get state() {
return states[index];
},
pushState(state, _, uri) {
const [pathname, search = ""] = uri.split("?");
index++;
stack.push({ pathname, search });
states.push(state);
},
replaceState(state, _, uri) {
const [pathname, search = ""] = uri.split("?");
stack[index] = { pathname, search };
states[index] = state;
},
},
};
};
// Global history uses window.history as the source if available,
// otherwise a memory history
const globalHistory = createHistory(
canUseDOM() ? window : createMemorySource()
);
const { navigate } = globalHistory;
export { globalHistory, navigate, createHistory, createMemorySource };

6
vendor/svelte-routing/src/index.js vendored Normal file
View File

@@ -0,0 +1,6 @@
export { default as Link } from "./Link.svelte";
export { default as Route } from "./Route.svelte";
export { default as Router } from "./Router.svelte";
export { link, links } from "./actions.js";
export * from "./contexts.js";
export { navigate } from "./history.js";

278
vendor/svelte-routing/src/utils.js vendored Normal file
View File

@@ -0,0 +1,278 @@
/**
* Adapted from https://github.com/reach/router/blob/b60e6dd781d5d3a4bdaaf4de665649c0f6a7e78d/src/lib/utils.js
* https://github.com/reach/router/blob/master/LICENSE
*/
const PARAM = /^:(.+)/;
const SEGMENT_POINTS = 4;
const STATIC_POINTS = 3;
const DYNAMIC_POINTS = 2;
const SPLAT_PENALTY = 1;
const ROOT_POINTS = 1;
/**
* Split up the URI into segments delimited by `/`
* Strip starting/ending `/`
* @param {string} uri
* @return {string[]}
*/
const segmentize = (uri) => uri.replace(/(^\/+|\/+$)/g, "").split("/");
/**
* Strip `str` of potential start and end `/`
* @param {string} string
* @return {string}
*/
const stripSlashes = (string) => string.replace(/(^\/+|\/+$)/g, "");
/**
* Score a route depending on how its individual segments look
* @param {object} route
* @param {number} index
* @return {object}
*/
const rankRoute = (route, index) => {
const score = route.default
? 0
: segmentize(route.path).reduce((score, segment) => {
score += SEGMENT_POINTS;
if (segment === "") {
score += ROOT_POINTS;
} else if (PARAM.test(segment)) {
score += DYNAMIC_POINTS;
} else if (segment[0] === "*") {
score -= SEGMENT_POINTS + SPLAT_PENALTY;
} else {
score += STATIC_POINTS;
}
return score;
}, 0);
return { route, score, index };
};
/**
* Give a score to all routes and sort them on that
* If two routes have the exact same score, we go by index instead
* @param {object[]} routes
* @return {object[]}
*/
const rankRoutes = (routes) =>
routes
.map(rankRoute)
.sort((a, b) =>
a.score < b.score ? 1 : a.score > b.score ? -1 : a.index - b.index
);
/**
* Ranks and picks the best route to match. Each segment gets the highest
* amount of points, then the type of segment gets an additional amount of
* points where
*
* static > dynamic > splat > root
*
* This way we don't have to worry about the order of our routes, let the
* computers do it.
*
* A route looks like this
*
* { path, default, value }
*
* And a returned match looks like:
*
* { route, params, uri }
*
* @param {object[]} routes
* @param {string} uri
* @return {?object}
*/
const pick = (routes, uri) => {
let match;
let default_;
const [uriPathname] = uri.split("?");
const uriSegments = segmentize(uriPathname);
const isRootUri = uriSegments[0] === "";
const ranked = rankRoutes(routes);
for (let i = 0, l = ranked.length; i < l; i++) {
const route = ranked[i].route;
let missed = false;
if (route.default) {
default_ = {
route,
params: {},
uri,
};
continue;
}
const routeSegments = segmentize(route.path);
const params = {};
const max = Math.max(uriSegments.length, routeSegments.length);
let index = 0;
for (; index < max; index++) {
const routeSegment = routeSegments[index];
const uriSegment = uriSegments[index];
if (routeSegment && routeSegment[0] === "*") {
// Hit a splat, just grab the rest, and return a match
// uri: /files/documents/work
// route: /files/* or /files/*splatname
const splatName =
routeSegment === "*" ? "*" : routeSegment.slice(1);
params[splatName] = uriSegments
.slice(index)
.map(decodeURIComponent)
.join("/");
break;
}
if (typeof uriSegment === "undefined") {
// URI is shorter than the route, no match
// uri: /users
// route: /users/:userId
missed = true;
break;
}
const dynamicMatch = PARAM.exec(routeSegment);
if (dynamicMatch && !isRootUri) {
const value = decodeURIComponent(uriSegment);
params[dynamicMatch[1]] = value;
} else if (routeSegment !== uriSegment) {
// Current segments don't match, not dynamic, not splat, so no match
// uri: /users/123/settings
// route: /users/:id/profile
missed = true;
break;
}
}
if (!missed) {
match = {
route,
params,
uri: "/" + uriSegments.slice(0, index).join("/"),
};
break;
}
}
return match || default_ || null;
};
/**
* Add the query to the pathname if a query is given
* @param {string} pathname
* @param {string} [query]
* @return {string}
*/
const addQuery = (pathname, query) => pathname + (query ? `?${query}` : "");
/**
* Resolve URIs as though every path is a directory, no files. Relative URIs
* in the browser can feel awkward because not only can you be "in a directory",
* you can be "at a file", too. For example:
*
* browserSpecResolve('foo', '/bar/') => /bar/foo
* browserSpecResolve('foo', '/bar') => /foo
*
* But on the command line of a file system, it's not as complicated. You can't
* `cd` from a file, only directories. This way, links have to know less about
* their current path. To go deeper you can do this:
*
* <Link to="deeper"/>
* // instead of
* <Link to=`{${props.uri}/deeper}`/>
*
* Just like `cd`, if you want to go deeper from the command line, you do this:
*
* cd deeper
* # not
* cd $(pwd)/deeper
*
* By treating every path as a directory, linking to relative paths should
* require less contextual information and (fingers crossed) be more intuitive.
* @param {string} to
* @param {string} base
* @return {string}
*/
const resolve = (to, base) => {
// /foo/bar, /baz/qux => /foo/bar
if (to.startsWith("/")) return to;
const [toPathname, toQuery] = to.split("?");
const [basePathname] = base.split("?");
const toSegments = segmentize(toPathname);
const baseSegments = segmentize(basePathname);
// ?a=b, /users?b=c => /users?a=b
if (toSegments[0] === "") return addQuery(basePathname, toQuery);
// profile, /users/789 => /users/789/profile
if (!toSegments[0].startsWith(".")) {
const pathname = baseSegments.concat(toSegments).join("/");
return addQuery((basePathname === "/" ? "" : "/") + pathname, toQuery);
}
// ./ , /users/123 => /users/123
// ../ , /users/123 => /users
// ../.. , /users/123 => /
// ../../one, /a/b/c/d => /a/b/one
// .././one , /a/b/c/d => /a/b/c/one
const allSegments = baseSegments.concat(toSegments);
const segments = [];
allSegments.forEach((segment) => {
if (segment === "..") segments.pop();
else if (segment !== ".") segments.push(segment);
});
return addQuery("/" + segments.join("/"), toQuery);
};
/**
* Combines the `basepath` and the `path` into one path.
* @param {string} basepath
* @param {string} path
*/
const combinePaths = (basepath, path) =>
`${stripSlashes(
path === "/"
? basepath
: `${stripSlashes(basepath)}/${stripSlashes(path)}`
)}/`;
/**
* Decides whether a given `event` should result in a navigation or not.
* @param {object} event
*/
const shouldNavigate = (event) =>
!event.defaultPrevented &&
event.button === 0 &&
!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
// svelte seems to kill anchor.host value in ie11, so fall back to checking href
const hostMatches = (anchor) => {
const host = location.host;
return (
anchor.host === host ||
anchor.href.indexOf(`https://${host}`) === 0 ||
anchor.href.indexOf(`http://${host}`) === 0
);
};
const canUseDOM = () =>
typeof window !== "undefined" &&
"document" in window &&
"location" in window;
export {
stripSlashes,
pick,
resolve,
combinePaths,
shouldNavigate,
hostMatches,
canUseDOM,
};