add backend supporting opengraph and comments

This commit is contained in:
2024-11-29 19:14:12 -07:00
parent 09a0c44e50
commit d2efb2f0cb
15 changed files with 633 additions and 167 deletions

View File

@ -1,13 +1,23 @@
<script setup lang="ts">
const props = defineProps({
content: {
type: String,
required: true,
},
avatar: {
type: String,
required: true,
},
});
</script>
<template>
<div class="comment">
<img src="https://cdn.vuetifyjs.com/images/lists/1.jpg" alt="User Avatar" class="avatar"/>
<img :src="props.avatar" alt="User Avatar" class="avatar"/>
<!-- <img src="https://cdn.vuetifyjs.com/images/lists/1.jpg" alt="User Avatar" class="avatar"/>-->
<div class="comment-content">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi.</p>
<p>{{props.content}}</p>
</div>
</div>
</template>

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { onMounted, watch, ref } from 'vue'
const BACKEND_URL = 'http://localhost:8080';
type OpenGraph = {
type: string;
@ -39,7 +40,7 @@ type OpenGraph = {
} | null;
};
const url = defineProps({
const props = defineProps({
url: {
type: String,
required: true,
@ -68,119 +69,24 @@ const og = ref<OpenGraph>({
},
});
function getOpenGraph() {
if (!props.url) {
return;
}
fetch(` ${BACKEND_URL}/api/v1/opengraph?url=${props.url}`)
// fetch(`https://anyremark.com/api/v1/opengraph?url=${url}`)
.then(response => response.json())
.then(data => {
og.value = data;
});
}
watch(() => props.url, () => {
getOpenGraph();
});
onMounted(() => {
// fetch(`https://anyremark.com/api/opengraph?url=${url}`)
// .then(response => response.json())
// .then(data => {
// og.value = data;
// });
// og.value = {
// type: "video.other",
// url: "https://www.youtube.com/watch?v=iedMwhLrFQQ",
// title: "Stingrays with Friends - FPV Formation",
// description: "My favorite thing to do in this hobby is fly FPV with friends and chase planes.Morning FPV flights with Ben and Eric.If you are interested in this plane (The...",
// determiner: "",
// site_name: "YouTube",
// locale: "",
// locales_alternate: "",
// images: [
// {
// url: "https://i.ytimg.com/vi/iedMwhLrFQQ/maxresdefault.jpg",
// secure_url: "",
// type: "",
// width: 1280,
// height: 720
// }
// ],
// audios: [],
// videos: [
// {
// url: "https://www.youtube.com/embed/iedMwhLrFQQ",
// secure_url: "https://www.youtube.com/embed/iedMwhLrFQQ",
// type: "text/html",
// width: 1280,
// height: 720
// }
// ],
// article: {
// published_time: null,
// modified_time: null,
// expiration_time: null,
// section: "",
// tags: null,
// authors: null
// },
// };
console.log(url);
// og.value = {
// "type": "video.other",
// "url": "https://vimeo.com/867950660",
// "title": "Hands Of Sicily.",
// "description": "The hands of a human can tell a million stories. This film is an intimate portrait of Sicily. It simply shows the hands of the people from the island in action.\u0026hellip;",
// "determiner": "",
// "site_name": "Vimeo",
// "locale": "",
// "locales_alternate": null,
// "images": [
// {
// "url": "https://i.vimeocdn.com/video/1728918730-bfdfd12d26406a06d263d62fef2da83aea8a684a0eeff059bb2ae6c41eda4aec-d?f=webp",
// "secure_url": "https://i.vimeocdn.com/video/1728918730-bfdfd12d26406a06d263d62fef2da83aea8a684a0eeff059bb2ae6c41eda4aec-d?f=webp",
// "type": "image/webp",
// "width": 1280,
// "height": 953
// }
// ],
// "audios": null,
// "videos": [
// {
// "url": "https://player.vimeo.com/video/867950660?autoplay=1\u0026amp;h=f094db59eb",
// "secure_url": "https://player.vimeo.com/video/867950660?autoplay=1\u0026amp;h=f094db59eb",
// "type": "text/html",
// "width": 1280,
// "height": 953
// }
// ],
// "article": {
// "published_time": null,
// "modified_time": null,
// "expiration_time": null,
// "section": "",
// "tags": null,
// "authors": null
// }
// };
og.value = {
"type": "article",
"url": "https://github.blog/changelog/2024-11-27-access-a-repositorys-secret-scanning-scan-history-with-the-rest-api/",
"title": "Access a repositorys secret scanning scan history with the REST API · GitHub Changelog",
"description": "Access a repository's secret scanning scan history with the REST API",
"determiner": "",
"site_name": "The GitHub Blog",
"locale": "en_US",
"locales_alternate": null,
"images": [
{
"url": "https://github.blog/wp-content/uploads/2024/08/d34e9c19123898a8a886147f37a1d167130d1c15be6d399a9c4b30ee6f2a7395-1200x630-1.png?fit=1200%2C630",
"secure_url": "",
"type": "image/png",
"width": 1200,
"height": 630
}
],
"audios": null,
"videos": null,
"article": {
"published_time": null,
"modified_time": null,
"expiration_time": null,
"section": "",
"tags": null,
"authors": null
}
};
getOpenGraph();
});
const videoVisible = ref(false);
@ -191,45 +97,14 @@ function showVideo() {
</script>
<template>
<!-- Show the topic webpage's open graph content-->
<!-- server side will use https://github.com/dyatlov/go-opengraph -->
<!-- example opengraph output here
{
"type": "video.other",
"url": "https://www.youtube.com/watch?v=iedMwhLrFQQ",
"title": "Stingrays with Friends - FPV Formation",
"description": "My favorite thing to do in this hobby is fly FPV with friends and chase planes.Morning FPV flights with Ben and Eric.If you are interested in this plane (The...",
"determiner": "",
"site_name": "YouTube",
"locale": "",
"locales_alternate": null,
"images": [
{
"url": "https://i.ytimg.com/vi/iedMwhLrFQQ/maxresdefault.jpg",
"secure_url": "",
"type": "",
"width": 1280,
"height": 720
}
],
"audios": null,
"videos": [
{
"url": "https://www.youtube.com/embed/iedMwhLrFQQ",
"secure_url": "https://www.youtube.com/embed/iedMwhLrFQQ",
"type": "text/html",
"width": 1280,
"height": 720
}
]
}
-->
<article>
<h1><a :href="og.url">{{og.title}}</a></h1>
<p>{{og.description}}</p>
<h1><a :href="!!og.url ? og.url : props.url">{{!!og.title ? og.title : props.url}}</a></h1>
<p v-if="!!og.description">{{og.description}}</p>
<div class="article-thumb-container" v-if="og.type === 'article'">
<img :src="!!og.images.length ? og.images[0].url : ''" alt="Article Thumbnail" />
<a :href="props.url" target="_blank">{{props.url}}</a>
<div class="images-container" v-if="!!og.images?.length && og.type !== 'video.other'">
<img v-on:error="() => og.images = []" :src="!!og.images.length ? og.images[0].url : ''" alt="Article Thumbnail" />
</div>
<div class="video-other" v-if="og.type === 'video.other'">
@ -239,7 +114,7 @@ function showVideo() {
<div class="non-yt-container" v-if="!!og.videos?.length && (!og.url.startsWith('https://www.youtube.com') && !og.url.startsWith('https://vimeo.com'))">
<div class="thumbnail-container" @click="showVideo" v-if="!videoVisible">
<img :src="!!og.images.length ? og.images[0].url : ''" alt="Video Thumbnail" />
<img v-on:error="() => og.images = []" :src="!!og.images.length ? og.images[0].url : ''" alt="Video Thumbnail" />
<div class="play-button"></div>
</div>
<div class="thumbnail-container" v-else>
@ -261,13 +136,13 @@ article {
align-items: flex-start;
}
.article-thumb-container {
.images-container {
width: 100%;
margin-top: 20px;
margin-bottom: 20px;
}
.article-thumb-container img {
.images-container img {
width: 100%;
height: auto;
max-width: 100%;
@ -298,9 +173,6 @@ article {
.non-yt-container, .non-yt-container>div {
width: 100%;
/* padding-bottom: 56.25%;
margin-top: 20px;
margin-bottom: 20px; */
}
.thumbnail-container {

View File

@ -2,10 +2,74 @@
import CommentComponent from '@/components/CommentComponent.vue'
import OpenGraphComponent from '@/components/OpenGraphComponent.vue'
import { ref } from 'vue'
import { onMounted, ref, watch } from 'vue'
const BACKEND_URL = 'http://localhost:8080';
interface Comment {
id: string;
content: string;
commenter: string;
parent_id: string;
webpage_id: string;
created_at: string;
}
// const url = ref('https://www.youtube.com/watch?v=iedMwhLrFQQ');
const url = ref('https://vimeo.com/867950660');
// const url = ref('https://vimeo.com/867950660');
const url = ref('');
const comment = ref('');
const comments = ref<Comment[]>([]);
onMounted(() => {
// get target url from query param 'url'
const urlParams = new URLSearchParams(window.location.search);
const urlParam = urlParams.get('url');
if (urlParam) {
url.value = urlParam;
}
});
watch(url, (newVal) => {
if (newVal) {
getComments();
}
});
function getComments() {
fetch(`${BACKEND_URL}/api/v1/comments?url=${url.value}`)
.then((res) => res.json())
.then((data) => {
if (!!data) {
comments.value = data;
}
console.log(data);
});
}
function handleSubmit(e: Event) {
e.preventDefault();
fetch(`${BACKEND_URL}/api/v1/comment`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: url.value,
comment: comment.value,
user_id: 'u_12345'
}),
})
.then((res) => {
if (res.ok) {
console.log('Comment submitted');
comment.value = '';
getComments();
}
}).catch((err) => {
console.error('Error:', err);
});
console.log('submit');
}
</script>
<template>
@ -18,13 +82,17 @@ const url = ref('https://vimeo.com/867950660');
<section class="comments">
<h2>Comments</h2>
<form class="comment-form">
<textarea placeholder="Add a comment..."></textarea>
<form class="comment-form" v-on:submit="handleSubmit">
<textarea placeholder="Add a comment..." v-model="comment"></textarea>
<button type="submit">Submit</button>
</form>
<div v-for="i in 5" :key="i">
<CommentComponent />
<div v-for="(remark, i) in comments" :key="i">
<CommentComponent :content="remark.content" :avatar="`https://cdn.vuetifyjs.com/images/lists/1.jpg`" />
</div>
<div v-if="comments.length === 0">
<p>No comments yet.</p>
</div>
</section>