mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-18 12:00:34 +03:00
feat: continued progress
This commit is contained in:
@@ -31,6 +31,9 @@ export const MetainfoImpl = defineComponent({
|
||||
const teleports: any = {}
|
||||
|
||||
const manager = getCurrentManager()
|
||||
if (!manager) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const key in metainfo) {
|
||||
const config = manager.config[key] || {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './globals'
|
||||
export * from './proxy'
|
||||
export * from './remove'
|
||||
export * from './set'
|
||||
export * from './update'
|
||||
@@ -1,13 +1,10 @@
|
||||
import { markRaw } from 'vue'
|
||||
import { isObject } from '@vue/shared'
|
||||
import { update } from './info/update'
|
||||
import { MetaContext, MetainfoInput, PathSegments } from './types'
|
||||
import { MetaContext, MetainfoInput, MetainfoProxy, PathSegments } from '../types'
|
||||
import { update } from './update'
|
||||
import { remove } from './remove'
|
||||
|
||||
interface Target extends MetainfoInput {
|
||||
__vm_proxy?: any // eslint-disable-line camelcase
|
||||
}
|
||||
|
||||
export function createProxy (target: Target, handler: ProxyHandler<object>): Target {
|
||||
export function createProxy (target: MetainfoInput, handler: ProxyHandler<object>): MetainfoProxy {
|
||||
return markRaw(new Proxy(target, handler))
|
||||
}
|
||||
|
||||
@@ -16,7 +13,7 @@ export function createHandler (context: MetaContext, pathSegments: PathSegments
|
||||
get (target: object, key: string, receiver: object) {
|
||||
const value = Reflect.get(target, key, receiver)
|
||||
|
||||
if (!isObject(value)) {
|
||||
if (!isObject(value) || key === '__vm_proxy') {
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -30,11 +27,20 @@ export function createHandler (context: MetaContext, pathSegments: PathSegments
|
||||
return value.__vm_proxy
|
||||
},
|
||||
set (
|
||||
target: object, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
target: { [key: string]: any }, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
key: string,
|
||||
value: unknown
|
||||
value: any
|
||||
): boolean {
|
||||
update(context, pathSegments, key, value)
|
||||
// target[key] = value
|
||||
return true
|
||||
},
|
||||
deleteProperty (
|
||||
target: { [key: string]: any },
|
||||
prop: string
|
||||
) {
|
||||
remove(context, pathSegments, prop)
|
||||
delete target[prop]
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MetaContext, PathSegments } from '../types'
|
||||
import { setByObject } from './set'
|
||||
import { update } from './update'
|
||||
|
||||
export function remove (context: MetaContext, pathSegments?: PathSegments, key?: string) {
|
||||
if (!key && (!pathSegments || !pathSegments.length)) {
|
||||
setByObject(context, {})
|
||||
return
|
||||
}
|
||||
|
||||
update(context, pathSegments || [], key || '', undefined)
|
||||
}
|
||||
@@ -11,18 +11,20 @@ export function resolveActive (
|
||||
) {
|
||||
let value
|
||||
|
||||
if (shadowParent[key].length > 1) {
|
||||
const shadowLength = shadowParent[key] ? shadowParent[key].length : 0
|
||||
|
||||
if (shadowLength > 1) {
|
||||
// Is using freeze useful? Idea is to prevent the user from messing with these options by mistake
|
||||
const getShadow: GetShadowNodes = () => Object.freeze(clone(shadowParent[key]))
|
||||
const getActive: GetActiveNode = () => Object.freeze(clone(activeParent[key]))
|
||||
|
||||
value = context.manager.resolver.resolve(
|
||||
value = context.resolve(
|
||||
key,
|
||||
pathSegments,
|
||||
getShadow,
|
||||
getActive
|
||||
)
|
||||
} else if (shadowParent[key].length) {
|
||||
} else if (shadowLength) {
|
||||
value = shadowParent[key][0].value
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { isArray, isPlainObject, /**/ hasOwn } from '@vue/shared'
|
||||
import { ActiveNode, MetaContext, PathSegments, ShadowNode } from '../types'
|
||||
import { resolveActive } from './resolve'
|
||||
|
||||
export function set (
|
||||
context: MetaContext,
|
||||
key: string,
|
||||
value: any,
|
||||
shadowParent?: ShadowNode,
|
||||
activeParent?: ActiveNode,
|
||||
pathSegments: PathSegments = []
|
||||
) {
|
||||
if (!shadowParent) {
|
||||
shadowParent = context.shadow
|
||||
}
|
||||
|
||||
if (!activeParent) {
|
||||
activeParent = context.active
|
||||
}
|
||||
|
||||
if (isPlainObject(value)) {
|
||||
// shadow & active should always be in sync
|
||||
// if not we have bigger fish to fry
|
||||
if (!shadowParent[key]) {
|
||||
shadowParent[key] = {}
|
||||
activeParent[key] = {}
|
||||
}
|
||||
|
||||
return setByObject(
|
||||
context,
|
||||
value,
|
||||
shadowParent[key],
|
||||
activeParent[key],
|
||||
pathSegments
|
||||
)
|
||||
} else if (isArray(value)) {
|
||||
|
||||
}
|
||||
|
||||
let idx = -1
|
||||
|
||||
// Step 1: First find the current data for this key
|
||||
|
||||
// If the activeParent is an array itself, then we are setting the key
|
||||
// for an array, so no need to use [key] on the active/shadow parents
|
||||
// ie this is to support proxy.myArrayValue[1] = 'value' (instead of proxy.myArrayValue = ['value'])
|
||||
if (isArray(activeParent)) {
|
||||
idx = shadowParent.findIndex(({ context: shadowContext }: { context: MetaContext }) => shadowContext === context)
|
||||
// Check if we already have values from other components
|
||||
} else if (!shadowParent[key]) {
|
||||
shadowParent[key] = []
|
||||
// If we already have values listed, try to find the one for the current context
|
||||
} else if (isArray(shadowParent[key])) {
|
||||
// check if we already have a value listed for this element for this context
|
||||
idx = shadowParent[key].findIndex(({ context: shadowContext }: { context: MetaContext }) => shadowContext === context)
|
||||
}
|
||||
|
||||
// Step 2: Set/update the data for this key
|
||||
|
||||
if (isArray(activeParent) && idx < 0) {
|
||||
// TODO: what now?
|
||||
console.warn('shadowParent not found')
|
||||
|
||||
// Change the array/key element in the shadowParent
|
||||
} else if (isArray(activeParent)) {
|
||||
if (value === undefined) {
|
||||
shadowParent[idx].value.splice(key, 1)
|
||||
} else {
|
||||
shadowParent[idx].value[key] = value
|
||||
}
|
||||
// if this context/key combo exists but value is undefined, remove it
|
||||
} else if (idx > -1 && value === undefined) {
|
||||
shadowParent[key].splice(idx, 1)
|
||||
// overwrite current value for context/key combo
|
||||
} else if (idx > -1) {
|
||||
shadowParent[key][idx].value = value
|
||||
|
||||
// new context/key combo so just add value
|
||||
} else if (idx === -1 && value) {
|
||||
shadowParent[key].push({ context, value })
|
||||
} else if (value === undefined) {
|
||||
delete shadowParent[key]
|
||||
}
|
||||
|
||||
// Step 3: Update the active data
|
||||
|
||||
resolveActive(context, key, pathSegments, shadowParent, activeParent)
|
||||
}
|
||||
|
||||
export function setByObject (
|
||||
context: MetaContext,
|
||||
value: any,
|
||||
shadowParent? : ShadowNode,
|
||||
activeParent?: ActiveNode,
|
||||
pathSegments: PathSegments = []
|
||||
) {
|
||||
if (!shadowParent) {
|
||||
shadowParent = context.shadow
|
||||
}
|
||||
|
||||
if (!activeParent) {
|
||||
activeParent = context.active
|
||||
}
|
||||
|
||||
// cleanup properties that no longer exists
|
||||
for (const key in shadowParent) {
|
||||
if (hasOwn(value, key)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (isPlainObject(shadowParent[key])) {
|
||||
setByObject(context, {}, shadowParent[key], activeParent[key], [
|
||||
...pathSegments,
|
||||
key
|
||||
])
|
||||
continue
|
||||
} /**/
|
||||
|
||||
set(context, key, undefined, shadowParent, activeParent, [
|
||||
...pathSegments,
|
||||
key
|
||||
])
|
||||
}
|
||||
|
||||
// set new values
|
||||
for (const key in value) {
|
||||
set(context, key, value[key], shadowParent, activeParent, [
|
||||
...pathSegments,
|
||||
key
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MetaContext, PathSegments, ShadowNode, ActiveNode } from '../types'
|
||||
import { shadow, active } from './globals'
|
||||
import { set } from './set'
|
||||
|
||||
export function update (
|
||||
@@ -8,12 +7,14 @@ export function update (
|
||||
key: string,
|
||||
value: any
|
||||
) {
|
||||
let shadowParent: ShadowNode = shadow
|
||||
const { active, shadow } = context
|
||||
|
||||
let activeParent: ActiveNode = active
|
||||
let shadowParent: ShadowNode = shadow
|
||||
|
||||
for (const segment of pathSegments) {
|
||||
shadowParent = shadowParent[segment]
|
||||
activeParent = activeParent[segment]
|
||||
shadowParent = shadowParent[segment]
|
||||
}
|
||||
|
||||
set(context, key, value, shadowParent, activeParent)
|
||||
@@ -1,4 +0,0 @@
|
||||
import { markRaw, reactive } from 'vue'
|
||||
|
||||
export const shadow = markRaw({})
|
||||
export const active = reactive({})
|
||||
@@ -1,6 +0,0 @@
|
||||
import { MetaContext } from '../types'
|
||||
import { setByObject } from './set'
|
||||
|
||||
export function remove (context: MetaContext) {
|
||||
setByObject(context, {})
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { isPlainObject, /**/ hasOwn } from '@vue/shared'
|
||||
import { ActiveNode, MetaContext, PathSegments, ShadowNode } from '../types'
|
||||
import { shadow, active } from './globals'
|
||||
import { resolveActive } from './resolve'
|
||||
|
||||
export function set (
|
||||
context: MetaContext,
|
||||
key: string,
|
||||
value: any,
|
||||
shadowParent: ShadowNode = shadow,
|
||||
activeParent: ActiveNode = active,
|
||||
pathSegments: PathSegments = []
|
||||
) {
|
||||
if (isPlainObject(value)) {
|
||||
// shadow & active should always be in sync
|
||||
// if not we have bigger fish to fry
|
||||
if (!shadowParent[key]) {
|
||||
shadowParent[key] = []
|
||||
activeParent[key] = {}
|
||||
}
|
||||
|
||||
return setByObject(
|
||||
context,
|
||||
value,
|
||||
shadowParent[key],
|
||||
activeParent[key],
|
||||
pathSegments
|
||||
)
|
||||
}/**/
|
||||
|
||||
let idx = -1
|
||||
if (!shadowParent[key]) {
|
||||
shadowParent[key] = []
|
||||
} else {
|
||||
// check if we already have a value listed for this element for this context
|
||||
idx = shadowParent[key].findIndex(
|
||||
({ context: shadowContext }: { context: MetaContext }) => shadowContext === context
|
||||
)
|
||||
}
|
||||
|
||||
// if this context/key combo exists but value is undefined, remove it
|
||||
if (idx > -1 && value === undefined) {
|
||||
shadowParent[key].splice(idx, 1)
|
||||
|
||||
// overwrite current value for context/key combo
|
||||
} else if (idx > -1) {
|
||||
shadowParent[key][idx].value = value
|
||||
|
||||
// new context/key combo so just add value
|
||||
} else if (value) {
|
||||
shadowParent[key].push({ context, value })
|
||||
}
|
||||
|
||||
resolveActive(context, key, pathSegments, shadowParent, activeParent)
|
||||
}
|
||||
|
||||
export function setByObject (
|
||||
context: MetaContext,
|
||||
value: any,
|
||||
shadowParent: ShadowNode = shadow,
|
||||
activeParent: ActiveNode = active,
|
||||
pathSegments: PathSegments = []
|
||||
) {
|
||||
// cleanup properties that no longer exists
|
||||
for (const key in shadowParent) {
|
||||
if (hasOwn(value, key)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (isPlainObject(shadowParent[key])) {
|
||||
console.log('HERERER')
|
||||
setByObject(context, {}, shadowParent[key], activeParent[key], [
|
||||
...pathSegments,
|
||||
key
|
||||
])
|
||||
continue
|
||||
} /**/
|
||||
|
||||
set(context, key, undefined, shadowParent, activeParent, [
|
||||
...pathSegments,
|
||||
key
|
||||
])
|
||||
}
|
||||
|
||||
// set new values
|
||||
for (const key in value) {
|
||||
set(context, key, value[key], shadowParent, activeParent, [
|
||||
...pathSegments,
|
||||
key
|
||||
])
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
import { App } from 'vue'
|
||||
import { Metainfo } from './Metainfo'
|
||||
import { metaInfoKey } from './symbols'
|
||||
import { active } from './info/globals'
|
||||
import { Manager } from './manager'
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
@@ -10,7 +9,7 @@ declare module '@vue/runtime-core' {
|
||||
}
|
||||
}
|
||||
|
||||
export function applyMetaPlugin (app: App, manager: Manager) {
|
||||
export function applyMetaPlugin (app: App, manager: Manager, active: Object) {
|
||||
app.component('Metainfo', Metainfo)
|
||||
|
||||
app.config.globalProperties.$metaManager = manager
|
||||
|
||||
+54
-25
@@ -1,42 +1,71 @@
|
||||
import { App } from 'vue'
|
||||
import { App, markRaw, reactive, onUnmounted, ComponentInternalInstance } from 'vue'
|
||||
import { isFunction } from '@vue/shared'
|
||||
import { createProxy, createHandler, setByObject, remove } from './continuous-object-merge'
|
||||
import { PolySymbol } from './symbols'
|
||||
import { applyMetaPlugin } from './install'
|
||||
// import * as deepestResolver from './resolvers/deepest'
|
||||
import { Config, ManagerResolverObject, GetActiveNode, ActiveResolverObject, MetaContext, PathSegments, GetShadowNodes } from './types'
|
||||
import { Config, ActiveResolverObject, ActiveResolverMethod, MetaContext, MetainfoInput, MetaProxy } from './types'
|
||||
|
||||
let contextCounter: number = 0
|
||||
|
||||
export type Manager = {
|
||||
readonly config: Config
|
||||
|
||||
resolver: ManagerResolverObject
|
||||
install(app: App): void
|
||||
createMetaProxy(obj: MetainfoInput, vm?: ComponentInternalInstance): MetaProxy
|
||||
}
|
||||
|
||||
export const shadow = markRaw({})
|
||||
export const active = reactive({})
|
||||
|
||||
export function createManager (config: Config, resolver: ActiveResolverObject): Manager {
|
||||
const resolve: ActiveResolverMethod = (key, pathSegments, getShadow, getActive) => {
|
||||
if (!resolver) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(key, pathSegments, getShadow, getActive)
|
||||
}
|
||||
|
||||
return resolver.resolve(key, pathSegments, getShadow, getActive)
|
||||
}
|
||||
|
||||
// TODO: validate resolver
|
||||
const manager: Manager = {
|
||||
resolver: {
|
||||
setup (context: MetaContext) {
|
||||
if (!resolver || !resolver.setup || isFunction(resolver)) {
|
||||
return
|
||||
}
|
||||
|
||||
resolver.setup(context)
|
||||
},
|
||||
resolve (key: string, pathSegments: PathSegments, getShadow: GetShadowNodes, getActive: GetActiveNode) {
|
||||
if (!resolver) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(key, pathSegments, getShadow, getActive)
|
||||
}
|
||||
|
||||
return resolver.resolve(key, pathSegments, getShadow, getActive)
|
||||
}
|
||||
},
|
||||
config,
|
||||
install (app: App) {
|
||||
applyMetaPlugin(app, this)
|
||||
|
||||
install (app) {
|
||||
applyMetaPlugin(app, this, active)
|
||||
},
|
||||
|
||||
createMetaProxy (obj, vm) {
|
||||
const context: MetaContext = {
|
||||
id: PolySymbol(`ctx${contextCounter++}`),
|
||||
vm: vm || undefined,
|
||||
resolve,
|
||||
shadow,
|
||||
active
|
||||
}
|
||||
|
||||
const unmount = <T extends Function = () => any>() => remove(context)
|
||||
if (vm) {
|
||||
onUnmounted(unmount)
|
||||
}
|
||||
|
||||
if (resolver && resolver.setup && isFunction(resolver)) {
|
||||
resolver.setup(context)
|
||||
}
|
||||
|
||||
setByObject(context, obj)
|
||||
|
||||
const handler = /* #__PURE__ */ createHandler(context)
|
||||
const meta = createProxy(obj, handler)
|
||||
|
||||
return {
|
||||
meta,
|
||||
unmount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ export function renderGroup (
|
||||
if (isArray(data)) {
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Specifying an array for group properties isnt supported as we didnt found a use-case for this yet. If you have one, please create an issue on the vue-meta repo')
|
||||
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 []
|
||||
|
||||
@@ -18,9 +18,12 @@ export function setup (context: DeepestResolverMetaContext): void {
|
||||
let { vm } = context
|
||||
|
||||
do {
|
||||
depth++
|
||||
vm = vm.parent
|
||||
} while (vm && vm !== vm.root)
|
||||
if (vm.parent) {
|
||||
depth++
|
||||
|
||||
vm = vm.parent
|
||||
}
|
||||
} while (vm && vm.parent && vm !== vm.root)
|
||||
}
|
||||
|
||||
context.depth = depth
|
||||
@@ -50,7 +53,6 @@ export function resolve (
|
||||
)
|
||||
|
||||
if (resolvedOption) {
|
||||
console.log(resolvedOption.value)
|
||||
return resolvedOption.value
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -1,5 +1,4 @@
|
||||
import { ComponentInternalInstance } from 'vue'
|
||||
import { Manager } from '../manager'
|
||||
|
||||
export type Immutable<T> = {
|
||||
readonly [P in keyof T]: Immutable<T[P]>
|
||||
@@ -28,6 +27,11 @@ export interface MetainfoInput {
|
||||
[key: string]: TODO
|
||||
}
|
||||
|
||||
export interface MetainfoProxy extends MetainfoInput {
|
||||
// Should be a symbol, but: https://github.com/microsoft/TypeScript/issues/1863
|
||||
__vm_proxy?: any // eslint-disable-line camelcase
|
||||
}
|
||||
|
||||
export interface MetainfoActive {
|
||||
[key: string]: TODO
|
||||
}
|
||||
@@ -35,7 +39,14 @@ export interface MetainfoActive {
|
||||
export type MetaContext = {
|
||||
id: string | symbol
|
||||
vm?: ComponentInternalInstance
|
||||
manager: Manager
|
||||
resolve: ActiveResolverMethod
|
||||
active: Object
|
||||
shadow: Object
|
||||
}
|
||||
|
||||
export type MetaProxy = {
|
||||
meta: MetainfoProxy
|
||||
unmount: TODO
|
||||
}
|
||||
|
||||
export type ActiveResolverSetup = (context: MetaContext) => void
|
||||
|
||||
+13
-34
@@ -1,16 +1,13 @@
|
||||
import { inject, getCurrentInstance, onUnmounted } from 'vue'
|
||||
import { setByObject, remove } from './info'
|
||||
import { inject, getCurrentInstance, ComponentInternalInstance } from 'vue'
|
||||
import { Manager } from './manager'
|
||||
import { createProxy, createHandler } from './proxy'
|
||||
import { metaInfoKey, PolySymbol } from './symbols'
|
||||
import { MetaContext, MetainfoActive, MetainfoInput } from './types'
|
||||
import { metaInfoKey } from './symbols'
|
||||
import { MetainfoActive, MetainfoInput, MetaProxy } from './types'
|
||||
|
||||
let contextCounter: number = 0
|
||||
|
||||
export function useMeta (obj: MetainfoInput, manager?: Manager) {
|
||||
export function useMeta (obj: MetainfoInput, manager?: Manager): MetaProxy {
|
||||
const vm = getCurrentInstance()
|
||||
if (vm) {
|
||||
manager = vm.appContext.config.globalProperties.$metaManager
|
||||
|
||||
if (!manager && vm) {
|
||||
manager = getCurrentManager(vm)
|
||||
}
|
||||
|
||||
if (!manager) {
|
||||
@@ -18,35 +15,17 @@ export function useMeta (obj: MetainfoInput, manager?: Manager) {
|
||||
throw new Error('No manager or current instance')
|
||||
}
|
||||
|
||||
const context: MetaContext = {
|
||||
id: PolySymbol(`context ${contextCounter++}`),
|
||||
vm: vm || undefined,
|
||||
manager
|
||||
}
|
||||
|
||||
const unmount = <T extends Function = () => any>() => remove(context)
|
||||
if (vm) {
|
||||
onUnmounted(unmount)
|
||||
}
|
||||
|
||||
manager.resolver.setup(context)
|
||||
|
||||
setByObject(context, obj)
|
||||
|
||||
const handler = /* #__PURE__ */ createHandler(context)
|
||||
const meta = createProxy(obj, handler)
|
||||
|
||||
return {
|
||||
meta,
|
||||
unmount
|
||||
}
|
||||
return manager.createMetaProxy(obj, vm || undefined)
|
||||
}
|
||||
|
||||
export function useMetainfo (): MetainfoActive {
|
||||
return inject(metaInfoKey)!
|
||||
}
|
||||
|
||||
export function getCurrentManager (): Manager {
|
||||
const vm = getCurrentInstance()!
|
||||
export function getCurrentManager (vm?: ComponentInternalInstance): Manager {
|
||||
if (!vm) {
|
||||
vm = getCurrentInstance()!
|
||||
}
|
||||
|
||||
return vm.appContext.config.globalProperties.$metaManager
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export function clone (v: any): any {
|
||||
const res: any = {}
|
||||
|
||||
for (const key in v) {
|
||||
// never clone the context
|
||||
if (key === 'context') {
|
||||
res[key] = v[key]
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user