From 1382c43c5efd9d00efb17fefefa732febbea2d80 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Mon, 1 Aug 2022 11:43:40 +0200 Subject: [PATCH] feat: add `titleTemplate` option (#22) (#49) Co-authored-by: Anthony Fu --- README.md | 3 +- example/app.tsx | 6 ++- src/index.ts | 75 ++++++++++++++++++++++++++-------- tests/snapshots/test.tsx.md | 6 +-- tests/snapshots/test.tsx.snap | Bin 1069 -> 1081 bytes tests/test.tsx | 6 +-- 6 files changed, 70 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 0aa4beb..7b13f3d 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ const finalHTML = ` ## API -### `createHead()` +### `createHead(head?: HeadObject | Ref)` Create the head manager instance. @@ -99,6 +99,7 @@ Create the head manager instance. ```ts interface HeadObject { title?: MaybeRef + titleTemplate?: MaybeRef | ((title?: string) => string) meta?: MaybeRef link?: MaybeRef base?: MaybeRef diff --git a/example/app.tsx b/example/app.tsx index a29f98b..056b7a0 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -91,6 +91,7 @@ export const createApp = () => { setup() { useHead({ title: "About", + titleTemplate: "%s | About Template", }) return () => (
@@ -136,7 +137,10 @@ export const createApp = () => { return () => }, }) - const head = createHead() + + const head = createHead({ + titleTemplate: "%s | @vueuse/head", + }) app.use(head) app.use(router) diff --git a/src/index.ts b/src/index.ts index 76d333c..c6d3dce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,8 @@ import { UnwrapRef, watchEffect, VNode, + unref, + shallowRef, } from "vue" import { PROVIDE_KEY, @@ -26,6 +28,7 @@ export type HeadAttrs = { [k: string]: any } export type HeadObject = { title?: MaybeRef + titleTemplate?: MaybeRef | ((title?: string) => string) meta?: MaybeRef link?: MaybeRef base?: MaybeRef @@ -101,7 +104,7 @@ export const injectHead = () => { return head } -const acceptFields: Array = [ +const acceptFields: Array> = [ "title", "meta", "link", @@ -113,25 +116,45 @@ const acceptFields: Array = [ "bodyAttrs", ] +const renderTemplate = ( + template: HeadObjectPlain["titleTemplate"], + title?: string, +): string => { + if (template == null) return "" + if (typeof template === "string") { + return template.replace("%s", title ?? "") + } + return template(unref(title)) +} + const headObjToTags = (obj: HeadObjectPlain) => { const tags: HeadTag[] = [] + const keys = Object.keys(obj) as Array - for (const key of Object.keys(obj) as Array) { + for (const key of keys) { if (obj[key] == null) continue - if (key === "title") { - tags.push({ tag: key, props: { children: obj[key] } }) - } else if (key === "base") { - tags.push({ tag: key, props: { key: "default", ...obj[key] } }) - } else if (acceptFields.includes(key)) { - const value = obj[key] - if (Array.isArray(value)) { - value.forEach((item) => { - tags.push({ tag: key, props: item }) - }) - } else if (value) { - tags.push({ tag: key, props: value }) - } + switch (key) { + case "title": + tags.push({ tag: key, props: { children: obj[key] } }) + break + case "titleTemplate": + break + case "base": + tags.push({ tag: key, props: { key: "default", ...obj[key] } }) + break + default: + if (acceptFields.includes(key)) { + const value = obj[key] + if (Array.isArray(value)) { + value.forEach((item) => { + tags.push({ tag: key, props: item }) + }) + } else if (value) { + tags.push({ tag: key, props: value }) + } + } + break } } @@ -252,10 +275,14 @@ const updateElements = ( ) } -export const createHead = () => { +export const createHead = (initHeadObject?: MaybeRef) => { let allHeadObjs: Ref[] = [] let previousTags = new Set() + if (initHeadObject) { + allHeadObjs.push(shallowRef(initHeadObject)) + } + const head: HeadClient = { install(app) { app.config.globalProperties.$head = head @@ -268,8 +295,13 @@ export const createHead = () => { get headTags() { const deduped: HeadTag[] = [] + const titleTemplate = allHeadObjs + .map((i) => unref(i).titleTemplate) + .reverse() + .find((i) => i != null) + allHeadObjs.forEach((objs) => { - const tags = headObjToTags(objs.value) + const tags = headObjToTags(unref(objs)) tags.forEach((tag) => { if ( tag.tag === "meta" || @@ -297,6 +329,13 @@ export const createHead = () => { } } + if (titleTemplate && tag.tag === "title") { + tag.props.children = renderTemplate( + titleTemplate, + tag.props.children, + ) + } + deduped.push(tag) }) }) @@ -356,8 +395,8 @@ export const createHead = () => { const IS_BROWSER = typeof window !== "undefined" export const useHead = (obj: MaybeRef) => { - const headObj = ref(obj) as Ref const head = injectHead() + const headObj = ref(obj) as Ref head.addHeadObjs(headObj) diff --git a/tests/snapshots/test.tsx.md b/tests/snapshots/test.tsx.md index 62d8ae7..7b2d6d2 100644 --- a/tests/snapshots/test.tsx.md +++ b/tests/snapshots/test.tsx.md @@ -20,7 +20,7 @@ Generated by [AVA](https://avajs.dev). ␊ - count: 0␊ + count: 0 | @vueuse/head` ## : basic @@ -37,7 +37,7 @@ Generated by [AVA](https://avajs.dev). }, { props: { - children: '0', + children: '0 | @vueuse/head', }, tag: 'title', }, @@ -83,7 +83,7 @@ Generated by [AVA](https://avajs.dev). }, { props: { - children: '1', + children: '1 | @vueuse/head', }, tag: 'title', }, diff --git a/tests/snapshots/test.tsx.snap b/tests/snapshots/test.tsx.snap index 60fd3ca8a71f090b971811ae7c8d34f7204fea56..68973ad4ec4d17d54d342abd3f0d0752fc2f0310 100644 GIT binary patch literal 1081 zcmV-91jhS8RzVd@V+OqG)q4!*2Ez0CK&9j zFB!YES|5uD00000000B+SV4~)H54AtB%5uPWw)Yd#LpaBW> zkddD9?D^jF^LyVs%em9*auJ-WTX(5un$w=5a!4gp2}xBfwEEyq7!pk`|LHk3CwoxU zIqTkY+6KNKQ_e-ZnGf21stHa=pLYC^sz9<-Gm-c>5Q(OV&S&wSKS8Eaq*Us0#}`ox z!XTWs2-j35MAMR0PcCcmehe0Q09eTYTnMpqqF}O_&(6deQxdiUF-UaD#q<9DBqvc< ziji87l(*e)X0meEtz&qKh4nZE=Y0_lIK*bcXdVoirpC|LIlT` zjv6oe`=wX0)@g%Y8L(l;zuY)JY`iS`DbcLU%bS1gl@1LfI%An{bjau^6|%g|5es$P z3F(jpv|$F%VwNz?2ydt?zn$Hk3x%t^%qCMUd;wp>2g3m!DB8?2Sg@czyJo~i7kp(i zrDc`qQ=GrT2CPZsKWqf*5=C(=X%7mQ@kx(idtDKZ@zpK~P9q742wPav@H$k|ylp39 zQqoH?Q<$VFyoQ2Fs_+RJl7bonkAysJH>dCkX&||Y5}fXE5k2J-|LOaL~&N-fZrpKw?N(j>497U zxd!qWT04uXhf#e=nJ)wa{?o{30-Zs!50P+*a?>4kD2d%h{QwKTkTF|vQ=n+8P z010g9`W*B_fP4({r46mmLB9vck08I<&_`yWnn){%k1?k|xE(wtdhn3~biS z+q7fgB$-y%rOChXQ4KgzYU(j#sBz;h=f+N9B|FppHf5!w@WE2{0?tnUpsi=JA)-XJUVLeJgd0#}Q9DFlwG%gO9rj3v@uu<}+mJ1r6!k9=!!Gohq zhxM0&{nApbby}xaPT8OxT&^D-)L#+(lxWuB<;}nTYMVwqIwhHJbin8^6|%g|A&Yd} zj_81ev~C73V3sh=2(POwzU`f@?Lt+aW)rC<-Y(K!n>Nzpq65O&Ov#QCeTwrXY`~iI zf=8`lIz&+%OWK7@Wd)>1u)U6mM)+EXgeN@-JCB-J(&z@H&aAc)G0x$om?=!s6qX@F zk}7;m2BfG3frmn#v>FrmxJn?Ji4vS{anXA&Ai?wZDwMP2r0@pnHvq*P0-2drF{L{A z?;?JAQpEk%YXI19V%Q@K@0Ie0<5KqTPD*(dRWHK(5a=zScYwM;SAebqJ%LuvBL6^2 zGD4`}Ha0dKRCCaZgVqqbl`8`7`|OPj`+LT^hFwL#dWJopu@%Fhpymi|sC&?t8E=~TA z4nO2Xsfos{B#jzxF*SC~D%m0SuQ4i}efJlm7jb?p;qtk}SufT-Nolf&Z{W_U)*`{z zLcxX153OadU-tTCuV421N2u5DEPMUK>Gk9P{`a3Qe*DdK;@LcpL#AUqsCUJjoBqx6 nofb*^8l|byJEbc|KAyR4Wz)MBZ1+BHEP(k3ob5inln?*_>TU#Q diff --git a/tests/test.tsx b/tests/test.tsx index 0a2d0e4..21a0224 100644 --- a/tests/test.tsx +++ b/tests/test.tsx @@ -80,13 +80,13 @@ test("browser", async (t) => { t.snapshot(headHTML) await page.click("button.counter") - t.is(await page.title(), "count: 1") + t.is(await page.title(), "count: 1 | @vueuse/head") await page.click("button.change-home-title") - t.is(await page.title(), "count: 1") + t.is(await page.title(), "count: 1 | @vueuse/head") await page.click('a[href="/about"]') - t.is(await page.title(), "About") + t.is(await page.title(), "About | About Template") }) test("useHead: server async setup", async (t) => {