page.vue 11 KB

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