blog-client/pages/post/_id.vue
2020-07-12 16:32:48 +08:00

750 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template lang="pug">
.postPage
article.articleDetails
.articleTitle
| {{ articels.post_title }}
.articalMeta
ul
li
Icon(:icon="['far', 'calendar-alt']")
| &nbsp; {{ articels.post_date }}
li
Icon(:icon="['far', 'bookmark']")
nuxt-link(v-for='(relationships, index) in articels.term_relationships', :key='index', v-if='relationships.term_taxonomy', :to="'/' + relationships.term_taxonomy.term.slug")
span
| &nbsp; {{ relationships.term_taxonomy.term.name }}
li(v-if='articels.postmetum.meta_value !== 0')
Icon(:icon="['fas', 'thermometer-' + articels.hotValue]")
| &nbsp; {{ articels.postmetum.meta_value }}
#articelToc.articelToc(v-if="articels.post_toc_show")
.articelTitle 目录
.articelTocList(v-html="articels.post_toc")
.articelContent(v-html='articels.post_content')
.tools
.reward(@click="reward") 打赏
.comment(id="comment")
.comment-title 交流区
span(v-if="comments.length") ({{commentAmount}})
.comment-item(v-for="(item, index) in comments", :id="'comment_ID_' + item.comment_ID", :key="index")
.comment-avatar
img(:src="item.comment_author_avatar_url ? item.comment_author_avatar_url : 'https://picsum.photos/100/100/?blur=' + item.user_id")
.comment-area
.comment-author {{ item.comment_author }}
.comment-content(v-html="item.comment_content_html")
.comment-info {{ item.comment_date }}
span.reply(@click="reply(item.comment_ID, null, true)" v-if="replyLastID === null && item.comment_ID === replyID") 取消回复
span.reply(@click="reply(item.comment_ID, null)" v-else) 回复
.comment-item(v-for="(i, inx) in item.children", :id="'comment_ID_' + i.comment_ID", :key="inx", style="margin-top: 15px; margin-left: 50px; background-color: #eeeeee; border-radius: 4px;")
.comment-avatar
img(:src="i.comment_author_avatar_url ? i.comment_author_avatar_url : 'https://picsum.photos/100/100/?blur=' + i.user_id")
.comment-area
.comment-author {{ i.comment_author }}
.comment-content(v-html="i.comment_content_html")
.comment-info {{ i.comment_date }}
span.reply(@click="reply(item.comment_ID, i.comment_ID)" v-if="replyLastID !== i.comment_ID") 回复
span.reply(@click="reply(item.comment_ID, i.comment_ID, true)" v-else) 取消回复
p(v-if="!comments.length", style="color: #cecece; text-align: center; margin-top: 40px") 暂无内容
.comment-default-commit
.login(v-if="!visitorToken", @click="login")
img(src="https://cdn.wyr.me/imgs/GitHub-Login.png")
.comment-userinfo(v-else)
.comment-avatar
img(:src="'https://picsum.photos/100/100/?blur=1'")
.comment-nickname 张三
#editor
.comment-reply-commit(id="comment-reply-commit", v-show="replyID")
.login(v-show="!visitorToken", @click="login")
img(src="https://cdn.wyr.me/imgs/GitHub-Login.png")
#editor-reply
ins.adsbygoogle(style="display:block; text-align:center; margin-top:20px;", data-ad-layout="in-article", data-ad-format="fluid", data-ad-client="ca-pub-2143583075951360", data-ad-slot="4741804954")
.articelRightToc(v-show="articels.post_toc_show && showRightToc", @click="articelRightTocClick")
.articelTitle 目录
.articelTocList(v-html="articels.post_toc")
nuxt-link(:to="articels.last ? '/post/' + articels.last.ID : ''")
.btnFooter.btnPrevt(:style="'color: ' + (articels.last ? '#333' : '#ccc')")
Icon(:icon="['fas', 'chevron-left']")
| &nbsp; 上一篇 {{ articels.last ? '(' + decodeTitle(articels.last.post_name) + ')' : ''}}
nuxt-link(:to="articels.next ? '/post/' + articels.next.ID : ''")
.btnFooter.btnNext(:style="'color: ' + (articels.next ? '#333' : '#ccc')")
| 下一篇 {{ articels.next ? '(' + decodeTitle(articels.next.post_name) + ')' : ''}} &nbsp;
Icon(:icon="['fas', 'chevron-right']")
</template>
<script>
export default {
async asyncData ({ route, app, $axios }) {
const data = await $axios.$get(`/public/article/details?id=${route.params.id}`)
const result = data.result
result.post_date = app.$moment(result.post_date).utc().format('lll')// 格式化时间
// 热度值计算
if (result.postmetum !== null && result.postmetum !== '') {
result.hotValue = app.$getHatValue(result.postmetum.meta_value)
} else {
result.hotValue = 0
result.postmetum = {}
result.postmetum.meta_value = 0
}
// 获取评论
const tmp = await $axios.$get(`/public/comments/post?ID=${route.params.id}`)
const comments = tmp.result.list || []
const commentAmount = tmp.result.amount || 0
return {
articels: result,
comments,
commentsMap: new Map(),
commentAmount,
showRightToc: false,
tocIds: result.post_toc_ids,
replyLastID: null,
replyID: null,
visitorToken: null
}
},
mounted () {
// 创建编辑器
if (process.client) {
if (!window.editorObj && window.Vditor) {
this.visitorToken = window.localStorage.visitorToken
const upload = {
accept: 'image/*, video/*, audio/*, text/*, application/*, .rar, .zip, .php, .pptx, .ppt, .doc, .docx, .txt, .xls, .xlsx',
url: process.env.baseURL + '/visitor/file/upload',
max: 10485760, // 10 MB
linkToImgUrl: process.env.baseURL + '/visitor/file/fetch',
headers: {
Authorization: 'Bearer ' + window.localStorage.visitorToken
},
filename: name => encodeURIComponent(name.replace(/[^(a-zA-Z0-9\u4E00-\u9FA5\.)]/g, '') // eslint-disable-line
.replace(/[\?\\/:|<>\*\[\]\(\)\$%\{\}@~]/g, '') // eslint-disable-line
.replace('/\\s/g', '')),
format (files, responseText) {
return responseText
},
error: (_) => {
this.$modal.show('dialog', {
title: '提示',
text: '上传失败,请检查网络稍后重试。<br>允许上传的文件类型图片、视频、压缩文件、文本文件、Office文件、各类代码文件。'
})
}
}
window.editorObj = new window.Vditor('editor', {
toolbar: [
'emoji',
'headings',
'bold',
'italic',
'strike',
'link',
'list',
'ordered-list',
'outdent',
'indent',
'quote',
'line',
'code',
'inline-code',
'upload',
'table',
'insert-before',
'insert-after',
'undo',
'redo',
'fullscreen',
{
name: 'more',
toolbar: [
'edit-mode',
'check',
'both',
'code-theme',
'content-theme',
'export',
'outline',
'preview',
'format',
'devtools'
]
}],
minHeight: 250,
toolbarConfig: {
pin: true
},
cache: {
enable: true
},
upload,
placeholder: '我们书写的不是代码,而是人生'
})
window.editorReplyObj = new window.Vditor('editor-reply', {
toolbar: [
'emoji',
'headings',
'bold',
'italic',
'strike',
'link',
'list',
'ordered-list',
'outdent',
'indent',
'quote',
'line',
'code',
'inline-code',
'upload',
'insert-before',
'insert-after',
'undo',
'redo',
{
name: 'more',
toolbar: [
'table',
'fullscreen',
'edit-mode',
'check',
'both',
'code-theme',
'content-theme',
'export',
'outline',
'preview',
'format',
'devtools'
]
}],
minHeight: 220,
toolbarConfig: {
pin: true
},
cache: {
enable: false
},
upload,
placeholder: '我们书写的不是代码,而是人生'
})
}
const comments = this.comments
for (let i = 0; i < comments.length; i++) {
this.commentsMap.set(comments[i].comment_ID, comments[i])
if (comments[i].children) {
for (let j = 0; j < comments[i].children.length; j++) {
this.commentsMap.set(comments[i].children[j].comment_ID, comments[i].children[j])
}
}
}
}
this.$finishLoad()
if (this.articels.post_toc_show) {
const tocShow = () => {
if (window.addEvent) {
window.addEvent(window, 'scroll', () => {
const windowWidth = window.innerWidth || document.documentElement.clientWidth
if (document.getElementById('articelToc') && !this.isOnScreen(document.getElementById('articelToc'))) {
if (windowWidth < 768) {
this.showRightToc = false
document.getElementById('mobileToc').style.display = 'block'
} else {
this.showRightToc = true
}
} else {
this.showRightToc = false
if (windowWidth < 768) {
document.getElementById('mobileToc').style.display = 'none'
}
}
})
} else {
setTimeout(() => {
tocShow()
}, 50)
}
}
tocShow()
const tocIdsTop = []
const tocIdsTopMap = new Map()
for (const n in this.tocIds) {
const tTop = document.getElementById(this.tocIds[n]).offsetTop
tocIdsTop.push(tTop)
tocIdsTopMap.set(tTop, '#' + this.tocIds[n])
}
const findInIds = (arr, num) => { for (let x = 0; x < arr.length; x++) { if (arr[x] > num) { return x - 1 } } }
let debounce = null
let lastIdInx = null
const scrollToc = () => {
let scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop
const windowWidth = window.innerWidth || document.documentElement.clientWidth
if (windowWidth < 768) { scrollTop = scrollTop * 2.7 + 175 }
const tocIdsInx = findInIds(tocIdsTop, scrollTop + 130)
if (tocIdsInx !== -1 && lastIdInx !== tocIdsInx && document.getElementsByClassName('articelRightToc')[0]) {
const tocAList = document.getElementsByClassName('articelRightToc')[0].getElementsByTagName('a')
const nowId = tocIdsTopMap.get(tocIdsTop[tocIdsInx])
for (const i in tocAList) {
if (tocAList[i] && tocAList[i].getAttribute && tocAList[i].getAttribute('href') === nowId) {
tocAList[i].style.color = '#ff8a8a'
} else if (tocAList[i] && tocAList[i].style) {
tocAList[i].style.color = '#bbb'
}
}
lastIdInx = tocIdsInx
}
}
window.addEvent(window, 'scroll', () => {
clearTimeout(debounce)
debounce = setTimeout(() => {
scrollToc()
}, 20)
})
document.getElementById('mobileToc').onclick = () => {
this.showRightToc = !this.showRightToc
}
}
try {
(adsbygoogle = window.adsbygoogle || []).push({}) // eslint-disable-line
} catch (_) { }
},
methods: {
decodeTitle (name) {
try {
return this.urldecode(name)
} catch (err) {
console.log(err)
return name
}
},
urldecode (encodedString) {
let output = encodedString
let binVal, thisString
const myregexp = /(%[^%]{2})/
function utf8to16 (str) {
let c
let char2, char3
let out = ''
const len = str.length
let i = 0
while (i < len) {
c = str.charCodeAt(i++)
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
out += str.charAt(i - 1)
break
case 12: case 13:
char2 = str.charCodeAt(i++)
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F))
break
case 14:
char2 = str.charCodeAt(i++)
char3 = str.charCodeAt(i++)
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0))
break
}
}
return out
}
let match = null
while ((match = myregexp.exec(output)) != null &&
match.length > 1 &&
match[1] !== '') {
binVal = parseInt(match[1].substr(1), 16)
thisString = String.fromCharCode(binVal)
output = output.replace(match[1], thisString)
}
output = output.replace(/\\+/g, ' ')
output = utf8to16(output)
return output
},
articelRightTocClick () {
const windowWidth = window.innerWidth || document.documentElement.clientWidth
if (windowWidth < 768) {
this.showRightToc = false
}
},
isOnScreen (element) {
const ON_SCREEN_HEIGHT = 50
const ON_SCREEN_WIDTH = 50
const rect = element.getBoundingClientRect()
const windowHeight = window.innerHeight || document.documentElement.clientHeight
const windowWidth = window.innerWidth || document.documentElement.clientWidth
const elementHeight = element.offsetHeight
const elementWidth = element.offsetWidth
const onScreenHeight = ON_SCREEN_HEIGHT > elementHeight ? elementHeight : ON_SCREEN_HEIGHT
const onScreenWidth = ON_SCREEN_WIDTH > elementWidth ? elementWidth : ON_SCREEN_WIDTH
// 元素在屏幕上方
const elementBottomToWindowTop = rect.top + elementHeight
const bottomBoundingOnScreen = elementBottomToWindowTop >= onScreenHeight
// 元素在屏幕下方
const elementTopToWindowBottom = windowHeight - (rect.bottom - elementHeight)
const topBoundingOnScreen = elementTopToWindowBottom >= onScreenHeight
// 元素在屏幕左侧
const elementRightToWindowLeft = rect.left + elementWidth
const rightBoundingOnScreen = elementRightToWindowLeft >= onScreenWidth
// 元素在屏幕右侧
const elementLeftToWindowRight = windowWidth - (rect.right - elementWidth)
const leftBoundingOnScreen = elementLeftToWindowRight >= onScreenWidth
return bottomBoundingOnScreen && topBoundingOnScreen && rightBoundingOnScreen && leftBoundingOnScreen
},
reward () {
this.$modal.show('dialog', {
title: '感谢支持',
text: '<img src="https://cdn.wyr.me/imgs/reward.jpg" style="max-width: 100%; width: 400px"/>'
})
},
reply (id, replyLastID, cancel) {
const editorReplyObject = document.getElementById('comment-reply-commit')
if (cancel) {
this.replyID = null
this.replyLastID = null
window.location.hash = '_'
} else {
this.replyID = id
this.replyLastID = replyLastID
document.getElementById('comment_ID_' + id).appendChild(editorReplyObject)
const comment = this.commentsMap.get(replyLastID)
window.editorReplyObj.setValue('', true)
this.$nextTick(() => {
window.editorReplyObj.focus()
if (replyLastID !== null) {
window.editorReplyObj.insertValue('> ' + comment.comment_content + ' \n\n\n', true)
} else {
window.editorReplyObj.insertValue('', true)
}
window.location.hash = '#comment_ID_' + id
})
}
},
login () {
const newWindow = window.open('https://github.com/login/oauth/authorize?client_id=ce1673a37333e47e482d&redirect_uri=https://www.wyr.me/auth', 'Github授权', 'scrollbars=yes,resizable=yes,status=no,location=yes,toolbar=no,menubar=no,width=800,height=600,left=0,top=0')
newWindow.onload = function () {
// newWindow.location
// newWindow.close()
}
}
},
head () {
return {
title: `${this.articels.post_title} - 轶哥`
}
}
}
</script>
<style lang="stylus">
.postPage
.articleDetails
margin-top 30px
background-color #fff
padding 50px 10px
.articleTitle
text-align center
font-size 24px
.articalMeta
text-align center
padding 20px 0
font-size 12px
color #a1887f
ul
list-style none
padding-right 20px
> li
display inline-block
margin-left 20px
line-height 2
> i
margin-right 8px
> span a
color #a1887f
margin-left 10px
a span
color #a1887f
> span a:first-child
margin-left 0
> li :first-child
margin-left 0
.articelContent
margin-top 20px
margin-bottom 30px
line-height 2
font-weight 300
font-size responsive
pre
text-align 0.16rem
overflow-x auto
line-height 1
code
padding 16px
code
color #24292e
background-color rgba(27, 31, 35, 0.05)
border-radius 3px
font-size 85%
padding 2px 5px
.comment
width 100%
margin-top 60px
.comment-title
color #777
margin-bottom 30px
.comment-item
margin-top 10px
padding 10px 8px
.comment-avatar
float left
margin-top 3px
img
border-radius 50%
width 40px
border-style none
cursor pointer
box-sizing border-box
margin 0
.comment-area
margin-left 50px
.comment-author, .comment-info
font-size 10px
.comment-author
color #a1887f
.comment-info
color #777
.reply
color #a1887f
cursor pointer
user-select none
margin-left 10px
.comment-userinfo
margin-left 5px
padding 10px 5px
.comment-avatar
float left
margin-top 3px
img
border-radius 50%
width 60px
border-style none
cursor pointer
box-sizing border-box
margin 0
.comment-nickname
color #a1887f
margin-left 80px
height 70px
line-height 70px
#editor
margin-top 10px
margin-bottom 10px
.comment-reply-commit
margin 10px 0 10px 50px
.comment-default-commit
margin-top 125px
.comment-default-commit, .comment-reply-commit
position relative
.login
position absolute
left 0
top 0
width 100%
height 100%
text-align center
line-height 1
margin auto
z-index 99
cursor pointer
user-select none
img
margin-top 70px
.login:hover
background-color #fff
.login:before
content ''
position absolute
background rgba(0, 0, 0, 0.1)
top 0
left 0
width 100%
height 100%
filter blur(5px)
z-index 9
#editor-reply
margin 0
.btnFooter
font-size 12px
margin-top 25px
i
margin 10px
.btnPrevt
float left
.btnNext
float right
footer .footer
color #cccccc
text-align left
line-height 1.5
margin 20px 0
.anchor-fix
display block
height 70px /* same height as header */
margin-top -70px /* same height as header */
visibility hidden
.articelRightToc
position fixed
width 15%
top 0
right 0
overflow-y auto
background-color #000
padding 10px
box-shadow 0 0 8px #000
z-index 999999
.articelToc
width 100%
.articelToc, .articelRightToc
color #bbb
user-select none
.articelTitle
font-weight 400
margin-bottom 10px
.articelTocList
color #bbb
a
color #bbb
&:visited
color #bbb
&:hover
color #d01c2e
ol.toc, ol.toc ol
counter-reset toc
list-style none
ol li
counter-increment toc
.toc li:before
content counters(toc, '.') ' '
ol
margin-left 20px
li
font-weight 400
ol
margin-left 20px
li
font-weight 300
.tools
margin-top 80px
margin-bottom 10px
text-align center
user-select none
.reward
margin 15px 0
border 1px solid #888
padding 5px 10px
display inline-block
color #888
font-weight 300
cursor pointer
margin auto
.vditor--fullscreen
z-index 9999999
@media (max-width: 768px)
.postPage
.articleDetails
padding 20px
margin-top 50px
.articelRightToc
position fixed
width 80%
top 100px
bottom 95px
right 10px
background-color #000
padding 10px
overflow-y auto
box-shadow 0 0 8px #000
.mobileToc
bottom 48px
</style>