@verbb/plugin-kit-core 2.0.4 → 2.0.5
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/CHANGELOG.md +14 -0
- package/dist/host/hostBridge.js +35 -0
- package/dist/host/hostBridge.js.map +1 -0
- package/dist/host/portal.js +52 -0
- package/dist/host/portal.js.map +1 -0
- package/dist/index.js +10 -0
- package/dist/utils/collections.js +155 -0
- package/dist/utils/collections.js.map +1 -0
- package/dist/utils/forms.js +46 -0
- package/dist/utils/forms.js.map +1 -0
- package/dist/utils/handle.js +69 -0
- package/dist/utils/handle.js.map +1 -0
- package/dist/utils/markdown.d.ts +19 -7
- package/dist/utils/markdown.d.ts.map +1 -1
- package/dist/utils/markdown.js +61 -0
- package/dist/utils/markdown.js.map +1 -0
- package/dist/utils/promises.js +36 -0
- package/dist/utils/promises.js.map +1 -0
- package/dist/utils/query.js +13 -0
- package/dist/utils/query.js.map +1 -0
- package/dist/utils/string.js +42 -0
- package/dist/utils/string.js.map +1 -0
- package/package.json +25 -4
- package/dist/plugin-kit-core.es.js +0 -464
- package/dist/plugin-kit-core.es.js.map +0 -1
- package/dist/plugin-kit-core.umd.js +0 -528
- package/dist/plugin-kit-core.umd.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 2.0.5 - 2026-07-21
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Importing string/handle utils (or anything else) from the package root no longer bundles `markdown-it` (~45 kB gzip) into a consumer. `markdown-it` is now constructed lazily and the package is published as `sideEffects: false`, so it only enters a bundle when `renderMarkdown` / `renderInlineMarkdown` (or `@verbb/plugin-kit-core/utils/markdown`) is actually used.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Granular subpath exports so consumers can cherry-pick exactly what they need: `./utils`, `./utils/string`, `./utils/handle`, `./utils/markdown`, and `./host`. The build now preserves the `src` module structure (`dist/utils/string.js`, `dist/host/index.js`, …) instead of one concatenated bundle.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- The package main entry is now `dist/index.js` (was `dist/plugin-kit-core.es.js`). Consumers importing from `@verbb/plugin-kit-core` via the `exports` map are unaffected; anything deep-linking the old bundle filename must switch to the package root or a subpath export.
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
- **Breaking:** removed the eager `export { md }` markdown-it instance (an exported pre-built instance is itself a module-level side effect that defeats tree-shaking). Use the new `getMarkdownIt()` accessor for direct markdown-it API access.
|
|
18
|
+
|
|
5
19
|
## 2.0.4 - 2026-07-21
|
|
6
20
|
|
|
7
21
|
### Changed
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/host/hostBridge.ts
|
|
2
|
+
var hostBridge = {};
|
|
3
|
+
var setHostBridge = (bridge = {}) => {
|
|
4
|
+
hostBridge = {
|
|
5
|
+
...hostBridge,
|
|
6
|
+
...bridge
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
var getHostBridge = () => {
|
|
10
|
+
return hostBridge;
|
|
11
|
+
};
|
|
12
|
+
var requireHostBridgeMethod = (methodName) => {
|
|
13
|
+
const method = hostBridge[methodName];
|
|
14
|
+
if (!method) throw new Error(`Plugin Kit host bridge method "${String(methodName)}" is required but was not configured.`);
|
|
15
|
+
return method;
|
|
16
|
+
};
|
|
17
|
+
var hostRequest = async (method, action, config) => {
|
|
18
|
+
return requireHostBridgeMethod("request")(method, action, config);
|
|
19
|
+
};
|
|
20
|
+
var hostOpenElementSelector = (elementType, options) => {
|
|
21
|
+
requireHostBridgeMethod("openElementSelector")(elementType, options);
|
|
22
|
+
};
|
|
23
|
+
var hostFormatDate = (date) => {
|
|
24
|
+
return requireHostBridgeMethod("formatDate")(date);
|
|
25
|
+
};
|
|
26
|
+
var hostGetTimepickerOptions = () => {
|
|
27
|
+
return requireHostBridgeMethod("getTimepickerOptions")();
|
|
28
|
+
};
|
|
29
|
+
var hostGetLocale = () => {
|
|
30
|
+
return requireHostBridgeMethod("getLocale")();
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { getHostBridge, hostFormatDate, hostGetLocale, hostGetTimepickerOptions, hostOpenElementSelector, hostRequest, setHostBridge };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=hostBridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostBridge.js","names":[],"sources":["../../src/host/hostBridge.ts"],"sourcesContent":["export type HostRequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\nexport type HostRequestConfig = {\n data?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport type HostSelectedElement = {\n id?: number;\n siteId?: number;\n label?: string;\n url?: string;\n [key: string]: unknown;\n};\n\nexport type HostElementSelectorOptions = {\n storageKey: string;\n sources?: string[];\n criteria?: Record<string, unknown>;\n multiSelect?: boolean;\n limit?: number | null;\n defaultSiteId?: number;\n autoFocusSearchBox?: boolean;\n showSiteMenu?: boolean;\n onShow?: () => void;\n onSelect: (elements: HostSelectedElement[]) => void;\n closeOtherModals?: boolean;\n};\n\nexport type PluginKitHostBridge = {\n request: <T = unknown>(method: HostRequestMethod, action: string, config?: HostRequestConfig) => Promise<T>;\n openElementSelector: (elementType: string, options: HostElementSelectorOptions) => void;\n formatDate: (date: Date) => string;\n getTimepickerOptions: () => Record<string, unknown>;\n getLocale: () => string;\n};\n\nlet hostBridge: Partial<PluginKitHostBridge> = {};\n\nexport const setHostBridge = (bridge: Partial<PluginKitHostBridge> = {}): void => {\n hostBridge = {\n ...hostBridge,\n ...bridge,\n };\n};\n\nexport const getHostBridge = (): Partial<PluginKitHostBridge> => {\n return hostBridge;\n};\n\nconst requireHostBridgeMethod = <K extends keyof PluginKitHostBridge>(\n methodName: K,\n): NonNullable<PluginKitHostBridge[K]> => {\n const method = hostBridge[methodName];\n\n if (!method) {\n throw new Error(`Plugin Kit host bridge method \"${String(methodName)}\" is required but was not configured.`);\n }\n\n return method as NonNullable<PluginKitHostBridge[K]>;\n};\n\nexport const hostRequest = async <T = unknown>(\n method: HostRequestMethod,\n action: string,\n config?: HostRequestConfig,\n): Promise<T> => {\n const request = requireHostBridgeMethod('request');\n return request(method, action, config);\n};\n\nexport const hostOpenElementSelector = (elementType: string, options: HostElementSelectorOptions): void => {\n const openElementSelector = requireHostBridgeMethod('openElementSelector');\n openElementSelector(elementType, options);\n};\n\nexport const hostFormatDate = (date: Date): string => {\n const formatDate = requireHostBridgeMethod('formatDate');\n return formatDate(date);\n};\n\nexport const hostGetTimepickerOptions = (): Record<string, unknown> => {\n const getTimepickerOptions = requireHostBridgeMethod('getTimepickerOptions');\n return getTimepickerOptions();\n};\n\nexport const hostGetLocale = (): string => {\n const getLocale = requireHostBridgeMethod('getLocale');\n return getLocale();\n};\n"],"mappings":";AAqCA,IAAI,aAA2C,EAAE;AAEjD,IAAa,iBAAiB,SAAuC,EAAE,KAAW;AAC9E,cAAa;EACT,GAAG;EACH,GAAG;EACN;;AAGL,IAAa,sBAAoD;AAC7D,QAAO;;AAGX,IAAM,2BACF,eACsC;CACtC,MAAM,SAAS,WAAW;AAE1B,KAAI,CAAC,OACD,OAAM,IAAI,MAAM,kCAAkC,OAAO,WAAW,CAAC,uCAAuC;AAGhH,QAAO;;AAGX,IAAa,cAAc,OACvB,QACA,QACA,WACa;AAEb,QADgB,wBAAwB,UACjC,CAAQ,QAAQ,QAAQ,OAAO;;AAG1C,IAAa,2BAA2B,aAAqB,YAA8C;AAC3E,yBAAwB,sBACpD,CAAoB,aAAa,QAAQ;;AAG7C,IAAa,kBAAkB,SAAuB;AAElD,QADmB,wBAAwB,aACpC,CAAW,KAAK;;AAG3B,IAAa,iCAA0D;AAEnE,QAD6B,wBAAwB,uBAC9C,EAAsB;;AAGjC,IAAa,sBAA8B;AAEvC,QADkB,wBAAwB,YACnC,EAAW"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/host/portal.ts
|
|
2
|
+
var defaultPortalClassName;
|
|
3
|
+
var defaultPortalContainer;
|
|
4
|
+
var defaultShadowRootSelectors = ["[data-pk-shadow-root]"];
|
|
5
|
+
var setPortalClassName = (className) => {
|
|
6
|
+
defaultPortalClassName = className.trim() || void 0;
|
|
7
|
+
};
|
|
8
|
+
var getPortalClassName = (className) => {
|
|
9
|
+
return (className ?? defaultPortalClassName)?.trim() || void 0;
|
|
10
|
+
};
|
|
11
|
+
var resolvePortalContainer = (container) => {
|
|
12
|
+
if (!container) return;
|
|
13
|
+
if (typeof container === "object" && "current" in container) return container.current ?? void 0;
|
|
14
|
+
return container ?? void 0;
|
|
15
|
+
};
|
|
16
|
+
var setPortalContainer = (container) => {
|
|
17
|
+
defaultPortalContainer = resolvePortalContainer(container);
|
|
18
|
+
};
|
|
19
|
+
var getPortalContainer = (container) => {
|
|
20
|
+
return resolvePortalContainer(container) ?? defaultPortalContainer;
|
|
21
|
+
};
|
|
22
|
+
var setShadowRootSelectors = (selectors) => {
|
|
23
|
+
const normalizedSelectors = (selectors || []).map((selector) => selector.trim()).filter(Boolean);
|
|
24
|
+
defaultShadowRootSelectors = normalizedSelectors.length > 0 ? normalizedSelectors : ["[data-pk-shadow-root]"];
|
|
25
|
+
};
|
|
26
|
+
var getShadowRootSelectors = () => {
|
|
27
|
+
return defaultShadowRootSelectors;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Resolve where floating content should be portaled.
|
|
31
|
+
* Falls back to document.body when no container is configured.
|
|
32
|
+
*/
|
|
33
|
+
var getPortalMountNode = (container) => {
|
|
34
|
+
const resolved = getPortalContainer(container);
|
|
35
|
+
if (resolved instanceof HTMLElement) return resolved;
|
|
36
|
+
if (resolved instanceof ShadowRoot) {
|
|
37
|
+
const target = getShadowRootSelectors().map((selector) => resolved.querySelector(selector)).find(Boolean) ?? (resolved.host instanceof HTMLElement ? resolved.host : void 0);
|
|
38
|
+
if (target) return target;
|
|
39
|
+
}
|
|
40
|
+
return document.body;
|
|
41
|
+
};
|
|
42
|
+
var getPortalTargetForAppend = () => {
|
|
43
|
+
const container = getPortalContainer();
|
|
44
|
+
if (!container) return;
|
|
45
|
+
if (container instanceof HTMLElement) return container;
|
|
46
|
+
const shadowRoot = container;
|
|
47
|
+
return getShadowRootSelectors().map((selector) => shadowRoot.querySelector(selector)).find(Boolean) ?? (shadowRoot.host instanceof HTMLElement ? shadowRoot.host : void 0);
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
export { getPortalClassName, getPortalContainer, getPortalMountNode, getPortalTargetForAppend, getShadowRootSelectors, setPortalClassName, setPortalContainer, setShadowRootSelectors };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=portal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal.js","names":[],"sources":["../../src/host/portal.ts"],"sourcesContent":["export type PortalContainer = HTMLElement | ShadowRoot | null;\n\nexport type PortalContainerRef = {\n current: PortalContainer;\n};\n\nexport type PortalContainerInput = PortalContainer | PortalContainerRef;\n\nlet defaultPortalClassName: string | undefined;\nlet defaultPortalContainer: PortalContainer | undefined;\nlet defaultShadowRootSelectors: string[] = ['[data-pk-shadow-root]'];\n\nexport const setPortalClassName = (className: string): void => {\n defaultPortalClassName = className.trim() || undefined;\n};\n\nexport const getPortalClassName = (className?: string): string | undefined => {\n return (className ?? defaultPortalClassName)?.trim() || undefined;\n};\n\nconst resolvePortalContainer = (container?: PortalContainerInput): PortalContainer | undefined => {\n if (!container) {\n return undefined;\n }\n\n if (typeof container === 'object' && 'current' in container) {\n return container.current ?? undefined;\n }\n\n return container ?? undefined;\n};\n\nexport const setPortalContainer = (container: PortalContainerInput): void => {\n defaultPortalContainer = resolvePortalContainer(container);\n};\n\nexport const getPortalContainer = (container?: PortalContainerInput): PortalContainer | undefined => {\n return resolvePortalContainer(container) ?? defaultPortalContainer;\n};\n\nexport const setShadowRootSelectors = (selectors: string[]): void => {\n const normalizedSelectors = (selectors || [])\n .map((selector) => selector.trim())\n .filter(Boolean);\n\n defaultShadowRootSelectors = normalizedSelectors.length > 0\n ? normalizedSelectors\n : ['[data-pk-shadow-root]'];\n};\n\nexport const getShadowRootSelectors = (): string[] => {\n return defaultShadowRootSelectors;\n};\n\n/**\n * Resolve where floating content should be portaled.\n * Falls back to document.body when no container is configured.\n */\nexport const getPortalMountNode = (container?: PortalContainerInput): HTMLElement => {\n const resolved = getPortalContainer(container);\n\n if (resolved instanceof HTMLElement) {\n return resolved;\n }\n\n if (resolved instanceof ShadowRoot) {\n const target = getShadowRootSelectors()\n .map((selector) => resolved.querySelector<HTMLElement>(selector))\n .find(Boolean)\n ?? (resolved.host instanceof HTMLElement ? resolved.host : undefined);\n\n if (target) {\n return target;\n }\n }\n\n return document.body;\n};\n\nexport const getPortalTargetForAppend = (): HTMLElement | undefined => {\n const container = getPortalContainer();\n if (!container) {\n return undefined;\n }\n\n if (container instanceof HTMLElement) {\n return container;\n }\n\n const shadowRoot = container;\n return getShadowRootSelectors()\n .map((selector) => shadowRoot.querySelector<HTMLElement>(selector))\n .find(Boolean)\n ?? (shadowRoot.host instanceof HTMLElement ? shadowRoot.host : undefined);\n};\n"],"mappings":";AAQA,IAAI;AACJ,IAAI;AACJ,IAAI,6BAAuC,CAAC,wBAAwB;AAEpE,IAAa,sBAAsB,cAA4B;AAC3D,0BAAyB,UAAU,MAAM,IAAI,KAAA;;AAGjD,IAAa,sBAAsB,cAA2C;AAC1E,SAAQ,aAAa,yBAAyB,MAAM,IAAI,KAAA;;AAG5D,IAAM,0BAA0B,cAAkE;AAC9F,KAAI,CAAC,UACD;AAGJ,KAAI,OAAO,cAAc,YAAY,aAAa,UAC9C,QAAO,UAAU,WAAW,KAAA;AAGhC,QAAO,aAAa,KAAA;;AAGxB,IAAa,sBAAsB,cAA0C;AACzE,0BAAyB,uBAAuB,UAAU;;AAG9D,IAAa,sBAAsB,cAAkE;AACjG,QAAO,uBAAuB,UAAU,IAAI;;AAGhD,IAAa,0BAA0B,cAA8B;CACjE,MAAM,uBAAuB,aAAa,EAAE,EACvC,KAAK,aAAa,SAAS,MAAM,CAAC,CAClC,OAAO,QAAQ;AAEpB,8BAA6B,oBAAoB,SAAS,IACpD,sBACA,CAAC,wBAAwB;;AAGnC,IAAa,+BAAyC;AAClD,QAAO;;;;;;AAOX,IAAa,sBAAsB,cAAkD;CACjF,MAAM,WAAW,mBAAmB,UAAU;AAE9C,KAAI,oBAAoB,YACpB,QAAO;AAGX,KAAI,oBAAoB,YAAY;EAChC,MAAM,SAAS,wBAAwB,CAClC,KAAK,aAAa,SAAS,cAA2B,SAAS,CAAC,CAChE,KAAK,QAAQ,KACV,SAAS,gBAAgB,cAAc,SAAS,OAAO,KAAA;AAE/D,MAAI,OACA,QAAO;;AAIf,QAAO,SAAS;;AAGpB,IAAa,iCAA0D;CACnE,MAAM,YAAY,oBAAoB;AACtC,KAAI,CAAC,UACD;AAGJ,KAAI,qBAAqB,YACrB,QAAO;CAGX,MAAM,aAAa;AACnB,QAAO,wBAAwB,CAC1B,KAAK,aAAa,WAAW,cAA2B,SAAS,CAAC,CAClE,KAAK,QAAQ,KACV,WAAW,gBAAgB,cAAc,WAAW,OAAO,KAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getPortalClassName, getPortalContainer, getPortalMountNode, getPortalTargetForAppend, getShadowRootSelectors, setPortalClassName, setPortalContainer, setShadowRootSelectors } from "./host/portal.js";
|
|
2
|
+
import { getHostBridge, hostFormatDate, hostGetLocale, hostGetTimepickerOptions, hostOpenElementSelector, hostRequest, setHostBridge } from "./host/hostBridge.js";
|
|
3
|
+
import { clone, createItem, deleteItem, duplicateItem, findItemById, findRecursive, generateId, getExistingItems, getNewItems, moveItem, normalizeCollection, updateItem } from "./utils/collections.js";
|
|
4
|
+
import { getErrorMessage } from "./utils/forms.js";
|
|
5
|
+
import { findUniqueHandle, generateHandle } from "./utils/string.js";
|
|
6
|
+
import { buildUniqueHandleFromSource, getDynamicReservedHandles } from "./utils/handle.js";
|
|
7
|
+
import { getMarkdownIt, renderInlineMarkdown, renderMarkdown } from "./utils/markdown.js";
|
|
8
|
+
import { takeAtLeast } from "./utils/promises.js";
|
|
9
|
+
import { getQueryParam, setQueryParam } from "./utils/query.js";
|
|
10
|
+
export { buildUniqueHandleFromSource, clone, createItem, deleteItem, duplicateItem, findItemById, findRecursive, findUniqueHandle, generateHandle, generateId, getDynamicReservedHandles, getErrorMessage, getExistingItems, getHostBridge, getMarkdownIt, getNewItems, getPortalClassName, getPortalContainer, getPortalMountNode, getPortalTargetForAppend, getQueryParam, getShadowRootSelectors, hostFormatDate, hostGetLocale, hostGetTimepickerOptions, hostOpenElementSelector, hostRequest, moveItem, normalizeCollection, renderInlineMarkdown, renderMarkdown, setHostBridge, setPortalClassName, setPortalContainer, setQueryParam, setShadowRootSelectors, takeAtLeast, updateItem };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//#region src/utils/collections.ts
|
|
2
|
+
/**
|
|
3
|
+
* Collections utility for managing client-side collections of models
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generates a unique client-side ID string
|
|
7
|
+
* @param {string} [prefix='client'] - Optional prefix for the generated ID
|
|
8
|
+
* @returns {string} A unique ID string in the format: prefix_timestamp_randomString
|
|
9
|
+
*/
|
|
10
|
+
var generateId = (prefix = "client") => {
|
|
11
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Normalize a collection by adding _id to each item that doesn't have one
|
|
15
|
+
* @param {Array} collection - Array of items
|
|
16
|
+
* @returns {Array} - Normalized collection with _id on each item
|
|
17
|
+
*/
|
|
18
|
+
var normalizeCollection = (collection = []) => {
|
|
19
|
+
return collection.map((item) => {
|
|
20
|
+
if (item._id) return item;
|
|
21
|
+
return {
|
|
22
|
+
...item,
|
|
23
|
+
_id: generateId()
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Create a new collection item
|
|
29
|
+
* @param {Object} itemData - Initial data for the new item
|
|
30
|
+
* @returns {Object} - The new item
|
|
31
|
+
*/
|
|
32
|
+
var createItem = (itemData = {}) => {
|
|
33
|
+
return {
|
|
34
|
+
...itemData,
|
|
35
|
+
_id: generateId()
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Duplicate an existing item in a collection
|
|
40
|
+
* @param {Array} collection - Current collection
|
|
41
|
+
* @param {Object} item - Item to duplicate
|
|
42
|
+
* @param {Function} transformCallback - Optional callback to transform the duplicated item
|
|
43
|
+
* @returns {Object} - Updated collection with duplicated item
|
|
44
|
+
*/
|
|
45
|
+
var duplicateItem = (collection = [], item, transformCallback = null) => {
|
|
46
|
+
const duplicatedItem = {
|
|
47
|
+
...item,
|
|
48
|
+
_id: generateId()
|
|
49
|
+
};
|
|
50
|
+
const finalItem = transformCallback ? transformCallback(duplicatedItem) : duplicatedItem;
|
|
51
|
+
return [...collection, finalItem];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Delete a item from a collection
|
|
55
|
+
* @param {Array} collection - Current collection
|
|
56
|
+
* @param {Object} item - Item to delete
|
|
57
|
+
* @returns {Object} - Updated collection without the deleted item
|
|
58
|
+
*/
|
|
59
|
+
var deleteItem = (collection = [], itemToDelete) => {
|
|
60
|
+
return collection.filter((item) => {
|
|
61
|
+
return item._id !== itemToDelete._id;
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Update an item in a collection
|
|
66
|
+
* @param {Array} collection - Current collection
|
|
67
|
+
* @param {Object} item - Item to update
|
|
68
|
+
* @param {Object} updates - Updates to apply to the item
|
|
69
|
+
* @returns {Object} - Updated collection with updated item
|
|
70
|
+
*/
|
|
71
|
+
var updateItem = (collection = [], itemToUpdate, updates) => {
|
|
72
|
+
return collection.map((item) => {
|
|
73
|
+
if (item._id === itemToUpdate._id) return {
|
|
74
|
+
...item,
|
|
75
|
+
...updates,
|
|
76
|
+
_id: item._id
|
|
77
|
+
};
|
|
78
|
+
return item;
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Move an item to a new position in a collection
|
|
83
|
+
* @param {Array} collection - Current collection
|
|
84
|
+
* @param {Object} fromItem - Item to move
|
|
85
|
+
* @param {Object} toItem - Item to move to (insert before this item)
|
|
86
|
+
* @returns {Array} - Updated collection with moved item
|
|
87
|
+
*/
|
|
88
|
+
var moveItem = (collection = [], fromItem, toItem) => {
|
|
89
|
+
if (fromItem._id === toItem._id) return collection;
|
|
90
|
+
const fromIndex = collection.findIndex((item) => {
|
|
91
|
+
return item._id === fromItem._id;
|
|
92
|
+
});
|
|
93
|
+
const toIndex = collection.findIndex((item) => {
|
|
94
|
+
return item._id === toItem._id;
|
|
95
|
+
});
|
|
96
|
+
if (fromIndex === -1 || toIndex === -1) return collection;
|
|
97
|
+
const newCollection = [...collection];
|
|
98
|
+
const [movedItem] = newCollection.splice(fromIndex, 1);
|
|
99
|
+
newCollection.splice(toIndex, 0, movedItem);
|
|
100
|
+
return newCollection;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Find an item in a collection by _id
|
|
104
|
+
* @param {Array} collection - Collection to search
|
|
105
|
+
* @param {string} _id - Client-side ID to find
|
|
106
|
+
* @returns {Object|null} - Found model or null
|
|
107
|
+
*/
|
|
108
|
+
var findItemById = (collection = [], _id) => {
|
|
109
|
+
return collection.find((item) => {
|
|
110
|
+
return item._id === _id;
|
|
111
|
+
}) || null;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Get all items that have server-side IDs (existing items)
|
|
115
|
+
* @param {Array} collection - Collection to filter
|
|
116
|
+
* @returns {Array} - Items with server-side IDs
|
|
117
|
+
*/
|
|
118
|
+
var getExistingItems = (collection = []) => {
|
|
119
|
+
return collection.filter((item) => {
|
|
120
|
+
return item.id;
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Get all items that don't have server-side IDs (new items)
|
|
125
|
+
* @param {Array} collection - Collection to filter
|
|
126
|
+
* @returns {Array} - Items without server-side IDs
|
|
127
|
+
*/
|
|
128
|
+
var getNewItems = (collection = []) => {
|
|
129
|
+
return collection.filter((item) => {
|
|
130
|
+
return !item.id;
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
var findRecursive = (items, predicate, optionsKey = "options") => {
|
|
134
|
+
for (const item of items) {
|
|
135
|
+
if (item[optionsKey] && Array.isArray(item[optionsKey])) {
|
|
136
|
+
const result = findRecursive(item[optionsKey], predicate, optionsKey);
|
|
137
|
+
if (result) return result;
|
|
138
|
+
}
|
|
139
|
+
if (predicate(item)) return item;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Creates a deep clone of a value using JSON serialization
|
|
145
|
+
* @param {any} value - The value to clone
|
|
146
|
+
* @returns {any} A deep clone of the input value, or undefined if input is undefined
|
|
147
|
+
*/
|
|
148
|
+
var clone = function(value) {
|
|
149
|
+
if (value === void 0) return;
|
|
150
|
+
return JSON.parse(JSON.stringify(value));
|
|
151
|
+
};
|
|
152
|
+
//#endregion
|
|
153
|
+
export { clone, createItem, deleteItem, duplicateItem, findItemById, findRecursive, generateId, getExistingItems, getNewItems, moveItem, normalizeCollection, updateItem };
|
|
154
|
+
|
|
155
|
+
//# sourceMappingURL=collections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collections.js","names":[],"sources":["../../src/utils/collections.ts"],"sourcesContent":["/**\n * Collections utility for managing client-side collections of models\n */\n\n/**\n * Generates a unique client-side ID string\n * @param {string} [prefix='client'] - Optional prefix for the generated ID\n * @returns {string} A unique ID string in the format: prefix_timestamp_randomString\n */\nexport const generateId = (prefix = 'client'): string => {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n};\n\n/**\n * Normalize a collection by adding _id to each item that doesn't have one\n * @param {Array} collection - Array of items\n * @returns {Array} - Normalized collection with _id on each item\n */\nexport const normalizeCollection = (collection: any[] = []): any[] => {\n return collection.map((item) => {\n // If item already has _id, keep it (for client-side created items)\n if (item._id) {\n return item;\n }\n\n // Generate a client-side internal ID for server-side items\n return {\n ...item,\n _id: generateId(),\n };\n });\n};\n\n/**\n * Create a new collection item\n * @param {Object} itemData - Initial data for the new item\n * @returns {Object} - The new item\n */\nexport const createItem = (itemData: any = {}): any => {\n return {\n ...itemData,\n _id: generateId(),\n };\n};\n\n/**\n * Duplicate an existing item in a collection\n * @param {Array} collection - Current collection\n * @param {Object} item - Item to duplicate\n * @param {Function} transformCallback - Optional callback to transform the duplicated item\n * @returns {Object} - Updated collection with duplicated item\n */\nexport const duplicateItem = (collection: any[] = [], item: any, transformCallback: ((item: any) => any) | null = null): any[] => {\n // Create base duplicated item\n const duplicatedItem = {\n ...item,\n // Generate new client-side ID\n _id: generateId(),\n };\n\n // Apply custom transformation if provided\n const finalItem = transformCallback ? transformCallback(duplicatedItem) : duplicatedItem;\n\n return [...collection, finalItem];\n};\n\n/**\n * Delete a item from a collection\n * @param {Array} collection - Current collection\n * @param {Object} item - Item to delete\n * @returns {Object} - Updated collection without the deleted item\n */\nexport const deleteItem = (collection: any[] = [], itemToDelete: any): any[] => {\n return collection.filter((item) => { return item._id !== itemToDelete._id; });\n};\n\n/**\n * Update an item in a collection\n * @param {Array} collection - Current collection\n * @param {Object} item - Item to update\n * @param {Object} updates - Updates to apply to the item\n * @returns {Object} - Updated collection with updated item\n */\nexport const updateItem = (collection: any[] = [], itemToUpdate: any, updates: any): any[] => {\n return collection.map((item) => {\n if (item._id === itemToUpdate._id) {\n return {\n ...item,\n ...updates,\n // Preserve the _id\n _id: item._id,\n };\n }\n return item;\n });\n};\n\n/**\n * Move an item to a new position in a collection\n * @param {Array} collection - Current collection\n * @param {Object} fromItem - Item to move\n * @param {Object} toItem - Item to move to (insert before this item)\n * @returns {Array} - Updated collection with moved item\n */\nexport const moveItem = (collection: any[] = [], fromItem: any, toItem: any): any[] => {\n if (fromItem._id === toItem._id) {\n return collection;\n }\n\n const fromIndex = collection.findIndex((item) => { return item._id === fromItem._id; });\n const toIndex = collection.findIndex((item) => { return item._id === toItem._id; });\n\n if (fromIndex === -1 || toIndex === -1) {\n return collection;\n }\n\n const newCollection = [...collection];\n const [movedItem] = newCollection.splice(fromIndex, 1);\n newCollection.splice(toIndex, 0, movedItem);\n\n return newCollection;\n};\n\n/**\n * Find an item in a collection by _id\n * @param {Array} collection - Collection to search\n * @param {string} _id - Client-side ID to find\n * @returns {Object|null} - Found model or null\n */\nexport const findItemById = (collection: any[] = [], _id: string): any | null => {\n return collection.find((item) => { return item._id === _id; }) || null;\n};\n\n/**\n * Get all items that have server-side IDs (existing items)\n * @param {Array} collection - Collection to filter\n * @returns {Array} - Items with server-side IDs\n */\nexport const getExistingItems = (collection: any[] = []): any[] => {\n return collection.filter((item) => { return item.id; });\n};\n\n/**\n * Get all items that don't have server-side IDs (new items)\n * @param {Array} collection - Collection to filter\n * @returns {Array} - Items without server-side IDs\n */\nexport const getNewItems = (collection: any[] = []): any[] => {\n return collection.filter((item) => { return !item.id; });\n};\n\nexport const findRecursive = <T = any>(items: T[], predicate: (item: T) => boolean, optionsKey = 'options'): T | null => {\n for (const item of items) {\n // If this item has nested options, search recursively\n if ((item as any)[optionsKey] && Array.isArray((item as any)[optionsKey])) {\n const result = findRecursive((item as any)[optionsKey], predicate, optionsKey);\n\n if (result) {\n return result;\n }\n }\n\n // Check if this item matches the predicate\n if (predicate(item)) {\n return item;\n }\n }\n\n return null;\n};\n\n/**\n * Creates a deep clone of a value using JSON serialization\n * @param {any} value - The value to clone\n * @returns {any} A deep clone of the input value, or undefined if input is undefined\n */\nexport const clone = function <T>(value: T): T | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n return JSON.parse(JSON.stringify(value));\n};\n"],"mappings":";;;;;;;;;AASA,IAAa,cAAc,SAAS,aAAqB;AACrD,QAAO,GAAG,OAAO,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,OAAO,GAAG,EAAE;;;;;;;AAQ7E,IAAa,uBAAuB,aAAoB,EAAE,KAAY;AAClE,QAAO,WAAW,KAAK,SAAS;AAE5B,MAAI,KAAK,IACL,QAAO;AAIX,SAAO;GACH,GAAG;GACH,KAAK,YAAY;GACpB;GACH;;;;;;;AAQN,IAAa,cAAc,WAAgB,EAAE,KAAU;AACnD,QAAO;EACH,GAAG;EACH,KAAK,YAAY;EACpB;;;;;;;;;AAUL,IAAa,iBAAiB,aAAoB,EAAE,EAAE,MAAW,oBAAiD,SAAgB;CAE9H,MAAM,iBAAiB;EACnB,GAAG;EAEH,KAAK,YAAY;EACpB;CAGD,MAAM,YAAY,oBAAoB,kBAAkB,eAAe,GAAG;AAE1E,QAAO,CAAC,GAAG,YAAY,UAAU;;;;;;;;AASrC,IAAa,cAAc,aAAoB,EAAE,EAAE,iBAA6B;AAC5E,QAAO,WAAW,QAAQ,SAAS;AAAE,SAAO,KAAK,QAAQ,aAAa;GAAO;;;;;;;;;AAUjF,IAAa,cAAc,aAAoB,EAAE,EAAE,cAAmB,YAAwB;AAC1F,QAAO,WAAW,KAAK,SAAS;AAC5B,MAAI,KAAK,QAAQ,aAAa,IAC1B,QAAO;GACH,GAAG;GACH,GAAG;GAEH,KAAK,KAAK;GACb;AAEL,SAAO;GACT;;;;;;;;;AAUN,IAAa,YAAY,aAAoB,EAAE,EAAE,UAAe,WAAuB;AACnF,KAAI,SAAS,QAAQ,OAAO,IACxB,QAAO;CAGX,MAAM,YAAY,WAAW,WAAW,SAAS;AAAE,SAAO,KAAK,QAAQ,SAAS;GAAO;CACvF,MAAM,UAAU,WAAW,WAAW,SAAS;AAAE,SAAO,KAAK,QAAQ,OAAO;GAAO;AAEnF,KAAI,cAAc,MAAM,YAAY,GAChC,QAAO;CAGX,MAAM,gBAAgB,CAAC,GAAG,WAAW;CACrC,MAAM,CAAC,aAAa,cAAc,OAAO,WAAW,EAAE;AACtD,eAAc,OAAO,SAAS,GAAG,UAAU;AAE3C,QAAO;;;;;;;;AASX,IAAa,gBAAgB,aAAoB,EAAE,EAAE,QAA4B;AAC7E,QAAO,WAAW,MAAM,SAAS;AAAE,SAAO,KAAK,QAAQ;GAAO,IAAI;;;;;;;AAQtE,IAAa,oBAAoB,aAAoB,EAAE,KAAY;AAC/D,QAAO,WAAW,QAAQ,SAAS;AAAE,SAAO,KAAK;GAAM;;;;;;;AAQ3D,IAAa,eAAe,aAAoB,EAAE,KAAY;AAC1D,QAAO,WAAW,QAAQ,SAAS;AAAE,SAAO,CAAC,KAAK;GAAM;;AAG5D,IAAa,iBAA0B,OAAY,WAAiC,aAAa,cAAwB;AACrH,MAAK,MAAM,QAAQ,OAAO;AAEtB,MAAK,KAAa,eAAe,MAAM,QAAS,KAAa,YAAY,EAAE;GACvE,MAAM,SAAS,cAAe,KAAa,aAAa,WAAW,WAAW;AAE9E,OAAI,OACA,QAAO;;AAKf,MAAI,UAAU,KAAK,CACf,QAAO;;AAIf,QAAO;;;;;;;AAQX,IAAa,QAAQ,SAAa,OAAyB;AACvD,KAAI,UAAU,KAAA,EACV;AAGJ,QAAO,KAAK,MAAM,KAAK,UAAU,MAAM,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/utils/forms.ts
|
|
2
|
+
var nl2br = (str) => {
|
|
3
|
+
return str.replace(/\n/g, "<br>");
|
|
4
|
+
};
|
|
5
|
+
var getErrorHeading = (error) => {
|
|
6
|
+
if (error.response?.statusText) return error.response.statusText;
|
|
7
|
+
if (error.message?.includes("Network Error")) return "Network Error";
|
|
8
|
+
if (error.message?.includes("timeout")) return "Request Timeout";
|
|
9
|
+
return "An error has occurred";
|
|
10
|
+
};
|
|
11
|
+
var getErrorText = (error) => {
|
|
12
|
+
if (error.response?.data?.message) return error.response.data.message;
|
|
13
|
+
if (error.response?.data?.error) return error.response.data.error;
|
|
14
|
+
if (error.message) return error.message;
|
|
15
|
+
return String(error);
|
|
16
|
+
};
|
|
17
|
+
var getErrorTrace = (error, maxTraceLines = 5) => {
|
|
18
|
+
const traces = [];
|
|
19
|
+
const file1 = error.response?.data?.file;
|
|
20
|
+
const line1 = error.response?.data?.line;
|
|
21
|
+
if (file1 && line1) traces.push(`${file1}:${line1}`);
|
|
22
|
+
const traceArray = error.response?.data?.trace || [];
|
|
23
|
+
for (let i = 0; i < Math.min(maxTraceLines, traceArray.length); i++) {
|
|
24
|
+
const traceItem = traceArray[i];
|
|
25
|
+
if (traceItem?.file && traceItem?.line) traces.push(`${traceItem.file}:${traceItem.line}`);
|
|
26
|
+
}
|
|
27
|
+
if (error.stack && traces.length === 0) traces.push(error.stack);
|
|
28
|
+
return {
|
|
29
|
+
traces,
|
|
30
|
+
traceAsString: traces.map(nl2br).join("<br>")
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
var getErrorMessage = function(error, maxTraceLines = 5) {
|
|
34
|
+
const { traces, traceAsString } = getErrorTrace(error, maxTraceLines);
|
|
35
|
+
return {
|
|
36
|
+
heading: getErrorHeading(error),
|
|
37
|
+
text: getErrorText(error),
|
|
38
|
+
trace: traceAsString,
|
|
39
|
+
traceAsString,
|
|
40
|
+
traceAsArray: traces
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { getErrorMessage };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=forms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forms.js","names":[],"sources":["../../src/utils/forms.ts"],"sourcesContent":["interface ErrorContent {\n heading: string;\n text: string;\n trace: string;\n traceAsString: string;\n traceAsArray: string[];\n}\n\ninterface ServerError {\n response?: {\n statusText?: string;\n data?: {\n message?: string;\n error?: string;\n file?: string;\n line?: string;\n trace?: Array<{\n file?: string;\n line?: string;\n }>;\n };\n };\n message?: string;\n stack?: string;\n}\n\nconst nl2br = (str: string): string => {\n return str.replace(/\\n/g, '<br>');\n};\n\nconst getErrorHeading = (error: ServerError): string => {\n // Server error status text (e.g., \"Internal Server Error\", \"Bad Request\")\n if (error.response?.statusText) {\n return error.response.statusText;\n }\n\n // Network errors or other client-side errors\n if (error.message?.includes('Network Error')) {\n return 'Network Error';\n }\n\n if (error.message?.includes('timeout')) {\n return 'Request Timeout';\n }\n\n // Default fallback\n return 'An error has occurred';\n};\n\nconst getErrorText = (error: ServerError): string => {\n // Server-side error messages\n if (error.response?.data?.message) {\n return error.response.data.message;\n }\n\n if (error.response?.data?.error) {\n return error.response.data.error;\n }\n\n // Client-side error messages\n if (error.message) {\n return error.message;\n }\n\n // Fallback to string representation\n return String(error);\n};\n\nconst getErrorTrace = (error: ServerError, maxTraceLines: number = 5): { traces: string[], traceAsString: string } => {\n const traces: string[] = [];\n\n // Server-side file and line information\n const file1 = error.response?.data?.file;\n const line1 = error.response?.data?.line;\n\n if (file1 && line1) {\n traces.push(`${file1}:${line1}`);\n }\n\n // Server-side stack trace (up to maxTraceLines)\n const traceArray = error.response?.data?.trace || [];\n\n for (let i = 0; i < Math.min(maxTraceLines, traceArray.length); i++) {\n const traceItem = traceArray[i];\n\n if (traceItem?.file && traceItem?.line) {\n traces.push(`${traceItem.file}:${traceItem.line}`);\n }\n }\n\n // Client-side stack trace - only if there's no server-side trace\n if (error.stack && traces.length === 0) {\n traces.push(error.stack);\n }\n\n return {\n traces,\n traceAsString: traces.map(nl2br).join('<br>'),\n };\n};\n\nexport const getErrorMessage = function(error: ServerError, maxTraceLines: number = 5): ErrorContent {\n const { traces, traceAsString } = getErrorTrace(error, maxTraceLines);\n\n return {\n heading: getErrorHeading(error),\n text: getErrorText(error),\n trace: traceAsString, // Keep for backward compatibility\n traceAsString,\n traceAsArray: traces,\n };\n};\n"],"mappings":";AA0BA,IAAM,SAAS,QAAwB;AACnC,QAAO,IAAI,QAAQ,OAAO,OAAO;;AAGrC,IAAM,mBAAmB,UAA+B;AAEpD,KAAI,MAAM,UAAU,WAChB,QAAO,MAAM,SAAS;AAI1B,KAAI,MAAM,SAAS,SAAS,gBAAgB,CACxC,QAAO;AAGX,KAAI,MAAM,SAAS,SAAS,UAAU,CAClC,QAAO;AAIX,QAAO;;AAGX,IAAM,gBAAgB,UAA+B;AAEjD,KAAI,MAAM,UAAU,MAAM,QACtB,QAAO,MAAM,SAAS,KAAK;AAG/B,KAAI,MAAM,UAAU,MAAM,MACtB,QAAO,MAAM,SAAS,KAAK;AAI/B,KAAI,MAAM,QACN,QAAO,MAAM;AAIjB,QAAO,OAAO,MAAM;;AAGxB,IAAM,iBAAiB,OAAoB,gBAAwB,MAAmD;CAClH,MAAM,SAAmB,EAAE;CAG3B,MAAM,QAAQ,MAAM,UAAU,MAAM;CACpC,MAAM,QAAQ,MAAM,UAAU,MAAM;AAEpC,KAAI,SAAS,MACT,QAAO,KAAK,GAAG,MAAM,GAAG,QAAQ;CAIpC,MAAM,aAAa,MAAM,UAAU,MAAM,SAAS,EAAE;AAEpD,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IAAI,eAAe,WAAW,OAAO,EAAE,KAAK;EACjE,MAAM,YAAY,WAAW;AAE7B,MAAI,WAAW,QAAQ,WAAW,KAC9B,QAAO,KAAK,GAAG,UAAU,KAAK,GAAG,UAAU,OAAO;;AAK1D,KAAI,MAAM,SAAS,OAAO,WAAW,EACjC,QAAO,KAAK,MAAM,MAAM;AAG5B,QAAO;EACH;EACA,eAAe,OAAO,IAAI,MAAM,CAAC,KAAK,OAAO;EAChD;;AAGL,IAAa,kBAAkB,SAAS,OAAoB,gBAAwB,GAAiB;CACjG,MAAM,EAAE,QAAQ,kBAAkB,cAAc,OAAO,cAAc;AAErE,QAAO;EACH,SAAS,gBAAgB,MAAM;EAC/B,MAAM,aAAa,MAAM;EACzB,OAAO;EACP;EACA,cAAc;EACjB"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { findUniqueHandle, generateHandle } from "./string.js";
|
|
2
|
+
//#region src/utils/handle.ts
|
|
3
|
+
/** Minimal dot/bracket path getter so this stays dependency-free (no lodash). */
|
|
4
|
+
var getByPath = (source, path) => {
|
|
5
|
+
if (source == null || typeof source !== "object" || !path) return;
|
|
6
|
+
const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
7
|
+
let current = source;
|
|
8
|
+
for (const segment of segments) {
|
|
9
|
+
if (current == null || typeof current !== "object") return;
|
|
10
|
+
current = current[segment];
|
|
11
|
+
}
|
|
12
|
+
return current;
|
|
13
|
+
};
|
|
14
|
+
var normalizeHandleSource = (value) => {
|
|
15
|
+
if (value == null) return "";
|
|
16
|
+
return String(value).replace(/\{[^}]*\}/g, " ").replace(/\s+/g, " ").trim();
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Resolve dynamic reserved handles from other field values (e.g. a sibling field whose
|
|
20
|
+
* value would collide once slugified).
|
|
21
|
+
*/
|
|
22
|
+
var getDynamicReservedHandles = (values, reservedFieldValues = []) => {
|
|
23
|
+
const dynamicHandles = [];
|
|
24
|
+
reservedFieldValues.forEach((fieldPath) => {
|
|
25
|
+
const value = getByPath(values, fieldPath);
|
|
26
|
+
if (value && typeof value === "string") {
|
|
27
|
+
const handleValue = generateHandle(normalizeHandleSource(value));
|
|
28
|
+
if (handleValue) dynamicHandles.push(handleValue);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return dynamicHandles;
|
|
32
|
+
};
|
|
33
|
+
var truncateHandle = (handle, maxLength) => {
|
|
34
|
+
if (!Number.isFinite(maxLength)) return handle;
|
|
35
|
+
const length = Math.max(Number(maxLength), 0);
|
|
36
|
+
return handle.slice(0, length);
|
|
37
|
+
};
|
|
38
|
+
var findUniqueHandleWithinMaxLength = (baseHandle, reservedHandles = [], maxLength) => {
|
|
39
|
+
if (!baseHandle) return "";
|
|
40
|
+
if (!Number.isFinite(maxLength)) return findUniqueHandle(baseHandle, reservedHandles);
|
|
41
|
+
const normalizedReserved = new Set((reservedHandles || []).map((handle) => {
|
|
42
|
+
return String(handle || "").toLowerCase();
|
|
43
|
+
}));
|
|
44
|
+
const truncatedBase = truncateHandle(baseHandle, maxLength);
|
|
45
|
+
if (!truncatedBase) return "";
|
|
46
|
+
if (!normalizedReserved.has(truncatedBase.toLowerCase())) return truncatedBase;
|
|
47
|
+
let suffix = 1;
|
|
48
|
+
while (suffix < 1e4) {
|
|
49
|
+
const suffixText = String(suffix);
|
|
50
|
+
const baseMaxLength = Math.max(Number(maxLength) - suffixText.length, 0);
|
|
51
|
+
const truncatedWithSuffix = `${truncatedBase.slice(0, baseMaxLength)}${suffixText}`;
|
|
52
|
+
if (!normalizedReserved.has(truncatedWithSuffix.toLowerCase())) return truncatedWithSuffix;
|
|
53
|
+
suffix += 1;
|
|
54
|
+
}
|
|
55
|
+
return truncatedBase;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Generate a unique handle from a human-readable source value, honouring reserved handles
|
|
59
|
+
* (static + dynamically derived from other field values) and an optional max length.
|
|
60
|
+
*/
|
|
61
|
+
var buildUniqueHandleFromSource = ({ sourceValue, values = {}, reservedHandles = [], reservedFieldValues = [], maxLength }) => {
|
|
62
|
+
const baseHandle = generateHandle(normalizeHandleSource(sourceValue));
|
|
63
|
+
const dynamicHandles = getDynamicReservedHandles(values, reservedFieldValues);
|
|
64
|
+
return findUniqueHandleWithinMaxLength(baseHandle, [...reservedHandles, ...dynamicHandles], maxLength);
|
|
65
|
+
};
|
|
66
|
+
//#endregion
|
|
67
|
+
export { buildUniqueHandleFromSource, getDynamicReservedHandles };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=handle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle.js","names":[],"sources":["../../src/utils/handle.ts"],"sourcesContent":["import { findUniqueHandle, generateHandle } from './string';\n\n/** Minimal dot/bracket path getter so this stays dependency-free (no lodash). */\nconst getByPath = (source: unknown, path: string): unknown => {\n if (source == null || typeof source !== 'object' || !path) {\n return undefined;\n }\n\n const segments = path.replace(/\\[(\\d+)\\]/g, '.$1').split('.').filter(Boolean);\n let current: unknown = source;\n\n for (const segment of segments) {\n if (current == null || typeof current !== 'object') {\n return undefined;\n }\n\n current = (current as Record<string, unknown>)[segment];\n }\n\n return current;\n};\n\nconst normalizeHandleSource = (value: unknown): string => {\n if (value == null) {\n return '';\n }\n\n return String(value)\n // Strip variable/template tokens so handle generation uses human text only.\n .replace(/\\{[^}]*\\}/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n};\n\n/**\n * Resolve dynamic reserved handles from other field values (e.g. a sibling field whose\n * value would collide once slugified).\n */\nexport const getDynamicReservedHandles = (\n values: Record<string, unknown>,\n reservedFieldValues: string[] = [],\n): string[] => {\n const dynamicHandles: string[] = [];\n\n reservedFieldValues.forEach((fieldPath) => {\n const value = getByPath(values, fieldPath);\n\n if (value && typeof value === 'string') {\n const handleValue = generateHandle(normalizeHandleSource(value));\n\n if (handleValue) {\n dynamicHandles.push(handleValue);\n }\n }\n });\n\n return dynamicHandles;\n};\n\nconst truncateHandle = (handle: string, maxLength?: number): string => {\n if (!Number.isFinite(maxLength)) {\n return handle;\n }\n\n const length = Math.max(Number(maxLength), 0);\n return handle.slice(0, length);\n};\n\nconst findUniqueHandleWithinMaxLength = (\n baseHandle: string,\n reservedHandles: string[] = [],\n maxLength?: number,\n): string => {\n if (!baseHandle) {\n return '';\n }\n\n if (!Number.isFinite(maxLength)) {\n return findUniqueHandle(baseHandle, reservedHandles);\n }\n\n const normalizedReserved = new Set((reservedHandles || []).map((handle) => {\n return String(handle || '').toLowerCase();\n }));\n\n const truncatedBase = truncateHandle(baseHandle, maxLength);\n if (!truncatedBase) {\n return '';\n }\n\n if (!normalizedReserved.has(truncatedBase.toLowerCase())) {\n return truncatedBase;\n }\n\n let suffix = 1;\n\n while (suffix < 10000) {\n const suffixText = String(suffix);\n const baseMaxLength = Math.max(Number(maxLength) - suffixText.length, 0);\n const truncatedWithSuffix = `${truncatedBase.slice(0, baseMaxLength)}${suffixText}`;\n\n if (!normalizedReserved.has(truncatedWithSuffix.toLowerCase())) {\n return truncatedWithSuffix;\n }\n\n suffix += 1;\n }\n\n return truncatedBase;\n};\n\n/**\n * Generate a unique handle from a human-readable source value, honouring reserved handles\n * (static + dynamically derived from other field values) and an optional max length.\n */\nexport const buildUniqueHandleFromSource = ({\n sourceValue,\n values = {},\n reservedHandles = [],\n reservedFieldValues = [],\n maxLength,\n}: {\n sourceValue: unknown;\n values?: Record<string, unknown>;\n reservedHandles?: string[];\n reservedFieldValues?: string[];\n maxLength?: number;\n}): string => {\n const baseHandle = generateHandle(normalizeHandleSource(sourceValue));\n const dynamicHandles = getDynamicReservedHandles(values, reservedFieldValues);\n const allReservedHandles = [...reservedHandles, ...dynamicHandles];\n\n return findUniqueHandleWithinMaxLength(baseHandle, allReservedHandles, maxLength);\n};\n"],"mappings":";;;AAGA,IAAM,aAAa,QAAiB,SAA0B;AAC1D,KAAI,UAAU,QAAQ,OAAO,WAAW,YAAY,CAAC,KACjD;CAGJ,MAAM,WAAW,KAAK,QAAQ,cAAc,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,QAAQ;CAC7E,IAAI,UAAmB;AAEvB,MAAK,MAAM,WAAW,UAAU;AAC5B,MAAI,WAAW,QAAQ,OAAO,YAAY,SACtC;AAGJ,YAAW,QAAoC;;AAGnD,QAAO;;AAGX,IAAM,yBAAyB,UAA2B;AACtD,KAAI,SAAS,KACT,QAAO;AAGX,QAAO,OAAO,MAAM,CAEf,QAAQ,cAAc,IAAI,CAC1B,QAAQ,QAAQ,IAAI,CACpB,MAAM;;;;;;AAOf,IAAa,6BACT,QACA,sBAAgC,EAAE,KACvB;CACX,MAAM,iBAA2B,EAAE;AAEnC,qBAAoB,SAAS,cAAc;EACvC,MAAM,QAAQ,UAAU,QAAQ,UAAU;AAE1C,MAAI,SAAS,OAAO,UAAU,UAAU;GACpC,MAAM,cAAc,eAAe,sBAAsB,MAAM,CAAC;AAEhE,OAAI,YACA,gBAAe,KAAK,YAAY;;GAG1C;AAEF,QAAO;;AAGX,IAAM,kBAAkB,QAAgB,cAA+B;AACnE,KAAI,CAAC,OAAO,SAAS,UAAU,CAC3B,QAAO;CAGX,MAAM,SAAS,KAAK,IAAI,OAAO,UAAU,EAAE,EAAE;AAC7C,QAAO,OAAO,MAAM,GAAG,OAAO;;AAGlC,IAAM,mCACF,YACA,kBAA4B,EAAE,EAC9B,cACS;AACT,KAAI,CAAC,WACD,QAAO;AAGX,KAAI,CAAC,OAAO,SAAS,UAAU,CAC3B,QAAO,iBAAiB,YAAY,gBAAgB;CAGxD,MAAM,qBAAqB,IAAI,KAAK,mBAAmB,EAAE,EAAE,KAAK,WAAW;AACvE,SAAO,OAAO,UAAU,GAAG,CAAC,aAAa;GAC3C,CAAC;CAEH,MAAM,gBAAgB,eAAe,YAAY,UAAU;AAC3D,KAAI,CAAC,cACD,QAAO;AAGX,KAAI,CAAC,mBAAmB,IAAI,cAAc,aAAa,CAAC,CACpD,QAAO;CAGX,IAAI,SAAS;AAEb,QAAO,SAAS,KAAO;EACnB,MAAM,aAAa,OAAO,OAAO;EACjC,MAAM,gBAAgB,KAAK,IAAI,OAAO,UAAU,GAAG,WAAW,QAAQ,EAAE;EACxE,MAAM,sBAAsB,GAAG,cAAc,MAAM,GAAG,cAAc,GAAG;AAEvE,MAAI,CAAC,mBAAmB,IAAI,oBAAoB,aAAa,CAAC,CAC1D,QAAO;AAGX,YAAU;;AAGd,QAAO;;;;;;AAOX,IAAa,+BAA+B,EACxC,aACA,SAAS,EAAE,EACX,kBAAkB,EAAE,EACpB,sBAAsB,EAAE,EACxB,gBAOU;CACV,MAAM,aAAa,eAAe,sBAAsB,YAAY,CAAC;CACrE,MAAM,iBAAiB,0BAA0B,QAAQ,oBAAoB;AAG7E,QAAO,gCAAgC,YAAY,CAFvB,GAAG,iBAAiB,GAAG,eAEA,EAAoB,UAAU"}
|
package/dist/utils/markdown.d.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Lazily construct the markdown-it instance.
|
|
3
|
+
*
|
|
4
|
+
* Instantiating markdown-it at module scope is a live side effect: bundlers must
|
|
5
|
+
* retain the ~2.3 MB dependency in any consumer that touches this module's graph,
|
|
6
|
+
* even one that only imported a sibling util (e.g. `generateHandle`). Deferring the
|
|
7
|
+
* `new MarkdownIt()` to first render keeps markdown-it out of the graph unless a
|
|
8
|
+
* `render*` helper is actually called.
|
|
9
|
+
*
|
|
10
|
+
* markdown-it is configured with secure defaults:
|
|
3
11
|
* - HTML is disabled for security
|
|
4
12
|
* - Links are auto-detected
|
|
5
13
|
* - Typography features like smart quotes are enabled
|
|
6
14
|
* - Line breaks are converted to <br> tags
|
|
7
15
|
*/
|
|
8
|
-
declare
|
|
16
|
+
declare function createMarkdownIt(): any;
|
|
17
|
+
/**
|
|
18
|
+
* Direct accessor to the shared markdown-it instance for advanced usage.
|
|
19
|
+
*
|
|
20
|
+
* Replaces the former eager `export { md }`: an exported pre-built instance is
|
|
21
|
+
* itself a module-level side effect and would defeat the lazy loading above.
|
|
22
|
+
* Call this only when you need the full markdown-it API.
|
|
23
|
+
*/
|
|
24
|
+
export declare const getMarkdownIt: () => ReturnType<typeof createMarkdownIt>;
|
|
9
25
|
/**
|
|
10
26
|
* Renders markdown content as block-level HTML
|
|
11
27
|
* Includes block elements like headers, paragraphs, lists etc.
|
|
@@ -22,9 +38,5 @@ export declare const renderMarkdown: (content: string) => string;
|
|
|
22
38
|
* @returns Rendered HTML string, or empty string if no content provided
|
|
23
39
|
*/
|
|
24
40
|
export declare const renderInlineMarkdown: (content: string) => string;
|
|
25
|
-
|
|
26
|
-
* Export the configured markdown-it instance for direct usage
|
|
27
|
-
* Allows access to the full markdown-it API if needed
|
|
28
|
-
*/
|
|
29
|
-
export { md };
|
|
41
|
+
export {};
|
|
30
42
|
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/utils/markdown.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/utils/markdown.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,iBAAS,gBAAgB,QAOxB;AAOD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,QAAO,UAAU,CAAC,OAAO,gBAAgB,CAAY,CAAC;AAEhF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,KAAG,MAIhD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,MAItD,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import MarkdownIt from "markdown-it";
|
|
2
|
+
//#region src/utils/markdown.ts
|
|
3
|
+
/**
|
|
4
|
+
* Lazily construct the markdown-it instance.
|
|
5
|
+
*
|
|
6
|
+
* Instantiating markdown-it at module scope is a live side effect: bundlers must
|
|
7
|
+
* retain the ~2.3 MB dependency in any consumer that touches this module's graph,
|
|
8
|
+
* even one that only imported a sibling util (e.g. `generateHandle`). Deferring the
|
|
9
|
+
* `new MarkdownIt()` to first render keeps markdown-it out of the graph unless a
|
|
10
|
+
* `render*` helper is actually called.
|
|
11
|
+
*
|
|
12
|
+
* markdown-it is configured with secure defaults:
|
|
13
|
+
* - HTML is disabled for security
|
|
14
|
+
* - Links are auto-detected
|
|
15
|
+
* - Typography features like smart quotes are enabled
|
|
16
|
+
* - Line breaks are converted to <br> tags
|
|
17
|
+
*/
|
|
18
|
+
function createMarkdownIt() {
|
|
19
|
+
return new MarkdownIt({
|
|
20
|
+
html: false,
|
|
21
|
+
linkify: true,
|
|
22
|
+
typographer: true,
|
|
23
|
+
breaks: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
var mdInstance;
|
|
27
|
+
var getMd = () => mdInstance ??= createMarkdownIt();
|
|
28
|
+
/**
|
|
29
|
+
* Direct accessor to the shared markdown-it instance for advanced usage.
|
|
30
|
+
*
|
|
31
|
+
* Replaces the former eager `export { md }`: an exported pre-built instance is
|
|
32
|
+
* itself a module-level side effect and would defeat the lazy loading above.
|
|
33
|
+
* Call this only when you need the full markdown-it API.
|
|
34
|
+
*/
|
|
35
|
+
var getMarkdownIt = () => getMd();
|
|
36
|
+
/**
|
|
37
|
+
* Renders markdown content as block-level HTML
|
|
38
|
+
* Includes block elements like headers, paragraphs, lists etc.
|
|
39
|
+
*
|
|
40
|
+
* @param content - The markdown string to render
|
|
41
|
+
* @returns Rendered HTML string, or empty string if no content provided
|
|
42
|
+
*/
|
|
43
|
+
var renderMarkdown = (content) => {
|
|
44
|
+
if (!content) return "";
|
|
45
|
+
return getMd().render(content);
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Renders markdown content as inline HTML only
|
|
49
|
+
* Excludes block-level elements, only processes inline markdown syntax
|
|
50
|
+
*
|
|
51
|
+
* @param content - The markdown string to render
|
|
52
|
+
* @returns Rendered HTML string, or empty string if no content provided
|
|
53
|
+
*/
|
|
54
|
+
var renderInlineMarkdown = (content) => {
|
|
55
|
+
if (!content) return "";
|
|
56
|
+
return getMd().renderInline(content);
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
export { getMarkdownIt, renderInlineMarkdown, renderMarkdown };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","names":[],"sources":["../../src/utils/markdown.ts"],"sourcesContent":["import MarkdownIt from 'markdown-it';\n\n/**\n * Lazily construct the markdown-it instance.\n *\n * Instantiating markdown-it at module scope is a live side effect: bundlers must\n * retain the ~2.3 MB dependency in any consumer that touches this module's graph,\n * even one that only imported a sibling util (e.g. `generateHandle`). Deferring the\n * `new MarkdownIt()` to first render keeps markdown-it out of the graph unless a\n * `render*` helper is actually called.\n *\n * markdown-it is configured with secure defaults:\n * - HTML is disabled for security\n * - Links are auto-detected\n * - Typography features like smart quotes are enabled\n * - Line breaks are converted to <br> tags\n */\nfunction createMarkdownIt() {\n return new MarkdownIt({\n html: false, // Disable HTML for security\n linkify: true, // Auto-detect links\n typographer: true, // Enable typographic replacements\n breaks: true, // Convert line breaks to <br>\n });\n}\n\n// Cached singleton — built on first use, reused thereafter.\nlet mdInstance: ReturnType<typeof createMarkdownIt> | undefined;\n\nconst getMd = () => (mdInstance ??= createMarkdownIt());\n\n/**\n * Direct accessor to the shared markdown-it instance for advanced usage.\n *\n * Replaces the former eager `export { md }`: an exported pre-built instance is\n * itself a module-level side effect and would defeat the lazy loading above.\n * Call this only when you need the full markdown-it API.\n */\nexport const getMarkdownIt = (): ReturnType<typeof createMarkdownIt> => getMd();\n\n/**\n * Renders markdown content as block-level HTML\n * Includes block elements like headers, paragraphs, lists etc.\n *\n * @param content - The markdown string to render\n * @returns Rendered HTML string, or empty string if no content provided\n */\nexport const renderMarkdown = (content: string): string => {\n if (!content) { return ''; }\n\n return getMd().render(content);\n};\n\n/**\n * Renders markdown content as inline HTML only\n * Excludes block-level elements, only processes inline markdown syntax\n *\n * @param content - The markdown string to render\n * @returns Rendered HTML string, or empty string if no content provided\n */\nexport const renderInlineMarkdown = (content: string): string => {\n if (!content) { return ''; }\n\n return getMd().renderInline(content);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,SAAS,mBAAmB;AACxB,QAAO,IAAI,WAAW;EAClB,MAAM;EACN,SAAS;EACT,aAAa;EACb,QAAQ;EACX,CAAC;;AAIN,IAAI;AAEJ,IAAM,cAAe,eAAe,kBAAkB;;;;;;;;AAStD,IAAa,sBAA2D,OAAO;;;;;;;;AAS/E,IAAa,kBAAkB,YAA4B;AACvD,KAAI,CAAC,QAAW,QAAO;AAEvB,QAAO,OAAO,CAAC,OAAO,QAAQ;;;;;;;;;AAUlC,IAAa,wBAAwB,YAA4B;AAC7D,KAAI,CAAC,QAAW,QAAO;AAEvB,QAAO,OAAO,CAAC,aAAa,QAAQ"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/utils/promises.ts
|
|
2
|
+
/**
|
|
3
|
+
* Creates a function that ensures a promise takes at least a minimum amount of time to resolve
|
|
4
|
+
*
|
|
5
|
+
* @param ms - The minimum time in milliseconds that the promise should take
|
|
6
|
+
* @returns A function that wraps a promise or promise-returning function
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const slowFetch = takeAtLeast(1000)(fetch('https://api.example.com'));
|
|
10
|
+
* // Will take at least 1 second even if the fetch is faster
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
var takeAtLeast = function(ms) {
|
|
14
|
+
/**
|
|
15
|
+
* Wraps a promise or promise-returning function to ensure minimum execution time
|
|
16
|
+
*
|
|
17
|
+
* @param promiseOrFn - A promise or a function that returns a promise
|
|
18
|
+
* @returns A promise that resolves with the original promise's value after at least `ms` milliseconds
|
|
19
|
+
* @throws Will throw if the original promise rejects
|
|
20
|
+
*/
|
|
21
|
+
return function(promiseOrFn) {
|
|
22
|
+
const promise = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn;
|
|
23
|
+
const delay = new Promise((resolve) => {
|
|
24
|
+
return setTimeout(resolve, ms);
|
|
25
|
+
});
|
|
26
|
+
return Promise.allSettled([promise, delay]).then((results) => {
|
|
27
|
+
const [promiseResult] = results;
|
|
28
|
+
if (promiseResult.status === "rejected") throw promiseResult.reason;
|
|
29
|
+
return promiseResult.value;
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { takeAtLeast };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=promises.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promises.js","names":[],"sources":["../../src/utils/promises.ts"],"sourcesContent":["/**\n * Creates a function that ensures a promise takes at least a minimum amount of time to resolve\n *\n * @param ms - The minimum time in milliseconds that the promise should take\n * @returns A function that wraps a promise or promise-returning function\n * @example\n * ```ts\n * const slowFetch = takeAtLeast(1000)(fetch('https://api.example.com'));\n * // Will take at least 1 second even if the fetch is faster\n * ```\n */\nexport const takeAtLeast = function(ms: number) {\n /**\n * Wraps a promise or promise-returning function to ensure minimum execution time\n *\n * @param promiseOrFn - A promise or a function that returns a promise\n * @returns A promise that resolves with the original promise's value after at least `ms` milliseconds\n * @throws Will throw if the original promise rejects\n */\n return function <T>(promiseOrFn: Promise<T> | (() => Promise<T>)): Promise<T> {\n const promise = typeof promiseOrFn === 'function' ? promiseOrFn() : promiseOrFn;\n const delay = new Promise<void>((resolve) => { return setTimeout(resolve, ms); });\n\n return Promise.allSettled([promise, delay]).then((results) => {\n const [promiseResult] = results;\n\n if (promiseResult.status === 'rejected') {\n throw promiseResult.reason;\n }\n\n return promiseResult.value;\n });\n };\n};\n"],"mappings":";;;;;;;;;;;;AAWA,IAAa,cAAc,SAAS,IAAY;;;;;;;;AAQ5C,QAAO,SAAa,aAA0D;EAC1E,MAAM,UAAU,OAAO,gBAAgB,aAAa,aAAa,GAAG;EACpE,MAAM,QAAQ,IAAI,SAAe,YAAY;AAAE,UAAO,WAAW,SAAS,GAAG;IAAI;AAEjF,SAAO,QAAQ,WAAW,CAAC,SAAS,MAAM,CAAC,CAAC,MAAM,YAAY;GAC1D,MAAM,CAAC,iBAAiB;AAExB,OAAI,cAAc,WAAW,WACzB,OAAM,cAAc;AAGxB,UAAO,cAAc;IACvB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/utils/query.ts
|
|
2
|
+
var getQueryParam = (key) => {
|
|
3
|
+
return new URLSearchParams(window.location.search).get(key);
|
|
4
|
+
};
|
|
5
|
+
var setQueryParam = (key, value) => {
|
|
6
|
+
const url = new URL(window.location.href);
|
|
7
|
+
url.searchParams.set(key, value);
|
|
8
|
+
window.history.replaceState({}, "", url.toString());
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { getQueryParam, setQueryParam };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","names":[],"sources":["../../src/utils/query.ts"],"sourcesContent":["export const getQueryParam = (key: string): string | null => {\n const urlParams = new URLSearchParams(window.location.search);\n return urlParams.get(key);\n};\n\nexport const setQueryParam = (key: string, value: string): void => {\n const url = new URL(window.location.href);\n url.searchParams.set(key, value);\n window.history.replaceState({}, '', url.toString());\n};\n"],"mappings":";AAAA,IAAa,iBAAiB,QAA+B;AAEzD,QAAO,IADe,gBAAgB,OAAO,SAAS,OAC/C,CAAU,IAAI,IAAI;;AAG7B,IAAa,iBAAiB,KAAa,UAAwB;CAC/D,MAAM,MAAM,IAAI,IAAI,OAAO,SAAS,KAAK;AACzC,KAAI,aAAa,IAAI,KAAK,MAAM;AAChC,QAAO,QAAQ,aAAa,EAAE,EAAE,IAAI,IAAI,UAAU,CAAC"}
|