Skip to content

Commit eb1ed7e

Browse files
committed
feat: add maxShowViewContainerHeightPx and compactShowPreview props for the markdown editor
https://web.tracklify.com/project/2b7ZVgE5/AdminForth/1401/j3wbyLxf/add-compactpreview-and-maxheig
1 parent e35ee9a commit eb1ed7e

File tree

3 files changed

+139
-33
lines changed

3 files changed

+139
-33
lines changed

custom/MarkdownRenderer.vue

Lines changed: 123 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,121 @@
11
<template>
2-
<div
3-
v-html="purifiedHtml"
4-
class="prose
5-
prose-p:my-0
6-
prose-headings:my-0
7-
prose-h1:text-[1.6rem]
8-
prose-ul:my-0
9-
prose-ol:my-0
10-
prose-li:my-0
11-
prose-pre:my-0
12-
prose-hr:my-1
13-
leading-tight
14-
dark: dark:text-gray-300
15-
dark:[&_th]:text-white
16-
dark:[&_td]:text-white
17-
dark:[&_thead]:border-b-gray-600
18-
dark:[&_code]:text-white
19-
dark:[&_h1]:text-white dark:[&_h2]:text-gray-100 dark:[&_h3]:text-gray-200
20-
dark:[&_a]:text-white dark:[&_a:hover]:text-white
21-
dark:[&_pre]:bg-black dark:[&_pre]:border dark:[&_border-slate-800]
22-
dark:[&_strong]:text-white
23-
dark:[&_em]:text-gray-400
24-
dark:[&_del]:text-gray-600">
25-
</div>
26-
</template>
27-
28-
<script setup lang="ts">
29-
import { computed } from 'vue';
30-
import { marked } from "marked";
31-
import DOMPurify from "dompurify";
32-
33-
const props = defineProps<{ column: any; record: any }>();
2+
<div>
3+
<div
4+
:class="{ 'overflow-hidden': !expanded && isOverflowing }"
5+
:style="!expanded && isOverflowing ? computedMaxHeight : {}"
6+
>
7+
<div
8+
ref="contentRef"
9+
v-html="purifiedHtml"
10+
class="prose
11+
prose-p:my-0
12+
prose-headings:my-0
13+
prose-h1:text-[1.6rem]
14+
prose-ul:my-0
15+
prose-ol:my-0
16+
prose-li:my-0
17+
prose-pre:my-0
18+
prose-hr:my-1
19+
prose-table:my-0
20+
leading-none
21+
dark: dark:text-gray-300
22+
dark:[&_th]:text-white
23+
dark:[&_td]:text-white
24+
dark:[&_thead]:border-b-gray-600
25+
dark:[&_code]:text-white
26+
dark:[&_h1]:text-white dark:[&_h2]:text-gray-100 dark:[&_h3]:text-gray-200
27+
dark:[&_a]:text-white dark:[&_a:hover]:text-white
28+
dark:[&_pre]:bg-black dark:[&_pre]:border dark:[&_border-slate-800]
29+
dark:[&_strong]:text-white
30+
dark:[&_em]:text-gray-400
31+
dark:[&_del]:text-gray-600"
32+
:class="compactPreviewStyles"
33+
></div>
34+
</div>
35+
<button
36+
v-if="isOverflowing || expanded"
37+
@click="expanded = !expanded"
38+
class="mt-1 text-sm text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 font-medium focus:outline-none hover:underline"
39+
>
40+
{{ expanded ? 'Show less' : 'Show more' }}
41+
</button>
42+
</div>
43+
</template>
44+
45+
<script setup lang="ts">
46+
import { computed, ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
47+
import { marked } from "marked";
48+
import DOMPurify from "dompurify";
3449
35-
const purifiedHtml = computed(() => {
50+
const props = defineProps<{
51+
column: any;
52+
record: any;
53+
meta: {
54+
pluginInstanceId: string;
55+
compactShowPreview?: boolean;
56+
maxShowViewContainerHeightPx?: number;
57+
}
58+
}>();
59+
60+
const compactPreviewStyles = computed(() => {
61+
if (props.meta.compactShowPreview) {
62+
return `
63+
prose-ul:leading-[0.2]
64+
prose-ol:leading-[0.2]
65+
prose-li:leading-[1]
66+
prose-h1:leading-none
67+
prose-h2:leading-none
68+
prose-h3:leading-none
69+
prose-h1:text-[1.3rem]
70+
prose-h2:text-[1.2rem]
71+
prose-h3:text-[1.1rem]
72+
prose-blockquote:my-0
73+
`
74+
} else {
75+
return '';
76+
}
77+
});
78+
79+
const computedMaxHeight = computed(() => {
80+
if (props.meta.maxShowViewContainerHeightPx) {
81+
return { maxHeight: `${props.meta.maxShowViewContainerHeightPx}px` };
82+
}
83+
return {};
84+
});
85+
86+
const contentRef = ref<HTMLElement | null>(null);
87+
const expanded = ref(false);
88+
const isOverflowing = ref(false);
3689
90+
let resizeObserver: ResizeObserver | null = null;
91+
92+
function checkOverflow() {
93+
if (!props.meta.maxShowViewContainerHeightPx || !contentRef.value) {
94+
isOverflowing.value = false;
95+
return;
96+
}
97+
isOverflowing.value = contentRef.value.scrollHeight > props.meta.maxShowViewContainerHeightPx;
98+
}
99+
100+
function setupObserver() {
101+
resizeObserver?.disconnect();
102+
if (!props.meta.maxShowViewContainerHeightPx || !contentRef.value) return;
103+
104+
resizeObserver = new ResizeObserver(() => checkOverflow());
105+
resizeObserver.observe(contentRef.value);
106+
}
107+
108+
onMounted(async () => {
109+
await nextTick();
110+
checkOverflow();
111+
setupObserver();
112+
});
113+
114+
onBeforeUnmount(() => {
115+
resizeObserver?.disconnect();
116+
});
117+
118+
const purifiedHtml = computed(() => {
37119
if (!props.record[props.column.name]) return '-';
38120
const html = marked(String(props.record[props.column.name]));
39121
if (html instanceof Promise) {
@@ -42,5 +124,13 @@
42124
}
43125
return DOMPurify.sanitize(html);
44126
});
127+
128+
// Re-check overflow whenever content changes
129+
watch(purifiedHtml, async () => {
130+
expanded.value = false;
131+
await nextTick();
132+
checkOverflow();
133+
setupObserver();
134+
});
45135
</script>
46136

index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ export default class MarkdownPlugin extends AdminForthPlugin {
106106
meta: {
107107
pluginInstanceId: this.pluginInstanceId,
108108
columnName: fieldName,
109+
compactShowPreview: this.options.compactShowPreview ?? true,
110+
maxShowViewContainerHeightPx: this.options.maxShowViewContainerHeightPx,
109111
},
110112
};
111113

@@ -114,6 +116,8 @@ export default class MarkdownPlugin extends AdminForthPlugin {
114116
meta: {
115117
pluginInstanceId: this.pluginInstanceId,
116118
columnName: fieldName,
119+
compactShowPreview: this.options.compactShowPreview ?? true,
120+
maxShowViewContainerHeightPx: this.options.maxShowViewContainerHeightPx,
117121
},
118122
};
119123

types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,16 @@ export interface PluginOptions {
8282
link?: boolean;
8383
codeBlock?: boolean;
8484
};
85+
86+
/**
87+
* When true, the preview will be shown in a compact mode with a "Show more" button if the content exceeds the specified max height.
88+
* This allows users to expand and collapse the preview as needed, improving readability for long content.
89+
* Default is true.
90+
*/
91+
compactShowPreview?: boolean;
92+
/**
93+
* Maximum height in pixels for the preview container when `compactShowPreview` is enabled.
94+
* If the content exceeds this height, a "Show more" button will appear to allow users to expand the preview.
95+
*/
96+
maxShowViewContainerHeightPx?: number;
8597
}

0 commit comments

Comments
 (0)