SimpleRichHtml.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <template>
  2. <div
  3. ref="scrollContainer"
  4. :class="['nana-rich-html-container', { 'no-scroll': noScroll }]"
  5. >
  6. <div class="rich-html">
  7. <slot name="prepend" />
  8. <template
  9. v-for="(content, i) in contents"
  10. :key="i"
  11. >
  12. <div
  13. v-if="content"
  14. :data-r-id="id"
  15. class="content"
  16. v-html="content"
  17. />
  18. </template>
  19. <slot name="append" />
  20. </div>
  21. <div class="rich-html-catalog" v-if="catalog && catalogItems.length > 0 && catalogShow">
  22. <CommonCatalog
  23. :items="catalogItems"
  24. :scrollContainer="scrollContainer"
  25. />
  26. </div>
  27. </div>
  28. </template>
  29. <script setup lang="ts">
  30. import { RandomUtils } from '@imengyu/imengyu-utils';
  31. import CommonCatalog, { type CatalogItem } from './content/CommonCatalog.vue';
  32. import { onBeforeUnmount, onMounted, ref, watch, type PropType } from 'vue';
  33. const props = defineProps({
  34. contents: {
  35. type: Array as PropType<string[]>,
  36. default: () => ([]),
  37. },
  38. tagStyle: {
  39. type: Object as PropType<Record<string, string>>,
  40. default: () => ({}),
  41. },
  42. catalog: {
  43. type: Boolean,
  44. default: true,
  45. },
  46. catalogShow: {
  47. type: Boolean,
  48. default: true,
  49. },
  50. noStyle: {
  51. type: Boolean,
  52. default: false,
  53. },
  54. noScroll: {
  55. type: Boolean,
  56. default: false,
  57. },
  58. })
  59. const id = RandomUtils.genNonDuplicateIDHEX(12);
  60. const catalogItems = ref<CatalogItem[]>([]);
  61. const scrollContainer = ref<HTMLElement|null>(null);
  62. let lastStyleTag : HTMLElement|null = null;
  63. function genTagCss() {
  64. if (Object.keys(props.tagStyle).length > 0) {
  65. const style = document.createElement('style');
  66. let css = '';
  67. for (const key in props.tagStyle) {
  68. css += `.rich-html div[data-r-id="${id}"] ${key} { ${props.tagStyle[key]} } `
  69. }
  70. style.innerHTML = css;
  71. document.body.appendChild(style);
  72. lastStyleTag = style;
  73. }
  74. }
  75. function generateCatalog() {
  76. catalogItems.value = [];
  77. if (!props.catalog)
  78. return;
  79. let anchrId = 0;
  80. for (let i = 1; i <= 6; i++) {
  81. const heades = document.querySelectorAll(`.rich-html div[data-r-id="${id}"] h${i}`);
  82. for (const header of heades) {
  83. anchrId++;
  84. if (header instanceof HTMLHeadingElement) {
  85. if (header.id == '')
  86. header.id = 'header' + anchrId + 'a' + RandomUtils.genNonDuplicateIDHEX(12);
  87. catalogItems.value.push({
  88. title: header.textContent || '',
  89. scrollPos: header.offsetTop,
  90. level: i,
  91. anchor: header.id,
  92. });
  93. }
  94. }
  95. }
  96. catalogItems.value.sort((a, b) => {
  97. return a.scrollPos - b.scrollPos;
  98. })
  99. }
  100. watch(() => props.contents, () => {
  101. setTimeout(() => {
  102. generateCatalog();
  103. }, 200);
  104. }, { immediate: true })
  105. onBeforeUnmount(() => {
  106. if (lastStyleTag) {
  107. lastStyleTag.parentElement?.removeChild(lastStyleTag);
  108. lastStyleTag = null;
  109. }
  110. })
  111. onMounted(() => {
  112. genTagCss()
  113. });
  114. </script>
  115. <style lang="scss">
  116. .nana-rich-html-container {
  117. position: relative;
  118. display: flex;
  119. flex-direction: row;
  120. overflow-y: scroll;
  121. &.no-scroll {
  122. overflow: hidden;
  123. }
  124. &::-webkit-scrollbar {
  125. width: 5px;
  126. height: 5px;
  127. }
  128. &::-webkit-scrollbar-thumb {
  129. background: #d6d6d6;
  130. opacity: .7;
  131. border-radius: 3px;
  132. &:hover {
  133. background: #707070;
  134. }
  135. }
  136. &::-webkit-scrollbar-track {
  137. background: transparent;
  138. }
  139. .rich-html {
  140. flex: 1 1 100%;
  141. }
  142. .rich-html-catalog {
  143. position: sticky;
  144. top: 0px;
  145. width: 18rem;
  146. }
  147. }
  148. </style>