Compare commits
10 Commits
0a9c119df1
...
9ba7172d45
| Author | SHA1 | Date |
|---|---|---|
|
|
9ba7172d45 | |
|
|
042cbd9733 | |
|
|
187fe3a7c9 | |
|
|
c2da413522 | |
|
|
b3e573792f | |
|
|
8e58a91210 | |
|
|
4b815b4e5a | |
|
|
355c086353 | |
|
|
1761010341 | |
|
|
93fb5d7e5d |
|
|
@ -11,12 +11,12 @@ build-dev:
|
||||||
name: docker:25.0.3-cli
|
name: docker:25.0.3-cli
|
||||||
services:
|
services:
|
||||||
- name: docker:25.0.3-dind
|
- name: docker:25.0.3-dind
|
||||||
command: ["--insecure-registry=103.82.242.92:8900"]
|
command: ["--insecure-registry=38.47.185.86:8900"]
|
||||||
script:
|
script:
|
||||||
- docker logout
|
- docker logout
|
||||||
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
|
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 38.47.185.86:8900
|
||||||
- docker build -t 103.82.242.92:8900/medols/web-arah-negeri:dev .
|
- docker build -t 38.47.185.86:8900/medols/web-arah-negeri:dev .
|
||||||
- docker push 103.82.242.92:8900/medols/web-arah-negeri:dev
|
- docker push 38.47.185.86:8900/medols/web-arah-negeri:dev
|
||||||
|
|
||||||
auto-deploy:
|
auto-deploy:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
|
|
@ -27,4 +27,4 @@ auto-deploy:
|
||||||
services:
|
services:
|
||||||
- docker:dind
|
- docker:dind
|
||||||
script:
|
script:
|
||||||
- curl --user admin:$JENKINS_PWD http://103.31.38.120:8080/job/auto-deploy-arah-negeri/build?token=autodeploymedols
|
- curl --user admin:$JENKINS_PWD http://38.47.185.86:8080/job/auto-deploy-arah-negeri/build?token=autodeploymedols
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import Link from "next/link";
|
||||||
import {
|
import {
|
||||||
getArticleById,
|
getArticleById,
|
||||||
getArticleBySlug,
|
getArticleBySlug,
|
||||||
|
getArticleFiles,
|
||||||
getListArticle,
|
getListArticle,
|
||||||
} from "@/service/article";
|
} from "@/service/article";
|
||||||
import { close, error, loading } from "@/config/swal";
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
|
@ -20,6 +21,7 @@ import {
|
||||||
} from "@/service/master-user";
|
} from "@/service/master-user";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { Badge } from "../ui/badge";
|
import { Badge } from "../ui/badge";
|
||||||
|
import { formatTextToHtmlTag } from "@/utils/global";
|
||||||
|
|
||||||
type TabKey = "trending" | "comments" | "latest";
|
type TabKey = "trending" | "comments" | "latest";
|
||||||
|
|
||||||
|
|
@ -75,13 +77,13 @@ export default function DetailContent() {
|
||||||
startDate: null,
|
startDate: null,
|
||||||
endDate: null,
|
endDate: null,
|
||||||
});
|
});
|
||||||
const [detailfiles, setDetailFiles] = useState<any>([]);
|
const [detailFiles, setDetailFiles] = useState<any[]>([]);
|
||||||
const [mainImage, setMainImage] = useState(0);
|
const [mainImage, setMainImage] = useState(0);
|
||||||
const [thumbnail, setThumbnail] = useState("-");
|
const [thumbnail, setThumbnail] = useState("-");
|
||||||
const [diseId, setDiseId] = useState(0);
|
const [diseId, setDiseId] = useState(0);
|
||||||
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
|
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
|
||||||
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
|
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
|
||||||
null
|
null,
|
||||||
);
|
);
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
|
|
||||||
|
|
@ -297,18 +299,39 @@ export default function DetailContent() {
|
||||||
initStateData();
|
initStateData();
|
||||||
}, [listCategory]);
|
}, [listCategory]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedIndex(0);
|
||||||
|
}, [detailFiles]);
|
||||||
|
|
||||||
async function initStateData() {
|
async function initStateData() {
|
||||||
loading();
|
loading();
|
||||||
|
try {
|
||||||
|
// 1️⃣ Ambil artikel by slug
|
||||||
const res = await getArticleBySlug(slug);
|
const res = await getArticleBySlug(slug);
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
|
|
||||||
setThumbnail(data?.thumbnailUrl);
|
if (!data?.id) return;
|
||||||
setDiseId(data?.aiArticleId);
|
|
||||||
setDetailFiles(data?.files);
|
setArticleDetail(data);
|
||||||
setArticleDetail(data); // <-- Add this
|
setThumbnail(data.thumbnailUrl);
|
||||||
|
setDiseId(data.aiArticleId);
|
||||||
|
|
||||||
|
// 2️⃣ Ambil SEMUA article files
|
||||||
|
const filesRes = await getArticleFiles();
|
||||||
|
const allFiles = filesRes?.data?.data ?? [];
|
||||||
|
|
||||||
|
// 3️⃣ FILTER sesuai articleId
|
||||||
|
const filteredFiles = allFiles.filter(
|
||||||
|
(file: any) => file.articleId === data.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
setDetailFiles(filteredFiles);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Init state detail error:", error);
|
||||||
|
} finally {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// if (!articleDetail?.files || articleDetail?.files?.length === 0) {
|
// if (!articleDetail?.files || articleDetail?.files?.length === 0) {
|
||||||
// return (
|
// return (
|
||||||
// <div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
|
// <div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
|
||||||
|
|
@ -316,6 +339,16 @@ export default function DetailContent() {
|
||||||
// </div>
|
// </div>
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
function removeImgTags(htmlString?: { __html: string }) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(String(htmlString?.__html), "text/html");
|
||||||
|
|
||||||
|
const images = doc.querySelectorAll("img");
|
||||||
|
images.forEach((img) => img.remove());
|
||||||
|
|
||||||
|
return { __html: doc.body.innerHTML };
|
||||||
|
}
|
||||||
function decodeHtmlString(raw: string = "") {
|
function decodeHtmlString(raw: string = "") {
|
||||||
if (!raw) return "";
|
if (!raw) return "";
|
||||||
|
|
||||||
|
|
@ -367,16 +400,18 @@ export default function DetailContent() {
|
||||||
</span>
|
</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span>
|
<span>
|
||||||
<span>
|
{new Date(articleDetail?.publishedAt ?? articleDetail?.createdAt)
|
||||||
{new Date(articleDetail?.publishedAt).toLocaleDateString(
|
.toLocaleString("id-ID", {
|
||||||
"id-ID",
|
|
||||||
{
|
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
month: "long",
|
month: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}
|
hour: "2-digit",
|
||||||
)}
|
minute: "2-digit",
|
||||||
</span>
|
hour12: false,
|
||||||
|
timeZone: "Asia/Jakarta",
|
||||||
|
})
|
||||||
|
.replace("pukul ", "")}{" "}
|
||||||
|
WIB
|
||||||
</span>
|
</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span>{articleDetail?.categories?.[0]?.title}</span>
|
<span>{articleDetail?.categories?.[0]?.title}</span>
|
||||||
|
|
@ -384,36 +419,30 @@ export default function DetailContent() {
|
||||||
|
|
||||||
<div className="w-full h-auto mb-6">
|
<div className="w-full h-auto mb-6">
|
||||||
{/* Gambar utama */}
|
{/* Gambar utama */}
|
||||||
{articleDetail?.files && articleDetail.files.length > 0 ? (
|
{detailFiles.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{/* Gambar utama */}
|
|
||||||
<div className="w-full">
|
|
||||||
<Image
|
<Image
|
||||||
src={articleDetail.files[selectedIndex]?.fileUrl}
|
src={detailFiles[selectedIndex]?.fileUrl}
|
||||||
alt={
|
alt={detailFiles[selectedIndex]?.fileAlt || "Berita"}
|
||||||
articleDetail.files[selectedIndex]?.fileAlt || "Berita"
|
|
||||||
}
|
|
||||||
width={800}
|
width={800}
|
||||||
height={400}
|
height={400}
|
||||||
className="rounded-lg w-full object-cover"
|
className="rounded-lg w-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Thumbnail */}
|
|
||||||
<div className="flex gap-2 mt-3 overflow-x-auto">
|
<div className="flex gap-2 mt-3 overflow-x-auto">
|
||||||
{articleDetail.files.map((file: any, index: number) => (
|
{detailFiles.map((file, index) => (
|
||||||
<button
|
<button
|
||||||
key={file?.id || index}
|
key={file.id}
|
||||||
onClick={() => setSelectedIndex(index)}
|
onClick={() => setSelectedIndex(index)}
|
||||||
className={`border-2 rounded-lg overflow-hidden ${
|
className={`border-2 rounded-lg ${
|
||||||
selectedIndex === index
|
selectedIndex === index
|
||||||
? "border-red-500"
|
? "border-red-500"
|
||||||
: "border-transparent"
|
: "border-transparent"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={file?.fileUrl}
|
src={file.fileUrl}
|
||||||
alt={file?.fileAlt || "Thumbnail"}
|
alt={file.fileAlt || "Thumbnail"}
|
||||||
width={100}
|
width={100}
|
||||||
height={80}
|
height={80}
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
|
|
@ -423,9 +452,8 @@ export default function DetailContent() {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
// Jika file kosong/null tapi tetap render data lainnya
|
<div className="h-[400px] flex items-center justify-center bg-gray-100 rounded-lg">
|
||||||
<div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
|
<p className="text-gray-400">Gambar tidak tersedia</p>
|
||||||
<p className="text-gray-400 text-sm">Gambar tidak tersedia</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -503,11 +531,10 @@ export default function DetailContent() {
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<div className="prose max-w-none text-justify">
|
<div className="prose max-w-none text-justify">
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={removeImgTags(
|
||||||
__html: decodeHtmlString(
|
formatTextToHtmlTag(articleDetail?.htmlDescription),
|
||||||
articleDetail?.htmlDescription || ""
|
)}
|
||||||
),
|
className="text-sm lg:text-xl lg:leading-8 text-justify space-y-4"
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -815,7 +842,7 @@ export default function DetailContent() {
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
month: "long",
|
month: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -847,7 +874,7 @@ export default function DetailContent() {
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
month: "long",
|
month: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// components/custom-editor.js
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { CKEditor } from "@ckeditor/ckeditor5-react";
|
import { CKEditor } from "@ckeditor/ckeditor5-react";
|
||||||
|
|
||||||
|
import "@/styles/custom-editor.css";
|
||||||
import Editor from "@/vendor/ckeditor5/build/ckeditor";
|
import Editor from "@/vendor/ckeditor5/build/ckeditor";
|
||||||
|
|
||||||
function CustomEditor(props) {
|
function CustomEditor(props) {
|
||||||
|
|
@ -47,7 +47,7 @@ function CustomEditor(props) {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0 !important;
|
||||||
}
|
}
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
margin: 1em 0 0.5em 0;
|
margin: 1em 0 0.5em 0;
|
||||||
|
|
@ -72,98 +72,6 @@ function CustomEditor(props) {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<style jsx>{`
|
|
||||||
.ckeditor-wrapper {
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
|
||||||
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ckeditor-wrapper :global(.ck.ck-editor__main) {
|
|
||||||
min-height: ${props.height || 400}px;
|
|
||||||
max-height: ${maxHeight}px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ckeditor-wrapper :global(.ck.ck-editor__editable) {
|
|
||||||
min-height: ${(props.height || 400) - 50}px;
|
|
||||||
max-height: ${maxHeight - 50}px;
|
|
||||||
overflow-y: auto !important;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: #cbd5e1 #f1f5f9;
|
|
||||||
background: #fff !important;
|
|
||||||
color: #111 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark mode support */
|
|
||||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable) {
|
|
||||||
background: #111 !important;
|
|
||||||
color: #f9fafb !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h1),
|
|
||||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h2),
|
|
||||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h3),
|
|
||||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h4),
|
|
||||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h5),
|
|
||||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h6) {
|
|
||||||
color: #f9fafb !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark)
|
|
||||||
.ckeditor-wrapper
|
|
||||||
:global(.ck.ck-editor__editable blockquote) {
|
|
||||||
background-color: #1f2937 !important;
|
|
||||||
border-left-color: #374151 !important;
|
|
||||||
color: #f3f4f6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom scrollbar styling for webkit browsers */
|
|
||||||
.ckeditor-wrapper :global(.ck.ck-editor__editable::-webkit-scrollbar) {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ckeditor-wrapper
|
|
||||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
|
|
||||||
background: #f1f5f9;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ckeditor-wrapper
|
|
||||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
|
|
||||||
background: #cbd5e1;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ckeditor-wrapper
|
|
||||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
|
|
||||||
background: #94a3b8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark mode scrollbar */
|
|
||||||
:global(.dark)
|
|
||||||
.ckeditor-wrapper
|
|
||||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
|
|
||||||
background: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark)
|
|
||||||
.ckeditor-wrapper
|
|
||||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
|
|
||||||
background: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark)
|
|
||||||
.ckeditor-wrapper
|
|
||||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
|
|
||||||
background: #6b7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure content doesn't overflow */
|
|
||||||
.ckeditor-wrapper :global(.ck.ck-editor__editable .ck-content) {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@ function ViewEditor(props) {
|
||||||
.ckeditor-view-wrapper {
|
.ckeditor-view-wrapper {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
box-shadow:
|
||||||
|
0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
||||||
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ const CustomEditor = dynamic(
|
||||||
() => {
|
() => {
|
||||||
return import("@/components/editor/custom-editor");
|
return import("@/components/editor/custom-editor");
|
||||||
},
|
},
|
||||||
{ ssr: false }
|
{ ssr: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
interface FileWithPreview extends File {
|
interface FileWithPreview extends File {
|
||||||
|
|
@ -118,14 +118,14 @@ export default function CreateArticleForm() {
|
||||||
const [tag, setTag] = useState("");
|
const [tag, setTag] = useState("");
|
||||||
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
|
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
|
||||||
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
|
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
|
||||||
null
|
null,
|
||||||
);
|
);
|
||||||
const [thumbnailValidation, setThumbnailValidation] = useState("");
|
const [thumbnailValidation, setThumbnailValidation] = useState("");
|
||||||
const [filesValidation, setFileValidation] = useState("");
|
const [filesValidation, setFileValidation] = useState("");
|
||||||
const [diseData, setDiseData] = useState<DiseData>();
|
const [diseData, setDiseData] = useState<DiseData>();
|
||||||
const [selectedWritingType, setSelectedWritingType] = useState("single");
|
const [selectedWritingType, setSelectedWritingType] = useState("single");
|
||||||
const [status, setStatus] = useState<"publish" | "draft" | "scheduled">(
|
const [status, setStatus] = useState<"publish" | "draft" | "scheduled">(
|
||||||
"publish"
|
"publish",
|
||||||
);
|
);
|
||||||
const [isScheduled, setIsScheduled] = useState(false);
|
const [isScheduled, setIsScheduled] = useState(false);
|
||||||
const [startDateValue, setStartDateValue] = useState<Date | undefined>();
|
const [startDateValue, setStartDateValue] = useState<Date | undefined>();
|
||||||
|
|
@ -230,7 +230,7 @@ export default function CreateArticleForm() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveArticleToDise = async (
|
const saveArticleToDise = async (
|
||||||
values: z.infer<typeof createArticleSchema>
|
values: z.infer<typeof createArticleSchema>,
|
||||||
) => {
|
) => {
|
||||||
if (useAi) {
|
if (useAi) {
|
||||||
const request = {
|
const request = {
|
||||||
|
|
@ -351,12 +351,12 @@ export default function CreateArticleForm() {
|
||||||
|
|
||||||
// format: 2025-10-08 14:30:00
|
// format: 2025-10-08 14:30:00
|
||||||
const formattedDateTime = `${combinedDate.getFullYear()}-${String(
|
const formattedDateTime = `${combinedDate.getFullYear()}-${String(
|
||||||
combinedDate.getMonth() + 1
|
combinedDate.getMonth() + 1,
|
||||||
).padStart(2, "0")}-${String(combinedDate.getDate()).padStart(
|
).padStart(2, "0")}-${String(combinedDate.getDate()).padStart(
|
||||||
2,
|
2,
|
||||||
"0"
|
"0",
|
||||||
)} ${String(combinedDate.getHours()).padStart(2, "0")}:${String(
|
)} ${String(combinedDate.getHours()).padStart(2, "0")}:${String(
|
||||||
combinedDate.getMinutes()
|
combinedDate.getMinutes(),
|
||||||
).padStart(2, "0")}:00`;
|
).padStart(2, "0")}:00`;
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
|
|
@ -493,7 +493,7 @@ export default function CreateArticleForm() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const uniqueArray = temp.filter(
|
const uniqueArray = temp.filter(
|
||||||
(item, index) => temp.indexOf(item) === index
|
(item, index) => temp.indexOf(item) === index,
|
||||||
);
|
);
|
||||||
|
|
||||||
setValue("tags", uniqueArray as [string, ...string[]]);
|
setValue("tags", uniqueArray as [string, ...string[]]);
|
||||||
|
|
@ -505,7 +505,7 @@ export default function CreateArticleForm() {
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
>
|
>
|
||||||
<div className="w-full lg:w-[65%] bg-white rounded-lg p-8 flex flex-col gap-1">
|
<div className="w-full lg:w-[65%] bg-white rounded-lg p-8 flex flex-col gap-1">
|
||||||
<p className="text-sm">Judul</p>
|
<p className="text-sm">Judulss</p>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="title"
|
name="title"
|
||||||
|
|
@ -514,7 +514,7 @@ export default function CreateArticleForm() {
|
||||||
id="title"
|
id="title"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Masukkan judul artikel"
|
placeholder="Masukkan judul artikel"
|
||||||
className="w-full border rounded-lg dark:border-gray-400"
|
className="h-16 px-4 text-2xl leading-tight"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -578,7 +578,7 @@ export default function CreateArticleForm() {
|
||||||
// });
|
// });
|
||||||
setValue(
|
setValue(
|
||||||
"description",
|
"description",
|
||||||
data?.articleBody ? data?.articleBody : ""
|
data?.articleBody ? data?.articleBody : "",
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -588,7 +588,7 @@ export default function CreateArticleForm() {
|
||||||
setDiseData(data);
|
setDiseData(data);
|
||||||
setValue(
|
setValue(
|
||||||
"description",
|
"description",
|
||||||
data?.articleBody ? data?.articleBody : ""
|
data?.articleBody ? data?.articleBody : "",
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -781,7 +781,7 @@ export default function CreateArticleForm() {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const filteredTags = value.filter(
|
const filteredTags = value.filter(
|
||||||
(tag: string) => tag !== item
|
(tag: string) => tag !== item,
|
||||||
);
|
);
|
||||||
if (filteredTags.length === 0) {
|
if (filteredTags.length === 0) {
|
||||||
setError("tags", {
|
setError("tags", {
|
||||||
|
|
@ -792,7 +792,7 @@ export default function CreateArticleForm() {
|
||||||
clearErrors("tags");
|
clearErrors("tags");
|
||||||
setValue(
|
setValue(
|
||||||
"tags",
|
"tags",
|
||||||
filteredTags as [string, ...string[]]
|
filteredTags as [string, ...string[]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import {
|
||||||
deleteArticleFiles,
|
deleteArticleFiles,
|
||||||
getArticleByCategory,
|
getArticleByCategory,
|
||||||
getArticleById,
|
getArticleById,
|
||||||
|
getArticleFiles,
|
||||||
submitApproval,
|
submitApproval,
|
||||||
unPublishArticle,
|
unPublishArticle,
|
||||||
updateArticle,
|
updateArticle,
|
||||||
|
|
@ -196,28 +197,50 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
|
|
||||||
async function initState() {
|
async function initState() {
|
||||||
loading();
|
loading();
|
||||||
const res = await getArticleById(id);
|
try {
|
||||||
const data = res.data?.data;
|
// 1️⃣ Ambil ARTICLE
|
||||||
setDetailData(data);
|
const articleRes = await getArticleById(id);
|
||||||
setValue("title", data?.title);
|
const articleData = articleRes.data?.data;
|
||||||
setValue("customCreatorName", data?.customCreatorName);
|
|
||||||
setValue("slug", data?.slug);
|
if (!articleData) return;
|
||||||
setValue("source", data?.source);
|
|
||||||
const cleanDescription = data?.htmlDescription
|
// ===== ARTICLE DATA =====
|
||||||
? data.htmlDescription
|
setDetailData(articleData);
|
||||||
|
setValue("title", articleData.title);
|
||||||
|
setValue("customCreatorName", articleData.customCreatorName);
|
||||||
|
setValue("slug", articleData.slug);
|
||||||
|
setValue("source", articleData.source);
|
||||||
|
|
||||||
|
const cleanDescription = articleData.htmlDescription
|
||||||
|
? articleData.htmlDescription
|
||||||
.replace(/\\"/g, '"')
|
.replace(/\\"/g, '"')
|
||||||
.replace(/\\n/g, "\n", "\\")
|
.replace(/\\n/g, "\n")
|
||||||
.trim()
|
.trim()
|
||||||
: "";
|
: "";
|
||||||
setValue("description", cleanDescription);
|
|
||||||
setValue("tags", data?.tags ? data.tags.split(",") : []);
|
|
||||||
setThumbnail(data?.thumbnailUrl);
|
|
||||||
setDiseId(data?.aiArticleId);
|
|
||||||
setDetailFiles(data?.files);
|
|
||||||
|
|
||||||
setupInitCategory(data?.categories);
|
setValue("description", cleanDescription);
|
||||||
|
setValue("tags", articleData.tags ? articleData.tags.split(",") : []);
|
||||||
|
|
||||||
|
setThumbnail(articleData.thumbnailUrl);
|
||||||
|
setDiseId(articleData.aiArticleId);
|
||||||
|
setupInitCategory(articleData.categories);
|
||||||
|
|
||||||
|
// 2️⃣ Ambil SEMUA article files
|
||||||
|
const filesRes = await getArticleFiles();
|
||||||
|
const allFiles = filesRes.data?.data ?? [];
|
||||||
|
|
||||||
|
// 3️⃣ FILTER berdasarkan ARTICLE ID yang sedang dibuka
|
||||||
|
const filteredFiles = allFiles.filter(
|
||||||
|
(file: any) => file.articleId === articleData.id
|
||||||
|
);
|
||||||
|
|
||||||
|
setDetailFiles(filteredFiles);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Init state error:", error);
|
||||||
|
} finally {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const setupInitCategory = (data: any) => {
|
const setupInitCategory = (data: any) => {
|
||||||
const temp: CategoryType[] = [];
|
const temp: CategoryType[] = [];
|
||||||
|
|
@ -667,9 +690,10 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
name="title"
|
name="title"
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label htmlFor="title" className="block text-sm font-medium mb-1">
|
<label htmlFor="title" className="block text-xl font-medium mb-2">
|
||||||
Judul
|
Judul
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
id="title"
|
id="title"
|
||||||
|
|
@ -677,7 +701,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
value={value ?? ""}
|
value={value ?? ""}
|
||||||
readOnly={isDetail}
|
readOnly={isDetail}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="w-full border rounded-lg"
|
className="h-16 px-4 text-2xl leading-tight"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ export default function ArticleTable() {
|
||||||
return cellValue;
|
return cellValue;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[article, page]
|
[article, page],
|
||||||
);
|
);
|
||||||
|
|
||||||
let typingTimer: NodeJS.Timeout;
|
let typingTimer: NodeJS.Timeout;
|
||||||
|
|
@ -445,8 +445,8 @@ export default function ArticleTable() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full overflow-x-hidden">
|
<div className="w-full overflow-x-hidden">
|
||||||
<div className="w-full mx-auto overflow-x-hidden">
|
<div className="w-full overflow-x-auto">
|
||||||
<Table className="w-full table-fixed border text-sm">
|
<Table className="min-w-[1000px] w-full table-auto border text-sm">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
{(username === "admin-mabes"
|
{(username === "admin-mabes"
|
||||||
|
|
@ -455,7 +455,18 @@ export default function ArticleTable() {
|
||||||
).map((column) => (
|
).map((column) => (
|
||||||
<TableHead
|
<TableHead
|
||||||
key={column.uid}
|
key={column.uid}
|
||||||
className="truncate bg-white dark:bg-black text-black dark:text-white border-b text-md"
|
className={`bg-white dark:bg-black text-black dark:text-white
|
||||||
|
text-sm font-semibold border-b px-3 py-3
|
||||||
|
${
|
||||||
|
column.uid === "no"
|
||||||
|
? "min-w-[60px] text-center"
|
||||||
|
: column.uid === "title"
|
||||||
|
? "min-w-[280px]"
|
||||||
|
: column.uid === "actions"
|
||||||
|
? "min-w-[100px] text-center"
|
||||||
|
: "min-w-[160px]"
|
||||||
|
}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
{column.name}
|
{column.name}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
@ -472,7 +483,17 @@ export default function ArticleTable() {
|
||||||
).map((column) => (
|
).map((column) => (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={column.uid}
|
key={column.uid}
|
||||||
className="truncate text-black dark:text-white max-w-[200px]"
|
className={`text-black dark:text-white text-sm px-3 py-3 align-top
|
||||||
|
${
|
||||||
|
column.uid === "no"
|
||||||
|
? "min-w-[60px] text-center font-medium"
|
||||||
|
: column.uid === "title"
|
||||||
|
? "min-w-[280px] whitespace-normal break-words leading-snug"
|
||||||
|
: column.uid === "actions"
|
||||||
|
? "min-w-[100px] text-center"
|
||||||
|
: "min-w-[160px] whitespace-normal break-words"
|
||||||
|
}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
{renderCell(item, column.uid)}
|
{renderCell(item, column.uid)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
import type { NextConfig } from "next";
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
const nextConfig: NextConfig = {
|
|
||||||
images: {
|
images: {
|
||||||
domains: ["mikulnews.com", "dev.mikulnews.com"],
|
domains: [
|
||||||
|
"mikulnews.com",
|
||||||
|
"dev.mikulnews.com",
|
||||||
|
"jaecoocihampelasbdg.com",
|
||||||
|
"dev.arahnegeri.com",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
eslint: {
|
eslint: {
|
||||||
ignoreDuringBuilds: true,
|
ignoreDuringBuilds: true,
|
||||||
},
|
},
|
||||||
// Add experimental features for better chunk handling
|
|
||||||
experimental: {
|
experimental: {
|
||||||
optimizePackageImports: ["@ckeditor/ckeditor5-react", "react-apexcharts"],
|
optimizePackageImports: ["@ckeditor/ckeditor5-react", "react-apexcharts"],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -39,12 +39,12 @@
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"lightningcss": "^1.30.1",
|
"lightningcss": "^1.30.1",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"next": "15.3.4",
|
"next": "^16.1.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.2.3",
|
||||||
"react-apexcharts": "^1.7.0",
|
"react-apexcharts": "^1.7.0",
|
||||||
"react-datepicker": "^8.4.0",
|
"react-datepicker": "^8.4.0",
|
||||||
"react-day-picker": "^9.7.0",
|
"react-day-picker": "^9.7.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.2.4",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
"react-hook-form": "^7.65.0",
|
"react-hook-form": "^7.65.0",
|
||||||
"react-password-checklist": "^1.8.1",
|
"react-password-checklist": "^1.8.1",
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,13 @@ export async function uploadArticleFile(id: string, data: any) {
|
||||||
return await httpPostInterceptor(`/article-files/${id}`, data, headers);
|
return await httpPostInterceptor(`/article-files/${id}`, data, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getArticleFiles() {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
};
|
||||||
|
return await httpGet(`/article-files`, headers);
|
||||||
|
}
|
||||||
|
|
||||||
export async function uploadArticleThumbnail(id: string, data: any) {
|
export async function uploadArticleThumbnail(id: string, data: any) {
|
||||||
const headers = {
|
const headers = {
|
||||||
"content-type": "multipart/form-data",
|
"content-type": "multipart/form-data",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
const baseURL = "https://dev.mikulnews.com/api";
|
const baseURL = "https://arahnegeri.com/api";
|
||||||
|
|
||||||
const axiosBaseInstance = axios.create({
|
const axiosBaseInstance = axios.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import axios from "axios";
|
||||||
import { postSignIn } from "../master-user";
|
import { postSignIn } from "../master-user";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
const baseURL = "https://dev.mikulnews.com/api";
|
const baseURL = "https://arahnegeri.com/api";
|
||||||
|
|
||||||
const refreshToken = Cookies.get("refresh_token");
|
const refreshToken = Cookies.get("refresh_token");
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ axiosInterceptorInstance.interceptors.request.use(
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Response interceptor
|
// Response interceptor
|
||||||
|
|
@ -66,7 +66,7 @@ axiosInterceptorInstance.interceptors.response.use(
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export default axiosInterceptorInstance;
|
export default axiosInterceptorInstance;
|
||||||
|
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
||||||
/* CKEditor Custom Styling */
|
|
||||||
|
|
||||||
/* Main editor container */
|
|
||||||
.ck.ck-editor {
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toolbar styling */
|
|
||||||
.ck.ck-toolbar {
|
|
||||||
background: #f8fafc;
|
|
||||||
border: 1px solid #d1d5db;
|
|
||||||
border-bottom: none;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-toolbar .ck-toolbar__items {
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main editable area */
|
|
||||||
.ck.ck-editor__editable {
|
|
||||||
background: #ffffff;
|
|
||||||
border: 1px solid #d1d5db;
|
|
||||||
border-top: none;
|
|
||||||
border-radius: 0 0 6px 6px;
|
|
||||||
padding: 1.5em 2em !important;
|
|
||||||
min-height: 400px;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Focus state */
|
|
||||||
.ck.ck-editor__editable.ck-focused {
|
|
||||||
border-color: #1a9aef;
|
|
||||||
box-shadow: 0 0 0 2px rgba(26, 154, 239, 0.2);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Content styling */
|
|
||||||
.ck.ck-editor__editable .ck-content {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Typography improvements */
|
|
||||||
.ck.ck-editor__editable p {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable h1,
|
|
||||||
.ck.ck-editor__editable h2,
|
|
||||||
.ck.ck-editor__editable h3,
|
|
||||||
.ck.ck-editor__editable h4,
|
|
||||||
.ck.ck-editor__editable h5,
|
|
||||||
.ck.ck-editor__editable h6 {
|
|
||||||
margin: 1em 0 0.5em 0;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable h1 { font-size: 1.75em; }
|
|
||||||
.ck.ck-editor__editable h2 { font-size: 1.5em; }
|
|
||||||
.ck.ck-editor__editable h3 { font-size: 1.25em; }
|
|
||||||
.ck.ck-editor__editable h4 { font-size: 1.1em; }
|
|
||||||
.ck.ck-editor__editable h5 { font-size: 1em; }
|
|
||||||
.ck.ck-editor__editable h6 { font-size: 0.9em; }
|
|
||||||
|
|
||||||
/* Lists */
|
|
||||||
.ck.ck-editor__editable ul,
|
|
||||||
.ck.ck-editor__editable ol {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable li {
|
|
||||||
margin: 0.25em 0;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Blockquotes */
|
|
||||||
.ck.ck-editor__editable blockquote {
|
|
||||||
margin: 1em 0;
|
|
||||||
padding: 0.75em 1em;
|
|
||||||
border-left: 4px solid #1a9aef;
|
|
||||||
background-color: #f8fafc;
|
|
||||||
border-radius: 0 4px 4px 0;
|
|
||||||
font-style: italic;
|
|
||||||
color: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tables */
|
|
||||||
.ck.ck-editor__editable table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable table td,
|
|
||||||
.ck.ck-editor__editable table th {
|
|
||||||
border: 1px solid #d1d5db;
|
|
||||||
padding: 0.5em 0.75em;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable table th {
|
|
||||||
background-color: #f8fafc;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Links */
|
|
||||||
.ck.ck-editor__editable a {
|
|
||||||
color: #1a9aef;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable a:hover {
|
|
||||||
color: #0d7cd6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Code blocks */
|
|
||||||
.ck.ck-editor__editable pre {
|
|
||||||
background-color: #f8fafc;
|
|
||||||
border: 1px solid #d1d5db;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 1em;
|
|
||||||
margin: 1em 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable code {
|
|
||||||
background-color: #f1f5f9;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Images */
|
|
||||||
.ck.ck-editor__editable img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Horizontal rule */
|
|
||||||
.ck.ck-editor__editable hr {
|
|
||||||
border: none;
|
|
||||||
border-top: 1px solid #d1d5db;
|
|
||||||
margin: 2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Placeholder text */
|
|
||||||
.ck.ck-editor__editable.ck-blurred:empty::before {
|
|
||||||
content: attr(data-placeholder);
|
|
||||||
color: #9ca3af;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile responsiveness */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.ck.ck-editor__editable {
|
|
||||||
padding: 1em 1.5em !important;
|
|
||||||
font-size: 16px; /* Better for mobile */
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-toolbar {
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-toolbar .ck-toolbar__items {
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark mode support */
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.ck.ck-editor__editable {
|
|
||||||
background: #1f2937;
|
|
||||||
color: #f9fafb;
|
|
||||||
border-color: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable h1,
|
|
||||||
.ck.ck-editor__editable h2,
|
|
||||||
.ck.ck-editor__editable h3,
|
|
||||||
.ck.ck-editor__editable h4,
|
|
||||||
.ck.ck-editor__editable h5,
|
|
||||||
.ck.ck-editor__editable h6 {
|
|
||||||
color: #f9fafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable blockquote {
|
|
||||||
background-color: #374151;
|
|
||||||
border-left-color: #1a9aef;
|
|
||||||
color: #d1d5db;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable pre {
|
|
||||||
background-color: #374151;
|
|
||||||
border-color: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-editor__editable code {
|
|
||||||
background-color: #4b5563;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
/* ========== CKEditor Wrapper ========== */
|
||||||
|
.ckeditor-wrapper {
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow:
|
||||||
|
0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
||||||
|
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== Main Editor Container ========== */
|
||||||
|
.ckeditor-wrapper .ck.ck-editor__main {
|
||||||
|
min-height: var(--editor-min-height, 400px);
|
||||||
|
max-height: var(--editor-max-height, 600px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== Editable Content Area (ClassicEditor) ========== */
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline {
|
||||||
|
min-height: calc(var(--editor-min-height, 400px) - 50px);
|
||||||
|
max-height: calc(var(--editor-max-height, 600px) - 50px);
|
||||||
|
overflow-y: auto !important;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||||
|
background: #fff !important;
|
||||||
|
color: #111 !important;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 1rem;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== Headings and Text Formatting ========== */
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h1,
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h2,
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h3,
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h4,
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h5,
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h6 {
|
||||||
|
margin: 1em 0 0.5em 0;
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline p {
|
||||||
|
margin: 0.5em 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline ul,
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline ol {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline blockquote {
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
border-left: 4px solid #d1d5db;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== Dark Mode Support ========== */
|
||||||
|
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline {
|
||||||
|
background: #111 !important;
|
||||||
|
color: #f9fafb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h1,
|
||||||
|
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h2,
|
||||||
|
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h3,
|
||||||
|
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h4,
|
||||||
|
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h5,
|
||||||
|
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h6 {
|
||||||
|
color: #f9fafb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline blockquote {
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
border-left-color: #374151 !important;
|
||||||
|
color: #f3f4f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== Custom Scrollbars (Light & Dark) ========== */
|
||||||
|
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper
|
||||||
|
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-track {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper
|
||||||
|
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper
|
||||||
|
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark
|
||||||
|
.ckeditor-wrapper
|
||||||
|
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-track {
|
||||||
|
background: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark
|
||||||
|
.ckeditor-wrapper
|
||||||
|
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb {
|
||||||
|
background: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark
|
||||||
|
.ckeditor-wrapper
|
||||||
|
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #6b7280;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|
@ -11,7 +15,7 @@
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|
@ -19,9 +23,19 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export function delay(ms: number) {
|
||||||
export function textEllipsis(
|
export function textEllipsis(
|
||||||
str: string,
|
str: string,
|
||||||
maxLength: number,
|
maxLength: number,
|
||||||
{ side = "end", ellipsis = "..." } = {}
|
{ side = "end", ellipsis = "..." } = {},
|
||||||
) {
|
) {
|
||||||
if (str !== undefined && str?.length > maxLength) {
|
if (str !== undefined && str?.length > maxLength) {
|
||||||
switch (side) {
|
switch (side) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue