SimpleRichHtml.vue 3.0 KB

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