Skip to content

Commit

Permalink
feat: 在线分享第一版-SEO优化
Browse files Browse the repository at this point in the history
  • Loading branch information
terwer committed Jun 17, 2023
1 parent b8f00c0 commit 12fafe3
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 25 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The notions sharing function you want is here too
* **Permission control**: Only shared pages can be viewed, and pages that have not been shared cannot be viewed without permission.
* **Personal homepage**: You can set a shared page as your homepage, which can be used as a custom blog homepage.
* **Theme integration**: The default theme is [Zhihu](https://github.com/terwer/siyuan-theme-zhihu), and other themes will be supported in the future.
* **SEO optimization**: support automatically generating titles, summaries, and cover images for better SEO

## TODO

Expand Down
1 change: 1 addition & 0 deletions README_zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- **权限控制**:只能查看已分享的页面,未分享的页面无权限查看
- **个人主页**:支持设置已分享的某个页面为主页,可作为自定义博客主页
- **主题集成**:默认集成 [Zhihu](https://github.com/terwer/siyuan-theme-zhihu) 主题,后续可支持切换其他主体
- **SEO优化**:支持自动生成标题、摘要、首图,便于SEO

## TODO

Expand Down
27 changes: 16 additions & 11 deletions composables/usePost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,27 @@ export const usePost = () => {
post: {} as Post,
})

// lifecycles
// https://vuejs.org/api/composition-api-lifecycle.html#onserverprefetch
onServerPrefetch(async () => {
const route = useRoute()
const id = (route.params.id ?? "") as string
currentPost.post = await getPost(id)
})
onBeforeMount(async () => {
const route = useRoute()
const id = (route.params.id ?? "") as string
/**
* 如果缓存已有直接返回,否则去远程抓取数据
*/
const setCurrentPost = async () => {
if (ObjectUtil.isEmptyObject(currentPost.post)) {
const route = useRoute()
const id = (route.params.id ?? "") as string
currentPost.post = await getPost(id)
} else {
logger.info("Post already cached, skip fetch")
}
}

// lifecycles
// https://vuejs.org/api/composition-api-lifecycle.html#onserverprefetch
onServerPrefetch(async () => {
await setCurrentPost()
})
onBeforeMount(async () => {
await setCurrentPost()
})

return { currentPost }
return { currentPost, setCurrentPost }
}
1 change: 1 addition & 0 deletions locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ export default {
"switch.unactive.text": "Normal",
"blog.index.no.home": "Oh, you haven't set up your homepage yet!",
"blog.index.goto.set.home": "Go to set up my homepage now",
"blog.share": "Share to web",
}
1 change: 1 addition & 0 deletions locales/zh_CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ export default {
"switch.unactive.text": "正常模式",
"blog.index.no.home": "啊哦,您还没设置自己的主页哟!",
"blog.index.goto.set.home": "马上去设置我的主页",
"blog.share": "在线分享",
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"cheerio": "1.0.0-rc.12",
"element-plus": "^2.3.6",
"highlight.js": "^11.6.0",
"zhi-device": "^2.3.0",
Expand Down
23 changes: 22 additions & 1 deletion pages/p/[id].vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
<script setup lang="ts">
import { usePost } from "~/composables/usePost"
import { getFirstImageSrc, getSummery } from "~/utils/utils"
import { createAppLogger } from "~/common/appLogger"
const logger = createAppLogger("share-page")
const { t } = useI18n()
const { currentPost, setCurrentPost } = usePost()
await setCurrentPost()
const title = currentPost.post.title
const desc = getSummery(currentPost.post.description)
const headImage = await getFirstImageSrc(currentPost.post.description)
const seoMeta = {
title: title,
ogTitle: title,
description: desc,
ogDescription: desc,
} as any
if (headImage) {
logger.info("get a head image from doc=>", headImage)
seoMeta.ogImage = headImage
}
useServerSeoMeta(seoMeta)
const { currentPost } = usePost()
// https://stackoverflow.com/a/71781246/4037224
const VNode = () =>
h("div", {
Expand Down
23 changes: 22 additions & 1 deletion pages/post/[id].vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
<script setup lang="ts">
import { usePost } from "~/composables/usePost"
import { getFirstImageSrc, getSummery } from "~/utils/utils"
import { createAppLogger } from "~/common/appLogger"
const logger = createAppLogger("share-page")
const { t } = useI18n()
const { currentPost, setCurrentPost } = usePost()
await setCurrentPost()
const title = currentPost.post.title
const desc = getSummery(currentPost.post.description)
const headImage = await getFirstImageSrc(currentPost.post.description)
const seoMeta = {
title: title,
ogTitle: title,
description: desc,
ogDescription: desc,
} as any
if (headImage) {
logger.info("get a head image from doc=>", headImage)
seoMeta.ogImage = headImage
}
useServerSeoMeta(seoMeta)
const { currentPost } = usePost()
// https://stackoverflow.com/a/71781246/4037224
const VNode = () =>
h("div", {
Expand Down
23 changes: 22 additions & 1 deletion pages/s/[id].vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
<script setup lang="ts">
import { usePost } from "~/composables/usePost"
import { getFirstImageSrc, getSummery } from "~/utils/utils"
import { createAppLogger } from "~/common/appLogger"
const logger = createAppLogger("share-page")
const { t } = useI18n()
const { currentPost, setCurrentPost } = usePost()
await setCurrentPost()
const title = currentPost.post.title + " - " + t("blog.share")
const desc = getSummery(currentPost.post.description)
const headImage = await getFirstImageSrc(currentPost.post.description)
const seoMeta = {
title: title,
ogTitle: title,
description: desc,
ogDescription: desc,
} as any
if (headImage) {
logger.info("get a head image from doc=>", headImage)
seoMeta.ogImage = headImage
}
useServerSeoMeta(seoMeta)
const { currentPost } = usePost()
// https://stackoverflow.com/a/71781246/4037224
const VNode = () =>
h("div", {
Expand Down
58 changes: 47 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions utils/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2023, Terwer . All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Terwer designates this
* particular file as subject to the "Classpath" exception as provided
* by Terwer in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Terwer, Shenzhen, Guangdong, China, youweics@163.com
* or visit www.terwer.space if you need additional information or have any
* questions.
*/

import { HtmlUtil } from "zhi-common"
import * as cheerio from "cheerio"

export const getSummery = (html: string) => {
const text = HtmlUtil.removeMdWidgetTag(html)
return HtmlUtil.parseHtml(text, 250)
}

export const getFirstImageSrc = async (html: string) => {
// 初始化Cheerio实例
const $ = cheerio.load(html)
// 获取第一个<img>元素
const firstImg = $("img").first()
if (firstImg.length === 0) {
// 没有找到<img>元素,返回空字符串
return ""
}
// 返回<img>元素的src属性
return firstImg.attr("src") || ""
}

0 comments on commit 12fafe3

Please sign in to comment.