NodeViewBody.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <template>
  2. <div
  3. class="node-view-body"
  4. :class="['node-view-body-' + mode, 'node-view-body-' + type]"
  5. >
  6. <slot name="default">
  7. <div class="node-view-body-wrapper" v-html="node.content" />
  8. <fullscreen-modal v-if="image" :image="image" :id="'modal-image-' + node.id" />
  9. <b-popover
  10. v-for="note in node.notes" :key="note.number"
  11. custom-class="footnote"
  12. :target="'note-' + note.number" :container="`node-${mode}-${node.id}`"
  13. placement="top" :fallback-placement="['bottom', 'right', 'left']"
  14. :triggers="['focus']"
  15. >
  16. <div class="footnote-content" v-html="note.content" />
  17. <div class="footnote-child-list" v-if="note.links">
  18. <node-view-title
  19. v-for="link in note.links" :key="link.id"
  20. :node="link.parents && link.parents.length ? link.parents[0] : link"
  21. link
  22. @open-node="onFootnoteLinkClick(link, 'note-' + note.number)"
  23. >
  24. <dot-button
  25. :variant="link.variant"
  26. class="mr-2"
  27. active
  28. >
  29. {{ $t('variants.' + link.variant) }}
  30. </dot-button>
  31. </node-view-title>
  32. </div>
  33. </b-popover>
  34. </slot>
  35. </div>
  36. </template>
  37. <script>
  38. import { getRelation } from '@/store/utils'
  39. import { NodeViewTitle } from '@/components/nodes'
  40. export default {
  41. name: 'NodeViewBody',
  42. components: {
  43. NodeViewTitle
  44. },
  45. props: {
  46. node: { type: Object, required: true },
  47. type: { type: String, required: true },
  48. mode: { type: String, required: true }
  49. },
  50. data () {
  51. return {
  52. image: { id: '', url: '', alt: '' }
  53. }
  54. },
  55. methods: {
  56. addFootnotes () {
  57. const links = this.$el.querySelectorAll('a')
  58. links.forEach((link, i) => {
  59. const number = parseInt(link.hash.replace('#', ''))
  60. if (!isNaN(number)) {
  61. link.classList.add('footnote-link')
  62. link.id = 'note-' + number
  63. link.innerHTML = link.textContent
  64. if (link.parentElement.nodeName === 'SUP') {
  65. link.parentElement.replaceWith(link)
  66. }
  67. link.setAttribute('href', 'javascript:;')
  68. }
  69. })
  70. },
  71. mountMedias () {
  72. const template = document.getElementById('expand-image-tmp')
  73. this.$el.querySelectorAll('.node-view-body-wrapper img').forEach(img => {
  74. const image = { id: img.dataset.entityUuid, url: img.src, alt: img.alt }
  75. const clone = document.importNode(template.content, true)
  76. const cloneImg = clone.querySelector('img')
  77. cloneImg.setAttribute('src', image.url)
  78. cloneImg.setAttribute('alt', image.alt)
  79. cloneImg.setAttribute('id', image.id)
  80. clone.querySelector('button').onclick = () => {
  81. this.image = image
  82. this.$bvModal.show('modal-image-' + this.node.id)
  83. }
  84. img.parentElement.replaceWith(clone)
  85. })
  86. },
  87. onFootnoteLinkClick (node, popoverBtnId) {
  88. this.$root.$emit('bv::hide::popover', popoverBtnId)
  89. this.$parent.$emit('open-node', getRelation(node))
  90. }
  91. },
  92. created () {
  93. if (this.mode === 'view' && this.node.notes) {
  94. // Query partial data for linked nodes in notes
  95. const ids = this.node.notes.filter(note => (note.links)).reduce((ids, note) => {
  96. return [...ids, ...note.links.map(link => link.id)]
  97. }, [])
  98. this.$store.dispatch('GET_NODES', { ids, dataLevel: 'initial' })
  99. }
  100. },
  101. mounted () {
  102. if (this.mode === 'view') {
  103. if (this.node.notes) {
  104. this.addFootnotes()
  105. }
  106. this.mountMedias()
  107. }
  108. }
  109. }
  110. </script>
  111. <style lang="scss">
  112. .node-view-body {
  113. width: 100%;
  114. img {
  115. max-width: 100%;
  116. }
  117. font: {
  118. family: $font-family-text;
  119. line-height: inherit;
  120. }
  121. @include media-breakpoint-up($size-bp) {
  122. font-size: 2rem;
  123. }
  124. &-view {
  125. padding: 0 $node-view-spacer-sm-x;
  126. font-size: 1.25rem;
  127. word-wrap: break-word;
  128. @include media-breakpoint-up($size-bp) {
  129. padding: 0 $node-view-spacer-x;
  130. &.node-view-body-ref {
  131. font-size: 2.6rem;
  132. }
  133. &.node-view-body-prod {
  134. font-size: 1.5rem;
  135. }
  136. }
  137. }
  138. &-card {
  139. padding: 0 $node-card-spacer-sm-x;
  140. font-size: 1.1rem;
  141. word-wrap: anywhere;
  142. @include media-breakpoint-up($size-bp) {
  143. font-size: 2rem;
  144. padding: 0 $node-card-spacer-x;
  145. }
  146. }
  147. &-card &-wrapper {
  148. @include line-clamp(3, 1.5em);
  149. }
  150. &-card#{&}-prod &-wrapper {
  151. @include line-clamp(4, 1.5em);
  152. }
  153. .footnote-link {
  154. display: inline-block;
  155. text-align: center;
  156. background-color: $black;
  157. color: $white;
  158. font-family: $font-family-text;
  159. font-weight: $font-weight-bold;
  160. font-size: 0.8em;
  161. text-decoration: none;
  162. border-radius: 1em;
  163. padding: 0 .5em;
  164. min-width: 1.25em;
  165. max-height: 1.4em;
  166. margin: 0 .2em;
  167. }
  168. }
  169. .footnote {
  170. background-color: $black !important;
  171. color: $white;
  172. font-size: 1.25rem;
  173. @include media-breakpoint-up($size-bp) {
  174. font-size: 1.5rem;
  175. }
  176. h6 {
  177. font-size: inherit;
  178. }
  179. &-content,
  180. &-child-list {
  181. > :last-child {
  182. margin-bottom: 0;
  183. }
  184. }
  185. &-child-list {
  186. margin-top: .5rem;
  187. }
  188. }
  189. </style>