initial commit

This commit is contained in:
2025-11-11 14:55:29 +07:00
commit 7c17aa7843
2490 changed files with 606138 additions and 0 deletions

View File

@@ -0,0 +1,606 @@
@extends('layouts.backendTemplate')
@section('content')
<style>
.video-responsive {
position: relative;
padding-bottom: 56.25%;
/* Aspect ratio 16:9 */
height: 0;
overflow: hidden;
max-width: 100%;
background: #000;
}
.video-responsive video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.cdx-input.image-tool__caption {
display: none;
}
.block-wrapper {
margin-top: 10px;
border: 1px solid #ddd;
padding: 10px;
border-radius: 4px;
}
.invalid {
border-color: red;
}
/* Responsive Video Container */
.video-wrapper {
position: relative;
padding-bottom: 56.25%;
/* 16:9 aspect ratio */
height: 0;
overflow: hidden;
}
/* Responsive Video Container */
.video-wrapper {
position: relative;
padding-bottom: 56.25%;
/* 16:9 aspect ratio */
height: 0;
overflow: hidden;
}
.video-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
</style>
<!--begin::Main-->
<div class="app-main flex-column flex-row-fluid" id="kt_app_main">
<div class="d-flex flex-column flex-column-fluid">
@include('uc.admin.breadcrumb', [
'title' => 'CHIANG MAI AND NOWHERE ELSE : Edit Article',
'pageName' => 'Edit',
'pageParent' => 'Article Management',
'pageParentLink' => url('no-where-else/article'),
])
<div id="kt_app_content" class="app-content flex-column-fluid">
<div id="kt_app_content_container" class="app-container container-xxl">
<form id="frmUpdate" class="form d-flex flex-column flex-lg-row"
action="{{ url('no-where-else/update-article') }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="id" value="{{ $itemView->id }}">
<input type="hidden" name="_method" value="POST">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
{{-- Left --}}
<div class="d-flex flex-column gap-7 gap-lg-10 w-100 w-lg-300px mb-7 me-lg-10">
<div class="card card-flush py-4">
<div class="card-body text-center pt-0">
<style>
.image-input-placeholder {
background-image: url('{{ url('assets/media/svg/files/blank-image.svg') }}');
}
[data-bs-theme="dark"] .image-input-placeholder {
background-image: url('../media/svg/files/blank-image-dark.svg');
}
</style>
<div class="image-input image-input-empty image-input-outline image-input-placeholder mb-3"
data-kt-image-input="true">
@if ($itemView->image_url && $itemView->image_name)
<div class="image-input-wrapper w-150px h-150px"
style="background-image: url('{{ $itemView->image_url . '/thumbnail/' . $itemView->image_name }}')">
</div>
@else
<div class="image-input-wrapper w-150px h-150px"></div>
@endif
<label
class="btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow"
data-kt-image-input-action="change" data-bs-toggle="tooltip"
title="Change avatar">
<i class="bi bi-pencil-fill fs-7"></i>
<input type="file" name="avatar" accept=".png, .jpg, .jpeg" />
<input type="hidden" name="avatar_remove" />
</label>
<span
class="btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow"
data-kt-image-input-action="cancel" data-bs-toggle="tooltip"
title="Cancel avatar">
<i class="bi bi-x fs-2"></i>
</span>
<span
class="btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow"
data-kt-image-input-action="remove" data-bs-toggle="tooltip"
title="Remove avatar">
<i class="bi bi-x fs-2"></i>
</span>
</div>
<div class="text-muted fs-7">Set the profile picture. Only *.png, *.jpg and *.jpeg
image
files are accepted</div>
</div>
<div class="card-header" style="border: unset;">
<!--begin::Card title-->
<div class="required card-title">
<h2>Active</h2>
</div>
<!--end::Card title-->
</div>
<div class="card-body pt-0">
<label class="form-check form-switch form-check-custom form-check-solid">
<input class="form-check-input" name="active" type="checkbox" value="1"
{{ $itemView->active == 1 ? 'checked' : '' }} />
</label>
</div>
</div>
</div>
{{-- Right --}}
<div class="d-flex flex-column flex-row-fluid gap-7 gap-lg-10">
<div class="card card-flush py-4">
@if (Session::has('messageSuccess'))
<div class="card-body">
@include('uc/messageSuccess')
</div>
@endif
@if (Session::has('messageFail'))
<div class="card-body">
@include('uc/messageFail')
</div>
@endif
<div class="card-header">
<div class="card-title">
<h2>Overview</h2>
</div>
</div>
<div class="card-body pt-0">
<div class="mb-10 fv-row">
<label class="required form-label">Sub Categories</label>
<div class="d-flex align-items-center mt-3">
@foreach ($subCategoriesView as $item)
<label
class="form-check form-check-custom form-check-inline form-check-solid me-5 is-invalid">
<input class="form-check-input" name="sub_categories[]" type="checkbox"
value="{{ $item->id }}"
@if (in_array($item->id, explode(',', $itemView->sub_category_ids))) checked @endif>
<span class="fw-semibold ps-2 fs-6">
{{ $item->name . '(' . $item->name_en . ')' }}
</span>
</label>
@endforeach
</div>
</div>
<div class="mb-10 fv-row">
<div class="position-relative d-flex align-items-center">
<span class="svg-icon svg-icon-2 position-absolute mx-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3"
d="M21 22H3C2.4 22 2 21.6 2 21V5C2 4.4 2.4 4 3 4H21C21.6 4 22 4.4 22 5V21C22 21.6 21.6 22 21 22Z"
fill="currentColor" />
<path
d="M6 6C5.4 6 5 5.6 5 5V3C5 2.4 5.4 2 6 2C6.6 2 7 2.4 7 3V5C7 5.6 6.6 6 6 6ZM11 5V3C11 2.4 10.6 2 10 2C9.4 2 9 2.4 9 3V5C9 5.6 9.4 6 10 6C10.6 6 11 5.6 11 5ZM15 5V3C15 2.4 14.6 2 14 2C13.4 2 13 2.4 13 3V5C13 5.6 13.4 6 14 6C14.6 6 15 5.6 15 5ZM19 5V3C19 2.4 18.6 2 18 2C17.4 2 17 2.4 17 3V5C17 5.6 17.4 6 18 6C18.6 6 19 5.6 19 5Z"
fill="currentColor" />
<path
d="M8.8 13.1C9.2 13.1 9.5 13 9.7 12.8C9.9 12.6 10.1 12.3 10.1 11.9C10.1 11.6 10 11.3 9.8 11.1C9.6 10.9 9.3 10.8 9 10.8C8.8 10.8 8.59999 10.8 8.39999 10.9C8.19999 11 8.1 11.1 8 11.2C7.9 11.3 7.8 11.4 7.7 11.6C7.6 11.8 7.5 11.9 7.5 12.1C7.5 12.2 7.4 12.2 7.3 12.3C7.2 12.4 7.09999 12.4 6.89999 12.4C6.69999 12.4 6.6 12.3 6.5 12.2C6.4 12.1 6.3 11.9 6.3 11.7C6.3 11.5 6.4 11.3 6.5 11.1C6.6 10.9 6.8 10.7 7 10.5C7.2 10.3 7.49999 10.1 7.89999 10C8.29999 9.90003 8.60001 9.80003 9.10001 9.80003C9.50001 9.80003 9.80001 9.90003 10.1 10C10.4 10.1 10.7 10.3 10.9 10.4C11.1 10.5 11.3 10.8 11.4 11.1C11.5 11.4 11.6 11.6 11.6 11.9C11.6 12.3 11.5 12.6 11.3 12.9C11.1 13.2 10.9 13.5 10.6 13.7C10.9 13.9 11.2 14.1 11.4 14.3C11.6 14.5 11.8 14.7 11.9 15C12 15.3 12.1 15.5 12.1 15.8C12.1 16.2 12 16.5 11.9 16.8C11.8 17.1 11.5 17.4 11.3 17.7C11.1 18 10.7 18.2 10.3 18.3C9.9 18.4 9.5 18.5 9 18.5C8.5 18.5 8.1 18.4 7.7 18.2C7.3 18 7 17.8 6.8 17.6C6.6 17.4 6.4 17.1 6.3 16.8C6.2 16.5 6.10001 16.3 6.10001 16.1C6.10001 15.9 6.2 15.7 6.3 15.6C6.4 15.5 6.6 15.4 6.8 15.4C6.9 15.4 7.00001 15.4 7.10001 15.5C7.20001 15.6 7.3 15.6 7.3 15.7C7.5 16.2 7.7 16.6 8 16.9C8.3 17.2 8.6 17.3 9 17.3C9.2 17.3 9.5 17.2 9.7 17.1C9.9 17 10.1 16.8 10.3 16.6C10.5 16.4 10.5 16.1 10.5 15.8C10.5 15.3 10.4 15 10.1 14.7C9.80001 14.4 9.50001 14.3 9.10001 14.3C9.00001 14.3 8.9 14.3 8.7 14.3C8.5 14.3 8.39999 14.3 8.39999 14.3C8.19999 14.3 7.99999 14.2 7.89999 14.1C7.79999 14 7.7 13.8 7.7 13.7C7.7 13.5 7.79999 13.4 7.89999 13.2C7.99999 13 8.2 13 8.5 13H8.8V13.1ZM15.3 17.5V12.2C14.3 13 13.6 13.3 13.3 13.3C13.1 13.3 13 13.2 12.9 13.1C12.8 13 12.7 12.8 12.7 12.6C12.7 12.4 12.8 12.3 12.9 12.2C13 12.1 13.2 12 13.6 11.8C14.1 11.6 14.5 11.3 14.7 11.1C14.9 10.9 15.2 10.6 15.5 10.3C15.8 10 15.9 9.80003 15.9 9.70003C15.9 9.60003 16.1 9.60004 16.3 9.60004C16.5 9.60004 16.7 9.70003 16.8 9.80003C16.9 9.90003 17 10.2 17 10.5V17.2C17 18 16.7 18.4 16.2 18.4C16 18.4 15.8 18.3 15.6 18.2C15.4 18.1 15.3 17.8 15.3 17.5Z"
fill="currentColor" />
</svg>
</span>
<input class="form-control form-control-solid ps-12" type="text"
id="due_date" name="due_date"
value="{{ $itemView->due_date ? date('d-m-Y', strtotime($itemView->due_date)) : '' }}">
</div>
</div>
<div class="mb-10 fv-row">
<label class="required form-label">Subject</label>
<input type="text" name="name" class="form-control mb-2 col-6"
value="{{ $itemView->name }}" />
</div>
<div class="mb-10 fv-row">
<label class="required form-label">Subject (EN)</label>
<input type="text" name="name_en" class="form-control mb-2 col-6"
value="{{ $itemView->name_en }}" />
</div>
<div class="mb-10 fv-row">
<label class="required form-label">Description</label>
<div class="form-control" id="description" name="description"></div>
<input type="hidden" id="description_input" name="description" />
</div>
<div class="mb-10 fv-row">
<label class="required form-label">Description (EN)</label>
<div class="form-control" id="description_en" name="description_en"></div>
<input type="hidden" id="description_input_en" name="description_en" />
</div>
</div>
</div>
<div class="d-flex justify-content-end">
<a href="{{ url('no-where-else/article') }}" id="kt_ecommerce_add_product_cancel"
class="btn btn-light me-5">Cancel</a>
<button type="button" id="btn_submit" class="btn btn-primary">
<span class="indicator-label">Save</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@stop
@section('script')
<script type="text/javascript">
$(function() {
$('#due_date').flatpickr({
enableTime: false,
dateFormat: 'd-m-Y',
defaultDate: null, // Do not preselect any date
onReady: function(selectedDates, dateStr, instance) {
// Highlight today's date with a custom CSS class
const today = instance.todayDateElem;
today.classList.add('flatpickr-today-highlight'); // Add a custom class to today
}
});
$(document).on("click", "#btn_submit", function(e) {
e.preventDefault();
alertLoading("Loading")
const form = $('#frmUpdate');
form.submit();
})
});
</script>
<!-- Include Editor.js core -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
<!-- Include the plugins -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/image"></script>
<script src="https://cdn.jsdelivr.net/npm/editorjs-inline-image"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/embed"></script>
<script src="https://cdn.jsdelivr.net/npm/editorjs-break-line"></script>
<script>
// Custom video tool for embedding videos
class VideoTool {
constructor({
data
}) {
this.data = data || {};
}
// Render input for video URL
render() {
const wrapper = document.createElement("div");
const input = document.createElement("input");
input.type = "text";
input.placeholder = "Enter video URL";
input.value = this.data.url || "";
input.style.width = "100%";
const video = document.createElement("video");
video.controls = true;
video.style.width = "100%";
video.style.marginTop = "10px";
video.src = this.data.url || "";
input.addEventListener("input", (event) => {
const value = event.target.value;
this.data.url = value;
video.src = value;
});
wrapper.appendChild(input);
wrapper.appendChild(video);
return wrapper;
}
// Save the video URL
save(blockContent) {
const input = blockContent.querySelector("input");
return {
url: input.value,
};
}
// Editor.js toolbox definition
static get toolbox() {
return {
title: "Video",
icon: '<svg width="20" height="20" viewBox="0 0 24 24"><path d="M10 8.64L15.27 12 10 15.36V8.64M10 3.14L21 12 10 20.86V3.14M3 4h4v16H3V4z"></path></svg>',
};
}
}
class YoutubeEmbed {
/**
* Define the toolbox settings for the Editor.js toolbar.
*/
static get toolbox() {
return {
title: "YouTube",
icon: '<svg width="18" height="18" viewBox="0 0 24 24"><path d="M19.615 3.184c-.403-1.516-1.589-2.693-3.09-3.091-2.719-.727-13.525-.727-16.244 0-1.514.398-2.687 1.57-3.09 3.091-.727 2.719-.727 13.525 0 16.244.403 1.514 1.576 2.693 3.09 3.09 2.719.727 13.525.727 16.244 0 1.516-.397 2.692-1.576 3.09-3.09.727-2.719.727-13.525 0-16.244zm-11.615 13.316v-8l8 4-8 4z"/></svg>',
};
}
constructor({
data,
config,
api,
readOnly
}) {
this.data = data || {};
this.readOnly = readOnly;
this.wrapper = null;
this.url = this.data.url || '';
this.isEdited = false;
}
/**
* Render the input field and YouTube preview.
*/
render() {
this.wrapper = document.createElement('div');
this.wrapper.classList.add('block-wrapper');
const input = document.createElement('input');
input.value = this.url;
input.placeholder = "Paste YouTube URL here...";
input.style.width = '100%';
this.wrapper.appendChild(input);
this._createIframe(this.url);
input.addEventListener('change', (event) => {
this.isEdited = true;
this.url = event.target.value;
this._createIframe(this.url);
});
return this.wrapper;
}
/**
* Create the iframe for the YouTube video.
*/
_createIframe(url) {
const videoId = this._extractVideoID(url);
if (!videoId) {
if (this.isEdited) {
this.wrapper.querySelector('input').classList.add('invalid');
}
return;
}
this.wrapper.innerHTML = ''; // Clear the wrapper
const plyrContainer = document.createElement('div');
plyrContainer.classList.add('video-wrapper');
const iframe = document.createElement('iframe');
iframe.src = `https://www.youtube.com/embed/${videoId}`;
iframe.allowFullscreen = true;
plyrContainer.appendChild(iframe);
this.wrapper.appendChild(plyrContainer);
}
/**
* Extract the YouTube video ID from the URL.
*/
_extractVideoID(url) {
const regex = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&?/]+)/;
const match = url.match(regex);
return match ? match[1] : null;
}
/**
* Return the saved data for this block.
*/
save(blockContent) {
return {
url: this.url,
};
}
/**
* Notify the core that this tool supports read-only mode.
*/
static get isReadOnlySupported() {
return true;
}
}
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const baseUrlUpload = "{{ url('no-where-else/article-upload-image') }}";
const tools = {
header: {
class: Header,
inlineToolbar: ['link', 'bold', 'italic'],
config: {
placeholder: 'Enter a header',
levels: [2, 3, 4], // Set header levels
defaultLevel: 3,
},
},
breakLine: {
class: BreakLine,
inlineToolbar: true,
shortcut: 'CMD+SHIFT+ENTER',
},
youtube: YoutubeEmbed,
embed: {
class: Embed,
inlineToolbar: true,
config: {
services: {
youtube: true,
vimeo: true,
},
},
},
video: {
class: VideoTool,
inlineToolbar: true,
},
image: {
class: ImageTool,
config: {
endpoints: {
byFile: baseUrlUpload,
byUrl: baseUrlUpload,
},
field: 'image',
types: 'image/*',
inlineToolbar: true,
additionalRequestHeaders: {
'X-CSRF-TOKEN': csrfToken, // Include CSRF token here
},
enableResizing: true, // This is hypothetical; check if your tool supports this
},
},
inlineImage: {
class: InlineImage,
inlineToolbar: true,
config: {
embed: {
display: true,
},
},
},
}
const descriptionData = {!! json_encode($itemView->description) !!};
const editorDescription = new EditorJS({
holder: 'description',
tools: tools,
data: descriptionData != "" ? JSON.parse(descriptionData) : {},
onChange: () => {
saveData(editorDescription, 'description_input')
}
});
const descriptionDataEn = {!! json_encode($itemView->description_en) !!};
const editorDescriptionEn = new EditorJS({
holder: 'description_en',
tools: tools,
data: descriptionDataEn !== "" ? JSON.parse(descriptionDataEn) : {},
onChange: () => {
saveData(editorDescriptionEn, 'description_input_en');
}
});
// Function to save data to hidden inputs
function saveData(editor, inputId) {
editor.save().then((outputData) => {
document.getElementById(inputId).value = JSON.stringify(outputData);
}).catch((error) => {
console.error('Saving failed: ', error);
});
}
$(document).on('click', '#btn_submit', function(e) {
e.preventDefault(); // Prevent default form submission
alertLoading("Loading..."); // Show loading alert (your custom function)
// Save both editor contents
Promise.all([editorDescription.save(), editorDescriptionEn.save()])
.then(([descriptionData, descriptionEnData]) => {
// Store the JSON output in the hidden input fields
$('#description_input').val(JSON.stringify(descriptionData));
$('#description_input_en').val(JSON.stringify(descriptionEnData));
// Submit the form after saving the data
$('#frmUpdate').submit();
})
.catch((error) => {
console.error('Error saving editor content:', error);
});
});
</script>
@endsection