separate vue logic into different components

This commit is contained in:
Julie Lenaerts 2023-11-20 15:50:02 +01:00
parent af3d06e7d3
commit 13c33567fd
3 changed files with 138 additions and 103 deletions

View File

@ -161,7 +161,7 @@ export interface LocationType {
title: TranslatableString; title: TranslatableString;
} }
export interface NewsItem { export interface NewsItemType {
id: number; id: number;
title: string; title: string;
content: string; content: string;

View File

@ -2,27 +2,7 @@
<div> <div>
<h1>{{ $t('widget.news.title') }}</h1> <h1>{{ $t('widget.news.title') }}</h1>
<ul class="scrollable"> <ul class="scrollable">
<li v-for="item in newsItems" :key="item.id"> <NewsItem v-for="item in newsItems" :item="item" :key="item.id" />
<h2>{{ item.title }}</h2>
<div class="content" v-if="shouldTruncate(item.content)">
<div v-html="prepareContent(item.content)"></div>
<span class="read-more" @click="() => openModal(item)">{{ $t('widget.news.readMore') }}</span>
</div>
<div class="content" v-else>
<div v-html="convertMarkdownToHtml(item.content)"></div>
</div>
<modal v-if="showModal" @close="closeModal">
<template #header>
<p class="news-title">{{ item.title }}</p>
</template>
<template #body>
<p class="news-date">
{{ $t('widget.news.date') }}: <span>{{ formatDate(item.startdate.date) }}</span>
</p>
<div v-html="convertMarkdownToHtml(item.content)"></div>
</template>
</modal>
</li>
</ul> </ul>
</div> </div>
</template> </template>
@ -30,54 +10,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { makeFetch } from '../../../lib/api/apiMethods'; import { makeFetch } from '../../../lib/api/apiMethods';
import Modal from '../../_components/Modal.vue'; // Adjust the import path import Modal from '../../_components/Modal.vue';
import { marked } from 'marked'; import { NewsItemType } from '../../../types';
import DOMPurify from 'dompurify'; import NewsItem from './NewsItem.vue';
import {DateTime, NewsItem} from '../../../types';
const newsItems = ref<NewsItem[]>([]) const newsItems = ref<NewsItemType[]>([])
const selectedArticle = ref<NewsItem | null>(null);
const showModal = ref(false);
const openModal = (item: NewsItem) => {
selectedArticle.value = item;
showModal.value = true;
};
const closeModal = () => {
selectedArticle.value = null;
showModal.value = false;
};;
const shouldTruncate = (content: string, maxLength = 100): boolean => {
return content.length > maxLength;
};
const truncateContent = (content: string, maxLength = 100): string => {
if (shouldTruncate(content, maxLength)) {
return content.slice(0, maxLength) + '...';
} else {
return content;
}
};
const convertMarkdownToHtml = (markdown: string): string => {
const rawHtml = marked(markdown);
return DOMPurify.sanitize(rawHtml)
};
const prepareContent = (content: string, maxLength = 100): string => {
const truncatedContent = truncateContent(content, maxLength);
return convertMarkdownToHtml(truncatedContent);
};
const formatDate = (datetime: DateTime): string => {
return new Date(datetime.toString()).toLocaleDateString('fr-FR')
}
onMounted(() => { onMounted(() => {
makeFetch('GET', '/api/1.0/main/news.json') makeFetch('GET', '/api/1.0/main/news.json')
.then((response: { results: NewsItem[] }) => { .then((response: { results: NewsItemType[] }) => {
// console.log('news articles', response.results) // console.log('news articles', response.results)
newsItems.value = response.results newsItems.value = response.results
}) })
@ -94,46 +35,13 @@ ul {
padding: 0; padding: 0;
} }
li { /*
margin-bottom: 20px;
overflow: hidden;
}
h2 {
margin-bottom: 10px;
}
.content {
max-height: 100px; /* Adjust the max height as needed */
overflow: hidden;
}
.scrollable { .scrollable {
overflow-y: auto; overflow-y: auto;
max-height: 150px; /* Same as .content max-height */ max-height: 150px; !* Same as .content max-height *!
margin-bottom: 10px; /* To give space for the scrollbar */ margin-bottom: 10px; !* To give space for the scrollbar *!
} }
*/
button {
cursor: pointer;
background-color: #3498db;
color: #fff;
border: none;
padding: 5px 10px;
border-radius: 3px;
}
.read-more {
color: #3498db; /* Adjust the color as needed */
cursor: pointer;
}
.news-date {
font-style: italic;
}
.news-title {
font-weight: bold;
text-transform: uppercase;
}
</style> </style>

View File

@ -0,0 +1,127 @@
<template>
<li>
<h2>{{ props.item.title }}</h2>
<div class="content" v-if="shouldTruncate(item.content)">
<div v-html="prepareContent(item.content)"></div>
<span class="read-more" @click="() => openModal(item)">{{ $t('widget.news.readMore') }}</span>
</div>
<div class="content" v-else>
<div v-html="convertMarkdownToHtml(item.content)"></div>
</div>
<modal v-if="showModal" @close="closeModal">
<template #header>
<p class="news-title">{{ item.title }}</p>
</template>
<template #body>
<p class="news-date">
{{ $t('widget.news.date') }}: <span>{{ formatDate(item.startdate.date) }}</span>
</p>
<div v-html="convertMarkdownToHtml(item.content)"></div>
</template>
</modal>
</li>
</template>
<script setup lang="ts">
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import { DateTime, NewsItemType } from "../../../types";
import type { PropType } from 'vue'
import { defineProps, ref } from "vue";
import {dateToISO} from "../../../chill/js/date";
const props = defineProps({
item: {
type: Object as PropType<NewsItemType>,
required: true
}
})
const selectedArticle = ref<NewsItemType | null>(null);
const showModal = ref(false);
const openModal = (item: NewsItemType) => {
selectedArticle.value = item;
showModal.value = true;
};
const closeModal = () => {
selectedArticle.value = null;
showModal.value = false;
};
const shouldTruncate = (content: string, maxLength = 100): boolean => {
return content.length > maxLength;
};
const truncateContent = (content: string, maxLength = 100): string => {
if (shouldTruncate(content, maxLength)) {
const lastSpaceIndex = content.lastIndexOf(' ', maxLength);
console.log(lastSpaceIndex)
if (lastSpaceIndex !== -1) {
return content.slice(0, lastSpaceIndex) + '...';
} else {
return content.slice(0, maxLength) + '...';
}
} else {
return content;
}
};
const convertMarkdownToHtml = (markdown: string): string => {
const rawHtml = marked(markdown);
return DOMPurify.sanitize(rawHtml)
};
const prepareContent = (content: string, maxLength = 100): string => {
const truncatedContent = truncateContent(content, maxLength);
return convertMarkdownToHtml(truncatedContent);
};
const formatDate = (datetime: DateTime): string|null => {
return dateToISO(new Date(datetime.toString()))
}
</script>
<style scoped>
li {
margin-bottom: 20px;
overflow: hidden;
}
h2 {
margin-bottom: 10px;
}
.content {
max-height: 100px; /* Adjust the max height as needed */
overflow: hidden;
}
button {
cursor: pointer;
background-color: #3498db;
color: #fff;
border: none;
padding: 5px 10px;
border-radius: 3px;
}
.read-more {
color: #3498db; /* Adjust the color as needed */
cursor: pointer;
}
.news-date {
font-style: italic;
}
.news-title {
font-weight: bold;
text-transform: uppercase;
}
</style>