diff --git a/components/editor/optimized-editor.tsx b/components/editor/optimized-editor.tsx index f9de03a..82a5508 100644 --- a/components/editor/optimized-editor.tsx +++ b/components/editor/optimized-editor.tsx @@ -1,7 +1,7 @@ "use client"; -import React, { useEffect, useRef } from 'react'; -import { Editor } from '@tinymce/tinymce-react'; +import React, { useEffect, useRef } from "react"; +import { Editor } from "@tinymce/tinymce-react"; interface OptimizedEditorProps { initialData?: string; @@ -13,10 +13,10 @@ interface OptimizedEditorProps { } const OptimizedEditor: React.FC = ({ - initialData = '', + initialData = "", onChange, height = 400, - placeholder = 'Start typing...', + placeholder = "Start typing...", disabled = false, readOnly = false, }) => { @@ -42,14 +42,30 @@ const OptimizedEditor: React.FC = ({ height, menubar: false, plugins: [ - 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview', - 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen', - 'insertdatetime', 'media', 'table', 'code', 'help', 'wordcount' + "advlist", + "autolink", + "lists", + "link", + "image", + "charmap", + "preview", + "anchor", + "searchreplace", + "visualblocks", + "code", + "fullscreen", + "insertdatetime", + "media", + "table", + "code", + "help", + "wordcount", ], - toolbar: 'undo redo | blocks | ' + - 'bold italic forecolor | alignleft aligncenter ' + - 'alignright alignjustify | bullist numlist outdent indent | ' + - 'removeformat | table | code | help', + toolbar: + "undo redo | blocks | " + + "bold italic forecolor | alignleft aligncenter " + + "alignright alignjustify | bullist numlist outdent indent | " + + "removeformat | table | code | help", content_style: ` body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; @@ -63,27 +79,27 @@ const OptimizedEditor: React.FC = ({ } `, placeholder, - readonly: readOnly, + // readonly: readOnly, branding: false, elementpath: false, resize: false, statusbar: false, // Performance optimizations - cache_suffix: '?v=1.0', + cache_suffix: "?v=1.0", browser_spellcheck: false, gecko_spellcheck: false, // Auto-save feature auto_save: true, - auto_save_interval: '30s', + auto_save_interval: "30s", // Better mobile support mobile: { - theme: 'silver', - plugins: ['lists', 'autolink', 'link', 'image', 'table'], - toolbar: 'bold italic | bullist numlist | link image' - } + theme: "silver", + plugins: ["lists", "autolink", "link", "image", "table"], + toolbar: "bold italic | bullist numlist | link image", + }, }} /> ); }; -export default OptimizedEditor; \ No newline at end of file +export default OptimizedEditor; diff --git a/components/editor/tinymce-editor.tsx b/components/editor/tinymce-editor.tsx index fb98164..b8d8e57 100644 --- a/components/editor/tinymce-editor.tsx +++ b/components/editor/tinymce-editor.tsx @@ -1,7 +1,7 @@ "use client"; -import React, { useRef, useState, useEffect } from 'react'; -import { Editor } from '@tinymce/tinymce-react'; +import React, { useRef, useState, useEffect } from "react"; +import { Editor } from "@tinymce/tinymce-react"; interface TinyMCEEditorProps { initialData?: string; @@ -11,7 +11,7 @@ interface TinyMCEEditorProps { placeholder?: string; disabled?: boolean; readOnly?: boolean; - features?: 'basic' | 'standard' | 'full'; + features?: "basic" | "standard" | "full"; toolbar?: string; language?: string; uploadUrl?: string; @@ -22,21 +22,21 @@ interface TinyMCEEditorProps { } const TinyMCEEditor: React.FC = ({ - initialData = '', + initialData = "", onChange, onReady, height = 400, - placeholder = 'Start typing...', + placeholder = "Start typing...", disabled = false, readOnly = false, - features = 'standard', + features = "standard", toolbar, - language = 'en', + language = "en", uploadUrl, uploadHeaders, - className = '', + className = "", autoSave = true, - autoSaveInterval = 30000 + autoSaveInterval = 30000, }) => { const editorRef = useRef(null); const [isEditorLoaded, setIsEditorLoaded] = useState(false); @@ -47,35 +47,74 @@ const TinyMCEEditor: React.FC = ({ const getFeatureConfig = (featureLevel: string) => { const configs = { basic: { - plugins: ['lists', 'link', 'autolink', 'wordcount'], - toolbar: 'bold italic | bullist numlist | link', - menubar: false + plugins: ["lists", "link", "autolink", "wordcount"], + toolbar: "bold italic | bullist numlist | link", + menubar: false, }, standard: { plugins: [ - 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview', - 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen', - 'insertdatetime', 'media', 'table', 'help', 'wordcount' + "advlist", + "autolink", + "lists", + "link", + "image", + "charmap", + "preview", + "anchor", + "searchreplace", + "visualblocks", + "code", + "fullscreen", + "insertdatetime", + "media", + "table", + "help", + "wordcount", ], - toolbar: 'undo redo | blocks | ' + - 'bold italic forecolor | alignleft aligncenter ' + - 'alignright alignjustify | bullist numlist outdent indent | ' + - 'removeformat | table | code | help', - menubar: false + toolbar: + "undo redo | blocks | " + + "bold italic forecolor | alignleft aligncenter " + + "alignright alignjustify | bullist numlist outdent indent | " + + "removeformat | table | code | help", + menubar: false, }, full: { plugins: [ - 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview', - 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen', - 'insertdatetime', 'media', 'table', 'help', 'wordcount', 'emoticons', - 'paste', 'textcolor', 'colorpicker', 'hr', 'pagebreak', 'nonbreaking', - 'toc', 'imagetools', 'textpattern', 'codesample' + "advlist", + "autolink", + "lists", + "link", + "image", + "charmap", + "preview", + "anchor", + "searchreplace", + "visualblocks", + "code", + "fullscreen", + "insertdatetime", + "media", + "table", + "help", + "wordcount", + "emoticons", + "paste", + "textcolor", + "colorpicker", + "hr", + "pagebreak", + "nonbreaking", + "toc", + "imagetools", + "textpattern", + "codesample", ], - toolbar: 'undo redo | formatselect | bold italic backcolor | ' + - 'alignleft aligncenter alignright alignjustify | ' + - 'bullist numlist outdent indent | removeformat | help', - menubar: 'file edit view insert format tools table help' - } + toolbar: + "undo redo | formatselect | bold italic backcolor | " + + "alignleft aligncenter alignright alignjustify | " + + "bullist numlist outdent indent | removeformat | help", + menubar: "file edit view insert format tools table help", + }, }; return configs[featureLevel as keyof typeof configs] || configs.standard; }; @@ -89,13 +128,13 @@ const TinyMCEEditor: React.FC = ({ const handleEditorInit = (evt: any, editor: any) => { editorRef.current = editor; setIsEditorLoaded(true); - + if (onReady) { onReady(editor); } // Set up word count tracking - editor.on('keyup', () => { + editor.on("keyup", () => { const count = editor.plugins.wordcount.body.getCharacterCount(); setWordCount(count); }); @@ -104,24 +143,24 @@ const TinyMCEEditor: React.FC = ({ if (autoSave && !readOnly) { setInterval(() => { const content = editor.getContent(); - localStorage.setItem('tinymce-autosave', content); + localStorage.setItem("tinymce-autosave", content); setLastSaved(new Date()); }, autoSaveInterval); } // Fix cursor jumping issues - editor.on('keyup', (e: any) => { + editor.on("keyup", (e: any) => { // Prevent cursor jumping on content changes e.stopPropagation(); }); - editor.on('input', (e: any) => { + editor.on("input", (e: any) => { // Prevent unnecessary re-renders e.stopPropagation(); }); // Handle paste events properly - editor.on('paste', (e: any) => { + editor.on("paste", (e: any) => { // Allow default paste behavior return true; }); @@ -130,23 +169,23 @@ const TinyMCEEditor: React.FC = ({ const handleImageUpload = (blobInfo: any, progress: any) => { return new Promise((resolve, reject) => { if (!uploadUrl) { - reject('No upload URL configured'); + reject("No upload URL configured"); return; } const formData = new FormData(); - formData.append('file', blobInfo.blob(), blobInfo.filename()); + formData.append("file", blobInfo.blob(), blobInfo.filename()); fetch(uploadUrl, { - method: 'POST', + method: "POST", headers: uploadHeaders || {}, - body: formData + body: formData, }) - .then(response => response.json()) - .then(result => { + .then((response) => response.json()) + .then((result) => { resolve(result.url); }) - .catch(error => { + .catch((error) => { reject(error); }); }); @@ -165,7 +204,7 @@ const TinyMCEEditor: React.FC = ({ resize: false, statusbar: !readOnly, // Performance optimizations - cache_suffix: '?v=1.0', + cache_suffix: "?v=1.0", browser_spellcheck: false, gecko_spellcheck: false, // Content styling @@ -188,40 +227,41 @@ const TinyMCEEditor: React.FC = ({ // Image upload configuration images_upload_handler: uploadUrl ? handleImageUpload : undefined, automatic_uploads: !!uploadUrl, - file_picker_types: 'image', + file_picker_types: "image", // Better mobile support mobile: { - theme: 'silver', - plugins: ['lists', 'autolink', 'link', 'image', 'table'], - toolbar: 'bold italic | bullist numlist | link image' + theme: "silver", + plugins: ["lists", "autolink", "link", "image", "table"], + toolbar: "bold italic | bullist numlist | link image", }, // Paste configuration paste_as_text: false, paste_enable_default_filters: true, - paste_word_valid_elements: 'b,strong,i,em,h1,h2,h3,h4,h5,h6', - paste_retain_style_properties: 'color background-color font-size font-weight', + paste_word_valid_elements: "b,strong,i,em,h1,h2,h3,h4,h5,h6", + paste_retain_style_properties: + "color background-color font-size font-weight", // Table configuration table_default_styles: { - width: '100%' + width: "100%", }, table_default_attributes: { - border: '1' + border: "1", }, // Code configuration codesample_languages: [ - { text: 'HTML/XML', value: 'markup' }, - { text: 'JavaScript', value: 'javascript' }, - { text: 'CSS', value: 'css' }, - { text: 'PHP', value: 'php' }, - { text: 'Python', value: 'python' }, - { text: 'Java', value: 'java' }, - { text: 'C', value: 'c' }, - { text: 'C++', value: 'cpp' } + { text: "HTML/XML", value: "markup" }, + { text: "JavaScript", value: "javascript" }, + { text: "CSS", value: "css" }, + { text: "PHP", value: "php" }, + { text: "Python", value: "python" }, + { text: "Java", value: "java" }, + { text: "C", value: "c" }, + { text: "C++", value: "cpp" }, ], // ...feature config ...featureConfig, // Custom toolbar if provided - ...(toolbar && { toolbar }) + ...(toolbar && { toolbar }), }; return ( @@ -232,15 +272,15 @@ const TinyMCEEditor: React.FC = ({ onEditorChange={handleEditorChange} disabled={disabled} apiKey={process.env.NEXT_PUBLIC_TINYMCE_API_KEY} - init={editorConfig} + // init={editorConfig} /> - + {/* Status bar */} {isEditorLoaded && (
- {autoSave && !readOnly ? 'Auto-save enabled' : 'Read-only mode'} + {autoSave && !readOnly ? "Auto-save enabled" : "Read-only mode"} {lastSaved && autoSave && !readOnly && ( • Last saved: {lastSaved.toLocaleTimeString()} @@ -255,10 +295,15 @@ const TinyMCEEditor: React.FC = ({ {/* Performance indicator */}
- Bundle size: {features === 'basic' ? '~150KB' : features === 'standard' ? '~200KB' : '~300KB'} + Bundle size:{" "} + {features === "basic" + ? "~150KB" + : features === "standard" + ? "~200KB" + : "~300KB"}
); }; -export default TinyMCEEditor; \ No newline at end of file +export default TinyMCEEditor; diff --git a/components/navigation.ts b/components/navigation.ts new file mode 100644 index 0000000..86e4b05 --- /dev/null +++ b/components/navigation.ts @@ -0,0 +1,5 @@ +// import {createSharedPathnamesNavigation} from 'next-intl/navigation'; +// import {locales} from '@/config'; + +// export const {Link, redirect, usePathname, useRouter} = +// createSharedPathnamesNavigation({locales,}); diff --git a/components/ui/calendar.tsx b/components/ui/calendar.tsx index 4d7c46a..1e8e67b 100644 --- a/components/ui/calendar.tsx +++ b/components/ui/calendar.tsx @@ -1,213 +1,75 @@ -"use client" +"use client"; +import * as React from "react"; +import { DayPicker } from "react-day-picker"; -import * as React from "react" -import { - ChevronDownIcon, - ChevronLeftIcon, - ChevronRightIcon, -} from "lucide-react" -import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker" - -import { cn } from "@/lib/utils" -import { Button, buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; function Calendar({ className, classNames, showOutsideDays = true, - captionLayout = "label", - buttonVariant = "ghost", - formatters, - components, ...props -}: React.ComponentProps & { - buttonVariant?: React.ComponentProps["variant"] -}) { - const defaultClassNames = getDefaultClassNames() - +}: React.ComponentProps) { return ( svg]:rotate-180`, - String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, - className - )} - captionLayout={captionLayout} - formatters={{ - formatMonthDropdown: (date) => - date.toLocaleString("default", { month: "short" }), - ...formatters, - }} + className={cn("p-3", className)} classNames={{ - root: cn("w-fit", defaultClassNames.root), - months: cn( - "flex gap-4 flex-col md:flex-row relative", - defaultClassNames.months + months: "flex flex-col sm:flex-row gap-2", + month: "flex flex-col gap-4", + caption: "flex justify-center pt-1 relative items-center w-full", + caption_label: "text-sm font-medium", + nav: "flex items-center gap-1", + nav_button: cn( + buttonVariants({ variant: "outline" }), + "size-7 bg-transparent p-0 opacity-50 hover:opacity-100" ), - month: cn("flex flex-col w-full gap-4", defaultClassNames.month), - nav: cn( - "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", - defaultClassNames.nav - ), - button_previous: cn( - buttonVariants({ variant: buttonVariant }), - "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", - defaultClassNames.button_previous - ), - button_next: cn( - buttonVariants({ variant: buttonVariant }), - "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", - defaultClassNames.button_next - ), - month_caption: cn( - "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", - defaultClassNames.month_caption - ), - dropdowns: cn( - "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", - defaultClassNames.dropdowns - ), - dropdown_root: cn( - "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", - defaultClassNames.dropdown_root - ), - dropdown: cn( - "absolute bg-popover inset-0 opacity-0", - defaultClassNames.dropdown - ), - caption_label: cn( - "select-none font-medium", - captionLayout === "label" - ? "text-sm" - : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", - defaultClassNames.caption_label - ), - table: "w-full border-collapse", - weekdays: cn("flex", defaultClassNames.weekdays), - weekday: cn( - "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", - defaultClassNames.weekday - ), - week: cn("flex w-full mt-2", defaultClassNames.week), - week_number_header: cn( - "select-none w-(--cell-size)", - defaultClassNames.week_number_header - ), - week_number: cn( - "text-[0.8rem] select-none text-muted-foreground", - defaultClassNames.week_number + nav_button_previous: "absolute left-1", + nav_button_next: "absolute right-1", + table: "w-full border-collapse space-x-1", + head_row: "flex", + head_cell: + "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]", + row: "flex w-full mt-2", + cell: cn( + "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md", + props.mode === "range" + ? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" + : "[&:has([aria-selected])]:rounded-md" ), day: cn( - "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", - defaultClassNames.day + buttonVariants({ variant: "ghost" }), + "size-8 p-0 font-normal aria-selected:opacity-100" ), - range_start: cn( - "rounded-l-md bg-accent", - defaultClassNames.range_start - ), - range_middle: cn("rounded-none", defaultClassNames.range_middle), - range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), - today: cn( - "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", - defaultClassNames.today - ), - outside: cn( - "text-muted-foreground aria-selected:text-muted-foreground", - defaultClassNames.outside - ), - disabled: cn( - "text-muted-foreground opacity-50", - defaultClassNames.disabled - ), - hidden: cn("invisible", defaultClassNames.hidden), + day_range_start: + "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground", + day_range_end: + "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground", + day_selected: + "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", + day_today: "bg-accent text-accent-foreground", + day_outside: + "day-outside text-muted-foreground aria-selected:text-muted-foreground", + day_disabled: "text-muted-foreground opacity-50", + day_range_middle: + "aria-selected:bg-accent aria-selected:text-accent-foreground", + day_hidden: "invisible", ...classNames, }} - components={{ - Root: ({ className, rootRef, ...props }) => { - return ( -
- ) - }, - Chevron: ({ className, orientation, ...props }) => { - if (orientation === "left") { - return ( - - ) - } - - if (orientation === "right") { - return ( - - ) - } - - return ( - - ) - }, - DayButton: CalendarDayButton, - WeekNumber: ({ children, ...props }) => { - return ( - -
- {children} -
- - ) - }, - ...components, - }} - {...props} - /> - ) -} - -function CalendarDayButton({ - className, - day, - modifiers, - ...props -}: React.ComponentProps) { - const defaultClassNames = getDefaultClassNames() - - const ref = React.useRef(null) - React.useEffect(() => { - if (modifiers.focused) ref.current?.focus() - }, [modifiers.focused]) - - return ( -