cp-metas
Version:
set website title
255 lines (233 loc) • 8.72 kB
text/typescript
// @ts-ignore
import { App, inject, watchEffect, getCurrentInstance, ComponentOptions } from 'vue'
import { HeadsOptions, MetasOptions, HeadAttrs, isClient } from "../types/index";
function htmlspecialchars(str: string){
str = str.replace(/&/g, '&');
str = str.replace(/</g, '<');
str = str.replace(/>/g, '>');
str = str.replace(/"/g, '"');
str = str.replace(/'/g, ''');
return str;
}
export class CpMetas {
readonly #SSR!: boolean
readonly #isPrivateApp: boolean = false
private head!: HeadsOptions
private readonly defaultTitle: string | undefined
constructor(heads: HeadsOptions, isSSR: boolean, isPrivateApp: boolean) {
this.head = heads
this.#SSR = isSSR
this.defaultTitle = heads.title
this.#isPrivateApp = isPrivateApp
}
setHead(heads: HeadsOptions = {}) {
!!heads.title && (this.head.title = heads.title || this.defaultTitle)
const metas: MetasOptions[] = []
for (const i in heads.meta) {
if (heads.meta.hasOwnProperty(i)) {
const item: MetasOptions = heads.meta[i]
const type = item.name || item.property
switch (type) {
case 'description':
this.head.description = item.content
break;
case 'keywords':
this.head.keywords = item.content
break;
case 'og:image':
this.head.image = item.content
break;
default:
metas.push(item);
break;
}
}
}
this.head.metas = metas
if (!this.#SSR) {
const title: HeadAttrs<HTMLElement> = document.getElementsByTagName('title')
if (title && title.length) {
title[0].innerText = heads.title || this.defaultTitle
} else {
const title = document.createElement('title');
(title.innerText as any) = heads.title || this.defaultTitle;
document.getElementsByTagName('head')[0].appendChild(title)
}
if (isClient && this.#isPrivateApp) {
const oldMetas = document.querySelectorAll('meta[data-mark="cp"]');
for (let i in oldMetas) {
if (oldMetas.hasOwnProperty(i)) {
const _item = oldMetas[i];
_item.remove();
}
}
for (let i in this.head.metas) {
const item = this.head.metas[i];
let key = '';
let val = '';
let content = '';
if (item.name) {
key = 'name';
val = item.name;
} else if (item.property) {
key = 'property';
val = item.property;
}
if (!key) {
return;
}
if (item.content) {
content = item.content;
}
const meta = document.createElement('meta');
meta.setAttribute(key, val);
meta.setAttribute('content', content);
meta.setAttribute('data-mark', 'cp');
document.getElementsByTagName('head')[0].appendChild(meta)
}
}
}
}
getHead() {
let description = this.head.description;
let keywords = this.head.keywords;
let ogDescription = this.head.description;
let ogImage = this.head.image;
const metas = [];
for (const i in this.head.metas) {
if (this.head.metas.hasOwnProperty(i)) {
const item: HeadAttrs<keyof MetasOptions> = this.head.metas[parseInt(i)];
const type = item.name || item.property;
switch (type) {
case 'description':
description = htmlspecialchars(item.content);
break;
case 'keywords':
if (typeof item.content === "string" ) {
keywords = htmlspecialchars(item.content);
} else if (item.content instanceof Array) {
let k = [];
for (const i in item.content) {
k.push(htmlspecialchars(item.content[i]))
}
keywords = k.join("、");
}
break;
case 'og:description':
ogDescription = htmlspecialchars(item.content);
break;
case 'og:image':
ogImage = item.content;
break;
default:
item.content = htmlspecialchars(item.content);
metas.push(item);
break;
}
}
}
return {
title: this.head.title,
meta: [
{
name: 'description',
content: description
},
{
name: 'keywords',
content: keywords
},
{
property: 'og:title',
content: this.head.title
},
{
property: 'og:description',
content: ogDescription
},
{
property: 'og:image',
content: ogImage
},
...metas
],
};
}
renderToString() {
const heads = this.getHead();
let string = `<title>${heads.title}</title>`;
for(const i in heads.meta) {
const meta = heads.meta[i];
let key = '';
let val = '';
if (meta.name) {
key = 'name';
val = meta.name;
} else if (meta.property) {
key = 'property';
val = meta.property;
}
if (!key) {
return;
}
string += `<meta ${key}="${val}" content="${meta.content}">`;
}
const date = new Date();
const time = date.getFullYear().toString()
+ '-' + (date.getMonth() + 1).toString()
+ '-' + date.getDate().toString()
+ ' ' + date.getHours().toString()
+ ':' + date.getMinutes().toString()
+ ':' + date.getSeconds().toString();
string += `<meta name="updatetime" content="${time}">`;
return string;
}
}
export function usMeta(obj: HeadsOptions | undefined): void {
const head: CpMetas | undefined = inject('cpMeta')
!!head && head.setHead(obj)
}
export const cpMeta = {
data() {
const instance = getCurrentInstance();
return {
// @ts-ignore
_cpMetaHeadFn: instance?.type.head
}
},
created() {
// @ts-ignore
if (this._cpMetaHeadFn) {
// @ts-ignore
this.$cpMeta.setHead(this._cpMetaHeadFn());
}
},
activated() {
// @ts-ignore
if (this._cpMetaHeadFn) {
// @ts-ignore
this.$cpMeta.setHead(this._cpMetaHeadFn());
}
},
mounted() {
// @ts-ignore
if (this._cpMetaHeadFn) {
watchEffect(() => {
// @ts-ignore
this.$cpMeta.setHead(this._cpMetaHeadFn());
})
}
}
}
export default function createMeta(heads: HeadsOptions, isSSR: boolean = false, isPrivateApp: boolean = false) {
return {
install(app: App, option: ComponentOptions) {
const $cpMeta = new CpMetas(heads, isSSR, isPrivateApp);
if (option.mixin) {
app.mixin(cpMeta)
}
app.provide('cpMeta', $cpMeta);
app.config.globalProperties.$cpMeta = $cpMeta;
}
}
}