akanjs 3.1.0-rc.5 → 3.1.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/href.ts +24 -0
- package/client/index.ts +1 -0
- package/client/router.ts +3 -1
- package/package.json +1 -1
- package/types/client/href.d.ts +11 -0
- package/types/client/index.d.ts +1 -0
- package/ui/Link/Anchor.tsx +7 -5
package/client/href.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class Href {
|
|
2
|
+
static readonly #blocked = new Set(["javascript:", "vbscript:", "data:"]);
|
|
3
|
+
|
|
4
|
+
/** External is what the browser takes off-site: any absolute URL with a scheme, and `//`/`\\` network-path
|
|
5
|
+
* forms it resolves to another host. javascript:, vbscript: and data: never count. */
|
|
6
|
+
static isExternal(href: string): boolean {
|
|
7
|
+
|
|
8
|
+
if (URL.canParse(href)) return !Href.#blocked.has(new URL(href).protocol);
|
|
9
|
+
|
|
10
|
+
return /^[\\/]{2}/.test(href.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020]+/, ""));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** An explicit http(s) scheme in any case; `//host` and `httpbin/x` do not qualify. */
|
|
14
|
+
static hasWebScheme(href: string): boolean {
|
|
15
|
+
return /^https?:/i.test(href);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Canonical absolute URL for an external href: scheme-relative and backslash hosts resolve against the page's
|
|
19
|
+
* http(s) origin, or https: when the page is a native `app://` shell. */
|
|
20
|
+
static toExternalUrl(href: string, base: string): string {
|
|
21
|
+
const baseProtocol = URL.canParse(base) ? new URL(base).protocol : "";
|
|
22
|
+
return new URL(href, /^https?:$/.test(baseProtocol) ? base : "https://localhost").href;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/client/index.ts
CHANGED
package/client/router.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getEnv } from "akanjs/base";
|
|
2
2
|
import { getBasePathFromPathname, Logger, parseAkanI18nEnv, parseBasePaths, trimSlashes } from "akanjs/common";
|
|
3
3
|
import { csrLocation } from "./csrLocation";
|
|
4
|
+
import { Href } from "./href";
|
|
4
5
|
import { isMcpView } from "./mcpView";
|
|
5
6
|
|
|
6
7
|
export interface RouteOptions {
|
|
@@ -158,7 +159,8 @@ export const getPathInfo = (requestUrl: string, lang: string, prefix: string) =>
|
|
|
158
159
|
? pathWithSubRoute.slice(prefixLength)
|
|
159
160
|
: pathWithSubRoute;
|
|
160
161
|
const subRoute = prefix ? `/${prefix}` : "";
|
|
161
|
-
|
|
162
|
+
|
|
163
|
+
const pathname = Href.hasWebScheme(path) ? path : path === "/" ? `/${lang}${subRoute}` : `/${lang}${subRoute}${path}`;
|
|
162
164
|
const href = `${pathname}${search ? `?${search}` : ""}${hash ? `#${hash}` : ""}`;
|
|
163
165
|
return { path, pathname, hash, search, href };
|
|
164
166
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class Href {
|
|
2
|
+
#private;
|
|
3
|
+
/** External is what the browser takes off-site: any absolute URL with a scheme, and `//`/`\\` network-path
|
|
4
|
+
* forms it resolves to another host. javascript:, vbscript: and data: never count. */
|
|
5
|
+
static isExternal(href: string): boolean;
|
|
6
|
+
/** An explicit http(s) scheme in any case; `//host` and `httpbin/x` do not qualify. */
|
|
7
|
+
static hasWebScheme(href: string): boolean;
|
|
8
|
+
/** Canonical absolute URL for an external href: scheme-relative and backslash hosts resolve against the page's
|
|
9
|
+
* http(s) origin, or https: when the page is a native `app://` shell. */
|
|
10
|
+
static toExternalUrl(href: string, base: string): string;
|
|
11
|
+
}
|
package/types/client/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./csrTypes.d.ts";
|
|
|
7
7
|
export * from "./decorators.d.ts";
|
|
8
8
|
export * from "./device.d.ts";
|
|
9
9
|
export * from "./frameConfig.d.ts";
|
|
10
|
+
export * from "./href.d.ts";
|
|
10
11
|
export * from "./locale.d.ts";
|
|
11
12
|
export * from "./makePageProto.d.ts";
|
|
12
13
|
export * from "./mcpView.d.ts";
|
package/ui/Link/Anchor.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { getEnv } from "akanjs/base";
|
|
3
|
-
import { cn, csrLocation, getPathInfo, router, usePage, usePathCtx } from "akanjs/client";
|
|
3
|
+
import { cn, csrLocation, getPathInfo, Href, router, usePage, usePathCtx } from "akanjs/client";
|
|
4
4
|
import { openExternalUrl } from "akanjs/client/native";
|
|
5
5
|
import { Logger } from "akanjs/common";
|
|
6
6
|
import { st } from "akanjs/store";
|
|
@@ -50,10 +50,12 @@ export const CsrLink = ({
|
|
|
50
50
|
onClick={(event) => {
|
|
51
51
|
props.onClick?.(event);
|
|
52
52
|
if (event.defaultPrevented) return;
|
|
53
|
-
const isExternal =
|
|
53
|
+
const isExternal = Href.isExternal(href);
|
|
54
54
|
const url = href.startsWith("#") ? `${csrLocation.pathname}#${hash}` : href;
|
|
55
|
-
if (isExternal)
|
|
56
|
-
|
|
55
|
+
if (isExternal) {
|
|
56
|
+
const target = Href.toExternalUrl(href, window.location.href);
|
|
57
|
+
void openExternalUrl(target).catch(() => window.open(target, "_blank", "noopener,noreferrer"));
|
|
58
|
+
} else if (replace) router.replace(url, { scrollToTop });
|
|
57
59
|
else router.push(url, { scrollToTop });
|
|
58
60
|
}}
|
|
59
61
|
>
|
|
@@ -78,7 +80,7 @@ export const SsrLink = ({
|
|
|
78
80
|
const { lang } = usePage();
|
|
79
81
|
const pathname = useCurrentPathname();
|
|
80
82
|
const currentPath = getPathInfo(pathname, lang, prefix).path;
|
|
81
|
-
const isExternal =
|
|
83
|
+
const isExternal = Href.isExternal(href);
|
|
82
84
|
const internalPathInfo = getPathInfo(href, lang, prefix);
|
|
83
85
|
const requestHref = getEnv().operationMode === "local" ? internalPathInfo.href : getPathInfo(href, lang, "").href;
|
|
84
86
|
const path = internalPathInfo.path;
|