page.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <template lang="pug">
  2. v-app(v-scroll='upBtnScroll', :dark='darkMode')
  3. nav-header
  4. v-navigation-drawer(
  5. :class='darkMode ? `grey darken-5` : `primary`'
  6. dark
  7. app
  8. clipped
  9. mobile-break-point='600'
  10. :temporary='$vuetify.breakpoint.mdAndDown'
  11. v-model='navShown'
  12. :right='$vuetify.rtl'
  13. )
  14. vue-scroll(:ops='scrollStyle')
  15. nav-sidebar(:color='darkMode ? `grey darken-5` : `primary`')
  16. slot(name='sidebar')
  17. v-fab-transition
  18. v-btn(
  19. fab
  20. color='primary'
  21. fixed
  22. bottom
  23. :right='$vuetify.rtl'
  24. :left='!$vuetify.rtl'
  25. small
  26. @click='navShown = !navShown'
  27. v-if='$vuetify.breakpoint.mdAndDown'
  28. v-show='!navShown'
  29. )
  30. v-icon menu
  31. v-content(ref='content')
  32. template(v-if='path !== `home`')
  33. v-toolbar(:color='darkMode ? `grey darken-4-d3` : `grey lighten-3`', flat, dense, v-if='$vuetify.breakpoint.smAndUp')
  34. //- v-btn.pl-0(v-if='$vuetify.breakpoint.xsOnly', flat, @click='toggleNavigation')
  35. //- v-icon(color='grey darken-2', left) menu
  36. //- span Navigation
  37. v-breadcrumbs.breadcrumbs-nav.pl-0(
  38. :items='breadcrumbs'
  39. divider='/'
  40. )
  41. template(slot='item', slot-scope='props')
  42. v-icon(v-if='props.item.path === "/"', small, @click='goHome') mdi-home
  43. v-btn.ma-0(v-else, :href='props.item.path', small, text) {{props.item.name}}
  44. template(v-if='!isPublished')
  45. v-spacer
  46. .caption.red--text {{$t('common:page.unpublished')}}
  47. status-indicator.ml-3(negative, pulse)
  48. v-divider
  49. v-toolbar.px-2(:color='darkMode ? `grey darken-4-l3` : `grey lighten-4`', flat, :height='90')
  50. div(style='padding-left: 376px;')
  51. .headline.grey--text(:class='darkMode ? `text--lighten-2` : `text--darken-3`') {{title}}
  52. .caption.grey--text.text--darken-1 {{description}}
  53. v-spacer
  54. v-divider
  55. v-container.pl-5.pt-2(fill-height, fluid, grid-list-xl)
  56. v-layout(row)
  57. v-flex.page-col-sd(lg3, xl2, fill-height, v-if='$vuetify.breakpoint.lgAndUp', style='margin-top: -90px;')
  58. v-card(v-if='toc.length')
  59. .overline.pa-5.pb-0(:class='darkMode ? `indigo--text text--lighten-3` : `primary--text`') {{$t('common:page.toc')}}
  60. v-list.pb-3(dense, nav, :class='darkMode ? `darken-3-d3` : ``')
  61. template(v-for='(tocItem, tocIdx) in toc')
  62. v-list-item(@click='$vuetify.goTo(tocItem.anchor, scrollOpts)')
  63. v-icon(color='grey', small) mdi-chevron-right
  64. v-list-item-title.pl-3 {{tocItem.title}}
  65. // v-divider(v-if='tocIdx < toc.length - 1 || tocItem.children.length')
  66. template(v-for='tocSubItem in tocItem.children')
  67. v-list-item(@click='$vuetify.goTo(tocSubItem.anchor, scrollOpts)')
  68. v-icon.pl-3(color='grey lighten-1', small) mdi-chevron-right
  69. v-list-item-title.pl-3.caption.grey--text.text--darken-1 {{tocSubItem.title}}
  70. // v-divider(inset, v-if='tocIdx < toc.length - 1')
  71. v-card.mt-5
  72. .pa-5.pt-3
  73. .overline.indigo--text.d-flex.align-center
  74. span {{$t('common:page.lastEditedBy')}}
  75. v-spacer
  76. v-tooltip(top, v-if='isAuthenticated')
  77. template(v-slot:activator='{ on }')
  78. v-btn.btn-animate-edit(icon, :href='"/h/" + locale + "/" + path', v-on='on', x-small)
  79. v-icon(color='grey', dense) mdi-history
  80. span History
  81. v-tooltip(top, v-if='isAuthenticated')
  82. template(v-slot:activator='{ on }')
  83. v-btn.btn-animate-edit(icon, :href='"/e/" + locale + "/" + path', v-on='on', x-small)
  84. v-icon(color='grey', dense) mdi-pencil
  85. span {{$t('common:page.editPage')}}
  86. .body-2.grey--text(:class='darkMode ? `` : `text--darken-3`') {{ authorName }}
  87. .caption.grey--text.text--darken-1 {{ updatedAt | moment('calendar') }}
  88. v-card.mt-5(v-if='tags.length > 0')
  89. .pa-5
  90. .overline.teal--text.pb-2 Tags
  91. v-chip.mr-1(
  92. label
  93. color='teal lighten-5'
  94. v-for='(tag, idx) in tags'
  95. :href='`/t/` + tag.slug'
  96. :key='tag.slug'
  97. )
  98. v-icon(color='teal', left, small) mdi-label
  99. span.teal--text.text--darken-2 {{tag.text}}
  100. v-card.mt-5
  101. .pa-5
  102. .overline.pb-2.yellow--text.text--darken-4 Rating
  103. .text-center
  104. v-rating(
  105. v-model='rating'
  106. color='yellow darken-3'
  107. background-color='grey lighten-1'
  108. half-increments
  109. hover
  110. )
  111. .caption.grey--text 5 votes
  112. v-divider
  113. v-toolbar(:color='darkMode ? `grey darken-3` : `grey lighten-4`', flat, dense)
  114. v-spacer
  115. v-tooltip(bottom)
  116. template(v-slot:activator='{ on }')
  117. v-btn(icon, small, v-on='on'): v-icon(color='grey') mdi-bookmark
  118. span {{$t('common:page.bookmark')}}
  119. v-tooltip(bottom)
  120. template(v-slot:activator='{ on }')
  121. v-btn(icon, small, v-on='on'): v-icon(color='grey') mdi-share-variant
  122. span {{$t('common:page.share')}}
  123. v-tooltip(bottom)
  124. template(v-slot:activator='{ on }')
  125. v-btn(icon, small, v-on='on'): v-icon(color='grey') mdi-printer
  126. span {{$t('common:page.printFormat')}}
  127. v-spacer
  128. v-flex.page-col-content(xs12, lg9, xl10)
  129. .contents(ref='container')
  130. slot(name='contents')
  131. nav-footer
  132. notify
  133. search-results
  134. v-fab-transition
  135. v-btn(
  136. v-if='upBtnShown'
  137. fab
  138. fixed
  139. bottom
  140. :right='!$vuetify.rtl'
  141. :left='$vuetify.rtl'
  142. small
  143. @click='$vuetify.goTo(0, scrollOpts)'
  144. color='primary'
  145. )
  146. v-icon mdi-arrow-up
  147. </template>
  148. <script>
  149. import { StatusIndicator } from 'vue-status-indicator'
  150. import Prism from 'prismjs'
  151. import { get } from 'vuex-pathify'
  152. import _ from 'lodash'
  153. Prism.plugins.autoloader.languages_path = '/js/prism/'
  154. Prism.plugins.NormalizeWhitespace.setDefaults({
  155. 'remove-trailing': true,
  156. 'remove-indent': true,
  157. 'left-trim': true,
  158. 'right-trim': true,
  159. 'break-lines': 160,
  160. 'remove-initial-line-feed': true,
  161. 'tabs-to-spaces': 2
  162. })
  163. export default {
  164. components: {
  165. StatusIndicator
  166. },
  167. props: {
  168. pageId: {
  169. type: Number,
  170. default: 0
  171. },
  172. locale: {
  173. type: String,
  174. default: 'en'
  175. },
  176. path: {
  177. type: String,
  178. default: 'home'
  179. },
  180. title: {
  181. type: String,
  182. default: 'Untitled Page'
  183. },
  184. description: {
  185. type: String,
  186. default: ''
  187. },
  188. createdAt: {
  189. type: String,
  190. default: ''
  191. },
  192. updatedAt: {
  193. type: String,
  194. default: ''
  195. },
  196. tags: {
  197. type: Array,
  198. default: () => ([])
  199. },
  200. authorName: {
  201. type: String,
  202. default: 'Unknown'
  203. },
  204. authorId: {
  205. type: Number,
  206. default: 0
  207. },
  208. isPublished: {
  209. type: Boolean,
  210. default: false
  211. },
  212. toc: {
  213. type: Array,
  214. default: () => []
  215. }
  216. },
  217. data() {
  218. return {
  219. navShown: false,
  220. navExpanded: false,
  221. upBtnShown: false,
  222. scrollOpts: {
  223. duration: 1500,
  224. offset: -75,
  225. easing: 'easeInOutCubic'
  226. },
  227. scrollStyle: {
  228. vuescroll: {},
  229. scrollPanel: {
  230. initialScrollX: 0.01, // fix scrollbar not disappearing on load
  231. scrollingX: false,
  232. speed: 50
  233. },
  234. rail: {
  235. gutterOfEnds: '2px'
  236. },
  237. bar: {
  238. onlyShowBarOnScroll: false,
  239. background: '#42A5F5',
  240. hoverStyle: {
  241. background: '#64B5F6'
  242. }
  243. }
  244. }
  245. }
  246. },
  247. computed: {
  248. darkMode: get('site/dark'),
  249. isAuthenticated: get('user/authenticated'),
  250. rating: {
  251. get () {
  252. return 3.5
  253. },
  254. set (val) {
  255. }
  256. },
  257. breadcrumbs() {
  258. return [{ path: '/', name: 'Home' }].concat(_.reduce(this.path.split('/'), (result, value, key) => {
  259. result.push({
  260. path: _.map(result, 'path').join('/') + `/${value}`,
  261. name: value
  262. })
  263. return result
  264. }, []))
  265. }
  266. },
  267. created() {
  268. this.$store.commit('page/SET_AUTHOR_ID', this.authorId)
  269. this.$store.commit('page/SET_AUTHOR_NAME', this.authorName)
  270. this.$store.commit('page/SET_CREATED_AT', this.createdAt)
  271. this.$store.commit('page/SET_DESCRIPTION', this.description)
  272. this.$store.commit('page/SET_IS_PUBLISHED', this.isPublished)
  273. this.$store.commit('page/SET_ID', this.pageId)
  274. this.$store.commit('page/SET_LOCALE', this.locale)
  275. this.$store.commit('page/SET_PATH', this.path)
  276. this.$store.commit('page/SET_TAGS', this.tags)
  277. this.$store.commit('page/SET_TITLE', this.title)
  278. this.$store.commit('page/SET_UPDATED_AT', this.updatedAt)
  279. this.$store.commit('page/SET_MODE', 'view')
  280. },
  281. mounted () {
  282. Prism.highlightAllUnder(this.$refs.container)
  283. this.navShown = this.$vuetify.breakpoint.smAndUp
  284. },
  285. methods: {
  286. goHome () {
  287. window.location.assign('/')
  288. },
  289. toggleNavigation () {
  290. this.navOpen = !this.navOpen
  291. },
  292. upBtnScroll () {
  293. const scrollOffset = window.pageYOffset || document.documentElement.scrollTop
  294. this.upBtnShown = scrollOffset > window.innerHeight * 0.33
  295. }
  296. }
  297. }
  298. </script>
  299. <style lang="scss">
  300. .breadcrumbs-nav {
  301. .v-btn {
  302. min-width: 0;
  303. &__content {
  304. text-transform: none;
  305. }
  306. }
  307. .v-breadcrumbs__divider:nth-child(2n) {
  308. padding: 0 6px;
  309. }
  310. .v-breadcrumbs__divider:nth-child(2) {
  311. padding: 0 6px 0 12px;
  312. }
  313. }
  314. </style>