mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-15 06:52:25 +03:00
chore(release): 3.0.0-alpha.0
This commit is contained in:
@@ -2,6 +2,29 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [3.0.0-alpha.0](https://github.com/nuxt/vue-meta/compare/v2.3.3...v3.0.0-alpha.0) (2021-01-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add amp-boilerplate as boolean attribute (resolves: [#530](https://github.com/nuxt/vue-meta/issues/530)) ([#531](https://github.com/nuxt/vue-meta/issues/531)) ([bb45319](https://github.com/nuxt/vue-meta/commit/bb453195747058d90862d2db20d6a538ef04811f))
|
||||
* add deepest resolver (wip) ([bb04dc0](https://github.com/nuxt/vue-meta/commit/bb04dc068dbcf1871bdd08eddd3bb7997b122f04))
|
||||
* add poc vue-compiler ([6d25ff2](https://github.com/nuxt/vue-meta/commit/6d25ff2f372f20af682389d2a8f85eacdc3423ed))
|
||||
* add support for attributes (wip) ([5eaa0ab](https://github.com/nuxt/vue-meta/commit/5eaa0ab5b63000a56e0f1d4460700cc6a10d3b79))
|
||||
* continued progress ([642a62c](https://github.com/nuxt/vue-meta/commit/642a62c56126f5dfdc094282f6bc179e07f022eb))
|
||||
* convert to ts (wip) ([28d3fc1](https://github.com/nuxt/vue-meta/commit/28d3fc192363b9caf0a8b25a357684e6e7ae337f))
|
||||
* first work on vue v3 composition metainfo app ([5d0eb1a](https://github.com/nuxt/vue-meta/commit/5d0eb1ab60ce476ed8a97e97d4d409e74284df9b))
|
||||
* implement first useApi basics ([b0edfbe](https://github.com/nuxt/vue-meta/commit/b0edfbe6bd638ae3404739ec881d6a0ef598e43c))
|
||||
* improve useApi ([303eae1](https://github.com/nuxt/vue-meta/commit/303eae1603a5f15611a9d66457a56b417784da8c))
|
||||
* make attributes part of the metainfo object ([5add8bf](https://github.com/nuxt/vue-meta/commit/5add8bf83f597e9576b3a4502d6d7a0f1a76014c))
|
||||
* make ssr work ([9cfde5b](https://github.com/nuxt/vue-meta/commit/9cfde5b5509e7cd0899ea450801bed753ec64075))
|
||||
* refactor of object merge & make vue-router example work ([e68b535](https://github.com/nuxt/vue-meta/commit/e68b53573e60969a0f616c053e58a90fe87ceee2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* resolving arrays (collections still wip) ([5c4ee7a](https://github.com/nuxt/vue-meta/commit/5c4ee7a54720ce54cd94217c2e663b350873f4f2))
|
||||
|
||||
### [2.3.3](https://github.com/nuxt/vue-meta/compare/v2.3.2...v2.3.3) (2020-02-26)
|
||||
|
||||
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import { VNodeProps } from 'vue';
|
||||
import { MetainfoActive } from './types';
|
||||
export interface MetainfoProps {
|
||||
metainfo: MetainfoActive;
|
||||
}
|
||||
export declare const MetainfoImpl: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
||||
[key: string]: any;
|
||||
}>[] | undefined, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, import("vue").EmitsOptions, string, VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<{} & {}>, {}>;
|
||||
export declare const Metainfo: new () => {
|
||||
$props: VNodeProps & MetainfoProps;
|
||||
};
|
||||
//# sourceMappingURL=Metainfo.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Metainfo.d.ts","sourceRoot":"","sources":["../../src/Metainfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,UAAU,EAAE,MAAM,KAAK,CAAA;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAExC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,cAAc,CAAA;CACzB;AAED,eAAO,MAAM,YAAY;;yPAavB,CAAA;AAEF,eAAO,MAAM,QAAQ,YACX;IACN,MAAM,EAAE,UAAU,GAAG,aAAa,CAAA;CAErC,CAAA"}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import { Config } from '../types';
|
||||
export declare const defaultConfig: Config;
|
||||
//# sourceMappingURL=default.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"default.d.ts","sourceRoot":"","sources":["../../../src/config/default.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEjC,eAAO,MAAM,aAAa,EAAE,MAoC3B,CAAA"}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import { Config } from '../types';
|
||||
export declare function hasConfig(name: string, config: Config): boolean;
|
||||
export declare function getConfigByKey(tagOrName: string | Array<string>, key: string, config: Config): any;
|
||||
//# sourceMappingURL=helpers.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/config/helpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,wBAAgB,SAAS,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,EACjC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,GAAG,CAmBL"}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
export * from './default';
|
||||
export * from './helpers';
|
||||
export * from './tags';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,QAAQ,CAAA"}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
export interface TagConfig {
|
||||
keyAttribute?: string;
|
||||
contentAsAttribute?: boolean | string;
|
||||
attributes: boolean | Array<string>;
|
||||
[key: string]: any;
|
||||
}
|
||||
declare const tags: {
|
||||
[key: string]: TagConfig;
|
||||
};
|
||||
export { tags };
|
||||
//# sourceMappingURL=tags.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"tags.d.ts","sourceRoot":"","sources":["../../../src/config/tags.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kBAAkB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACrC,UAAU,EAAE,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAA;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED,QAAA,MAAM,IAAI,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAiDrC,CAAA;AAED,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
import * as deepestResolver from './resolvers/deepest';
|
||||
export { defaultConfig } from './config';
|
||||
export { createMetaManager } from './manager';
|
||||
export { resolveOption } from './resolvers';
|
||||
export * from './ssr';
|
||||
export * from './types';
|
||||
export * from './useApi';
|
||||
export { deepestResolver };
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,eAAe,MAAM,qBAAqB,CAAA;AAEtD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AAExB,OAAO,EACL,eAAe,EAChB,CAAA"}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import { VNode } from 'vue';
|
||||
import type { ResolveMethod } from './object-merge';
|
||||
import type { Manager, Config, Resolver, MetainfoActive } from './types';
|
||||
export declare const ssrAttribute = "data-vm-ssr";
|
||||
export declare const active: MetainfoActive;
|
||||
export declare function addVnode(teleports: any, to: string, _vnodes: VNode | Array<VNode>): void;
|
||||
export declare function createMetaManager(config: Config, resolver: Resolver | ResolveMethod): Manager;
|
||||
//# sourceMappingURL=manager.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,EAAW,MAAM,KAAK,CAAA;AAMxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAe,cAAc,EAAE,MAAM,SAAS,CAAA;AAErF,eAAO,MAAM,YAAY,gBAAgB,CAAA;AAEzC,eAAO,MAAM,MAAM,EAAE,cAA6B,CAAA;AAElD,wBAAgB,QAAQ,CAAE,SAAS,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,QA2BlF;AAED,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,aAAa,GAAG,OAAO,CAsG9F"}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
export declare const IS_PROXY: string;
|
||||
export declare const PROXY_SOURCES: string;
|
||||
export declare const PROXY_TARGET: string;
|
||||
export declare const RESOLVE_CONTEXT: string;
|
||||
//# sourceMappingURL=constants.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/object-merge/constants.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,QAAQ,QAA0C,CAAA;AAC/D,eAAO,MAAM,aAAa,QAA+C,CAAA;AACzE,eAAO,MAAM,YAAY,QAA8C,CAAA;AACvE,eAAO,MAAM,eAAe,QAAiD,CAAA"}
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
export declare type MergeSource = {
|
||||
[key: string]: any;
|
||||
};
|
||||
export declare type MergedObjectValue = boolean | number | string | MergedObject | any;
|
||||
export declare type MergedObject = {
|
||||
[key: string]: MergedObjectValue;
|
||||
};
|
||||
export declare type PathSegments = Array<string>;
|
||||
export declare type ResolveContext = {};
|
||||
export declare type ResolveMethod = (options: Array<any>, contexts: Array<ResolveContext>, active: MergedObjectValue, key: string | number | symbol, pathSegments: PathSegments) => MergedObjectValue;
|
||||
export declare type MergeContext = {
|
||||
resolve: ResolveMethod;
|
||||
active: MergedObject;
|
||||
sources: Array<MergeSource>;
|
||||
};
|
||||
export declare const createMergedObject: (resolve: ResolveMethod, active?: MergedObject) => {
|
||||
context: MergeContext;
|
||||
active: MergedObject;
|
||||
resolve: ResolveMethod;
|
||||
sources: MergeSource[];
|
||||
addSource: (source: MergeSource, resolveContext: ResolveContext | undefined, recompute?: Boolean) => any;
|
||||
delSource: (sourceOrProxy: MergeSource, recompute?: boolean) => boolean;
|
||||
compute: () => void;
|
||||
};
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/object-merge/index.ts"],"names":[],"mappings":"AAIA,oBAAY,WAAW,GAAG;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB,CAAA;AAGD,oBAAY,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,GAAG,CAAA;AAE9E,oBAAY,YAAY,GAAG;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAA;CACjC,CAAA;AAED,oBAAY,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAA;AAExC,oBAAY,cAAc,GAAG,EAAE,CAAA;AAE/B,oBAAY,aAAa,GAAG,CAC1B,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,EACnB,QAAQ,EAAE,KAAK,CAAC,cAAc,CAAC,EAC/B,MAAM,EAAE,iBAAiB,EACzB,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAC7B,YAAY,EAAE,YAAY,KACvB,iBAAiB,CAAA;AAEtB,oBAAY,YAAY,GAAG;IACzB,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,YAAY,CAAA;IACpB,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;CAC5B,CAAA;AAED,eAAO,MAAM,kBAAkB,YAAa,aAAa,WAAU,YAAY;;;;;wBAelD,WAAW,kBAAkB,cAAc,GAAG,SAAS,cAAa,OAAO;+BAUpE,WAAW,cAAa,OAAO,KAAU,OAAO;;CAyBnF,CAAA"}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import type { MergeContext, MergeSource, PathSegments, ResolveContext } from '.';
|
||||
export declare const createProxy: (context: MergeContext, target: MergeSource, resolveContext: ResolveContext, pathSegments?: PathSegments) => any;
|
||||
export declare const createHandler: (context: MergeContext, resolveContext: ResolveContext, pathSegments: PathSegments) => ProxyHandler<any>;
|
||||
//# sourceMappingURL=proxy.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../../src/object-merge/proxy.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAqB,YAAY,EAAE,cAAc,EAAE,MAAM,GAAG,CAAA;AAEnG,eAAO,MAAM,WAAW,YAAa,YAAY,UAAU,WAAW,kBAAkB,cAAc,qCASrG,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,KAAK,YAAY,CAAC,GAAG,CA2KjI,CAAA"}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import type { MergeContext, MergeSource, MergedObject, PathSegments } from '.';
|
||||
export declare const allKeys: (source?: MergeSource | undefined, ...sources: Array<MergeSource>) => Array<string>;
|
||||
export declare const recompute: (context: MergeContext, sources?: MergeSource[] | undefined, target?: MergedObject | undefined, path?: PathSegments) => void;
|
||||
//# sourceMappingURL=recompute.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"recompute.d.ts","sourceRoot":"","sources":["../../../src/object-merge/recompute.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAkB,MAAM,GAAG,CAAA;AAE9F,eAAO,MAAM,OAAO,iDAAsC,MAAM,WAAW,CAAC,KAAG,MAAM,MAAM,CAoB1F,CAAA;AAED,eAAO,MAAM,SAAS,YAAa,YAAY,kGAAiF,IAkE/H,CAAA"}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
import { VNode } from 'vue';
|
||||
import { TODO } from './types';
|
||||
export interface RenderContext {
|
||||
slots: any;
|
||||
[key: string]: TODO;
|
||||
}
|
||||
export interface GroupConfig {
|
||||
group: string;
|
||||
data: Array<TODO> | TODO;
|
||||
tagNamespace?: string;
|
||||
fullName?: string;
|
||||
slotName?: string;
|
||||
}
|
||||
export interface SlotScopeProperties {
|
||||
content: any;
|
||||
metainfo: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
export declare type RenderedMetainfoNode = {
|
||||
vnode: VNode;
|
||||
to?: string;
|
||||
};
|
||||
export declare type RenderedMetainfo = Array<RenderedMetainfoNode>;
|
||||
export declare function renderMeta(context: RenderContext, key: string, data: TODO, config: TODO): void | RenderedMetainfo | RenderedMetainfoNode;
|
||||
export declare function renderGroup(context: RenderContext, key: string, data: TODO, config: TODO): RenderedMetainfo | RenderedMetainfoNode;
|
||||
export declare function renderTag(context: RenderContext, key: string, data: TODO, config?: TODO, groupConfig?: GroupConfig): RenderedMetainfo | RenderedMetainfoNode;
|
||||
export declare function renderAttributes(context: RenderContext, key: string, data: TODO, config?: TODO): RenderedMetainfoNode | void;
|
||||
export declare function getSlotContent({ metainfo, slots }: RenderContext, slotName: string, content: any, groupConfig?: GroupConfig): TODO;
|
||||
//# sourceMappingURL=render.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,KAAK,EAAE,MAAM,KAAK,CAAA;AAG9B,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAS9B,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,GAAG,CAAA;IACV,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,GAAG,CAAA;IACZ,QAAQ,EAAE,GAAG,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED,oBAAY,oBAAoB,GAAG;IACjC,KAAK,EAAE,KAAK,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,oBAAY,gBAAgB,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAA;AAE1D,wBAAgB,UAAU,CACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,IAAI,GACX,IAAI,GAAG,gBAAgB,GAAG,oBAAoB,CAYhD;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,IAAI,GACX,gBAAgB,GAAG,oBAAoB,CAgCzC;AAED,wBAAgB,SAAS,CACvB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,EACV,MAAM,GAAE,IAAS,EACjB,WAAW,CAAC,EAAE,WAAW,GACxB,gBAAgB,GAAG,oBAAoB,CA8HzC;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,EACV,MAAM,GAAE,IAAS,GAChB,oBAAoB,GAAG,IAAI,CAkD7B;AAED,wBAAgB,cAAc,CAC5B,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,aAAa,EAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,GAAG,EACZ,WAAW,CAAC,EAAE,WAAW,GACxB,IAAI,CAqBN"}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
import { ResolveMethod } from '../object-merge';
|
||||
import { MetaContext } from '../types';
|
||||
declare type MergeContextDeepest = MetaContext & {
|
||||
depth: number;
|
||||
};
|
||||
export declare function setup(context: MergeContextDeepest): void;
|
||||
export declare const resolve: ResolveMethod;
|
||||
export {};
|
||||
//# sourceMappingURL=deepest.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"deepest.d.ts","sourceRoot":"","sources":["../../../src/resolvers/deepest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAGtC,aAAK,mBAAmB,GAAG,WAAW,GAAG;IACvC,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,wBAAgB,KAAK,CAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAgBzD;AAED,eAAO,MAAM,OAAO,EAAE,aAKpB,CAAA"}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import { ResolveContext, ResolveMethod } from '../object-merge';
|
||||
export declare type ResolveOptionReducer = (accumulator: any, context: ResolveContext) => ResolveMethod;
|
||||
export declare const resolveOption: (predicament: ResolveOptionReducer) => ResolveMethod;
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/resolvers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE/D,oBAAY,oBAAoB,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE,OAAO,EAAE,cAAc,KAAK,aAAa,CAAA;AAE/F,eAAO,MAAM,aAAa,EAAE,CAAC,WAAW,EAAE,oBAAoB,KAAK,aAiBlE,CAAA"}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import type { App } from 'vue';
|
||||
import type { SSRContext } from '@vue/server-renderer';
|
||||
export declare function renderToStringWithMeta(app: App): Promise<[string, SSRContext]>;
|
||||
//# sourceMappingURL=ssr.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../../src/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAC9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAKtD,wBAAsB,sBAAsB,CAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAsBrF"}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import { InjectionKey } from 'vue';
|
||||
import { MetainfoActive } from './types';
|
||||
export declare const hasSymbol: boolean;
|
||||
export declare const PolySymbol: (name: string) => string | symbol;
|
||||
export declare const metaInfoKey: InjectionKey<MetainfoActive>;
|
||||
//# sourceMappingURL=symbols.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../../src/symbols.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,KAAK,CAAA;AAClC,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAExC,eAAO,MAAM,SAAS,SACkD,CAAA;AAExE,eAAO,MAAM,UAAU,SAAU,MAAM,oBAIM,CAAA;AAE7C,eAAO,MAAM,WAAW,8BAES,CAAA"}
|
||||
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
import type { App, VNode, ComponentInternalInstance } from 'vue';
|
||||
import type { MergedObject, ResolveContext, ResolveMethod } from '../object-merge';
|
||||
export declare type TODO = any;
|
||||
export declare type MetainfoInput = {
|
||||
[key: string]: TODO;
|
||||
};
|
||||
export declare type MetaContext = ResolveContext & {
|
||||
vm: ComponentInternalInstance | undefined;
|
||||
};
|
||||
export interface ConfigOption {
|
||||
tag?: string;
|
||||
to?: string;
|
||||
group?: boolean;
|
||||
keyAttribute?: string;
|
||||
valueAttribute?: string;
|
||||
nameless?: boolean;
|
||||
namespaced?: boolean;
|
||||
namespacedAttribute?: boolean;
|
||||
attributesFor?: string;
|
||||
}
|
||||
export interface Config {
|
||||
[key: string]: ConfigOption;
|
||||
}
|
||||
export interface MetainfoProxy extends MergedObject {
|
||||
}
|
||||
export interface MetainfoActive {
|
||||
[key: string]: TODO;
|
||||
}
|
||||
export declare type MetaProxy = {
|
||||
meta: MetainfoProxy;
|
||||
unmount: TODO;
|
||||
};
|
||||
export declare type ResolveSetup = (context: MetaContext) => void;
|
||||
export declare type Resolver = {
|
||||
setup?: ResolveSetup;
|
||||
resolve: ResolveMethod;
|
||||
};
|
||||
export declare type Manager = {
|
||||
readonly config: Config;
|
||||
install(app: App): void;
|
||||
addMeta(obj: MetainfoInput, vm?: ComponentInternalInstance): MetaProxy;
|
||||
render(ctx: {
|
||||
slots?: any;
|
||||
}): Array<VNode>;
|
||||
};
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentInternalInstance {
|
||||
$metaManager: Manager;
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Process {
|
||||
client: boolean;
|
||||
server: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,yBAAyB,EAAE,MAAM,KAAK,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAElF,oBAAY,IAAI,GAAG,GAAG,CAAA;AAEtB,oBAAY,aAAa,GAAG;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB,CAAA;AAED,oBAAY,WAAW,GAAG,cAAc,GAAG;IACzC,EAAE,EAAE,yBAAyB,GAAG,SAAS,CAAA;CAC1C,CAAA;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,MAAM;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;CAC5B;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;CAElD;AAED,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,oBAAY,SAAS,GAAG;IACtB,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,EAAE,IAAI,CAAA;CACd,CAAA;AAED,oBAAY,YAAY,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;AAEzD,oBAAY,QAAQ,GAAG;IACrB,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB,OAAO,EAAE,aAAa,CAAA;CACvB,CAAA;AAED,oBAAY,OAAO,GAAG;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAEvB,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAA;IACvB,OAAO,CAAC,GAAG,EAAE,aAAa,EAAE,EAAE,CAAC,EAAE,yBAAyB,GAAG,SAAS,CAAA;IAEtE,MAAM,CAAC,GAAG,EAAE;QAAE,KAAK,CAAC,EAAE,GAAG,CAAA;KAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAA;CAC3C,CAAA;AAED,OAAO,QAAQ,mBAAmB,CAAC;IACjC,UAAU,yBAAyB;QACjC,YAAY,EAAE,OAAO,CAAA;KACtB;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM,CAAC;QACf,UAAU,OAAO;YACf,MAAM,EAAE,OAAO,CAAA;YACf,MAAM,EAAE,OAAO,CAAA;SAChB;KACF;CACF"}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import { ComponentInternalInstance } from 'vue';
|
||||
import type { Manager, MetainfoActive, MetainfoInput, MetaProxy } from './types';
|
||||
export declare function getCurrentManager(vm?: ComponentInternalInstance): Manager;
|
||||
export declare function useMeta(obj: MetainfoInput, manager?: Manager): MetaProxy;
|
||||
export declare function useMetainfo(): MetainfoActive;
|
||||
//# sourceMappingURL=useApi.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"useApi.d.ts","sourceRoot":"","sources":["../../src/useApi.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,yBAAyB,EAAE,MAAM,KAAK,CAAA;AAE3E,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEhF,wBAAgB,iBAAiB,CAAE,EAAE,CAAC,EAAE,yBAAyB,GAAG,OAAO,CAM1E;AAED,wBAAgB,OAAO,CAAE,GAAG,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAazE;AAED,wBAAgB,WAAW,IAAK,cAAc,CAE7C"}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export declare function clone(v: any): any;
|
||||
//# sourceMappingURL=clone.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"clone.d.ts","sourceRoot":"","sources":["../../../src/utils/clone.ts"],"names":[],"mappings":"AAGA,wBAAgB,KAAK,CAAE,CAAC,EAAE,GAAG,GAAG,GAAG,CAoBlC"}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export declare const pluck: (collection: Array<any>, key: string, callback?: ((row: any) => void) | undefined) => any[];
|
||||
//# sourceMappingURL=collection.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../../src/utils/collection.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,KAAK,eAAgB,MAAM,GAAG,CAAC,OAAO,MAAM,oBAAmB,GAAG,KAAK,IAAI,uBAcvF,CAAA"}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
interface DebugInterface {
|
||||
(...args: any[]): void;
|
||||
warn: Function;
|
||||
error: Function;
|
||||
}
|
||||
export declare const debugFn: (logFn: Function, setChildFns?: boolean) => {
|
||||
(...args: any[]): void;
|
||||
warn: any;
|
||||
error: any;
|
||||
};
|
||||
export declare const debug: DebugInterface;
|
||||
export {};
|
||||
//# sourceMappingURL=debug.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../../src/utils/debug.ts"],"names":[],"mappings":"AACA,UAAU,cAAc;IACtB,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IACtB,IAAI,EAAE,QAAQ,CAAA;IACd,KAAK,EAAE,QAAQ,CAAA;CAChB;AAED,eAAO,MAAM,OAAO,UAAW,QAAQ,gBAAe,OAAO;cACtC,GAAG,EAAE;;;CAc3B,CAAA;AAED,eAAO,MAAM,KAAK,EAAE,cAA2C,CAAA"}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
export * from './clone';
|
||||
export * from './collection';
|
||||
export * from './debug';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA"}
|
||||
Vendored
+801
@@ -0,0 +1,801 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.0
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var vue = require('vue');
|
||||
|
||||
const resolveOption = predicament => (options, contexts) => {
|
||||
let resolvedIndex = -1;
|
||||
contexts.reduce((acc, context, index) => {
|
||||
const retval = predicament(acc, context);
|
||||
if (retval !== acc) {
|
||||
resolvedIndex = index;
|
||||
return retval;
|
||||
}
|
||||
return acc;
|
||||
}, undefined);
|
||||
if (resolvedIndex > -1) {
|
||||
return options[resolvedIndex];
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
do {
|
||||
if (vm.parent) {
|
||||
depth++;
|
||||
vm = vm.parent;
|
||||
}
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
const resolve = resolveOption((acc, context) => {
|
||||
const { depth } = context;
|
||||
if (!acc || depth > acc) {
|
||||
return acc;
|
||||
}
|
||||
});
|
||||
|
||||
var deepest = /*#__PURE__*/Object.freeze({
|
||||
__proto__: null,
|
||||
setup: setup,
|
||||
resolve: resolve
|
||||
});
|
||||
|
||||
const defaultConfig = {
|
||||
body: {
|
||||
tag: 'script',
|
||||
to: 'body'
|
||||
},
|
||||
base: {
|
||||
valueAttribute: 'href'
|
||||
},
|
||||
charset: {
|
||||
tag: 'meta',
|
||||
nameless: true,
|
||||
valueAttribute: 'charset'
|
||||
},
|
||||
description: {
|
||||
tag: 'meta'
|
||||
},
|
||||
og: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta',
|
||||
keyAttribute: 'property'
|
||||
},
|
||||
twitter: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta'
|
||||
},
|
||||
htmlAttrs: {
|
||||
attributesFor: 'html'
|
||||
},
|
||||
headAttrs: {
|
||||
attributesFor: 'head'
|
||||
},
|
||||
bodyAttrs: {
|
||||
attributesFor: 'body'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a map and return a function for checking if a key
|
||||
* is in that map.
|
||||
* IMPORTANT: all calls of this function must be prefixed with
|
||||
* \/\*#\_\_PURE\_\_\*\/
|
||||
* So that rollup can tree-shake them if necessary.
|
||||
*/
|
||||
(process.env.NODE_ENV !== 'production')
|
||||
? Object.freeze({})
|
||||
: {};
|
||||
(process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
|
||||
const isArray = Array.isArray;
|
||||
const isFunction = (val) => typeof val === 'function';
|
||||
const isString = (val) => typeof val === 'string';
|
||||
const isObject = (val) => val !== null && typeof val === 'object';
|
||||
const objectToString = Object.prototype.toString;
|
||||
const toTypeString = (value) => objectToString.call(value);
|
||||
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
|
||||
|
||||
/*
|
||||
* This is a fixed config for real HTML tags
|
||||
*
|
||||
* TODO: we probably dont need all attributes
|
||||
*/
|
||||
const tags = {
|
||||
title: {
|
||||
attributes: false
|
||||
},
|
||||
base: {
|
||||
contentAsAttribute: true,
|
||||
attributes: ['href', 'target']
|
||||
},
|
||||
meta: {
|
||||
contentAsAttribute: true,
|
||||
keyAttribute: 'name',
|
||||
attributes: ['content', 'name', 'http-equiv', 'charset']
|
||||
},
|
||||
link: {
|
||||
contentAsAttribute: true,
|
||||
attributes: [
|
||||
'href',
|
||||
'crossorigin',
|
||||
'rel',
|
||||
'media',
|
||||
'integrity',
|
||||
'hreflang',
|
||||
'type',
|
||||
'referrerpolicy',
|
||||
'sizes',
|
||||
'imagesrcset',
|
||||
'imagesizes',
|
||||
'as',
|
||||
'color'
|
||||
]
|
||||
},
|
||||
style: {
|
||||
attributes: ['media']
|
||||
},
|
||||
script: {
|
||||
attributes: [
|
||||
'src',
|
||||
'type',
|
||||
'nomodule',
|
||||
'async',
|
||||
'defer',
|
||||
'crossorigin',
|
||||
'integrity',
|
||||
'referrerpolicy'
|
||||
]
|
||||
},
|
||||
noscript: {
|
||||
attributes: false
|
||||
}
|
||||
};
|
||||
|
||||
function getConfigByKey(tagOrName, key, config) {
|
||||
if (config && key in config) {
|
||||
return config[key];
|
||||
}
|
||||
if (isArray(tagOrName)) {
|
||||
for (const name of tagOrName) {
|
||||
if (name && name in tags) {
|
||||
return tags[name][key];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (tagOrName in tags) {
|
||||
const tag = tags[tagOrName];
|
||||
return tag[key];
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/microsoft/TypeScript/issues/1863
|
||||
const IS_PROXY = Symbol('kIsProxy');
|
||||
const PROXY_SOURCES = Symbol('kProxySources');
|
||||
const PROXY_TARGET = Symbol('kProxyTarget');
|
||||
const RESOLVE_CONTEXT = Symbol('kResolveContext');
|
||||
|
||||
// See: https://github.com/vuejs/vue-next/blob/08b4e8815da4e8911058ccbab986bea6365c3352/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
|
||||
function clone(v) {
|
||||
if (isArray(v)) {
|
||||
return v.map(clone);
|
||||
}
|
||||
if (isObject(v)) {
|
||||
const res = {};
|
||||
for (const key in v) {
|
||||
// never clone the context
|
||||
if (key === 'context') {
|
||||
res[key] = v[key];
|
||||
}
|
||||
else {
|
||||
res[key] = clone(v[key]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
return plucked;
|
||||
};
|
||||
|
||||
const allKeys = (source, ...sources) => {
|
||||
const keys = source ? Object.keys(source) : [];
|
||||
if (sources) {
|
||||
for (const source of sources) {
|
||||
if (!source || !isObject(source)) {
|
||||
continue;
|
||||
}
|
||||
for (const key in source) {
|
||||
if (!keys.includes(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
return;
|
||||
}
|
||||
const keys = allKeys(...sources);
|
||||
// Clean up properties that dont exists anymore
|
||||
const targetKeys = Object.keys(target);
|
||||
for (const key of targetKeys) {
|
||||
if (!keys.includes(key)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
const keySources = [];
|
||||
for (const source of sources) {
|
||||
if (key in source) {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
if (!target[key] && isArray(sources[0][key])) {
|
||||
target[key] = [];
|
||||
}
|
||||
const keyContexts = [];
|
||||
const keySources = pluck(sources, key, source => keyContexts.push(source[RESOLVE_CONTEXT]));
|
||||
let resolved = context.resolve(keySources, keyContexts, target[key], key, path);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
|
||||
const createProxy = (context, target, resolveContext, pathSegments = []) => {
|
||||
const handler = createHandler(context, resolveContext, pathSegments);
|
||||
const proxy = vue.markRaw(new Proxy(target, handler));
|
||||
if (!pathSegments.length && context.sources) {
|
||||
context.sources.push(proxy);
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
get: (target, key, receiver) => {
|
||||
if (key === IS_PROXY) {
|
||||
return true;
|
||||
}
|
||||
if (key === PROXY_SOURCES) {
|
||||
return context.sources;
|
||||
}
|
||||
if (key === PROXY_TARGET) {
|
||||
return target;
|
||||
}
|
||||
if (key === RESOLVE_CONTEXT) {
|
||||
return resolveContext;
|
||||
}
|
||||
let value = Reflect.get(target, key, receiver);
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
target[key] = value;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: (target, key, value) => {
|
||||
const success = Reflect.set(target, key, value);
|
||||
// console.warn(success, 'PROXY SET\nkey:', key, '\npath:', pathSegments, '\ntarget:', isArray(target), target, '\ncontext:\n', context)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let hasArrayParent = false;
|
||||
let { sources: proxies, active } = context;
|
||||
let activeSegmentKey;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = pluck(proxies, segment);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
if (isArray(active)) {
|
||||
hasArrayParent = true;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
if (hasArrayParent) {
|
||||
// TODO: fix that we dont have to recompute the full merged object
|
||||
// we should only have to recompute the branch that has changed
|
||||
// but there is an issue here with supporting both arrays of strings
|
||||
// as collections (parent vs parent of parent we need to trigger the
|
||||
// update from)
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
// console.log('CONTEXT.ACTIVE', context.active, '\nparent:\n', target)
|
||||
return success;
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
let proxies = context.sources;
|
||||
let active = context.active;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete active[key];
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
});
|
||||
|
||||
const createMergedObject = (resolve, active = {}) => {
|
||||
const sources = [];
|
||||
if (!active) {
|
||||
active = {};
|
||||
}
|
||||
const context = {
|
||||
active,
|
||||
resolve,
|
||||
sources
|
||||
};
|
||||
const compute = () => recompute(context);
|
||||
const addSource = (source, resolveContext, recompute = false) => {
|
||||
const proxy = createProxy(context, source, resolveContext || {});
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const delSource = (sourceOrProxy, recompute = true) => {
|
||||
const index = sources.findIndex(src => src === sourceOrProxy || src[PROXY_TARGET] === sourceOrProxy);
|
||||
if (index > -1) {
|
||||
sources.splice(index, 1);
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return {
|
||||
context,
|
||||
active,
|
||||
resolve,
|
||||
sources,
|
||||
addSource,
|
||||
delSource,
|
||||
compute
|
||||
};
|
||||
};
|
||||
|
||||
function renderMeta(context, key, data, config) {
|
||||
// console.info('renderMeta', key, data, config)
|
||||
if (config.attributesFor) {
|
||||
return renderAttributes(context, key, data, config);
|
||||
}
|
||||
if (config.group) {
|
||||
return renderGroup(context, key, data, config);
|
||||
}
|
||||
return renderTag(context, key, data, config);
|
||||
}
|
||||
function renderGroup(context, key, data, config) {
|
||||
// console.info('renderGroup', key, data, config)
|
||||
if (isArray(data)) {
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Specifying an array for group properties isnt supported mostly as we didnt found a use-case for this yet. If you have one, please create an issue on the vue-meta repo');
|
||||
}
|
||||
// config.attributes = getConfigKey([key, config.tag], 'attributes', config)
|
||||
return [];
|
||||
}
|
||||
return Object.keys(data)
|
||||
.map((childKey) => {
|
||||
const groupConfig = {
|
||||
group: key,
|
||||
data
|
||||
};
|
||||
if (config.namespaced) {
|
||||
groupConfig.tagNamespace = config.namespaced === true ? key : config.namespaced;
|
||||
}
|
||||
else if (config.namespacedAttribute) {
|
||||
const namespace = config.namespacedAttribute === true ? key : config.namespacedAttribute;
|
||||
groupConfig.fullName = `${namespace}:${childKey}`;
|
||||
groupConfig.slotName = `${namespace}(${childKey})`;
|
||||
}
|
||||
return renderTag(context, key, data[childKey], config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
// console.info('renderTag', key, data, config, groupConfig)
|
||||
const contentAttributes = ['content', 'json', 'rawContent'];
|
||||
const getConfig = (key) => getConfigByKey([tag, config.tag], key, config);
|
||||
if (isArray(data)) {
|
||||
return data
|
||||
.map((child) => {
|
||||
return renderTag(context, key, child, config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
const { tag = config.tag || key } = data;
|
||||
let content;
|
||||
let hasChilds = false;
|
||||
let isRaw = false;
|
||||
if (isString(data)) {
|
||||
content = data;
|
||||
}
|
||||
else if (data.children && isArray(data.children)) {
|
||||
hasChilds = true;
|
||||
content = data.children.map((child) => {
|
||||
const data = renderTag(context, key, child, config, groupConfig);
|
||||
if (isArray(data)) {
|
||||
return data.map(({ vnode }) => vnode);
|
||||
}
|
||||
return data.vnode;
|
||||
});
|
||||
}
|
||||
else {
|
||||
let i = 0;
|
||||
for (const contentAttribute of contentAttributes) {
|
||||
if (!content && data[contentAttribute]) {
|
||||
if (i === 1) {
|
||||
content = JSON.stringify(data[contentAttribute]);
|
||||
}
|
||||
else {
|
||||
content = data[contentAttribute];
|
||||
}
|
||||
isRaw = i > 1;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
const fullName = (groupConfig && groupConfig.fullName) || key;
|
||||
const slotName = (groupConfig && groupConfig.slotName) || key;
|
||||
let { attrs: attributes } = data;
|
||||
if (!attributes && typeof data === 'object') {
|
||||
attributes = { ...data };
|
||||
delete attributes.tag;
|
||||
delete attributes.children;
|
||||
delete attributes.to;
|
||||
// cleanup all content attributes
|
||||
for (const attr of contentAttributes) {
|
||||
delete attributes[attr];
|
||||
}
|
||||
}
|
||||
else if (!attributes) {
|
||||
attributes = {};
|
||||
}
|
||||
if (hasChilds) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
const contentAsAttribute = getConfig('contentAsAttribute');
|
||||
let valueAttribute = config.valueAttribute;
|
||||
if (!valueAttribute && contentAsAttribute) {
|
||||
const tagAttributes = getConfig('attributes');
|
||||
valueAttribute = isString(contentAsAttribute) ? contentAsAttribute : tagAttributes[0];
|
||||
}
|
||||
if (!valueAttribute) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
if (!config.nameless) {
|
||||
const keyAttribute = getConfig('keyAttribute');
|
||||
if (keyAttribute) {
|
||||
attributes[keyAttribute] = fullName;
|
||||
}
|
||||
}
|
||||
attributes[valueAttribute] = getSlotContent(context, slotName, attributes[valueAttribute] || content, groupConfig);
|
||||
content = undefined;
|
||||
}
|
||||
}
|
||||
const finalTag = groupConfig && groupConfig.tagNamespace
|
||||
? `${groupConfig.tagNamespace}:${tag}`
|
||||
: tag;
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
let vnode;
|
||||
if (isRaw) {
|
||||
attributes.innerHTML = content;
|
||||
vnode = vue.h(finalTag, attributes);
|
||||
}
|
||||
else {
|
||||
vnode = vue.h(finalTag, attributes, content);
|
||||
}
|
||||
return {
|
||||
to: data.to,
|
||||
vnode
|
||||
};
|
||||
}
|
||||
function renderAttributes(context, key, data, config = {}) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
{
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
to: '',
|
||||
vnode: vue.h(`ssr-${attributesFor}`, data)
|
||||
};
|
||||
}
|
||||
}
|
||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||
if (!slots || !slots[slotName]) {
|
||||
return content;
|
||||
}
|
||||
const slotProps = {
|
||||
content,
|
||||
metainfo
|
||||
};
|
||||
if (groupConfig && groupConfig.group) {
|
||||
slotProps[groupConfig.group] = groupConfig.data;
|
||||
}
|
||||
const slotContent = slots[slotName](slotProps);
|
||||
if (slotContent && slotContent.length) {
|
||||
return slotContent[0].children;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
|
||||
const PolySymbol = (name) =>
|
||||
// vm = vue meta
|
||||
hasSymbol
|
||||
? Symbol( '[vue-meta]: ' + name )
|
||||
: ( '[vue-meta]: ' ) + name;
|
||||
const metaInfoKey = PolySymbol( 'metainfo' );
|
||||
|
||||
function getCurrentManager(vm) {
|
||||
if (!vm) {
|
||||
vm = vue.getCurrentInstance();
|
||||
}
|
||||
return vm.appContext.config.globalProperties.$metaManager;
|
||||
}
|
||||
function useMeta(obj, manager) {
|
||||
const vm = vue.getCurrentInstance();
|
||||
if (!manager && vm) {
|
||||
manager = getCurrentManager(vm);
|
||||
}
|
||||
if (!manager) {
|
||||
// oopsydoopsy
|
||||
throw new Error('No manager or current instance');
|
||||
}
|
||||
return manager.addMeta(obj, vm || undefined);
|
||||
}
|
||||
function useMetainfo() {
|
||||
return vue.inject(metaInfoKey);
|
||||
}
|
||||
|
||||
const MetainfoImpl = vue.defineComponent({
|
||||
name: 'Metainfo',
|
||||
inheritAttrs: false,
|
||||
setup(_, { slots }) {
|
||||
return () => {
|
||||
const manager = getCurrentManager();
|
||||
if (!manager) {
|
||||
return;
|
||||
}
|
||||
return manager.render({ slots });
|
||||
};
|
||||
}
|
||||
});
|
||||
const Metainfo = MetainfoImpl;
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = vue.reactive({});
|
||||
function addVnode(teleports, to, _vnodes) {
|
||||
const vnodes = (isArray(_vnodes) ? _vnodes : [_vnodes]);
|
||||
{
|
||||
// dont add ssrAttribute for attribute vnode placeholder
|
||||
if (!to.endsWith('Attrs')) {
|
||||
vnodes.forEach((vnode) => {
|
||||
if (!vnode.props) {
|
||||
vnode.props = {};
|
||||
}
|
||||
vnode.props[ssrAttribute] = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!teleports[to]) {
|
||||
teleports[to] = [];
|
||||
}
|
||||
teleports[to].push(...vnodes);
|
||||
}
|
||||
function createMetaManager(config, resolver) {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
}
|
||||
return resolver.resolve(options, contexts, active, key, pathSegments);
|
||||
};
|
||||
const { addSource, delSource } = createMergedObject(resolve, active);
|
||||
// TODO: validate resolver
|
||||
const manager = {
|
||||
config,
|
||||
install(app) {
|
||||
app.component('Metainfo', Metainfo);
|
||||
app.config.globalProperties.$metaManager = manager;
|
||||
app.provide(metaInfoKey, active);
|
||||
},
|
||||
addMeta(metaObj, vm) {
|
||||
const resolveContext = { vm };
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute
|
||||
const meta = addSource(metaObj, resolveContext, true);
|
||||
const unmount = () => delSource(meta);
|
||||
if (vm) {
|
||||
vue.onUnmounted(unmount);
|
||||
}
|
||||
return {
|
||||
meta,
|
||||
unmount
|
||||
};
|
||||
},
|
||||
render({ slots } = {}) {
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
const vnode = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
if (!vnode) {
|
||||
continue;
|
||||
}
|
||||
const vnodes = isArray(vnode) ? vnode : [vnode];
|
||||
const defaultTo = (key !== 'base' && active[key].to) || config.to || (config.attributesFor ? key : 'head');
|
||||
for (const { to, vnode } of vnodes) {
|
||||
addVnode(teleports, to || defaultTo, vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
for (const tag in slots) {
|
||||
const slotFn = slots[tag];
|
||||
if (isFunction(slotFn)) {
|
||||
addVnode(teleports, tag === 'default' ? 'head' : tag, slotFn({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return vue.h(vue.Teleport, { to }, teleports[to]);
|
||||
});
|
||||
}
|
||||
};
|
||||
return manager;
|
||||
}
|
||||
|
||||
// rollup doesnt like an import, cant find export so use require
|
||||
const { renderToString } = require('@vue/server-renderer');
|
||||
async function renderToStringWithMeta(app) {
|
||||
const ctx = {};
|
||||
const html = await renderToString(app, ctx);
|
||||
// TODO: better way of determining whether meta was rendered with the component or not
|
||||
if (!ctx.teleports || !ctx.teleports.head) {
|
||||
const teleports = app.config.globalProperties.$metaManager.render();
|
||||
await Promise.all(teleports.map((teleport) => renderToString(teleport, ctx)));
|
||||
}
|
||||
const { teleports } = ctx;
|
||||
for (const target in teleports) {
|
||||
if (target.endsWith('Attrs')) {
|
||||
const str = teleports[target];
|
||||
// match from first space to first >, these should be all rendered attributes
|
||||
teleports[target] = str.slice(str.indexOf(' ') + 1, str.indexOf('>'));
|
||||
}
|
||||
}
|
||||
return [html, ctx];
|
||||
}
|
||||
|
||||
exports.createMetaManager = createMetaManager;
|
||||
exports.deepestResolver = deepest;
|
||||
exports.defaultConfig = defaultConfig;
|
||||
exports.getCurrentManager = getCurrentManager;
|
||||
exports.renderToStringWithMeta = renderToStringWithMeta;
|
||||
exports.resolveOption = resolveOption;
|
||||
exports.useMeta = useMeta;
|
||||
exports.useMetainfo = useMetainfo;
|
||||
Vendored
+797
@@ -0,0 +1,797 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.0
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var vue = require('vue');
|
||||
|
||||
const resolveOption = predicament => (options, contexts) => {
|
||||
let resolvedIndex = -1;
|
||||
contexts.reduce((acc, context, index) => {
|
||||
const retval = predicament(acc, context);
|
||||
if (retval !== acc) {
|
||||
resolvedIndex = index;
|
||||
return retval;
|
||||
}
|
||||
return acc;
|
||||
}, undefined);
|
||||
if (resolvedIndex > -1) {
|
||||
return options[resolvedIndex];
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
do {
|
||||
if (vm.parent) {
|
||||
depth++;
|
||||
vm = vm.parent;
|
||||
}
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
const resolve = resolveOption((acc, context) => {
|
||||
const { depth } = context;
|
||||
if (!acc || depth > acc) {
|
||||
return acc;
|
||||
}
|
||||
});
|
||||
|
||||
var deepest = /*#__PURE__*/Object.freeze({
|
||||
__proto__: null,
|
||||
setup: setup,
|
||||
resolve: resolve
|
||||
});
|
||||
|
||||
const defaultConfig = {
|
||||
body: {
|
||||
tag: 'script',
|
||||
to: 'body'
|
||||
},
|
||||
base: {
|
||||
valueAttribute: 'href'
|
||||
},
|
||||
charset: {
|
||||
tag: 'meta',
|
||||
nameless: true,
|
||||
valueAttribute: 'charset'
|
||||
},
|
||||
description: {
|
||||
tag: 'meta'
|
||||
},
|
||||
og: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta',
|
||||
keyAttribute: 'property'
|
||||
},
|
||||
twitter: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta'
|
||||
},
|
||||
htmlAttrs: {
|
||||
attributesFor: 'html'
|
||||
},
|
||||
headAttrs: {
|
||||
attributesFor: 'head'
|
||||
},
|
||||
bodyAttrs: {
|
||||
attributesFor: 'body'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a map and return a function for checking if a key
|
||||
* is in that map.
|
||||
* IMPORTANT: all calls of this function must be prefixed with
|
||||
* \/\*#\_\_PURE\_\_\*\/
|
||||
* So that rollup can tree-shake them if necessary.
|
||||
*/
|
||||
(process.env.NODE_ENV !== 'production')
|
||||
? Object.freeze({})
|
||||
: {};
|
||||
(process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
|
||||
const isArray = Array.isArray;
|
||||
const isFunction = (val) => typeof val === 'function';
|
||||
const isString = (val) => typeof val === 'string';
|
||||
const isObject = (val) => val !== null && typeof val === 'object';
|
||||
const objectToString = Object.prototype.toString;
|
||||
const toTypeString = (value) => objectToString.call(value);
|
||||
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
|
||||
|
||||
/*
|
||||
* This is a fixed config for real HTML tags
|
||||
*
|
||||
* TODO: we probably dont need all attributes
|
||||
*/
|
||||
const tags = {
|
||||
title: {
|
||||
attributes: false
|
||||
},
|
||||
base: {
|
||||
contentAsAttribute: true,
|
||||
attributes: ['href', 'target']
|
||||
},
|
||||
meta: {
|
||||
contentAsAttribute: true,
|
||||
keyAttribute: 'name',
|
||||
attributes: ['content', 'name', 'http-equiv', 'charset']
|
||||
},
|
||||
link: {
|
||||
contentAsAttribute: true,
|
||||
attributes: [
|
||||
'href',
|
||||
'crossorigin',
|
||||
'rel',
|
||||
'media',
|
||||
'integrity',
|
||||
'hreflang',
|
||||
'type',
|
||||
'referrerpolicy',
|
||||
'sizes',
|
||||
'imagesrcset',
|
||||
'imagesizes',
|
||||
'as',
|
||||
'color'
|
||||
]
|
||||
},
|
||||
style: {
|
||||
attributes: ['media']
|
||||
},
|
||||
script: {
|
||||
attributes: [
|
||||
'src',
|
||||
'type',
|
||||
'nomodule',
|
||||
'async',
|
||||
'defer',
|
||||
'crossorigin',
|
||||
'integrity',
|
||||
'referrerpolicy'
|
||||
]
|
||||
},
|
||||
noscript: {
|
||||
attributes: false
|
||||
}
|
||||
};
|
||||
|
||||
function getConfigByKey(tagOrName, key, config) {
|
||||
if (config && key in config) {
|
||||
return config[key];
|
||||
}
|
||||
if (isArray(tagOrName)) {
|
||||
for (const name of tagOrName) {
|
||||
if (name && name in tags) {
|
||||
return tags[name][key];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (tagOrName in tags) {
|
||||
const tag = tags[tagOrName];
|
||||
return tag[key];
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/microsoft/TypeScript/issues/1863
|
||||
const IS_PROXY = Symbol('kIsProxy');
|
||||
const PROXY_SOURCES = Symbol('kProxySources');
|
||||
const PROXY_TARGET = Symbol('kProxyTarget');
|
||||
const RESOLVE_CONTEXT = Symbol('kResolveContext');
|
||||
|
||||
// See: https://github.com/vuejs/vue-next/blob/08b4e8815da4e8911058ccbab986bea6365c3352/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
|
||||
function clone(v) {
|
||||
if (isArray(v)) {
|
||||
return v.map(clone);
|
||||
}
|
||||
if (isObject(v)) {
|
||||
const res = {};
|
||||
for (const key in v) {
|
||||
// never clone the context
|
||||
if (key === 'context') {
|
||||
res[key] = v[key];
|
||||
}
|
||||
else {
|
||||
res[key] = clone(v[key]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
return plucked;
|
||||
};
|
||||
|
||||
const allKeys = (source, ...sources) => {
|
||||
const keys = source ? Object.keys(source) : [];
|
||||
if (sources) {
|
||||
for (const source of sources) {
|
||||
if (!source || !isObject(source)) {
|
||||
continue;
|
||||
}
|
||||
for (const key in source) {
|
||||
if (!keys.includes(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
return;
|
||||
}
|
||||
const keys = allKeys(...sources);
|
||||
// Clean up properties that dont exists anymore
|
||||
const targetKeys = Object.keys(target);
|
||||
for (const key of targetKeys) {
|
||||
if (!keys.includes(key)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
const keySources = [];
|
||||
for (const source of sources) {
|
||||
if (key in source) {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
if (!target[key] && isArray(sources[0][key])) {
|
||||
target[key] = [];
|
||||
}
|
||||
const keyContexts = [];
|
||||
const keySources = pluck(sources, key, source => keyContexts.push(source[RESOLVE_CONTEXT]));
|
||||
let resolved = context.resolve(keySources, keyContexts, target[key], key, path);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
|
||||
const createProxy = (context, target, resolveContext, pathSegments = []) => {
|
||||
const handler = createHandler(context, resolveContext, pathSegments);
|
||||
const proxy = vue.markRaw(new Proxy(target, handler));
|
||||
if (!pathSegments.length && context.sources) {
|
||||
context.sources.push(proxy);
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
get: (target, key, receiver) => {
|
||||
if (key === IS_PROXY) {
|
||||
return true;
|
||||
}
|
||||
if (key === PROXY_SOURCES) {
|
||||
return context.sources;
|
||||
}
|
||||
if (key === PROXY_TARGET) {
|
||||
return target;
|
||||
}
|
||||
if (key === RESOLVE_CONTEXT) {
|
||||
return resolveContext;
|
||||
}
|
||||
let value = Reflect.get(target, key, receiver);
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
target[key] = value;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: (target, key, value) => {
|
||||
const success = Reflect.set(target, key, value);
|
||||
// console.warn(success, 'PROXY SET\nkey:', key, '\npath:', pathSegments, '\ntarget:', isArray(target), target, '\ncontext:\n', context)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let hasArrayParent = false;
|
||||
let { sources: proxies, active } = context;
|
||||
let activeSegmentKey;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = pluck(proxies, segment);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
if (isArray(active)) {
|
||||
hasArrayParent = true;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
if (hasArrayParent) {
|
||||
// TODO: fix that we dont have to recompute the full merged object
|
||||
// we should only have to recompute the branch that has changed
|
||||
// but there is an issue here with supporting both arrays of strings
|
||||
// as collections (parent vs parent of parent we need to trigger the
|
||||
// update from)
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
// console.log('CONTEXT.ACTIVE', context.active, '\nparent:\n', target)
|
||||
return success;
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
let proxies = context.sources;
|
||||
let active = context.active;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete active[key];
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
});
|
||||
|
||||
const createMergedObject = (resolve, active = {}) => {
|
||||
const sources = [];
|
||||
if (!active) {
|
||||
active = {};
|
||||
}
|
||||
const context = {
|
||||
active,
|
||||
resolve,
|
||||
sources
|
||||
};
|
||||
const compute = () => recompute(context);
|
||||
const addSource = (source, resolveContext, recompute = false) => {
|
||||
const proxy = createProxy(context, source, resolveContext || {});
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const delSource = (sourceOrProxy, recompute = true) => {
|
||||
const index = sources.findIndex(src => src === sourceOrProxy || src[PROXY_TARGET] === sourceOrProxy);
|
||||
if (index > -1) {
|
||||
sources.splice(index, 1);
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return {
|
||||
context,
|
||||
active,
|
||||
resolve,
|
||||
sources,
|
||||
addSource,
|
||||
delSource,
|
||||
compute
|
||||
};
|
||||
};
|
||||
|
||||
function renderMeta(context, key, data, config) {
|
||||
// console.info('renderMeta', key, data, config)
|
||||
if (config.attributesFor) {
|
||||
return renderAttributes(context, key, data, config);
|
||||
}
|
||||
if (config.group) {
|
||||
return renderGroup(context, key, data, config);
|
||||
}
|
||||
return renderTag(context, key, data, config);
|
||||
}
|
||||
function renderGroup(context, key, data, config) {
|
||||
// console.info('renderGroup', key, data, config)
|
||||
if (isArray(data)) {
|
||||
// config.attributes = getConfigKey([key, config.tag], 'attributes', config)
|
||||
return [];
|
||||
}
|
||||
return Object.keys(data)
|
||||
.map((childKey) => {
|
||||
const groupConfig = {
|
||||
group: key,
|
||||
data
|
||||
};
|
||||
if (config.namespaced) {
|
||||
groupConfig.tagNamespace = config.namespaced === true ? key : config.namespaced;
|
||||
}
|
||||
else if (config.namespacedAttribute) {
|
||||
const namespace = config.namespacedAttribute === true ? key : config.namespacedAttribute;
|
||||
groupConfig.fullName = `${namespace}:${childKey}`;
|
||||
groupConfig.slotName = `${namespace}(${childKey})`;
|
||||
}
|
||||
return renderTag(context, key, data[childKey], config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
// console.info('renderTag', key, data, config, groupConfig)
|
||||
const contentAttributes = ['content', 'json', 'rawContent'];
|
||||
const getConfig = (key) => getConfigByKey([tag, config.tag], key, config);
|
||||
if (isArray(data)) {
|
||||
return data
|
||||
.map((child) => {
|
||||
return renderTag(context, key, child, config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
const { tag = config.tag || key } = data;
|
||||
let content;
|
||||
let hasChilds = false;
|
||||
let isRaw = false;
|
||||
if (isString(data)) {
|
||||
content = data;
|
||||
}
|
||||
else if (data.children && isArray(data.children)) {
|
||||
hasChilds = true;
|
||||
content = data.children.map((child) => {
|
||||
const data = renderTag(context, key, child, config, groupConfig);
|
||||
if (isArray(data)) {
|
||||
return data.map(({ vnode }) => vnode);
|
||||
}
|
||||
return data.vnode;
|
||||
});
|
||||
}
|
||||
else {
|
||||
let i = 0;
|
||||
for (const contentAttribute of contentAttributes) {
|
||||
if (!content && data[contentAttribute]) {
|
||||
if (i === 1) {
|
||||
content = JSON.stringify(data[contentAttribute]);
|
||||
}
|
||||
else {
|
||||
content = data[contentAttribute];
|
||||
}
|
||||
isRaw = i > 1;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
const fullName = (groupConfig && groupConfig.fullName) || key;
|
||||
const slotName = (groupConfig && groupConfig.slotName) || key;
|
||||
let { attrs: attributes } = data;
|
||||
if (!attributes && typeof data === 'object') {
|
||||
attributes = { ...data };
|
||||
delete attributes.tag;
|
||||
delete attributes.children;
|
||||
delete attributes.to;
|
||||
// cleanup all content attributes
|
||||
for (const attr of contentAttributes) {
|
||||
delete attributes[attr];
|
||||
}
|
||||
}
|
||||
else if (!attributes) {
|
||||
attributes = {};
|
||||
}
|
||||
if (hasChilds) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
const contentAsAttribute = getConfig('contentAsAttribute');
|
||||
let valueAttribute = config.valueAttribute;
|
||||
if (!valueAttribute && contentAsAttribute) {
|
||||
const tagAttributes = getConfig('attributes');
|
||||
valueAttribute = isString(contentAsAttribute) ? contentAsAttribute : tagAttributes[0];
|
||||
}
|
||||
if (!valueAttribute) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
if (!config.nameless) {
|
||||
const keyAttribute = getConfig('keyAttribute');
|
||||
if (keyAttribute) {
|
||||
attributes[keyAttribute] = fullName;
|
||||
}
|
||||
}
|
||||
attributes[valueAttribute] = getSlotContent(context, slotName, attributes[valueAttribute] || content, groupConfig);
|
||||
content = undefined;
|
||||
}
|
||||
}
|
||||
const finalTag = groupConfig && groupConfig.tagNamespace
|
||||
? `${groupConfig.tagNamespace}:${tag}`
|
||||
: tag;
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
let vnode;
|
||||
if (isRaw) {
|
||||
attributes.innerHTML = content;
|
||||
vnode = vue.h(finalTag, attributes);
|
||||
}
|
||||
else {
|
||||
vnode = vue.h(finalTag, attributes, content);
|
||||
}
|
||||
return {
|
||||
to: data.to,
|
||||
vnode
|
||||
};
|
||||
}
|
||||
function renderAttributes(context, key, data, config = {}) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
{
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
to: '',
|
||||
vnode: vue.h(`ssr-${attributesFor}`, data)
|
||||
};
|
||||
}
|
||||
}
|
||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||
if (!slots || !slots[slotName]) {
|
||||
return content;
|
||||
}
|
||||
const slotProps = {
|
||||
content,
|
||||
metainfo
|
||||
};
|
||||
if (groupConfig && groupConfig.group) {
|
||||
slotProps[groupConfig.group] = groupConfig.data;
|
||||
}
|
||||
const slotContent = slots[slotName](slotProps);
|
||||
if (slotContent && slotContent.length) {
|
||||
return slotContent[0].children;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
|
||||
const PolySymbol = (name) =>
|
||||
// vm = vue meta
|
||||
hasSymbol
|
||||
? Symbol( name)
|
||||
: ( '_vm_') + name;
|
||||
const metaInfoKey = PolySymbol( 'mi');
|
||||
|
||||
function getCurrentManager(vm) {
|
||||
if (!vm) {
|
||||
vm = vue.getCurrentInstance();
|
||||
}
|
||||
return vm.appContext.config.globalProperties.$metaManager;
|
||||
}
|
||||
function useMeta(obj, manager) {
|
||||
const vm = vue.getCurrentInstance();
|
||||
if (!manager && vm) {
|
||||
manager = getCurrentManager(vm);
|
||||
}
|
||||
if (!manager) {
|
||||
// oopsydoopsy
|
||||
throw new Error('No manager or current instance');
|
||||
}
|
||||
return manager.addMeta(obj, vm || undefined);
|
||||
}
|
||||
function useMetainfo() {
|
||||
return vue.inject(metaInfoKey);
|
||||
}
|
||||
|
||||
const MetainfoImpl = vue.defineComponent({
|
||||
name: 'Metainfo',
|
||||
inheritAttrs: false,
|
||||
setup(_, { slots }) {
|
||||
return () => {
|
||||
const manager = getCurrentManager();
|
||||
if (!manager) {
|
||||
return;
|
||||
}
|
||||
return manager.render({ slots });
|
||||
};
|
||||
}
|
||||
});
|
||||
const Metainfo = MetainfoImpl;
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = vue.reactive({});
|
||||
function addVnode(teleports, to, _vnodes) {
|
||||
const vnodes = (isArray(_vnodes) ? _vnodes : [_vnodes]);
|
||||
{
|
||||
// dont add ssrAttribute for attribute vnode placeholder
|
||||
if (!to.endsWith('Attrs')) {
|
||||
vnodes.forEach((vnode) => {
|
||||
if (!vnode.props) {
|
||||
vnode.props = {};
|
||||
}
|
||||
vnode.props[ssrAttribute] = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!teleports[to]) {
|
||||
teleports[to] = [];
|
||||
}
|
||||
teleports[to].push(...vnodes);
|
||||
}
|
||||
function createMetaManager(config, resolver) {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
}
|
||||
return resolver.resolve(options, contexts, active, key, pathSegments);
|
||||
};
|
||||
const { addSource, delSource } = createMergedObject(resolve, active);
|
||||
// TODO: validate resolver
|
||||
const manager = {
|
||||
config,
|
||||
install(app) {
|
||||
app.component('Metainfo', Metainfo);
|
||||
app.config.globalProperties.$metaManager = manager;
|
||||
app.provide(metaInfoKey, active);
|
||||
},
|
||||
addMeta(metaObj, vm) {
|
||||
const resolveContext = { vm };
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute
|
||||
const meta = addSource(metaObj, resolveContext, true);
|
||||
const unmount = () => delSource(meta);
|
||||
if (vm) {
|
||||
vue.onUnmounted(unmount);
|
||||
}
|
||||
return {
|
||||
meta,
|
||||
unmount
|
||||
};
|
||||
},
|
||||
render({ slots } = {}) {
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
const vnode = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
if (!vnode) {
|
||||
continue;
|
||||
}
|
||||
const vnodes = isArray(vnode) ? vnode : [vnode];
|
||||
const defaultTo = (key !== 'base' && active[key].to) || config.to || (config.attributesFor ? key : 'head');
|
||||
for (const { to, vnode } of vnodes) {
|
||||
addVnode(teleports, to || defaultTo, vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
for (const tag in slots) {
|
||||
const slotFn = slots[tag];
|
||||
if (isFunction(slotFn)) {
|
||||
addVnode(teleports, tag === 'default' ? 'head' : tag, slotFn({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return vue.h(vue.Teleport, { to }, teleports[to]);
|
||||
});
|
||||
}
|
||||
};
|
||||
return manager;
|
||||
}
|
||||
|
||||
// rollup doesnt like an import, cant find export so use require
|
||||
const { renderToString } = require('@vue/server-renderer');
|
||||
async function renderToStringWithMeta(app) {
|
||||
const ctx = {};
|
||||
const html = await renderToString(app, ctx);
|
||||
// TODO: better way of determining whether meta was rendered with the component or not
|
||||
if (!ctx.teleports || !ctx.teleports.head) {
|
||||
const teleports = app.config.globalProperties.$metaManager.render();
|
||||
await Promise.all(teleports.map((teleport) => renderToString(teleport, ctx)));
|
||||
}
|
||||
const { teleports } = ctx;
|
||||
for (const target in teleports) {
|
||||
if (target.endsWith('Attrs')) {
|
||||
const str = teleports[target];
|
||||
// match from first space to first >, these should be all rendered attributes
|
||||
teleports[target] = str.slice(str.indexOf(' ') + 1, str.indexOf('>'));
|
||||
}
|
||||
}
|
||||
return [html, ctx];
|
||||
}
|
||||
|
||||
exports.createMetaManager = createMetaManager;
|
||||
exports.deepestResolver = deepest;
|
||||
exports.defaultConfig = defaultConfig;
|
||||
exports.getCurrentManager = getCurrentManager;
|
||||
exports.renderToStringWithMeta = renderToStringWithMeta;
|
||||
exports.resolveOption = resolveOption;
|
||||
exports.useMeta = useMeta;
|
||||
exports.useMetainfo = useMetainfo;
|
||||
Vendored
+801
@@ -0,0 +1,801 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.0
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { markRaw, h, getCurrentInstance, inject, defineComponent, reactive, onUnmounted, Teleport, Comment } from 'vue';
|
||||
|
||||
const resolveOption = predicament => (options, contexts) => {
|
||||
let resolvedIndex = -1;
|
||||
contexts.reduce((acc, context, index) => {
|
||||
const retval = predicament(acc, context);
|
||||
if (retval !== acc) {
|
||||
resolvedIndex = index;
|
||||
return retval;
|
||||
}
|
||||
return acc;
|
||||
}, undefined);
|
||||
if (resolvedIndex > -1) {
|
||||
return options[resolvedIndex];
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
do {
|
||||
if (vm.parent) {
|
||||
depth++;
|
||||
vm = vm.parent;
|
||||
}
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
const resolve = resolveOption((acc, context) => {
|
||||
const { depth } = context;
|
||||
if (!acc || depth > acc) {
|
||||
return acc;
|
||||
}
|
||||
});
|
||||
|
||||
var deepest = /*#__PURE__*/Object.freeze({
|
||||
__proto__: null,
|
||||
setup: setup,
|
||||
resolve: resolve
|
||||
});
|
||||
|
||||
const defaultConfig = {
|
||||
body: {
|
||||
tag: 'script',
|
||||
to: 'body'
|
||||
},
|
||||
base: {
|
||||
valueAttribute: 'href'
|
||||
},
|
||||
charset: {
|
||||
tag: 'meta',
|
||||
nameless: true,
|
||||
valueAttribute: 'charset'
|
||||
},
|
||||
description: {
|
||||
tag: 'meta'
|
||||
},
|
||||
og: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta',
|
||||
keyAttribute: 'property'
|
||||
},
|
||||
twitter: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta'
|
||||
},
|
||||
htmlAttrs: {
|
||||
attributesFor: 'html'
|
||||
},
|
||||
headAttrs: {
|
||||
attributesFor: 'head'
|
||||
},
|
||||
bodyAttrs: {
|
||||
attributesFor: 'body'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a map and return a function for checking if a key
|
||||
* is in that map.
|
||||
* IMPORTANT: all calls of this function must be prefixed with
|
||||
* \/\*#\_\_PURE\_\_\*\/
|
||||
* So that rollup can tree-shake them if necessary.
|
||||
*/
|
||||
(process.env.NODE_ENV !== 'production')
|
||||
? Object.freeze({})
|
||||
: {};
|
||||
(process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
|
||||
const isArray = Array.isArray;
|
||||
const isFunction = (val) => typeof val === 'function';
|
||||
const isString = (val) => typeof val === 'string';
|
||||
const isObject = (val) => val !== null && typeof val === 'object';
|
||||
const objectToString = Object.prototype.toString;
|
||||
const toTypeString = (value) => objectToString.call(value);
|
||||
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
|
||||
|
||||
/*
|
||||
* This is a fixed config for real HTML tags
|
||||
*
|
||||
* TODO: we probably dont need all attributes
|
||||
*/
|
||||
const tags = {
|
||||
title: {
|
||||
attributes: false
|
||||
},
|
||||
base: {
|
||||
contentAsAttribute: true,
|
||||
attributes: ['href', 'target']
|
||||
},
|
||||
meta: {
|
||||
contentAsAttribute: true,
|
||||
keyAttribute: 'name',
|
||||
attributes: ['content', 'name', 'http-equiv', 'charset']
|
||||
},
|
||||
link: {
|
||||
contentAsAttribute: true,
|
||||
attributes: [
|
||||
'href',
|
||||
'crossorigin',
|
||||
'rel',
|
||||
'media',
|
||||
'integrity',
|
||||
'hreflang',
|
||||
'type',
|
||||
'referrerpolicy',
|
||||
'sizes',
|
||||
'imagesrcset',
|
||||
'imagesizes',
|
||||
'as',
|
||||
'color'
|
||||
]
|
||||
},
|
||||
style: {
|
||||
attributes: ['media']
|
||||
},
|
||||
script: {
|
||||
attributes: [
|
||||
'src',
|
||||
'type',
|
||||
'nomodule',
|
||||
'async',
|
||||
'defer',
|
||||
'crossorigin',
|
||||
'integrity',
|
||||
'referrerpolicy'
|
||||
]
|
||||
},
|
||||
noscript: {
|
||||
attributes: false
|
||||
}
|
||||
};
|
||||
|
||||
function getConfigByKey(tagOrName, key, config) {
|
||||
if (config && key in config) {
|
||||
return config[key];
|
||||
}
|
||||
if (isArray(tagOrName)) {
|
||||
for (const name of tagOrName) {
|
||||
if (name && name in tags) {
|
||||
return tags[name][key];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (tagOrName in tags) {
|
||||
const tag = tags[tagOrName];
|
||||
return tag[key];
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/microsoft/TypeScript/issues/1863
|
||||
const IS_PROXY = Symbol('kIsProxy');
|
||||
const PROXY_SOURCES = Symbol('kProxySources');
|
||||
const PROXY_TARGET = Symbol('kProxyTarget');
|
||||
const RESOLVE_CONTEXT = Symbol('kResolveContext');
|
||||
|
||||
// See: https://github.com/vuejs/vue-next/blob/08b4e8815da4e8911058ccbab986bea6365c3352/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
|
||||
function clone(v) {
|
||||
if (isArray(v)) {
|
||||
return v.map(clone);
|
||||
}
|
||||
if (isObject(v)) {
|
||||
const res = {};
|
||||
for (const key in v) {
|
||||
// never clone the context
|
||||
if (key === 'context') {
|
||||
res[key] = v[key];
|
||||
}
|
||||
else {
|
||||
res[key] = clone(v[key]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
return plucked;
|
||||
};
|
||||
|
||||
const allKeys = (source, ...sources) => {
|
||||
const keys = source ? Object.keys(source) : [];
|
||||
if (sources) {
|
||||
for (const source of sources) {
|
||||
if (!source || !isObject(source)) {
|
||||
continue;
|
||||
}
|
||||
for (const key in source) {
|
||||
if (!keys.includes(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
return;
|
||||
}
|
||||
const keys = allKeys(...sources);
|
||||
// Clean up properties that dont exists anymore
|
||||
const targetKeys = Object.keys(target);
|
||||
for (const key of targetKeys) {
|
||||
if (!keys.includes(key)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
const keySources = [];
|
||||
for (const source of sources) {
|
||||
if (key in source) {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
if (!target[key] && isArray(sources[0][key])) {
|
||||
target[key] = [];
|
||||
}
|
||||
const keyContexts = [];
|
||||
const keySources = pluck(sources, key, source => keyContexts.push(source[RESOLVE_CONTEXT]));
|
||||
let resolved = context.resolve(keySources, keyContexts, target[key], key, path);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
|
||||
const createProxy = (context, target, resolveContext, pathSegments = []) => {
|
||||
const handler = createHandler(context, resolveContext, pathSegments);
|
||||
const proxy = markRaw(new Proxy(target, handler));
|
||||
if (!pathSegments.length && context.sources) {
|
||||
context.sources.push(proxy);
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
get: (target, key, receiver) => {
|
||||
if (key === IS_PROXY) {
|
||||
return true;
|
||||
}
|
||||
if (key === PROXY_SOURCES) {
|
||||
return context.sources;
|
||||
}
|
||||
if (key === PROXY_TARGET) {
|
||||
return target;
|
||||
}
|
||||
if (key === RESOLVE_CONTEXT) {
|
||||
return resolveContext;
|
||||
}
|
||||
let value = Reflect.get(target, key, receiver);
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
target[key] = value;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: (target, key, value) => {
|
||||
const success = Reflect.set(target, key, value);
|
||||
// console.warn(success, 'PROXY SET\nkey:', key, '\npath:', pathSegments, '\ntarget:', isArray(target), target, '\ncontext:\n', context)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let hasArrayParent = false;
|
||||
let { sources: proxies, active } = context;
|
||||
let activeSegmentKey;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = pluck(proxies, segment);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
if (isArray(active)) {
|
||||
hasArrayParent = true;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
if (hasArrayParent) {
|
||||
// TODO: fix that we dont have to recompute the full merged object
|
||||
// we should only have to recompute the branch that has changed
|
||||
// but there is an issue here with supporting both arrays of strings
|
||||
// as collections (parent vs parent of parent we need to trigger the
|
||||
// update from)
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
// console.log('CONTEXT.ACTIVE', context.active, '\nparent:\n', target)
|
||||
return success;
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
let proxies = context.sources;
|
||||
let active = context.active;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete active[key];
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
});
|
||||
|
||||
const createMergedObject = (resolve, active = {}) => {
|
||||
const sources = [];
|
||||
if (!active) {
|
||||
active = {};
|
||||
}
|
||||
const context = {
|
||||
active,
|
||||
resolve,
|
||||
sources
|
||||
};
|
||||
const compute = () => recompute(context);
|
||||
const addSource = (source, resolveContext, recompute = false) => {
|
||||
const proxy = createProxy(context, source, resolveContext || {});
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const delSource = (sourceOrProxy, recompute = true) => {
|
||||
const index = sources.findIndex(src => src === sourceOrProxy || src[PROXY_TARGET] === sourceOrProxy);
|
||||
if (index > -1) {
|
||||
sources.splice(index, 1);
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return {
|
||||
context,
|
||||
active,
|
||||
resolve,
|
||||
sources,
|
||||
addSource,
|
||||
delSource,
|
||||
compute
|
||||
};
|
||||
};
|
||||
|
||||
const cachedElements = {};
|
||||
function renderMeta(context, key, data, config) {
|
||||
// console.info('renderMeta', key, data, config)
|
||||
if (config.attributesFor) {
|
||||
return renderAttributes(context, key, data, config);
|
||||
}
|
||||
if (config.group) {
|
||||
return renderGroup(context, key, data, config);
|
||||
}
|
||||
return renderTag(context, key, data, config);
|
||||
}
|
||||
function renderGroup(context, key, data, config) {
|
||||
// console.info('renderGroup', key, data, config)
|
||||
if (isArray(data)) {
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Specifying an array for group properties isnt supported mostly as we didnt found a use-case for this yet. If you have one, please create an issue on the vue-meta repo');
|
||||
}
|
||||
// config.attributes = getConfigKey([key, config.tag], 'attributes', config)
|
||||
return [];
|
||||
}
|
||||
return Object.keys(data)
|
||||
.map((childKey) => {
|
||||
const groupConfig = {
|
||||
group: key,
|
||||
data
|
||||
};
|
||||
if (config.namespaced) {
|
||||
groupConfig.tagNamespace = config.namespaced === true ? key : config.namespaced;
|
||||
}
|
||||
else if (config.namespacedAttribute) {
|
||||
const namespace = config.namespacedAttribute === true ? key : config.namespacedAttribute;
|
||||
groupConfig.fullName = `${namespace}:${childKey}`;
|
||||
groupConfig.slotName = `${namespace}(${childKey})`;
|
||||
}
|
||||
return renderTag(context, key, data[childKey], config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
// console.info('renderTag', key, data, config, groupConfig)
|
||||
const contentAttributes = ['content', 'json', 'rawContent'];
|
||||
const getConfig = (key) => getConfigByKey([tag, config.tag], key, config);
|
||||
if (isArray(data)) {
|
||||
return data
|
||||
.map((child) => {
|
||||
return renderTag(context, key, child, config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
const { tag = config.tag || key } = data;
|
||||
let content;
|
||||
let hasChilds = false;
|
||||
let isRaw = false;
|
||||
if (isString(data)) {
|
||||
content = data;
|
||||
}
|
||||
else if (data.children && isArray(data.children)) {
|
||||
hasChilds = true;
|
||||
content = data.children.map((child) => {
|
||||
const data = renderTag(context, key, child, config, groupConfig);
|
||||
if (isArray(data)) {
|
||||
return data.map(({ vnode }) => vnode);
|
||||
}
|
||||
return data.vnode;
|
||||
});
|
||||
}
|
||||
else {
|
||||
let i = 0;
|
||||
for (const contentAttribute of contentAttributes) {
|
||||
if (!content && data[contentAttribute]) {
|
||||
if (i === 1) {
|
||||
content = JSON.stringify(data[contentAttribute]);
|
||||
}
|
||||
else {
|
||||
content = data[contentAttribute];
|
||||
}
|
||||
isRaw = i > 1;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
const fullName = (groupConfig && groupConfig.fullName) || key;
|
||||
const slotName = (groupConfig && groupConfig.slotName) || key;
|
||||
let { attrs: attributes } = data;
|
||||
if (!attributes && typeof data === 'object') {
|
||||
attributes = { ...data };
|
||||
delete attributes.tag;
|
||||
delete attributes.children;
|
||||
delete attributes.to;
|
||||
// cleanup all content attributes
|
||||
for (const attr of contentAttributes) {
|
||||
delete attributes[attr];
|
||||
}
|
||||
}
|
||||
else if (!attributes) {
|
||||
attributes = {};
|
||||
}
|
||||
if (hasChilds) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
const contentAsAttribute = getConfig('contentAsAttribute');
|
||||
let valueAttribute = config.valueAttribute;
|
||||
if (!valueAttribute && contentAsAttribute) {
|
||||
const tagAttributes = getConfig('attributes');
|
||||
valueAttribute = isString(contentAsAttribute) ? contentAsAttribute : tagAttributes[0];
|
||||
}
|
||||
if (!valueAttribute) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
if (!config.nameless) {
|
||||
const keyAttribute = getConfig('keyAttribute');
|
||||
if (keyAttribute) {
|
||||
attributes[keyAttribute] = fullName;
|
||||
}
|
||||
}
|
||||
attributes[valueAttribute] = getSlotContent(context, slotName, attributes[valueAttribute] || content, groupConfig);
|
||||
content = undefined;
|
||||
}
|
||||
}
|
||||
const finalTag = groupConfig && groupConfig.tagNamespace
|
||||
? `${groupConfig.tagNamespace}:${tag}`
|
||||
: tag;
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
let vnode;
|
||||
if (isRaw) {
|
||||
attributes.innerHTML = content;
|
||||
vnode = h(finalTag, attributes);
|
||||
}
|
||||
else {
|
||||
vnode = h(finalTag, attributes, content);
|
||||
}
|
||||
return {
|
||||
to: data.to,
|
||||
vnode
|
||||
};
|
||||
}
|
||||
function renderAttributes(context, key, data, config = {}) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
if (!cachedElements[attributesFor]) {
|
||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
||||
if ( !el) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Could not find element with selector', attributesFor, ', won\'t render attributes');
|
||||
return;
|
||||
}
|
||||
if ( el2) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Found multiple elements with selector', attributesFor);
|
||||
}
|
||||
cachedElements[attributesFor] = {
|
||||
el,
|
||||
attrs: []
|
||||
};
|
||||
}
|
||||
const { el, attrs } = cachedElements[attributesFor];
|
||||
for (const attr in data) {
|
||||
const content = getSlotContent(context, `${key}(${attr})`, data[attr], data);
|
||||
el.setAttribute(attr, `${content || ''}`);
|
||||
if (!attrs.includes(attr)) {
|
||||
attrs.push(attr);
|
||||
}
|
||||
}
|
||||
const attrsToRemove = attrs.filter(attr => !data[attr]);
|
||||
for (const attr of attrsToRemove) {
|
||||
el.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||
if (!slots || !slots[slotName]) {
|
||||
return content;
|
||||
}
|
||||
const slotProps = {
|
||||
content,
|
||||
metainfo
|
||||
};
|
||||
if (groupConfig && groupConfig.group) {
|
||||
slotProps[groupConfig.group] = groupConfig.data;
|
||||
}
|
||||
const slotContent = slots[slotName](slotProps);
|
||||
if (slotContent && slotContent.length) {
|
||||
return slotContent[0].children;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
|
||||
const PolySymbol = (name) =>
|
||||
// vm = vue meta
|
||||
hasSymbol
|
||||
? Symbol( '[vue-meta]: ' + name )
|
||||
: ( '[vue-meta]: ' ) + name;
|
||||
const metaInfoKey = PolySymbol( 'metainfo' );
|
||||
|
||||
function getCurrentManager(vm) {
|
||||
if (!vm) {
|
||||
vm = getCurrentInstance();
|
||||
}
|
||||
return vm.appContext.config.globalProperties.$metaManager;
|
||||
}
|
||||
function useMeta(obj, manager) {
|
||||
const vm = getCurrentInstance();
|
||||
if (!manager && vm) {
|
||||
manager = getCurrentManager(vm);
|
||||
}
|
||||
if (!manager) {
|
||||
// oopsydoopsy
|
||||
throw new Error('No manager or current instance');
|
||||
}
|
||||
return manager.addMeta(obj, vm || undefined);
|
||||
}
|
||||
function useMetainfo() {
|
||||
return inject(metaInfoKey);
|
||||
}
|
||||
|
||||
const MetainfoImpl = defineComponent({
|
||||
name: 'Metainfo',
|
||||
inheritAttrs: false,
|
||||
setup(_, { slots }) {
|
||||
return () => {
|
||||
const manager = getCurrentManager();
|
||||
if (!manager) {
|
||||
return;
|
||||
}
|
||||
return manager.render({ slots });
|
||||
};
|
||||
}
|
||||
});
|
||||
const Metainfo = MetainfoImpl;
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = reactive({});
|
||||
function addVnode(teleports, to, _vnodes) {
|
||||
const vnodes = (isArray(_vnodes) ? _vnodes : [_vnodes]);
|
||||
{
|
||||
// Comments shouldnt have any use on the client as they are not reactive anyway
|
||||
vnodes.forEach((vnode, idx) => {
|
||||
if (vnode.type === Comment) {
|
||||
vnodes.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!teleports[to]) {
|
||||
teleports[to] = [];
|
||||
}
|
||||
teleports[to].push(...vnodes);
|
||||
}
|
||||
function createMetaManager(config, resolver) {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
}
|
||||
return resolver.resolve(options, contexts, active, key, pathSegments);
|
||||
};
|
||||
const { addSource, delSource } = createMergedObject(resolve, active);
|
||||
let cleanedUpSsr = false;
|
||||
// TODO: validate resolver
|
||||
const manager = {
|
||||
config,
|
||||
install(app) {
|
||||
app.component('Metainfo', Metainfo);
|
||||
app.config.globalProperties.$metaManager = manager;
|
||||
app.provide(metaInfoKey, active);
|
||||
},
|
||||
addMeta(metaObj, vm) {
|
||||
const resolveContext = { vm };
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute
|
||||
const meta = addSource(metaObj, resolveContext, true);
|
||||
const unmount = () => delSource(meta);
|
||||
if (vm) {
|
||||
onUnmounted(unmount);
|
||||
}
|
||||
return {
|
||||
meta,
|
||||
unmount
|
||||
};
|
||||
},
|
||||
render({ slots } = {}) {
|
||||
// cleanup ssr tags if not yet done
|
||||
if ( !cleanedUpSsr) {
|
||||
cleanedUpSsr = true;
|
||||
// Listen for DOM loaded because tags in the body couldnt be loaded
|
||||
// yet once the manager does it first render
|
||||
// (preferable there should only be one render on hydration)
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`);
|
||||
if (ssrTags && ssrTags.length) {
|
||||
Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el));
|
||||
}
|
||||
});
|
||||
}
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
const vnode = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
if (!vnode) {
|
||||
continue;
|
||||
}
|
||||
const vnodes = isArray(vnode) ? vnode : [vnode];
|
||||
const defaultTo = (key !== 'base' && active[key].to) || config.to || (config.attributesFor ? key : 'head');
|
||||
for (const { to, vnode } of vnodes) {
|
||||
addVnode(teleports, to || defaultTo, vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
for (const tag in slots) {
|
||||
const slotFn = slots[tag];
|
||||
if (isFunction(slotFn)) {
|
||||
addVnode(teleports, tag === 'default' ? 'head' : tag, slotFn({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return h(Teleport, { to }, teleports[to]);
|
||||
});
|
||||
}
|
||||
};
|
||||
return manager;
|
||||
}
|
||||
|
||||
export { createMetaManager, deepest as deepestResolver, defaultConfig, getCurrentManager, resolveOption, useMeta, useMetainfo };
|
||||
Vendored
+8
File diff suppressed because one or more lines are too long
Vendored
+790
@@ -0,0 +1,790 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.0
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { markRaw, h, getCurrentInstance, inject, defineComponent, reactive, onUnmounted, Teleport } from 'vue';
|
||||
|
||||
const resolveOption = predicament => (options, contexts) => {
|
||||
let resolvedIndex = -1;
|
||||
contexts.reduce((acc, context, index) => {
|
||||
const retval = predicament(acc, context);
|
||||
if (retval !== acc) {
|
||||
resolvedIndex = index;
|
||||
return retval;
|
||||
}
|
||||
return acc;
|
||||
}, undefined);
|
||||
if (resolvedIndex > -1) {
|
||||
return options[resolvedIndex];
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
do {
|
||||
if (vm.parent) {
|
||||
depth++;
|
||||
vm = vm.parent;
|
||||
}
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
const resolve = resolveOption((acc, context) => {
|
||||
const { depth } = context;
|
||||
if (!acc || depth > acc) {
|
||||
return acc;
|
||||
}
|
||||
});
|
||||
|
||||
var deepest = /*#__PURE__*/Object.freeze({
|
||||
__proto__: null,
|
||||
setup: setup,
|
||||
resolve: resolve
|
||||
});
|
||||
|
||||
const defaultConfig = {
|
||||
body: {
|
||||
tag: 'script',
|
||||
to: 'body'
|
||||
},
|
||||
base: {
|
||||
valueAttribute: 'href'
|
||||
},
|
||||
charset: {
|
||||
tag: 'meta',
|
||||
nameless: true,
|
||||
valueAttribute: 'charset'
|
||||
},
|
||||
description: {
|
||||
tag: 'meta'
|
||||
},
|
||||
og: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta',
|
||||
keyAttribute: 'property'
|
||||
},
|
||||
twitter: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta'
|
||||
},
|
||||
htmlAttrs: {
|
||||
attributesFor: 'html'
|
||||
},
|
||||
headAttrs: {
|
||||
attributesFor: 'head'
|
||||
},
|
||||
bodyAttrs: {
|
||||
attributesFor: 'body'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a map and return a function for checking if a key
|
||||
* is in that map.
|
||||
* IMPORTANT: all calls of this function must be prefixed with
|
||||
* \/\*#\_\_PURE\_\_\*\/
|
||||
* So that rollup can tree-shake them if necessary.
|
||||
*/
|
||||
(process.env.NODE_ENV !== 'production')
|
||||
? Object.freeze({})
|
||||
: {};
|
||||
(process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
|
||||
const isArray = Array.isArray;
|
||||
const isFunction = (val) => typeof val === 'function';
|
||||
const isString = (val) => typeof val === 'string';
|
||||
const isObject = (val) => val !== null && typeof val === 'object';
|
||||
const objectToString = Object.prototype.toString;
|
||||
const toTypeString = (value) => objectToString.call(value);
|
||||
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
|
||||
|
||||
/*
|
||||
* This is a fixed config for real HTML tags
|
||||
*
|
||||
* TODO: we probably dont need all attributes
|
||||
*/
|
||||
const tags = {
|
||||
title: {
|
||||
attributes: false
|
||||
},
|
||||
base: {
|
||||
contentAsAttribute: true,
|
||||
attributes: ['href', 'target']
|
||||
},
|
||||
meta: {
|
||||
contentAsAttribute: true,
|
||||
keyAttribute: 'name',
|
||||
attributes: ['content', 'name', 'http-equiv', 'charset']
|
||||
},
|
||||
link: {
|
||||
contentAsAttribute: true,
|
||||
attributes: [
|
||||
'href',
|
||||
'crossorigin',
|
||||
'rel',
|
||||
'media',
|
||||
'integrity',
|
||||
'hreflang',
|
||||
'type',
|
||||
'referrerpolicy',
|
||||
'sizes',
|
||||
'imagesrcset',
|
||||
'imagesizes',
|
||||
'as',
|
||||
'color'
|
||||
]
|
||||
},
|
||||
style: {
|
||||
attributes: ['media']
|
||||
},
|
||||
script: {
|
||||
attributes: [
|
||||
'src',
|
||||
'type',
|
||||
'nomodule',
|
||||
'async',
|
||||
'defer',
|
||||
'crossorigin',
|
||||
'integrity',
|
||||
'referrerpolicy'
|
||||
]
|
||||
},
|
||||
noscript: {
|
||||
attributes: false
|
||||
}
|
||||
};
|
||||
|
||||
function getConfigByKey(tagOrName, key, config) {
|
||||
if (config && key in config) {
|
||||
return config[key];
|
||||
}
|
||||
if (isArray(tagOrName)) {
|
||||
for (const name of tagOrName) {
|
||||
if (name && name in tags) {
|
||||
return tags[name][key];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (tagOrName in tags) {
|
||||
const tag = tags[tagOrName];
|
||||
return tag[key];
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/microsoft/TypeScript/issues/1863
|
||||
const IS_PROXY = Symbol('kIsProxy');
|
||||
const PROXY_SOURCES = Symbol('kProxySources');
|
||||
const PROXY_TARGET = Symbol('kProxyTarget');
|
||||
const RESOLVE_CONTEXT = Symbol('kResolveContext');
|
||||
|
||||
// See: https://github.com/vuejs/vue-next/blob/08b4e8815da4e8911058ccbab986bea6365c3352/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
|
||||
function clone(v) {
|
||||
if (isArray(v)) {
|
||||
return v.map(clone);
|
||||
}
|
||||
if (isObject(v)) {
|
||||
const res = {};
|
||||
for (const key in v) {
|
||||
// never clone the context
|
||||
if (key === 'context') {
|
||||
res[key] = v[key];
|
||||
}
|
||||
else {
|
||||
res[key] = clone(v[key]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
return plucked;
|
||||
};
|
||||
|
||||
const allKeys = (source, ...sources) => {
|
||||
const keys = source ? Object.keys(source) : [];
|
||||
if (sources) {
|
||||
for (const source of sources) {
|
||||
if (!source || !isObject(source)) {
|
||||
continue;
|
||||
}
|
||||
for (const key in source) {
|
||||
if (!keys.includes(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
return;
|
||||
}
|
||||
const keys = allKeys(...sources);
|
||||
// Clean up properties that dont exists anymore
|
||||
const targetKeys = Object.keys(target);
|
||||
for (const key of targetKeys) {
|
||||
if (!keys.includes(key)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
const keySources = [];
|
||||
for (const source of sources) {
|
||||
if (key in source) {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
if (!target[key] && isArray(sources[0][key])) {
|
||||
target[key] = [];
|
||||
}
|
||||
const keyContexts = [];
|
||||
const keySources = pluck(sources, key, source => keyContexts.push(source[RESOLVE_CONTEXT]));
|
||||
let resolved = context.resolve(keySources, keyContexts, target[key], key, path);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
|
||||
const createProxy = (context, target, resolveContext, pathSegments = []) => {
|
||||
const handler = createHandler(context, resolveContext, pathSegments);
|
||||
const proxy = markRaw(new Proxy(target, handler));
|
||||
if (!pathSegments.length && context.sources) {
|
||||
context.sources.push(proxy);
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
get: (target, key, receiver) => {
|
||||
if (key === IS_PROXY) {
|
||||
return true;
|
||||
}
|
||||
if (key === PROXY_SOURCES) {
|
||||
return context.sources;
|
||||
}
|
||||
if (key === PROXY_TARGET) {
|
||||
return target;
|
||||
}
|
||||
if (key === RESOLVE_CONTEXT) {
|
||||
return resolveContext;
|
||||
}
|
||||
let value = Reflect.get(target, key, receiver);
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
target[key] = value;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: (target, key, value) => {
|
||||
const success = Reflect.set(target, key, value);
|
||||
// console.warn(success, 'PROXY SET\nkey:', key, '\npath:', pathSegments, '\ntarget:', isArray(target), target, '\ncontext:\n', context)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let hasArrayParent = false;
|
||||
let { sources: proxies, active } = context;
|
||||
let activeSegmentKey;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = pluck(proxies, segment);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
if (isArray(active)) {
|
||||
hasArrayParent = true;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
if (hasArrayParent) {
|
||||
// TODO: fix that we dont have to recompute the full merged object
|
||||
// we should only have to recompute the branch that has changed
|
||||
// but there is an issue here with supporting both arrays of strings
|
||||
// as collections (parent vs parent of parent we need to trigger the
|
||||
// update from)
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
// console.log('CONTEXT.ACTIVE', context.active, '\nparent:\n', target)
|
||||
return success;
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
let proxies = context.sources;
|
||||
let active = context.active;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete active[key];
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
});
|
||||
|
||||
const createMergedObject = (resolve, active = {}) => {
|
||||
const sources = [];
|
||||
if (!active) {
|
||||
active = {};
|
||||
}
|
||||
const context = {
|
||||
active,
|
||||
resolve,
|
||||
sources
|
||||
};
|
||||
const compute = () => recompute(context);
|
||||
const addSource = (source, resolveContext, recompute = false) => {
|
||||
const proxy = createProxy(context, source, resolveContext || {});
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const delSource = (sourceOrProxy, recompute = true) => {
|
||||
const index = sources.findIndex(src => src === sourceOrProxy || src[PROXY_TARGET] === sourceOrProxy);
|
||||
if (index > -1) {
|
||||
sources.splice(index, 1);
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return {
|
||||
context,
|
||||
active,
|
||||
resolve,
|
||||
sources,
|
||||
addSource,
|
||||
delSource,
|
||||
compute
|
||||
};
|
||||
};
|
||||
|
||||
function renderMeta(context, key, data, config) {
|
||||
// console.info('renderMeta', key, data, config)
|
||||
if (config.attributesFor) {
|
||||
return renderAttributes(context, key, data, config);
|
||||
}
|
||||
if (config.group) {
|
||||
return renderGroup(context, key, data, config);
|
||||
}
|
||||
return renderTag(context, key, data, config);
|
||||
}
|
||||
function renderGroup(context, key, data, config) {
|
||||
// console.info('renderGroup', key, data, config)
|
||||
if (isArray(data)) {
|
||||
if ((process.env.NODE_ENV !== 'production')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Specifying an array for group properties isnt supported mostly as we didnt found a use-case for this yet. If you have one, please create an issue on the vue-meta repo');
|
||||
}
|
||||
// config.attributes = getConfigKey([key, config.tag], 'attributes', config)
|
||||
return [];
|
||||
}
|
||||
return Object.keys(data)
|
||||
.map((childKey) => {
|
||||
const groupConfig = {
|
||||
group: key,
|
||||
data
|
||||
};
|
||||
if (config.namespaced) {
|
||||
groupConfig.tagNamespace = config.namespaced === true ? key : config.namespaced;
|
||||
}
|
||||
else if (config.namespacedAttribute) {
|
||||
const namespace = config.namespacedAttribute === true ? key : config.namespacedAttribute;
|
||||
groupConfig.fullName = `${namespace}:${childKey}`;
|
||||
groupConfig.slotName = `${namespace}(${childKey})`;
|
||||
}
|
||||
return renderTag(context, key, data[childKey], config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
// console.info('renderTag', key, data, config, groupConfig)
|
||||
const contentAttributes = ['content', 'json', 'rawContent'];
|
||||
const getConfig = (key) => getConfigByKey([tag, config.tag], key, config);
|
||||
if (isArray(data)) {
|
||||
return data
|
||||
.map((child) => {
|
||||
return renderTag(context, key, child, config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
const { tag = config.tag || key } = data;
|
||||
let content;
|
||||
let hasChilds = false;
|
||||
let isRaw = false;
|
||||
if (isString(data)) {
|
||||
content = data;
|
||||
}
|
||||
else if (data.children && isArray(data.children)) {
|
||||
hasChilds = true;
|
||||
content = data.children.map((child) => {
|
||||
const data = renderTag(context, key, child, config, groupConfig);
|
||||
if (isArray(data)) {
|
||||
return data.map(({ vnode }) => vnode);
|
||||
}
|
||||
return data.vnode;
|
||||
});
|
||||
}
|
||||
else {
|
||||
let i = 0;
|
||||
for (const contentAttribute of contentAttributes) {
|
||||
if (!content && data[contentAttribute]) {
|
||||
if (i === 1) {
|
||||
content = JSON.stringify(data[contentAttribute]);
|
||||
}
|
||||
else {
|
||||
content = data[contentAttribute];
|
||||
}
|
||||
isRaw = i > 1;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
const fullName = (groupConfig && groupConfig.fullName) || key;
|
||||
const slotName = (groupConfig && groupConfig.slotName) || key;
|
||||
let { attrs: attributes } = data;
|
||||
if (!attributes && typeof data === 'object') {
|
||||
attributes = { ...data };
|
||||
delete attributes.tag;
|
||||
delete attributes.children;
|
||||
delete attributes.to;
|
||||
// cleanup all content attributes
|
||||
for (const attr of contentAttributes) {
|
||||
delete attributes[attr];
|
||||
}
|
||||
}
|
||||
else if (!attributes) {
|
||||
attributes = {};
|
||||
}
|
||||
if (hasChilds) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
const contentAsAttribute = getConfig('contentAsAttribute');
|
||||
let valueAttribute = config.valueAttribute;
|
||||
if (!valueAttribute && contentAsAttribute) {
|
||||
const tagAttributes = getConfig('attributes');
|
||||
valueAttribute = isString(contentAsAttribute) ? contentAsAttribute : tagAttributes[0];
|
||||
}
|
||||
if (!valueAttribute) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
if (!config.nameless) {
|
||||
const keyAttribute = getConfig('keyAttribute');
|
||||
if (keyAttribute) {
|
||||
attributes[keyAttribute] = fullName;
|
||||
}
|
||||
}
|
||||
attributes[valueAttribute] = getSlotContent(context, slotName, attributes[valueAttribute] || content, groupConfig);
|
||||
content = undefined;
|
||||
}
|
||||
}
|
||||
const finalTag = groupConfig && groupConfig.tagNamespace
|
||||
? `${groupConfig.tagNamespace}:${tag}`
|
||||
: tag;
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
let vnode;
|
||||
if (isRaw) {
|
||||
attributes.innerHTML = content;
|
||||
vnode = h(finalTag, attributes);
|
||||
}
|
||||
else {
|
||||
vnode = h(finalTag, attributes, content);
|
||||
}
|
||||
return {
|
||||
to: data.to,
|
||||
vnode
|
||||
};
|
||||
}
|
||||
function renderAttributes(context, key, data, config = {}) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
{
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
to: '',
|
||||
vnode: h(`ssr-${attributesFor}`, data)
|
||||
};
|
||||
}
|
||||
}
|
||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||
if (!slots || !slots[slotName]) {
|
||||
return content;
|
||||
}
|
||||
const slotProps = {
|
||||
content,
|
||||
metainfo
|
||||
};
|
||||
if (groupConfig && groupConfig.group) {
|
||||
slotProps[groupConfig.group] = groupConfig.data;
|
||||
}
|
||||
const slotContent = slots[slotName](slotProps);
|
||||
if (slotContent && slotContent.length) {
|
||||
return slotContent[0].children;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
|
||||
const PolySymbol = (name) =>
|
||||
// vm = vue meta
|
||||
hasSymbol
|
||||
? Symbol((process.env.NODE_ENV !== 'production') ? '[vue-meta]: ' + name : name)
|
||||
: ((process.env.NODE_ENV !== 'production') ? '[vue-meta]: ' : '_vm_') + name;
|
||||
const metaInfoKey = PolySymbol((process.env.NODE_ENV !== 'production') ? 'metainfo' : 'mi');
|
||||
|
||||
function getCurrentManager(vm) {
|
||||
if (!vm) {
|
||||
vm = getCurrentInstance();
|
||||
}
|
||||
return vm.appContext.config.globalProperties.$metaManager;
|
||||
}
|
||||
function useMeta(obj, manager) {
|
||||
const vm = getCurrentInstance();
|
||||
if (!manager && vm) {
|
||||
manager = getCurrentManager(vm);
|
||||
}
|
||||
if (!manager) {
|
||||
// oopsydoopsy
|
||||
throw new Error('No manager or current instance');
|
||||
}
|
||||
return manager.addMeta(obj, vm || undefined);
|
||||
}
|
||||
function useMetainfo() {
|
||||
return inject(metaInfoKey);
|
||||
}
|
||||
|
||||
const MetainfoImpl = defineComponent({
|
||||
name: 'Metainfo',
|
||||
inheritAttrs: false,
|
||||
setup(_, { slots }) {
|
||||
return () => {
|
||||
const manager = getCurrentManager();
|
||||
if (!manager) {
|
||||
return;
|
||||
}
|
||||
return manager.render({ slots });
|
||||
};
|
||||
}
|
||||
});
|
||||
const Metainfo = MetainfoImpl;
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = reactive({});
|
||||
function addVnode(teleports, to, _vnodes) {
|
||||
const vnodes = (isArray(_vnodes) ? _vnodes : [_vnodes]);
|
||||
{
|
||||
// dont add ssrAttribute for attribute vnode placeholder
|
||||
if (!to.endsWith('Attrs')) {
|
||||
vnodes.forEach((vnode) => {
|
||||
if (!vnode.props) {
|
||||
vnode.props = {};
|
||||
}
|
||||
vnode.props[ssrAttribute] = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!teleports[to]) {
|
||||
teleports[to] = [];
|
||||
}
|
||||
teleports[to].push(...vnodes);
|
||||
}
|
||||
function createMetaManager(config, resolver) {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
}
|
||||
return resolver.resolve(options, contexts, active, key, pathSegments);
|
||||
};
|
||||
const { addSource, delSource } = createMergedObject(resolve, active);
|
||||
// TODO: validate resolver
|
||||
const manager = {
|
||||
config,
|
||||
install(app) {
|
||||
app.component('Metainfo', Metainfo);
|
||||
app.config.globalProperties.$metaManager = manager;
|
||||
app.provide(metaInfoKey, active);
|
||||
},
|
||||
addMeta(metaObj, vm) {
|
||||
const resolveContext = { vm };
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute
|
||||
const meta = addSource(metaObj, resolveContext, true);
|
||||
const unmount = () => delSource(meta);
|
||||
if (vm) {
|
||||
onUnmounted(unmount);
|
||||
}
|
||||
return {
|
||||
meta,
|
||||
unmount
|
||||
};
|
||||
},
|
||||
render({ slots } = {}) {
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
const vnode = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
if (!vnode) {
|
||||
continue;
|
||||
}
|
||||
const vnodes = isArray(vnode) ? vnode : [vnode];
|
||||
const defaultTo = (key !== 'base' && active[key].to) || config.to || (config.attributesFor ? key : 'head');
|
||||
for (const { to, vnode } of vnodes) {
|
||||
addVnode(teleports, to || defaultTo, vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
for (const tag in slots) {
|
||||
const slotFn = slots[tag];
|
||||
if (isFunction(slotFn)) {
|
||||
addVnode(teleports, tag === 'default' ? 'head' : tag, slotFn({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return h(Teleport, { to }, teleports[to]);
|
||||
});
|
||||
}
|
||||
};
|
||||
return manager;
|
||||
}
|
||||
|
||||
// rollup doesnt like an import, cant find export so use require
|
||||
const { renderToString } = require('@vue/server-renderer');
|
||||
async function renderToStringWithMeta(app) {
|
||||
const ctx = {};
|
||||
const html = await renderToString(app, ctx);
|
||||
// TODO: better way of determining whether meta was rendered with the component or not
|
||||
if (!ctx.teleports || !ctx.teleports.head) {
|
||||
const teleports = app.config.globalProperties.$metaManager.render();
|
||||
await Promise.all(teleports.map((teleport) => renderToString(teleport, ctx)));
|
||||
}
|
||||
const { teleports } = ctx;
|
||||
for (const target in teleports) {
|
||||
if (target.endsWith('Attrs')) {
|
||||
const str = teleports[target];
|
||||
// match from first space to first >, these should be all rendered attributes
|
||||
teleports[target] = str.slice(str.indexOf(' ') + 1, str.indexOf('>'));
|
||||
}
|
||||
}
|
||||
return [html, ctx];
|
||||
}
|
||||
|
||||
export { createMetaManager, deepest as deepestResolver, defaultConfig, getCurrentManager, renderToStringWithMeta, resolveOption, useMeta, useMetainfo };
|
||||
Vendored
+814
@@ -0,0 +1,814 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.0
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
var VueMeta = (function (exports, vue) {
|
||||
'use strict';
|
||||
|
||||
const resolveOption = predicament => (options, contexts) => {
|
||||
let resolvedIndex = -1;
|
||||
contexts.reduce((acc, context, index) => {
|
||||
const retval = predicament(acc, context);
|
||||
if (retval !== acc) {
|
||||
resolvedIndex = index;
|
||||
return retval;
|
||||
}
|
||||
return acc;
|
||||
}, undefined);
|
||||
if (resolvedIndex > -1) {
|
||||
return options[resolvedIndex];
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
do {
|
||||
if (vm.parent) {
|
||||
depth++;
|
||||
vm = vm.parent;
|
||||
}
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
const resolve = resolveOption((acc, context) => {
|
||||
const { depth } = context;
|
||||
if (!acc || depth > acc) {
|
||||
return acc;
|
||||
}
|
||||
});
|
||||
|
||||
var deepest = /*#__PURE__*/Object.freeze({
|
||||
__proto__: null,
|
||||
setup: setup,
|
||||
resolve: resolve
|
||||
});
|
||||
|
||||
const defaultConfig = {
|
||||
body: {
|
||||
tag: 'script',
|
||||
to: 'body'
|
||||
},
|
||||
base: {
|
||||
valueAttribute: 'href'
|
||||
},
|
||||
charset: {
|
||||
tag: 'meta',
|
||||
nameless: true,
|
||||
valueAttribute: 'charset'
|
||||
},
|
||||
description: {
|
||||
tag: 'meta'
|
||||
},
|
||||
og: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta',
|
||||
keyAttribute: 'property'
|
||||
},
|
||||
twitter: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta'
|
||||
},
|
||||
htmlAttrs: {
|
||||
attributesFor: 'html'
|
||||
},
|
||||
headAttrs: {
|
||||
attributesFor: 'head'
|
||||
},
|
||||
bodyAttrs: {
|
||||
attributesFor: 'body'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a map and return a function for checking if a key
|
||||
* is in that map.
|
||||
* IMPORTANT: all calls of this function must be prefixed with
|
||||
* \/\*#\_\_PURE\_\_\*\/
|
||||
* So that rollup can tree-shake them if necessary.
|
||||
*/
|
||||
(process.env.NODE_ENV !== 'production')
|
||||
? Object.freeze({})
|
||||
: {};
|
||||
(process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
|
||||
const isArray = Array.isArray;
|
||||
const isFunction = (val) => typeof val === 'function';
|
||||
const isString = (val) => typeof val === 'string';
|
||||
const isObject = (val) => val !== null && typeof val === 'object';
|
||||
const objectToString = Object.prototype.toString;
|
||||
const toTypeString = (value) => objectToString.call(value);
|
||||
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
|
||||
|
||||
/*
|
||||
* This is a fixed config for real HTML tags
|
||||
*
|
||||
* TODO: we probably dont need all attributes
|
||||
*/
|
||||
const tags = {
|
||||
title: {
|
||||
attributes: false
|
||||
},
|
||||
base: {
|
||||
contentAsAttribute: true,
|
||||
attributes: ['href', 'target']
|
||||
},
|
||||
meta: {
|
||||
contentAsAttribute: true,
|
||||
keyAttribute: 'name',
|
||||
attributes: ['content', 'name', 'http-equiv', 'charset']
|
||||
},
|
||||
link: {
|
||||
contentAsAttribute: true,
|
||||
attributes: [
|
||||
'href',
|
||||
'crossorigin',
|
||||
'rel',
|
||||
'media',
|
||||
'integrity',
|
||||
'hreflang',
|
||||
'type',
|
||||
'referrerpolicy',
|
||||
'sizes',
|
||||
'imagesrcset',
|
||||
'imagesizes',
|
||||
'as',
|
||||
'color'
|
||||
]
|
||||
},
|
||||
style: {
|
||||
attributes: ['media']
|
||||
},
|
||||
script: {
|
||||
attributes: [
|
||||
'src',
|
||||
'type',
|
||||
'nomodule',
|
||||
'async',
|
||||
'defer',
|
||||
'crossorigin',
|
||||
'integrity',
|
||||
'referrerpolicy'
|
||||
]
|
||||
},
|
||||
noscript: {
|
||||
attributes: false
|
||||
}
|
||||
};
|
||||
|
||||
function getConfigByKey(tagOrName, key, config) {
|
||||
if (config && key in config) {
|
||||
return config[key];
|
||||
}
|
||||
if (isArray(tagOrName)) {
|
||||
for (const name of tagOrName) {
|
||||
if (name && name in tags) {
|
||||
return tags[name][key];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (tagOrName in tags) {
|
||||
const tag = tags[tagOrName];
|
||||
return tag[key];
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/microsoft/TypeScript/issues/1863
|
||||
const IS_PROXY = Symbol('kIsProxy');
|
||||
const PROXY_SOURCES = Symbol('kProxySources');
|
||||
const PROXY_TARGET = Symbol('kProxyTarget');
|
||||
const RESOLVE_CONTEXT = Symbol('kResolveContext');
|
||||
|
||||
// See: https://github.com/vuejs/vue-next/blob/08b4e8815da4e8911058ccbab986bea6365c3352/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
|
||||
function clone(v) {
|
||||
if (isArray(v)) {
|
||||
return v.map(clone);
|
||||
}
|
||||
if (isObject(v)) {
|
||||
const res = {};
|
||||
for (const key in v) {
|
||||
// never clone the context
|
||||
if (key === 'context') {
|
||||
res[key] = v[key];
|
||||
}
|
||||
else {
|
||||
res[key] = clone(v[key]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
return plucked;
|
||||
};
|
||||
|
||||
const allKeys = (source, ...sources) => {
|
||||
const keys = source ? Object.keys(source) : [];
|
||||
if (sources) {
|
||||
for (const source of sources) {
|
||||
if (!source || !isObject(source)) {
|
||||
continue;
|
||||
}
|
||||
for (const key in source) {
|
||||
if (!keys.includes(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
return;
|
||||
}
|
||||
const keys = allKeys(...sources);
|
||||
// Clean up properties that dont exists anymore
|
||||
const targetKeys = Object.keys(target);
|
||||
for (const key of targetKeys) {
|
||||
if (!keys.includes(key)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
const keySources = [];
|
||||
for (const source of sources) {
|
||||
if (key in source) {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
if (!target[key] && isArray(sources[0][key])) {
|
||||
target[key] = [];
|
||||
}
|
||||
const keyContexts = [];
|
||||
const keySources = pluck(sources, key, source => keyContexts.push(source[RESOLVE_CONTEXT]));
|
||||
let resolved = context.resolve(keySources, keyContexts, target[key], key, path);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
|
||||
const createProxy = (context, target, resolveContext, pathSegments = []) => {
|
||||
const handler = createHandler(context, resolveContext, pathSegments);
|
||||
const proxy = vue.markRaw(new Proxy(target, handler));
|
||||
if (!pathSegments.length && context.sources) {
|
||||
context.sources.push(proxy);
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
get: (target, key, receiver) => {
|
||||
if (key === IS_PROXY) {
|
||||
return true;
|
||||
}
|
||||
if (key === PROXY_SOURCES) {
|
||||
return context.sources;
|
||||
}
|
||||
if (key === PROXY_TARGET) {
|
||||
return target;
|
||||
}
|
||||
if (key === RESOLVE_CONTEXT) {
|
||||
return resolveContext;
|
||||
}
|
||||
let value = Reflect.get(target, key, receiver);
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
target[key] = value;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: (target, key, value) => {
|
||||
const success = Reflect.set(target, key, value);
|
||||
// console.warn(success, 'PROXY SET\nkey:', key, '\npath:', pathSegments, '\ntarget:', isArray(target), target, '\ncontext:\n', context)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let hasArrayParent = false;
|
||||
let { sources: proxies, active } = context;
|
||||
let activeSegmentKey;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = pluck(proxies, segment);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
if (isArray(active)) {
|
||||
hasArrayParent = true;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
if (hasArrayParent) {
|
||||
// TODO: fix that we dont have to recompute the full merged object
|
||||
// we should only have to recompute the branch that has changed
|
||||
// but there is an issue here with supporting both arrays of strings
|
||||
// as collections (parent vs parent of parent we need to trigger the
|
||||
// update from)
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
// console.log('CONTEXT.ACTIVE', context.active, '\nparent:\n', target)
|
||||
return success;
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
let proxies = context.sources;
|
||||
let active = context.active;
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
}
|
||||
active = active[segment];
|
||||
index++;
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
keySources = proxies;
|
||||
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT]);
|
||||
}
|
||||
else {
|
||||
keySources = pluck(proxies, key, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]));
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
else {
|
||||
active[key] = resolved;
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete active[key];
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
});
|
||||
|
||||
const createMergedObject = (resolve, active = {}) => {
|
||||
const sources = [];
|
||||
if (!active) {
|
||||
active = {};
|
||||
}
|
||||
const context = {
|
||||
active,
|
||||
resolve,
|
||||
sources
|
||||
};
|
||||
const compute = () => recompute(context);
|
||||
const addSource = (source, resolveContext, recompute = false) => {
|
||||
const proxy = createProxy(context, source, resolveContext || {});
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
const delSource = (sourceOrProxy, recompute = true) => {
|
||||
const index = sources.findIndex(src => src === sourceOrProxy || src[PROXY_TARGET] === sourceOrProxy);
|
||||
if (index > -1) {
|
||||
sources.splice(index, 1);
|
||||
if (recompute) {
|
||||
compute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return {
|
||||
context,
|
||||
active,
|
||||
resolve,
|
||||
sources,
|
||||
addSource,
|
||||
delSource,
|
||||
compute
|
||||
};
|
||||
};
|
||||
|
||||
const cachedElements = {};
|
||||
function renderMeta(context, key, data, config) {
|
||||
// console.info('renderMeta', key, data, config)
|
||||
if (config.attributesFor) {
|
||||
return renderAttributes(context, key, data, config);
|
||||
}
|
||||
if (config.group) {
|
||||
return renderGroup(context, key, data, config);
|
||||
}
|
||||
return renderTag(context, key, data, config);
|
||||
}
|
||||
function renderGroup(context, key, data, config) {
|
||||
// console.info('renderGroup', key, data, config)
|
||||
if (isArray(data)) {
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Specifying an array for group properties isnt supported mostly as we didnt found a use-case for this yet. If you have one, please create an issue on the vue-meta repo');
|
||||
}
|
||||
// config.attributes = getConfigKey([key, config.tag], 'attributes', config)
|
||||
return [];
|
||||
}
|
||||
return Object.keys(data)
|
||||
.map((childKey) => {
|
||||
const groupConfig = {
|
||||
group: key,
|
||||
data
|
||||
};
|
||||
if (config.namespaced) {
|
||||
groupConfig.tagNamespace = config.namespaced === true ? key : config.namespaced;
|
||||
}
|
||||
else if (config.namespacedAttribute) {
|
||||
const namespace = config.namespacedAttribute === true ? key : config.namespacedAttribute;
|
||||
groupConfig.fullName = `${namespace}:${childKey}`;
|
||||
groupConfig.slotName = `${namespace}(${childKey})`;
|
||||
}
|
||||
return renderTag(context, key, data[childKey], config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
// console.info('renderTag', key, data, config, groupConfig)
|
||||
const contentAttributes = ['content', 'json', 'rawContent'];
|
||||
const getConfig = (key) => getConfigByKey([tag, config.tag], key, config);
|
||||
if (isArray(data)) {
|
||||
return data
|
||||
.map((child) => {
|
||||
return renderTag(context, key, child, config, groupConfig);
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
const { tag = config.tag || key } = data;
|
||||
let content;
|
||||
let hasChilds = false;
|
||||
let isRaw = false;
|
||||
if (isString(data)) {
|
||||
content = data;
|
||||
}
|
||||
else if (data.children && isArray(data.children)) {
|
||||
hasChilds = true;
|
||||
content = data.children.map((child) => {
|
||||
const data = renderTag(context, key, child, config, groupConfig);
|
||||
if (isArray(data)) {
|
||||
return data.map(({ vnode }) => vnode);
|
||||
}
|
||||
return data.vnode;
|
||||
});
|
||||
}
|
||||
else {
|
||||
let i = 0;
|
||||
for (const contentAttribute of contentAttributes) {
|
||||
if (!content && data[contentAttribute]) {
|
||||
if (i === 1) {
|
||||
content = JSON.stringify(data[contentAttribute]);
|
||||
}
|
||||
else {
|
||||
content = data[contentAttribute];
|
||||
}
|
||||
isRaw = i > 1;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
const fullName = (groupConfig && groupConfig.fullName) || key;
|
||||
const slotName = (groupConfig && groupConfig.slotName) || key;
|
||||
let { attrs: attributes } = data;
|
||||
if (!attributes && typeof data === 'object') {
|
||||
attributes = { ...data };
|
||||
delete attributes.tag;
|
||||
delete attributes.children;
|
||||
delete attributes.to;
|
||||
// cleanup all content attributes
|
||||
for (const attr of contentAttributes) {
|
||||
delete attributes[attr];
|
||||
}
|
||||
}
|
||||
else if (!attributes) {
|
||||
attributes = {};
|
||||
}
|
||||
if (hasChilds) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
const contentAsAttribute = getConfig('contentAsAttribute');
|
||||
let valueAttribute = config.valueAttribute;
|
||||
if (!valueAttribute && contentAsAttribute) {
|
||||
const tagAttributes = getConfig('attributes');
|
||||
valueAttribute = isString(contentAsAttribute) ? contentAsAttribute : tagAttributes[0];
|
||||
}
|
||||
if (!valueAttribute) {
|
||||
content = getSlotContent(context, slotName, content, data);
|
||||
}
|
||||
else {
|
||||
if (!config.nameless) {
|
||||
const keyAttribute = getConfig('keyAttribute');
|
||||
if (keyAttribute) {
|
||||
attributes[keyAttribute] = fullName;
|
||||
}
|
||||
}
|
||||
attributes[valueAttribute] = getSlotContent(context, slotName, attributes[valueAttribute] || content, groupConfig);
|
||||
content = undefined;
|
||||
}
|
||||
}
|
||||
const finalTag = groupConfig && groupConfig.tagNamespace
|
||||
? `${groupConfig.tagNamespace}:${tag}`
|
||||
: tag;
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
let vnode;
|
||||
if (isRaw) {
|
||||
attributes.innerHTML = content;
|
||||
vnode = vue.h(finalTag, attributes);
|
||||
}
|
||||
else {
|
||||
vnode = vue.h(finalTag, attributes, content);
|
||||
}
|
||||
return {
|
||||
to: data.to,
|
||||
vnode
|
||||
};
|
||||
}
|
||||
function renderAttributes(context, key, data, config = {}) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
if (!cachedElements[attributesFor]) {
|
||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
||||
if ( !el) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Could not find element with selector', attributesFor, ', won\'t render attributes');
|
||||
return;
|
||||
}
|
||||
if ( el2) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Found multiple elements with selector', attributesFor);
|
||||
}
|
||||
cachedElements[attributesFor] = {
|
||||
el,
|
||||
attrs: []
|
||||
};
|
||||
}
|
||||
const { el, attrs } = cachedElements[attributesFor];
|
||||
for (const attr in data) {
|
||||
const content = getSlotContent(context, `${key}(${attr})`, data[attr], data);
|
||||
el.setAttribute(attr, `${content || ''}`);
|
||||
if (!attrs.includes(attr)) {
|
||||
attrs.push(attr);
|
||||
}
|
||||
}
|
||||
const attrsToRemove = attrs.filter(attr => !data[attr]);
|
||||
for (const attr of attrsToRemove) {
|
||||
el.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||
if (!slots || !slots[slotName]) {
|
||||
return content;
|
||||
}
|
||||
const slotProps = {
|
||||
content,
|
||||
metainfo
|
||||
};
|
||||
if (groupConfig && groupConfig.group) {
|
||||
slotProps[groupConfig.group] = groupConfig.data;
|
||||
}
|
||||
const slotContent = slots[slotName](slotProps);
|
||||
if (slotContent && slotContent.length) {
|
||||
return slotContent[0].children;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
|
||||
const PolySymbol = (name) =>
|
||||
// vm = vue meta
|
||||
hasSymbol
|
||||
? Symbol( '[vue-meta]: ' + name )
|
||||
: ( '[vue-meta]: ' ) + name;
|
||||
const metaInfoKey = PolySymbol( 'metainfo' );
|
||||
|
||||
function getCurrentManager(vm) {
|
||||
if (!vm) {
|
||||
vm = vue.getCurrentInstance();
|
||||
}
|
||||
return vm.appContext.config.globalProperties.$metaManager;
|
||||
}
|
||||
function useMeta(obj, manager) {
|
||||
const vm = vue.getCurrentInstance();
|
||||
if (!manager && vm) {
|
||||
manager = getCurrentManager(vm);
|
||||
}
|
||||
if (!manager) {
|
||||
// oopsydoopsy
|
||||
throw new Error('No manager or current instance');
|
||||
}
|
||||
return manager.addMeta(obj, vm || undefined);
|
||||
}
|
||||
function useMetainfo() {
|
||||
return vue.inject(metaInfoKey);
|
||||
}
|
||||
|
||||
const MetainfoImpl = vue.defineComponent({
|
||||
name: 'Metainfo',
|
||||
inheritAttrs: false,
|
||||
setup(_, { slots }) {
|
||||
return () => {
|
||||
const manager = getCurrentManager();
|
||||
if (!manager) {
|
||||
return;
|
||||
}
|
||||
return manager.render({ slots });
|
||||
};
|
||||
}
|
||||
});
|
||||
const Metainfo = MetainfoImpl;
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = vue.reactive({});
|
||||
function addVnode(teleports, to, _vnodes) {
|
||||
const vnodes = (isArray(_vnodes) ? _vnodes : [_vnodes]);
|
||||
{
|
||||
// Comments shouldnt have any use on the client as they are not reactive anyway
|
||||
vnodes.forEach((vnode, idx) => {
|
||||
if (vnode.type === vue.Comment) {
|
||||
vnodes.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!teleports[to]) {
|
||||
teleports[to] = [];
|
||||
}
|
||||
teleports[to].push(...vnodes);
|
||||
}
|
||||
function createMetaManager(config, resolver) {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
}
|
||||
return resolver.resolve(options, contexts, active, key, pathSegments);
|
||||
};
|
||||
const { addSource, delSource } = createMergedObject(resolve, active);
|
||||
let cleanedUpSsr = false;
|
||||
// TODO: validate resolver
|
||||
const manager = {
|
||||
config,
|
||||
install(app) {
|
||||
app.component('Metainfo', Metainfo);
|
||||
app.config.globalProperties.$metaManager = manager;
|
||||
app.provide(metaInfoKey, active);
|
||||
},
|
||||
addMeta(metaObj, vm) {
|
||||
const resolveContext = { vm };
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute
|
||||
const meta = addSource(metaObj, resolveContext, true);
|
||||
const unmount = () => delSource(meta);
|
||||
if (vm) {
|
||||
vue.onUnmounted(unmount);
|
||||
}
|
||||
return {
|
||||
meta,
|
||||
unmount
|
||||
};
|
||||
},
|
||||
render({ slots } = {}) {
|
||||
// cleanup ssr tags if not yet done
|
||||
if ( !cleanedUpSsr) {
|
||||
cleanedUpSsr = true;
|
||||
// Listen for DOM loaded because tags in the body couldnt be loaded
|
||||
// yet once the manager does it first render
|
||||
// (preferable there should only be one render on hydration)
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`);
|
||||
if (ssrTags && ssrTags.length) {
|
||||
Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el));
|
||||
}
|
||||
});
|
||||
}
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
const vnode = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
if (!vnode) {
|
||||
continue;
|
||||
}
|
||||
const vnodes = isArray(vnode) ? vnode : [vnode];
|
||||
const defaultTo = (key !== 'base' && active[key].to) || config.to || (config.attributesFor ? key : 'head');
|
||||
for (const { to, vnode } of vnodes) {
|
||||
addVnode(teleports, to || defaultTo, vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
for (const tag in slots) {
|
||||
const slotFn = slots[tag];
|
||||
if (isFunction(slotFn)) {
|
||||
addVnode(teleports, tag === 'default' ? 'head' : tag, slotFn({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return vue.h(vue.Teleport, { to }, teleports[to]);
|
||||
});
|
||||
}
|
||||
};
|
||||
return manager;
|
||||
}
|
||||
|
||||
exports.createMetaManager = createMetaManager;
|
||||
exports.deepestResolver = deepest;
|
||||
exports.defaultConfig = defaultConfig;
|
||||
exports.getCurrentManager = getCurrentManager;
|
||||
exports.resolveOption = resolveOption;
|
||||
exports.useMeta = useMeta;
|
||||
exports.useMetainfo = useMetainfo;
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, Vue));
|
||||
Vendored
+8
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-meta",
|
||||
"version": "2.3.3",
|
||||
"version": "3.0.0-alpha.0",
|
||||
"description": "Manage HTML metadata in Vue.js components with SSR support",
|
||||
"main": "dist/vue-meta.cjs.js",
|
||||
"unpkg": "dist/vue-meta.global.js",
|
||||
|
||||
Reference in New Issue
Block a user