3 changed files with 104 additions and 190 deletions
-
100src/views/blog/ContentDetail.vue
-
97src/views/blog/blogcontent/BlogDetailView.vue
-
97src/views/blog/diarycontent/DiaryDetailView.vue
@ -0,0 +1,100 @@ |
|||||
|
<template> |
||||
|
<div :id="containerId"> |
||||
|
<div v-for="anchor in titles" :style="{ padding: `4px 0 4px ${anchor.indent * 20}px` }" |
||||
|
@click="handleAnchorClick(anchor)" :key="anchor.lineIndex"> |
||||
|
<a style="cursor: pointer">{{ anchor.title }}</a> |
||||
|
</div> |
||||
|
<a-card> |
||||
|
<v-md-preview v-if="text.content" :text="text.content" v-bind="previewProps" /> |
||||
|
</a-card> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, onMounted, nextTick } from 'vue'; |
||||
|
import { useRoute } from 'vue-router'; |
||||
|
import { get } from '@/tools/request'; |
||||
|
|
||||
|
const route = useRoute(); |
||||
|
|
||||
|
const props = defineProps<{ |
||||
|
apiUrl: string; |
||||
|
contentField: string; |
||||
|
containerId: string; |
||||
|
}>(); |
||||
|
|
||||
|
const text = ref({ content: "" }); |
||||
|
|
||||
|
const titles = ref<{ title: string; lineIndex: string; indent: number }[]>([]); |
||||
|
|
||||
|
// Props for v-md-preview component |
||||
|
const previewProps = { |
||||
|
leftToolbar: "undo redo clear | h bold italic strikethrough quote | ul ol table hr | link image code | save | tip | emoji" |
||||
|
}; |
||||
|
|
||||
|
// Fetch content |
||||
|
const fetchContent = async () => { |
||||
|
try { |
||||
|
const response = await get(`${props.apiUrl}/${route.params.id}`); |
||||
|
if (response.data.data) { |
||||
|
text.value.content = response.data.data[props.contentField]; |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error("Error fetching content:", error); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Process headings after content is fetched |
||||
|
const processHeadings = async () => { |
||||
|
await nextTick(); |
||||
|
const anchors = document.querySelectorAll(`#${props.containerId} h1, #${props.containerId} h2, #${props.containerId} h3, #${props.containerId} h4, #${props.containerId} h5, #${props.containerId} h6`); |
||||
|
const titlesArray = Array.from(anchors).filter((title: any) => !!title.innerText.trim()); |
||||
|
|
||||
|
if (!titlesArray.length) { |
||||
|
titles.value = []; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const hTags = Array.from(new Set(titlesArray.map((title: any) => title.tagName))).sort(); |
||||
|
|
||||
|
titles.value = titlesArray.map((el: any) => ({ |
||||
|
title: el.innerText, |
||||
|
lineIndex: el.getAttribute('data-v-md-line') || '', |
||||
|
indent: hTags.indexOf(el.tagName), |
||||
|
})); |
||||
|
}; |
||||
|
|
||||
|
// Handle anchor click |
||||
|
const handleAnchorClick = (anchor: { title: string; lineIndex: string }) => { |
||||
|
const heading = document.querySelector(`#${props.containerId} [data-v-md-line="${anchor.lineIndex}"]`); |
||||
|
|
||||
|
if (heading) { |
||||
|
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
||||
|
} else { |
||||
|
// Use MutationObserver to wait for the target element to be available |
||||
|
const observer = new MutationObserver((mutations, obs) => { |
||||
|
const heading = document.querySelector(`#${props.containerId} [data-v-md-line="${anchor.lineIndex}"]`); |
||||
|
if (heading) { |
||||
|
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
||||
|
obs.disconnect(); // Stop observing once the target element is found |
||||
|
} |
||||
|
}); |
||||
|
const container = document.querySelector(`#${props.containerId}`) as HTMLElement; |
||||
|
observer.observe(container, { childList: true, subtree: true }); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Lifecycle hook to fetch content and process headings |
||||
|
onMounted(async () => { |
||||
|
await fetchContent(); |
||||
|
await processHeadings(); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
#blogDetail, #diaryDetail { |
||||
|
width: 45%; |
||||
|
margin: 0 24px; |
||||
|
} |
||||
|
</style> |
||||
|
|
@ -1,100 +1,7 @@ |
|||||
<template> |
<template> |
||||
<div id="blogDetail"> |
|
||||
<div v-for="anchor in titles" :style="{ padding: `4px 0 4px ${anchor.indent * 20}px` }" |
|
||||
@click="handleAnchorClick(anchor)" :key="anchor.lineIndex"> |
|
||||
<a style="cursor: pointer">{{ anchor.title }}</a> |
|
||||
</div> |
|
||||
<a-card> |
|
||||
<v-md-preview v-if="text.blogcontent" :text="text.blogcontent" v-bind="previewProps" /> |
|
||||
</a-card> |
|
||||
</div> |
|
||||
|
|
||||
|
<ContentDetail apiUrl="/blogs/list" contentField="blogcontent" containerId="blogDetail" /> |
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
import { ref, onMounted, nextTick } from 'vue'; |
|
||||
import { useRoute } from 'vue-router'; |
|
||||
import { get } from '@/tools/request'; |
|
||||
|
|
||||
const route = useRoute(); |
|
||||
const text = ref({ |
|
||||
blogtitle: "", |
|
||||
blogcontent: "" |
|
||||
}); |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
const titles = ref<{ title: string; lineIndex: string; indent: number }[]>([]); |
|
||||
|
|
||||
// Props for v-md-preview component |
|
||||
const previewProps = { |
|
||||
leftToolbar: "undo redo clear | h bold italic strikethrough quote | ul ol table hr | link image code | save | tip | emoji" |
|
||||
}; |
|
||||
|
|
||||
// Fetch blog content |
|
||||
const blogOneList = async () => { |
|
||||
try { |
|
||||
const response = await get(`/blogs/list/${route.params.id}`); |
|
||||
if (response.data.data) { |
|
||||
text.value.blogcontent = response.data.data.blogcontent; |
|
||||
} |
|
||||
} catch (error) { |
|
||||
console.error("Error fetching blog content:", error); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// Process headings after blog content is fetched |
|
||||
const processHeadings = async () => { |
|
||||
await nextTick(); |
|
||||
const anchors = document.querySelectorAll('#blogDetail h1, #blogDetail h2, #blogDetail h3, #blogDetail h4, #blogDetail h5, #blogDetail h6'); |
|
||||
const titlesArray = Array.from(anchors).filter((title: any) => !!title.innerText.trim()); |
|
||||
|
|
||||
if (!titlesArray.length) { |
|
||||
titles.value = []; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const hTags = Array.from(new Set(titlesArray.map((title: any) => title.tagName))).sort(); |
|
||||
|
|
||||
titles.value = titlesArray.map((el: any) => ({ |
|
||||
title: el.innerText, |
|
||||
lineIndex: el.getAttribute('data-v-md-line') || '', |
|
||||
indent: hTags.indexOf(el.tagName), |
|
||||
})); |
|
||||
}; |
|
||||
|
|
||||
// Handle anchor click |
|
||||
const handleAnchorClick = (anchor: { title: string; lineIndex: string }) => { |
|
||||
const heading = document.querySelector(`#blogDetail [data-v-md-line="${anchor.lineIndex}"]`); |
|
||||
|
|
||||
if (heading) { |
|
||||
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|
||||
} else { |
|
||||
// Use MutationObserver to wait for the target element to be available |
|
||||
const observer = new MutationObserver((mutations, obs) => { |
|
||||
const heading = document.querySelector(`#blogDetail [data-v-md-line="${anchor.lineIndex}"]`); |
|
||||
if (heading) { |
|
||||
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|
||||
obs.disconnect(); // Stop observing once the target element is found |
|
||||
} |
|
||||
}); |
|
||||
const blogDetail = document.querySelector('#blogDetail') as HTMLElement; |
|
||||
observer.observe(blogDetail, { childList: true, subtree: true }); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// Lifecycle hook to fetch blog content and process headings |
|
||||
onMounted(async () => { |
|
||||
await blogOneList(); |
|
||||
await processHeadings(); |
|
||||
}); |
|
||||
|
import ContentDetail from '@/views/blog/ContentDetail.vue'; |
||||
</script> |
</script> |
||||
|
|
||||
|
|
||||
<style scoped> |
|
||||
#blogDetail { |
|
||||
width: 45%; |
|
||||
margin: 0 24px; |
|
||||
} |
|
||||
</style> |
|
@ -1,100 +1,7 @@ |
|||||
<template> |
<template> |
||||
<div id="diaryDetail"> |
|
||||
<!-- <div v-for="anchor in titles" :style="{ padding: `4px 0 4px ${anchor.indent * 20}px` }" |
|
||||
@click="handleAnchorClick(anchor)" :key="anchor.lineIndex"> |
|
||||
<a style="cursor: pointer">{{ anchor.title }}</a> |
|
||||
</div> --> |
|
||||
<a-card> |
|
||||
<v-md-preview v-if="text.diarycontent" :text="text.diarycontent" v-bind="previewProps" /> |
|
||||
</a-card> |
|
||||
</div> |
|
||||
|
|
||||
|
<ContentDetail apiUrl="/diarys/list" contentField="diarycontent" containerId="diaryDetail" /> |
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
import { ref, onMounted, nextTick } from 'vue'; |
|
||||
import { useRoute } from 'vue-router'; |
|
||||
import { get } from '@/tools/request'; |
|
||||
|
|
||||
const route = useRoute(); |
|
||||
const text = ref({ |
|
||||
diarytitle: "", |
|
||||
diarycontent: "" |
|
||||
}); |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
const titles = ref<{ title: string; lineIndex: string; indent: number }[]>([]); |
|
||||
|
|
||||
// Props for v-md-preview component |
|
||||
const previewProps = { |
|
||||
leftToolbar: "undo redo clear | h bold italic strikethrough quote | ul ol table hr | link image code | save | tip | emoji" |
|
||||
}; |
|
||||
|
|
||||
// Fetch diary content |
|
||||
const diaryOneList = async () => { |
|
||||
try { |
|
||||
const response = await get(`/diarys/list/${route.params.id}`); |
|
||||
if (response.data.data) { |
|
||||
text.value.diarycontent = response.data.data.diarycontent; |
|
||||
} |
|
||||
} catch (error) { |
|
||||
console.error("Error fetching diary content:", error); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// Process headings after diary content is fetched |
|
||||
const processHeadings = async () => { |
|
||||
await nextTick(); |
|
||||
const anchors = document.querySelectorAll('#diaryDetail h1, #diaryDetail h2, #diaryDetail h3, #diaryDetail h4, #diaryDetail h5, #diaryDetail h6'); |
|
||||
const titlesArray = Array.from(anchors).filter((title: any) => !!title.innerText.trim()); |
|
||||
|
|
||||
if (!titlesArray.length) { |
|
||||
titles.value = []; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const hTags = Array.from(new Set(titlesArray.map((title: any) => title.tagName))).sort(); |
|
||||
|
|
||||
titles.value = titlesArray.map((el: any) => ({ |
|
||||
title: el.innerText, |
|
||||
lineIndex: el.getAttribute('data-v-md-line') || '', |
|
||||
indent: hTags.indexOf(el.tagName), |
|
||||
})); |
|
||||
}; |
|
||||
|
|
||||
// Handle anchor click |
|
||||
const handleAnchorClick = (anchor: { title: string; lineIndex: string }) => { |
|
||||
const heading = document.querySelector(`#diaryDetail [data-v-md-line="${anchor.lineIndex}"]`); |
|
||||
|
|
||||
if (heading) { |
|
||||
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|
||||
} else { |
|
||||
// Use MutationObserver to wait for the target element to be available |
|
||||
const observer = new MutationObserver((mutations, obs) => { |
|
||||
const heading = document.querySelector(`#diaryDetail [data-v-md-line="${anchor.lineIndex}"]`); |
|
||||
if (heading) { |
|
||||
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|
||||
obs.disconnect(); // Stop observing once the target element is found |
|
||||
} |
|
||||
}); |
|
||||
const diaryDetail = document.querySelector('#diaryDetail') as HTMLElement; |
|
||||
observer.observe(diaryDetail, { childList: true, subtree: true }); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// Lifecycle hook to fetch diary content and process headings |
|
||||
onMounted(async () => { |
|
||||
await diaryOneList(); |
|
||||
await processHeadings(); |
|
||||
}); |
|
||||
|
import ContentDetail from '@/views/blog/ContentDetail.vue'; |
||||
</script> |
</script> |
||||
|
|
||||
|
|
||||
<style scoped> |
|
||||
#diaryDetail { |
|
||||
width: 45%; |
|
||||
margin: 0 24px; |
|
||||
} |
|
||||
</style> |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue