- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
186 lines
6.3 KiB
JavaScript
186 lines
6.3 KiB
JavaScript
import { createMemo, getOwner, runWithOwner } from "solid-js";
|
|
const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
|
|
const trimPathRegex = /^\/+|(\/)\/+$/g;
|
|
export const mockBase = "http://sr";
|
|
export function normalizePath(path, omitSlash = false) {
|
|
const s = path.replace(trimPathRegex, "$1");
|
|
return s ? (omitSlash || /^[?#]/.test(s) ? s : "/" + s) : "";
|
|
}
|
|
export function resolvePath(base, path, from) {
|
|
if (hasSchemeRegex.test(path)) {
|
|
return undefined;
|
|
}
|
|
const basePath = normalizePath(base);
|
|
const fromPath = from && normalizePath(from);
|
|
let result = "";
|
|
if (!fromPath || path.startsWith("/")) {
|
|
result = basePath;
|
|
}
|
|
else if (fromPath.toLowerCase().indexOf(basePath.toLowerCase()) !== 0) {
|
|
result = basePath + fromPath;
|
|
}
|
|
else {
|
|
result = fromPath;
|
|
}
|
|
return (result || "/") + normalizePath(path, !result);
|
|
}
|
|
export function invariant(value, message) {
|
|
if (value == null) {
|
|
throw new Error(message);
|
|
}
|
|
return value;
|
|
}
|
|
export function joinPaths(from, to) {
|
|
return normalizePath(from).replace(/\/*(\*.*)?$/g, "") + normalizePath(to);
|
|
}
|
|
export function extractSearchParams(url) {
|
|
const params = {};
|
|
url.searchParams.forEach((value, key) => {
|
|
if (key in params) {
|
|
if (Array.isArray(params[key]))
|
|
params[key].push(value);
|
|
else
|
|
params[key] = [params[key], value];
|
|
}
|
|
else
|
|
params[key] = value;
|
|
});
|
|
return params;
|
|
}
|
|
export function createMatcher(path, partial, matchFilters) {
|
|
const [pattern, splat] = path.split("/*", 2);
|
|
const segments = pattern.split("/").filter(Boolean);
|
|
const len = segments.length;
|
|
return (location) => {
|
|
const locSegments = location.split("/").filter(Boolean);
|
|
const lenDiff = locSegments.length - len;
|
|
if (lenDiff < 0 || (lenDiff > 0 && splat === undefined && !partial)) {
|
|
return null;
|
|
}
|
|
const match = {
|
|
path: len ? "" : "/",
|
|
params: {}
|
|
};
|
|
const matchFilter = (s) => matchFilters === undefined ? undefined : matchFilters[s];
|
|
for (let i = 0; i < len; i++) {
|
|
const segment = segments[i];
|
|
const dynamic = segment[0] === ":";
|
|
const locSegment = dynamic ? locSegments[i] : locSegments[i].toLowerCase();
|
|
const key = dynamic ? segment.slice(1) : segment.toLowerCase();
|
|
if (dynamic && matchSegment(locSegment, matchFilter(key))) {
|
|
match.params[key] = locSegment;
|
|
}
|
|
else if (dynamic || !matchSegment(locSegment, key)) {
|
|
return null;
|
|
}
|
|
match.path += `/${locSegment}`;
|
|
}
|
|
if (splat) {
|
|
const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
|
if (matchSegment(remainder, matchFilter(splat))) {
|
|
match.params[splat] = remainder;
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
}
|
|
return match;
|
|
};
|
|
}
|
|
function matchSegment(input, filter) {
|
|
const isEqual = (s) => s === input;
|
|
if (filter === undefined) {
|
|
return true;
|
|
}
|
|
else if (typeof filter === "string") {
|
|
return isEqual(filter);
|
|
}
|
|
else if (typeof filter === "function") {
|
|
return filter(input);
|
|
}
|
|
else if (Array.isArray(filter)) {
|
|
return filter.some(isEqual);
|
|
}
|
|
else if (filter instanceof RegExp) {
|
|
return filter.test(input);
|
|
}
|
|
return false;
|
|
}
|
|
export function scoreRoute(route) {
|
|
const [pattern, splat] = route.pattern.split("/*", 2);
|
|
const segments = pattern.split("/").filter(Boolean);
|
|
return segments.reduce((score, segment) => score + (segment.startsWith(":") ? 2 : 3), segments.length - (splat === undefined ? 0 : 1));
|
|
}
|
|
export function createMemoObject(fn) {
|
|
const map = new Map();
|
|
const owner = getOwner();
|
|
return new Proxy({}, {
|
|
get(_, property) {
|
|
if (!map.has(property)) {
|
|
runWithOwner(owner, () => map.set(property, createMemo(() => fn()[property])));
|
|
}
|
|
return map.get(property)();
|
|
},
|
|
getOwnPropertyDescriptor() {
|
|
return {
|
|
enumerable: true,
|
|
configurable: true
|
|
};
|
|
},
|
|
ownKeys() {
|
|
return Reflect.ownKeys(fn());
|
|
},
|
|
has(_, property) {
|
|
return property in fn();
|
|
}
|
|
});
|
|
}
|
|
export function mergeSearchString(search, params) {
|
|
const merged = new URLSearchParams(search);
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value == null || value === "" || (value instanceof Array && !value.length)) {
|
|
merged.delete(key);
|
|
}
|
|
else {
|
|
if (value instanceof Array) {
|
|
// Delete all instances of the key before appending
|
|
merged.delete(key);
|
|
value.forEach(v => {
|
|
merged.append(key, String(v));
|
|
});
|
|
}
|
|
else {
|
|
merged.set(key, String(value));
|
|
}
|
|
}
|
|
});
|
|
const s = merged.toString();
|
|
return s ? `?${s}` : "";
|
|
}
|
|
export function expandOptionals(pattern) {
|
|
let match = /(\/?\:[^\/]+)\?/.exec(pattern);
|
|
if (!match)
|
|
return [pattern];
|
|
let prefix = pattern.slice(0, match.index);
|
|
let suffix = pattern.slice(match.index + match[0].length);
|
|
const prefixes = [prefix, (prefix += match[1])];
|
|
// This section handles adjacent optional params. We don't actually want all permuations since
|
|
// that will lead to equivalent routes which have the same number of params. For example
|
|
// `/:a?/:b?/:c`? only has the unique expansion: `/`, `/:a`, `/:a/:b`, `/:a/:b/:c` and we can
|
|
// discard `/:b`, `/:c`, `/:b/:c` by building them up in order and not recursing. This also helps
|
|
// ensure predictability where earlier params have precidence.
|
|
while ((match = /^(\/\:[^\/]+)\?/.exec(suffix))) {
|
|
prefixes.push((prefix += match[1]));
|
|
suffix = suffix.slice(match[0].length);
|
|
}
|
|
return expandOptionals(suffix).reduce((results, expansion) => [...results, ...prefixes.map(p => p + expansion)], []);
|
|
}
|
|
export function setFunctionName(obj, value) {
|
|
Object.defineProperty(obj, "name", {
|
|
value,
|
|
writable: false,
|
|
configurable: false
|
|
});
|
|
return obj;
|
|
}
|