PostController
public function fetchUrlPreview(Request $request)
{
$data = $request->validate([
'url' => 'url'
]);
$url = $data['url'];
$html = file_get_contents($url);
$dom = new \DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($html);
libxml_use_internal_errors(false);
$ogTags = [];
$metaTags = $dom->getElementsByTagName('meta');
foreach ($metaTags as $tag) {
$property = $tag->getAttribute('property');
if (str_starts_with($property, 'og:')) {
$ogTags[$property] = $tag->getAttribute('content');
}
}
return $ogTags;
}
StorePostRequest
return [
'preview' => ['nullable', 'array'],
'preview_url' => ['nullable', 'string'],
];
protected function prepareForValidation()
{
$body = $this->input('body') ?: '';
$previewUrl = $this->input('preview_url') ?: '';
$trimmedBody = trim(strip_tags($body));
if ($trimmedBody === $previewUrl) {
$body = '';
}
$this->merge([
'user_id' => auth()->user()->id,
'body' => $body
]);
}
PostResources
return [
'preview' => $this->preview,
'preview_url' => $this->preview_url,
];
Post
protected $fillable = ['user_id', 'body', 'group_id', 'preview', 'preview_url'];
protected $casts = [
'preview' => 'json',
];
composer.json
"require": {
"ext-dom": "*",
}
post migration
$table->json('preview')->nullable();
$table->string('preview_url', 2000)->nullable();
PostModal.vue
import UrlPreview from "@/Components/app/UrlPreview.vue";
const editor = ClassicEditor;
const editorConfig = {
mediaEmbed: {
removeProviders: ['dailymotion', 'spotify', 'youtube', 'vimeo', 'instagram', 'twitter', 'googleMaps', 'flickr', 'facebook']
},
toolbar: [
'heading',
'|',
'bold',
'italic',
'|',
'link',
'|',
'bulletedList',
'numberedList',
'|',
'outdent',
'indent',
'|',
'blockQuote', ]
};
const form = useForm({
body: '',
group_id : null,
attachments : [],
deleted_file_ids : [],
preview: {},
preview_url: null,
_method : 'POST'
})
watch(() => props.post, () => {
form.body = props.post.body ||''
onInputChange();
})
function fetchPreview(url) {
if (url === form.preview_url) {
return;
}
form.preview_url = url
form.preview = {}
if (url) {
axiosClient.post(route('post.fetchUrlPreview'), {url})
.then(({data}) => {
form.preview = {
title: data['og:title'],
description: data['og:description'],
image: data['og:image']
}
})
.catch(err => {
console.log(err)
})
}
}
function onInputChange() {
let url = matchHref()
if (!url) {
url = matchLink()
}
fetchPreview(url)
}
function matchHref() {
const urlRegex = /<a.+href="((https?):\/\/[^"]+)"/;
const match = form.body.match(urlRegex);
if (match && match.length > 0) {
return match[1];
}
return null;
}
function matchLink() {
const urlRegex = /(?:https?):\/\/[^\s<]+/;
const match = form.body.match(urlRegex);
if (match && match.length > 0) {
return match[0];
}
return null
}
<ckeditor :editor="editor" v-model="form.body" :config="editorConfig" @input="onInputChange"></ckeditor>
<UrlPreview :preview="form.preview" :url="form.preview_url" />
UrlPreview.vue
<script setup>
defineProps({
preview: Object,
url: String
})
</script>
<template>
<a :href="url" target="_blank"
v-if="preview && preview.title"
class="block mt-4 border border-indigo-200 bg-indigo-50">
<img :src="preview.image"
class="max-w-full"
:alt="preview.title"/>
<div class="p-2">
<h3 class="font-semibold">{{ preview.title }}</h3>
<p class="text-sm m-none">{{ preview.description }}</p>
</div>
</a>
<a :href="url" target="_blank"
v-else-if="url"
class="block mt-4 border border-indigo-200 bg-indigo-50">
<div class="p-2">
{{url}}
</div>
</a>
</template>
route
Route::post('/fetch-url-preview', [PostController::class, 'fetchUrlPreview'])->name('post.fetchUrlPreview');
Kaynaklar:
Implement URL Preview - https://www.youtube.com/watch?v=XTGBLHwvE20
Debounce – How to Delay a Function in JavaScript (JS ES6 Example) - https://www.freecodecamp.org/news/javascript-debounce-example