diff --git a/docs/CKEDITOR5_CUSTOMIZATION.md b/docs/CKEDITOR5_CUSTOMIZATION.md index c3613e3..0346ff8 100644 --- a/docs/CKEDITOR5_CUSTOMIZATION.md +++ b/docs/CKEDITOR5_CUSTOMIZATION.md @@ -1,247 +1,89 @@ -# CKEditor5 Unfold Theme Integration +# CKEditor 5 in the Unfold admin -This implementation provides a comprehensive customization of CKEditor5 to seamlessly integrate with the Unfold admin theme, utilizing Tailwind CSS 3 and DaisyUI 4 with the `ui-` prefix. +django-whiteneuron ships [django-ckeditor-5](https://github.com/hvlads/django-ckeditor-5) configured +for the admin, plus a small stylesheet that themes the editor with Unfold's colors in light and dark +mode. -## Features +## Enabling the editor -### 🎨 **Visual Design** -- **Modern UI**: Clean, minimalist design matching Unfold's aesthetic -- **Dark Mode Support**: Automatic theme detection and switching -- **Responsive Design**: Mobile-first approach with adaptive toolbars -- **DaisyUI Integration**: Uses `ui-` prefixed components for consistency +The default `TextField` widget of `whiteneuron.base.modeladmin.ModelAdmin` is `wysiwyg`. To use +CKEditor in a model admin: -### 🛠 **Enhanced Functionality** -- **Auto-save**: Automatic content preservation with localStorage -- **Accessibility**: WCAG 2.1 compliant with proper ARIA labels -- **Word Count**: Real-time statistics display -- **Responsive Toolbar**: Simplified mobile interface -- **Theme Synchronization**: Automatic dark/light mode switching - -### 🎯 **User Experience** -- **Smooth Animations**: Subtle transitions and hover effects -- **Focus Management**: Enhanced keyboard navigation -- **Error States**: Visual feedback for form validation -- **Loading States**: Progressive enhancement indicators - -## File Structure - -``` -whiteneuron/static/base/ -├── css/ -│ └── ckeditor5.css # Main styling with Tailwind classes -└── js/ - └── ckeditor5-unfold.js # Enhanced functionality and configuration -``` - -## Implementation Details - -### CSS Architecture (`ckeditor5.css`) - -The CSS file uses Tailwind's `@apply` directive to leverage the existing design system: - -- **Container Styling**: Matches Django form field appearance -- **Toolbar Design**: Uses DaisyUI button components with `ui-` prefix -- **Content Area**: Styled with typography utilities -- **Dark Mode**: Automatic color scheme adaptation -- **Responsive Breakpoints**: Mobile-optimized layouts - -### JavaScript Enhancements (`ckeditor5-unfold.js`) - -The JavaScript file provides advanced functionality: - -- **Theme Management**: Detects and responds to theme changes -- **Configuration**: Comprehensive CKEditor setup optimized for content editing -- **Auto-save**: Background content preservation -- **Accessibility**: Enhanced keyboard and screen reader support -- **Mobile Optimization**: Responsive toolbar configuration - -## Configuration Options - -### Default Toolbar Configuration - -```javascript -toolbar: { - items: [ - 'heading', '|', - 'bold', 'italic', 'link', '|', - 'bulletedList', 'numberedList', 'blockQuote', '|', - 'uploadImage', 'insertTable', '|', - 'code', 'codeBlock', '|', - 'subscript', 'superscript', 'highlight', '|', - 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', '|', - 'mediaEmbed', 'removeFormat', '|', - 'sourceEditing' - ] -} +```python +class ArticleAdmin(ModelAdmin): + text_field_widget = "ckeditor" # options: 'wysiwyg', 'ckeditor', 'mdeditor', '' ``` -### Color Palette Integration - -The color picker integrates with your theme's primary colors: - -```javascript -fontColor: { - colors: [ - // Standard colors - { color: 'hsl(0, 0%, 0%)', label: 'Black' }, - // ... other colors - // Theme integration - { color: 'rgb(var(--color-sky-500))', label: 'Primary' }, - { color: 'rgb(var(--color-sky-600))', label: 'Primary Dark' }, - { color: 'rgb(var(--color-sky-400))', label: 'Primary Light' } - ] -} -``` - -## Usage - -### Basic Implementation +Or use `django_ckeditor_5.fields.CKEditor5Field` / `django_ckeditor_5.widgets.CKEditor5Widget` directly. -```html - - - +The package settings (`whiteneuron/base/settings.py`) define `CKEDITOR_5_CONFIGS` (toolbars), +`CKEDITOR_5_FILE_STORAGE`, `CKEDITOR_5_FILE_UPLOAD_PERMISSION` and: - -
- -
+```python +CKEDITOR_5_CUSTOM_CSS = "base/css/ckeditor5.css" ``` -### Django Template Integration +django-ckeditor-5 adds that file to the widget's media after CKEditor's own CSS. -```html -{% load static %} +## The theme stylesheet -
- -
- {{ form.content }} -
-
+`whiteneuron/static/base/css/ckeditor5.css` is **plain CSS with no build step**. Browsers get the +file exactly as committed, so it must not contain Tailwind syntax (`@apply`, `theme()`, +`@custom-variant`); a regression test checks this. - - - -``` - -### Custom Configuration - -```javascript -// Initialize with custom configuration -window.initUnfoldCKEditor(document.querySelector('#my-editor'), { - toolbar: ['bold', 'italic', 'link'], - placeholder: 'Custom placeholder text...', - // Other custom options -}); -``` - -## Customization +It only overrides CKEditor's `--ck-*` CSS variables, which is +[CKEditor's documented theming method](https://ckeditor.com/docs/ckeditor5/latest/framework/deep-dive/ui/theme-customization.html). +Layout, sizes and feature styles (colour pickers, highlights, image styles, tables, code blocks, +todo lists) stay CKEditor's own. -### Adding New Toolbar Items +- **Colors** come from Unfold: `--color-base-*`, `--color-primary-*` and `--color-font-*` + (`UNFOLD["COLORS"]`, remapped per theme by `daisy-themes.css`). The `var()` fallbacks only apply + without them: CKEditor's defaults in light mode, Tailwind grays in dark mode. +- **Content font** is Inter, Unfold's font. +- **Dark mode** follows Unfold's `dark` class on ``, which daisyUI dark themes also set. + CKEditor's light defaults that are not derived from its base variables (button and input states, + placeholders, selection, content text, captions, panels) are overridden one by one. A regression + test lists the ones left as they are (accents, translucent overlays, badges) and fails when a + django-ckeditor-5 upgrade adds a new one. +- **Fixed colors without a variable** get a dark-mode rule each: code block text (`#353535`), todo + list checkbox borders (`#333`) and the text of find & replace matches. -```javascript -// Extend the default configuration -window.UnfoldCKEditorConfig.toolbar.items.push('specialCharacters'); -``` +## Customizing in a project -### Custom Color Themes +django-ckeditor-5 loads a single `CKEDITOR_5_CUSTOM_CSS` file. To keep the packaged theme and adjust +it, point the setting at your own static file and import the packaged one first: -```javascript -// Add custom colors to the palette -window.UnfoldCKEditorConfig.fontColor.colors.push({ - color: '#your-color', - label: 'Custom Color' -}); +```python +CKEDITOR_5_CUSTOM_CSS = "myproject/css/ckeditor5.css" ``` -### Styling Overrides - ```css -/* Add custom styles in your CSS file */ -.ck.ck-editor.my-custom-editor { - @apply border-2 border-blue-500; -} +/* static/myproject/css/ckeditor5.css */ +@import url("../../base/css/ckeditor5.css"); -.ck.ck-editor.my-custom-editor .ck-toolbar { - @apply bg-blue-50 dark:bg-blue-900; +:root { + --ck-color-focus-border: #7c3aed; } -``` - -## Browser Support - -- **Modern Browsers**: Chrome 63+, Firefox 60+, Safari 12+, Edge 79+ -- **Mobile Support**: iOS Safari 12+, Chrome Mobile 63+ -- **Accessibility**: WCAG 2.1 AA compliant -- **RTL Support**: Full right-to-left text direction support - -## Performance Considerations - -- **Lazy Loading**: CKEditor initializes only when needed -- **Asset Optimization**: Minified CSS and JavaScript -- **Memory Management**: Proper cleanup on navigation -- **Auto-save Throttling**: Configurable save intervals - -## Accessibility Features - -- **Keyboard Navigation**: Full keyboard support for all features -- **Screen Reader Support**: Comprehensive ARIA labels and descriptions -- **High Contrast Mode**: Enhanced visibility in high contrast environments -- **Focus Management**: Proper focus indicators and tab order - -## Error Handling - -The implementation includes robust error handling: - -- **Initialization Errors**: Graceful fallback to plain textarea -- **Network Issues**: Local storage backup for content -- **Browser Compatibility**: Feature detection and polyfills - -## Development Notes -### CSS Processing - -The CSS file uses Tailwind's `@apply` directive and requires processing through your build pipeline: - -```bash -# Process through Tailwind -npx tailwindcss -i ./static/base/css/ckeditor5.css -o ./dist/ckeditor5.css +html.dark { + --ck-color-base-background: #0b1020; +} ``` -### JavaScript Dependencies +Your rules come after the import and use the same selectors, so they win. Django's manifest +storages rewrite the relative `@import` to the hashed file name. -The JavaScript file depends on: -- CKEditor5 Classic Editor build -- Modern browser APIs (MutationObserver, localStorage) -- CSS custom properties support +CKEditor declares its variables on `:root` and derives most of them from a few base ones +(`--ck-color-base-background`, `-foreground`, `-border`, `-text`). Override them on `` (`:root`, +`html.dark`) so the derived variables follow. The full list is in CKEditor's CSS +(`django_ckeditor_5/static/django_ckeditor_5/dist/styles.css`). -### Testing Considerations - -Test the implementation across: -- Different screen sizes (mobile, tablet, desktop) -- Theme switching (light/dark modes) -- Form validation states -- Content restoration from auto-save +If you need Tailwind utilities, compile your stylesheet in your own build and point +`CKEDITOR_5_CUSTOM_CSS` at the output, never at the source. ## Troubleshooting -### Common Issues - -1. **Styles not applying**: Ensure Tailwind CSS is processing the `@apply` directives -2. **Theme not switching**: Check that `data-theme` attribute changes are detected -3. **Auto-save not working**: Verify localStorage availability and permissions -4. **Mobile toolbar issues**: Check responsive breakpoints and toolbar configuration - -### Debug Mode - -Enable debug logging by setting: - -```javascript -window.CKEditorDebug = true; -``` - -This will log initialization steps and theme changes to the console. - -## License - -This implementation follows the same license as your Django project and includes integration code compatible with CKEditor5's GPL license. \ No newline at end of file +- **Transparent editor or black text in dark mode**: the stylesheet has invalid values, usually + Tailwind source served without a build. Check the file in the browser's network tab. +- **Theme does not switch**: the variables follow the `dark` class on ``. Check that Unfold's + theme switch sets it. diff --git a/whiteneuron/base/tests_regressions.py b/whiteneuron/base/tests_regressions.py index 96f0009..08e6235 100644 --- a/whiteneuron/base/tests_regressions.py +++ b/whiteneuron/base/tests_regressions.py @@ -1,10 +1,12 @@ """Regression tests for the hardening / performance changes.""" import asyncio +import re +from pathlib import Path from unittest import mock from django.contrib.admin.sites import AdminSite from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase, override_settings +from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings from django.urls import reverse from django.utils import translation @@ -712,3 +714,71 @@ def broken(**kwargs): with self.settings(MIDDLEWARE=middleware), self.assertLogs("whiteneuron.base.middleware", level="ERROR"): page = self.client.get(reverse("admin:login"), {"next": "/admin/"}) self.assertEqual(page.status_code, 200) + + +class CKEditorCustomCssTests(SimpleTestCase): + # CKEditor's fixed light colors that stay readable in dark mode: accents, translucent overlays, + # self-contained badges and tooltips; highlights get dark text from the browser (MarkText) + READABLE_IN_DARK = { + "--ck-color-base-action", "--ck-color-base-active", "--ck-color-base-active-focus", + "--ck-color-base-error", "--ck-color-base-focus", "--ck-color-button-action-active-background", + "--ck-color-button-action-disabled-background", "--ck-color-button-action-hover-background", + "--ck-color-button-cancel", "--ck-color-button-save", "--ck-color-color-grid-check-icon", + "--ck-color-image-upload-icon", "--ck-color-image-upload-icon-background", "--ck-color-light-red", + "--ck-color-switch-button-off-background", "--ck-color-switch-button-off-hover-background", + "--ck-color-switch-button-on-hover-background", "--ck-color-upload-bar-background", + "--ck-color-upload-placeholder-loader", "--ck-color-widget-hover-border", + "--ck-html-embed-source-disabled-color", "--ck-show-blocks-border-color", + # translucent + "--ck-color-focus-disabled-shadow", "--ck-color-focus-error-shadow", "--ck-color-link-fake-selection", + "--ck-color-switch-button-inner-shadow", "--ck-color-table-focused-cell-background", + "--ck-content-color-mention-background", "--ck-dialog-overlay-background-color", + "--ck-table-selected-cell-background", + # badges and tooltips with their own background + "--ck-color-resizer-tooltip-background", "--ck-color-resizer-tooltip-text", + "--ck-evaluation-badge-background", "--ck-evaluation-badge-text-color", + "--ck-powered-by-background", "--ck-powered-by-text-color", + # highlights: find results get dark text from a rule, markers from the browser + "--ck-color-highlight-background", "--ck-content-highlight-marker-blue", + "--ck-content-highlight-marker-green", "--ck-content-highlight-marker-pink", + "--ck-content-highlight-marker-yellow", "--ck-content-highlight-pen-green", + "--ck-content-highlight-pen-red", + } + + @staticmethod + def _read(package, path): + return (Path(package.__file__).parent / path).read_text(encoding="utf-8") + + def setUp(self): + import whiteneuron + + # the packaged file itself: staticfiles finders may return a project's file of the same name + self.css = self._read(whiteneuron, "static/base/css/ckeditor5.css") + + def test_packaged_css_is_plain_css(self): + # django-ckeditor-5 links CKEDITOR_5_CUSTOM_CSS as-is: the file used to be Tailwind source, so + # browsers dropped every @apply rule and theme() left the editor transparent (unreadable in dark mode) + source_only = ( + "@apply", "theme(", "@custom-variant", "@tailwind", "@theme", "@utility", "@variant", + "@source", "@plugin", "@reference", "--alpha(", "--spacing(", + ) + for token in source_only: + self.assertNotIn(token, self.css) + self.assertIn("--ck-color-base-background", self.css) + + def test_dark_mode_covers_ckeditor_light_colors(self): + # CKEditor's fixed light colors (not derived from --ck-color-base-*) must be overridden for dark + # mode or reviewed above, so a django-ckeditor-5 upgrade that adds one fails here + import django_ckeditor_5 + + ckeditor_css = self._read(django_ckeditor_5, "static/django_ckeditor_5/dist/styles.css") + fixed = { + name + for block in re.findall(r":root\{([^}]*)\}", ckeditor_css) + for name, value in re.findall(r"(--ck-[\w-]+):([^;]+)", block) + if re.match(r"\s*(#[0-9a-f]{3,8}\b|rgba?\(|hsla?\()", value, re.I) and "var(" not in value + } + self.assertGreater(len(fixed), 50) # the parse still finds CKEditor's variables + dark = re.search(r"html\.dark \{([^}]*)\}", self.css).group(1) + overridden = set(re.findall(r"(--ck-[\w-]+):", dark)) + self.assertEqual(sorted(fixed - overridden - self.READABLE_IN_DARK), []) diff --git a/whiteneuron/static/base/css/ckeditor5.css b/whiteneuron/static/base/css/ckeditor5.css index af3e550..1fd0433 100644 --- a/whiteneuron/static/base/css/ckeditor5.css +++ b/whiteneuron/static/base/css/ckeditor5.css @@ -1,550 +1,116 @@ -/* CKEditor5 Custom Styles for Unfold Theme with Tailwind CSS 3 & DaisyUI 4 */ +/* + * CKEditor 5 theme for Unfold (settings.CKEDITOR_5_CUSTOM_CSS). + * + * Plain CSS, no build step: django-ckeditor-5 links this file as-is after CKEditor's own CSS. + * It only overrides CKEditor's --ck-* variables (its documented theming method), so the editor's + * layout and features keep CKEditor's own styles. Colors come from unfold (UNFOLD["COLORS"], + * daisy-themes.css); the var() fallbacks only apply without them: CKEditor's defaults in light + * mode, Tailwind grays in dark mode. + * + * CKEditor declares its variables on :root and derives most of them from a few base ones + * (--ck-color-base-*), so overriding those on recolors the whole UI. The others are + * fixed light values and are overridden one by one for dark mode; tests_regressions.py fails + * when a CKEditor upgrade adds one that is neither overridden nor reviewed. + */ -/* Container styling to match Unfold form fields */ -.ck-editor-container { - @apply relative w-full; -} - -/* Main editor wrapper */ -.ck.ck-editor { - @apply relative w-full bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm; - @apply focus-within:border-primary-600 focus-within:ring focus-within:ring-primary-500/40 dark:focus-within:border-primary-600 dark:focus-within:ring-primary-500/30; - @apply transition-all duration-300 ease-in-out; -} - -/* Toolbar styling */ -.ck.ck-toolbar { - @apply bg-base-100 dark:bg-base-900 border-b border-gray-200 dark:border-gray-700; - @apply px-2 py-1.5 rounded-t-lg; -} - -/* Toolbar items container */ -.ck.ck-toolbar__items { - @apply flex flex-wrap items-center gap-1; -} - -/* Toolbar separators */ -.ck.ck-toolbar__separator { - @apply w-px h-6 bg-gray-300 dark:bg-gray-600 mx-1; -} - -/* Buttons styling refined */ -.ck.ck-button { - @apply inline-flex items-center justify-center h-8 min-h-8 px-2.5 py-1.5; - @apply text-[13px] font-medium text-gray-700 dark:text-gray-300 tracking-tight; - @apply bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700/70; - @apply border border-transparent rounded-md; - @apply transition-colors duration-150 ease-in-out; - @apply cursor-pointer select-none; - @apply focus:outline-none focus:ring-2 focus:ring-primary-500/40 dark:focus:ring-primary-500/30; -} - -.ck.ck-button:hover { - @apply bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100; -} - -.ck.ck-button.ck-on { - @apply bg-primary-100 dark:bg-primary-700/70 text-primary-700 dark:text-white; - @apply border-primary-300 dark:border-primary-600; -} - -.ck.ck-button.ck-disabled, -.ck.ck-button:disabled { - @apply opacity-50 cursor-not-allowed pointer-events-none; -} - -/* Button icons */ -.ck.ck-button .ck-icon { - @apply w-4 h-4 opacity-90 text-gray-600 dark:text-gray-300; -} - -.ck.ck-button:hover .ck-icon, -.ck.ck-button.ck-on .ck-icon { - @apply opacity-100 text-primary-600 dark:text-white; -} - -/* Dropdown buttons */ -.ck.ck-dropdown__button { - @apply inline-flex items-center gap-2; -} - -.ck.ck-dropdown__button .ck-dropdown__arrow { - @apply w-3 h-3 opacity-60; -} - -/* Split buttons styling */ -.ck.ck-splitbutton .ck-splitbutton__action { - @apply rounded-l-md border-r border-gray-300 dark:border-gray-600; -} - -.ck.ck-splitbutton .ck-splitbutton__arrow { - @apply rounded-r-md px-2; -} - -/* Dropdown panels */ -.ck.ck-dropdown__panel { - @apply bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700; - @apply rounded-lg shadow-lg mt-1 p-1 min-w-48; -} - -/* High contrast nested dropdown variants (language, heading, font size) */ -.ck.ck-code-block-dropdown .ck-dropdown__panel, -.ck.ck-font-size-dropdown .ck-dropdown__panel, -.ck.ck-heading-dropdown .ck-dropdown__panel { - @apply bg-white dark:bg-base-900 border border-gray-200 dark:border-gray-700 shadow-xl; -} - -/* Improve list item readability inside panels */ -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item { @apply rounded-md; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item + .ck-list__item { @apply mt-0.5; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button { @apply w-full text-left px-3 py-2 text-[13px] font-medium; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button:hover { @apply bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button.ck-on { @apply bg-primary-100 dark:bg-primary-700 text-primary-700 dark:text-white; } - -/* Visible check holder */ -.ck.ck-dropdown__panel .ck-list-item-button__check-holder { @apply w-4 h-4 mr-2 flex items-center justify-center text-[11px] rounded-sm border border-gray-300 dark:border-gray-600 bg-white dark:bg-base-800; } - -/* Ensure visibility when panel is shown */ -.ck.ck-dropdown__panel.ck-dropdown__panel-visible { @apply bg-white dark:bg-base-900; } - -/* Scrollbars inside dropdown lists */ -.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar { @apply w-2; } -.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar-thumb { @apply bg-gray-300 dark:bg-gray-600 rounded; } - -/* Zebra table rows inside content */ -.ck.ck-content table tbody tr:nth-child(odd) { @apply bg-gray-50 dark:bg-base-800; } -.ck.ck-content table tbody tr:nth-child(even) { @apply bg-white dark:bg-base-900; } -.ck.ck-content table th, .ck.ck-content table td { @apply border border-gray-200 dark:border-gray-700; } - -/* Text selection highlight */ -.ck.ck-content ::selection { background: theme('colors.primary.500'); color: white; } -.ck.ck-content ::-moz-selection { background: theme('colors.primary.500'); color: white; } - -/* Main editing area */ -.ck.ck-content { - @apply min-h-48 max-h-[32rem] p-5 bg-white dark:bg-base-800; - @apply text-gray-900 dark:text-gray-100 text-[15px] leading-relaxed; - @apply rounded-b-lg border-none outline-none; - @apply overflow-y-auto; - font-family: 'Inter', system-ui, -apple-system, sans-serif; -} - -.ck.ck-content:focus { - @apply outline-none ring-0; -} - -/* Content styling within editor */ -.ck.ck-content h1, -.ck.ck-content h2, -.ck.ck-content h3, -.ck.ck-content h4, -.ck.ck-content h5, -.ck.ck-content h6 { - @apply font-semibold text-gray-900 dark:text-gray-100 mb-3 mt-4; -} - -.ck.ck-content h1 { @apply text-2xl; } -.ck.ck-content h2 { @apply text-xl; } -.ck.ck-content h3 { @apply text-lg; } -.ck.ck-content h4 { @apply text-base font-bold; } -.ck.ck-content h5 { @apply text-sm font-bold; } -.ck.ck-content h6 { @apply text-xs font-bold uppercase tracking-wide; } - -.ck.ck-content p { - @apply mb-4 text-gray-700 dark:text-gray-300; -} - -.ck.ck-content blockquote { - /* Refined blockquote styling */ - @apply relative pl-5 py-3 my-5 rounded-md overflow-hidden italic; - @apply bg-gray-50 dark:bg-base-900 text-gray-700 dark:text-gray-300; - @apply border-l-4 border-primary-500; -} - -.ck.ck-content blockquote:before { - content: ""; - @apply absolute left-0 top-0 h-full w-1 bg-primary-500; -} - -.ck.ck-content ul, -.ck.ck-content ol { - @apply ml-6 mb-4; -} - -.ck.ck-content ul { - @apply list-disc; -} - -.ck.ck-content ol { - @apply list-decimal; -} - -.ck.ck-content li { - @apply mb-1 text-gray-700 dark:text-gray-300; -} - -.ck.ck-content a { - @apply text-primary-600 dark:text-primary-500 hover:text-primary-700 dark:hover:text-primary-400; - @apply underline transition-colors duration-200; -} - -.ck.ck-content code { - @apply bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200; - @apply px-2 py-1 rounded font-mono text-sm; -} - -.ck.ck-content pre { - @apply bg-gray-900/90 dark:bg-gray-900 text-gray-100 dark:text-gray-100; - @apply border border-gray-800 dark:border-gray-700; - @apply p-4 rounded-md overflow-x-auto my-5 font-mono text-[13px] leading-snug; -} - -/* Table styling */ -.ck.ck-content table { - @apply w-full border-collapse my-4; -} - -.ck.ck-content table td, -.ck.ck-content table th { - @apply border border-gray-300 dark:border-gray-600 px-3 py-2; -} - -.ck.ck-content table th { - @apply bg-gray-100 dark:bg-gray-700 font-semibold text-gray-900 dark:text-gray-100; -} - -.ck.ck-content table td { - @apply bg-white dark:bg-base-800 text-gray-700 dark:text-gray-300; -} - -/* Image widget styling */ -.ck.ck-content figure.image { - @apply my-4 mx-auto max-w-full; -} - -.ck.ck-content figure.image img { - @apply max-w-full h-auto rounded-lg shadow-md; -} - -/* Widget styling */ -.ck.ck-widget { - @apply relative; -} - -.ck.ck-widget.ck-widget_selected { - @apply ring-2 ring-primary-400 ring-opacity-50 rounded; -} - -.ck.ck-widget__type-around__button { - @apply bg-primary-600 hover:bg-primary-700 text-white; - @apply w-6 h-6 rounded-full flex items-center justify-center; - @apply transition-colors duration-200; -} - -/* Resizer styling */ -.ck.ck-widget__resizer { - @apply border border-primary-400 bg-primary-50 dark:bg-primary-900 bg-opacity-20; -} - -.ck.ck-widget__resizer__handle { - @apply bg-primary-600 w-3 h-3 border border-primary-700 rounded; -} - -/* Placeholder styling */ -.ck.ck-content .ck-placeholder:before { - @apply text-gray-400 dark:text-gray-500 italic; -} - -/* Balloon panels */ -.ck.ck-balloon-panel { - @apply bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700; - @apply rounded-lg shadow-lg p-2; -} - -/* Link balloon styling */ -.ck.ck-link-form, -.ck.ck-link-actions { - @apply flex items-center gap-2 p-2; -} - -.ck.ck-link-form .ck-input { - @apply flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600; - @apply bg-white dark:bg-base-800 text-gray-900 dark:text-gray-100; - @apply rounded-md text-sm focus:ring-2 focus:ring-primary-300 focus:border-primary-600; -} - -/* Color picker styling */ -.ck.ck-color-selector { - @apply grid grid-cols-6 gap-1 p-2; -} - -.ck.ck-color-selector .ck-color-selector__option { - @apply w-7 h-7 rounded-md border border-gray-300 dark:border-gray-600 shadow-sm; - @apply hover:scale-110 transition-transform duration-150; -} -.ck.ck-color-selector .ck-color-selector__option.ck-on { - @apply ring-2 ring-primary-500 ring-offset-1 ring-offset-white dark:ring-offset-base-800 border-primary-500; -} - -/* Font size dropdown */ -.ck.ck-font-size-dropdown .ck-dropdown__panel { - @apply max-h-48 overflow-y-auto; -} - -.ck.ck-list .ck-list__item { - @apply px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700; - @apply text-gray-700 dark:text-gray-300 cursor-pointer; - @apply transition-colors duration-200; -} - -.ck.ck-list .ck-list__item.ck-on { - @apply bg-primary-100 dark:bg-primary-700 text-primary-700 dark:text-white font-medium rounded; -} - -/* Code block dropdown */ -.ck.ck-code-block-dropdown .ck-dropdown__panel { - @apply min-w-56; -} - -/* Media embed styling */ -.ck.ck-content .ck-media__wrapper { - @apply my-4 rounded-lg overflow-hidden shadow-md; -} - -.ck.ck-content .ck-media__placeholder { - @apply bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400; - @apply p-8 text-center rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600; -} - -/* Todo list styling */ -.ck.ck-content .todo-list { - @apply list-none ml-0; -} - -.ck.ck-content .todo-list li { - @apply flex items-start gap-3 mb-2; -} - -.ck.ck-content .todo-list li .todo-list__label { - @apply flex items-center gap-2; -} - -.ck.ck-content .todo-list li .todo-list__label input { - @apply w-4 h-4 text-primary-600 border border-gray-300 dark:border-gray-600 rounded; - @apply focus:ring-2 focus:ring-primary-300; -} - -/* Highlight styling */ -.ck.ck-content mark { - @apply bg-yellow-200 dark:bg-yellow-600 text-gray-900 dark:text-gray-100; - @apply px-1 rounded; -} - -/* Source editing mode */ -.ck.ck-source-editing-area { - @apply w-full min-h-48 p-4 bg-gray-50 dark:bg-gray-900; - @apply text-gray-900 dark:text-gray-100 font-mono text-sm; - @apply border border-gray-300 dark:border-gray-600 rounded-lg; - @apply focus:ring-2 focus:ring-primary-300 focus:border-primary-600; - @apply resize-y; -} - -/* Word count styling */ -.word-count { - @apply mt-2 text-right; -} - -.ck.ck-word-count { - @apply text-xs text-gray-500 dark:text-gray-400 flex gap-4 justify-end; -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .ck.ck-toolbar__items { - @apply gap-0.5; - } - - .ck.ck-button { - @apply h-7 min-h-7 px-2 text-xs; - } - - .ck.ck-content { - @apply min-h-32 p-3 text-sm; - } - - .ck.ck-dropdown__panel { - @apply min-w-full; - } -} - -/* Animation improvements */ -@media (prefers-reduced-motion: no-preference) { - .ck.ck-button { - @apply transition-all duration-200 ease-in-out; - } - - .ck.ck-dropdown__panel { - @apply transition-opacity duration-200 ease-in-out; - } - - .ck.ck-widget { - @apply transition-all duration-200 ease-in-out; - } -} - -/* Dark mode specific adjustments */ -@media (prefers-color-scheme: dark) { - .ck.ck-editor { - @apply border-gray-600; - } - - .ck.ck-content { - @apply scrollbar-thin scrollbar-track-gray-800 scrollbar-thumb-gray-600; - } -} - -/* Focus states for accessibility */ -.ck.ck-button:focus-visible { - @apply ring-2 ring-primary-400 ring-offset-1 ring-offset-white dark:ring-offset-base-800; -} - -.ck.ck-content:focus { - @apply ring-0 outline-none; -} - -/* High contrast mode support */ -@media (prefers-contrast: high) { - .ck.ck-button { - @apply border border-gray-400 dark:border-gray-500; - } - - .ck.ck-content { - @apply border border-gray-400 dark:border-gray-500; - } -} - -/* Override default CKEditor colors for better integration */ :root { - --ck-color-base-background: theme('colors.white'); - --ck-color-base-foreground: theme('colors.gray.100'); - --ck-color-focus-border: theme('colors.primary.600'); - --ck-color-text: theme('colors.gray.900'); - --ck-color-shadow-drop: rgba(0, 0, 0, 0.15); - --ck-color-shadow-inner: rgba(0, 0, 0, 0.1); - --ck-color-button-default-background: transparent; - --ck-color-button-default-hover-background: theme('colors.gray.100'); - --ck-color-button-on-background: theme('colors.primary.100'); - --ck-color-button-on-hover-background: theme('colors.primary.200'); - --ck-color-dropdown-panel-background: theme('colors.white'); - --ck-color-dropdown-panel-border: theme('colors.gray.200'); - --ck-color-dialog-background: theme('colors.white'); - --ck-color-dialog-form-header-border: theme('colors.gray.200'); - --ck-color-input-background: theme('colors.white'); - --ck-color-input-border: theme('colors.gray.300'); - --ck-color-input-error-border: theme('colors.red.500'); - --ck-color-list-background: theme('colors.white'); - --ck-color-list-button-hover-background: theme('colors.gray.100'); - --ck-color-list-button-on-background: theme('colors.primary.100'); - --ck-color-panel-background: theme('colors.white'); - --ck-color-panel-border: theme('colors.gray.200'); - --ck-color-toolbar-background: theme('colors.gray.50'); - --ck-color-toolbar-border: theme('colors.gray.200'); - --ck-color-tooltip-background: theme('colors.gray.800'); - --ck-color-tooltip-text: theme('colors.white'); - --ck-color-engine-placeholder-text: theme('colors.gray.400'); - --ck-color-upload-bar-background: theme('colors.primary.600'); - --ck-color-link-default: theme('colors.primary.600'); - --ck-color-link-selected-background: theme('colors.primary.100'); -} - -[data-theme="dark"] { - --ck-color-base-background: theme('colors.base.800'); - --ck-color-base-foreground: theme('colors.base.700'); - --ck-color-focus-border: theme('colors.primary.600'); - --ck-color-text: theme('colors.gray.100'); - --ck-color-shadow-drop: rgba(0, 0, 0, 0.3); - --ck-color-shadow-inner: rgba(0, 0, 0, 0.2); - --ck-color-button-default-background: transparent; - --ck-color-button-default-hover-background: theme('colors.gray.700'); - --ck-color-button-on-background: theme('colors.primary.600'); - --ck-color-button-on-hover-background: theme('colors.primary.700'); - --ck-color-dropdown-panel-background: theme('colors.base.800'); - --ck-color-dropdown-panel-border: theme('colors.gray.600'); - --ck-color-dialog-background: theme('colors.base.800'); - --ck-color-dialog-form-header-border: theme('colors.gray.600'); - --ck-color-input-background: theme('colors.base.800'); - --ck-color-input-border: theme('colors.gray.600'); - --ck-color-input-error-border: theme('colors.red.500'); - --ck-color-list-background: theme('colors.base.800'); - --ck-color-list-button-hover-background: theme('colors.gray.700'); - --ck-color-list-button-on-background: theme('colors.primary.600'); - --ck-color-panel-background: theme('colors.base.800'); - --ck-color-panel-border: theme('colors.gray.600'); - --ck-color-toolbar-background: theme('colors.base.900'); - --ck-color-toolbar-border: theme('colors.gray.700'); - --ck-color-tooltip-background: theme('colors.gray.700'); - --ck-color-tooltip-text: theme('colors.white'); - --ck-color-engine-placeholder-text: theme('colors.gray.500'); - --ck-color-upload-bar-background: theme('colors.primary.600'); - --ck-color-link-default: theme('colors.primary.500'); - --ck-color-link-selected-background: theme('colors.primary.800'); -} - -/* Animation keyframes */ -@keyframes ck-fade-in { - from { - opacity: 0; - transform: translateY(-4px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Apply fade-in animation to dropdowns */ -.ck.ck-dropdown__panel { - animation: ck-fade-in 0.2s ease-out; -} - -/* Custom scrollbar for content area */ -.ck.ck-content::-webkit-scrollbar { - @apply w-2; -} - -.ck.ck-content::-webkit-scrollbar-track { - @apply bg-gray-100 dark:bg-gray-800 rounded; -} - -.ck.ck-content::-webkit-scrollbar-thumb { - @apply bg-gray-400 dark:bg-gray-600 rounded hover:bg-gray-500 dark:hover:bg-gray-500; -} - -/* Ensure proper spacing for nested editor containers */ -.ck-editor-container .ck-editor-container { - @apply mt-0; -} - -/* Integration with Django form styling */ -.form-row .ck-editor-container, -.field-box .ck-editor-container { - @apply w-full; -} - -/* Error state styling */ -.errors .ck.ck-editor { - @apply border-red-600 dark:border-red-500; -} - -.errors .ck.ck-editor:focus-within { - @apply ring-red-200 dark:ring-red-600/40 border-red-600 dark:border-red-500; -} - -/* Success state styling */ -.success .ck.ck-editor { - @apply border-green-600 dark:border-green-500; -} - -.success .ck.ck-editor:focus-within { - @apply ring-green-200 dark:ring-green-600/40 border-green-600 dark:border-green-500; + /* unfold's font, written out: Tailwind scans this file, and referencing its font theme variable + makes styles.css emit Tailwind's default font, overriding unfold's */ + --ck-content-font-family: "Inter", system-ui, -apple-system, sans-serif; + + --ck-color-base-border: var(--color-base-200, #ccced1); + --ck-color-base-text: var(--color-font-important-light, #333); + --ck-color-focus-border: var(--color-primary-600, hsl(218, 81.8%, 56.9%)); + --ck-color-focus-outer-shadow: var(--color-primary-200, #cae1fc); + --ck-color-link-default: var(--color-primary-600, #0000f0); + + --ck-color-button-on-color: var(--color-primary-600, #2977ff); + --ck-color-button-on-background: var(--color-primary-50, #f0f7ff); + --ck-color-button-on-hover-background: var(--color-primary-100, #dbecff); + --ck-color-button-on-active-background: var(--color-primary-100, #dbecff); +} + +/* unfold's dark mode: the "dark" class on , also set for daisyUI dark themes */ +html.dark { + --wn-ck-primary: var(--color-primary-500, #0ea5e9); + + --ck-color-base-background: var(--color-base-900, #111827); + --ck-color-base-foreground: var(--color-base-800, #1f2937); + --ck-color-base-border: var(--color-base-700, #374151); + --ck-color-base-text: var(--color-font-important-dark, #f9fafb); + --ck-color-focus-border: var(--wn-ck-primary); + --ck-color-focus-outer-shadow: color-mix(in srgb, var(--wn-ck-primary) 35%, transparent); + --ck-color-shadow-drop: rgba(0, 0, 0, 0.4); + --ck-color-shadow-drop-active: rgba(0, 0, 0, 0.5); + --ck-color-shadow-inner: rgba(0, 0, 0, 0.3); + + /* buttons */ + --ck-color-button-default-hover-background: var(--color-base-800, #1f2937); + --ck-color-button-default-active-background: var(--color-base-700, #374151); + --ck-color-button-on-color: var(--color-primary-400, #38bdf8); + --ck-color-button-on-background: color-mix(in srgb, var(--wn-ck-primary) 20%, transparent); + --ck-color-button-on-hover-background: color-mix(in srgb, var(--wn-ck-primary) 30%, transparent); + --ck-color-button-on-active-background: color-mix(in srgb, var(--wn-ck-primary) 35%, transparent); + --ck-color-button-on-disabled-background: var(--color-base-800, #1f2937); + --ck-color-button-action-text: #fff; + --ck-color-split-button-hover-background: var(--color-base-700, #374151); + --ck-color-split-button-hover-border: var(--color-base-600, #4b5563); + + /* inputs and placeholders */ + --ck-color-input-disabled-background: var(--color-base-800, #1f2937); + --ck-color-input-disabled-text: var(--color-font-subtle-dark, #9ca3af); + --ck-color-engine-placeholder-text: var(--color-font-subtle-dark, #9ca3af); + --ck-color-media-embed-placeholder-url-text: var(--color-font-subtle-dark, #9ca3af); + + /* selection, widgets, links */ + --ck-color-editable-blur-selection: var(--color-base-700, #374151); + --ck-color-widget-blurred-border: var(--color-base-600, #4b5563); + --ck-color-link-default: var(--color-primary-400, #38bdf8); + --ck-color-link-selected-background: color-mix(in srgb, var(--wn-ck-primary) 20%, transparent); + --ck-color-code-block-label-background: var(--color-base-600, #4b5563); + --ck-color-image-caption-highlighted-background: var(--color-base-600, #4b5563); + --ck-color-table-caption-highlighted-background: var(--color-base-600, #4b5563); + + /* panels: keyboard shortcuts dialog (Alt+0), style grid */ + --ck-accessibility-help-dialog-border-color: var(--color-base-700, #374151); + --ck-accessibility-help-dialog-code-background-color: var(--color-base-700, #374151); + --ck-accessibility-help-dialog-kbd-shadow-color: var(--color-base-950, #030712); + --ck-style-panel-button-label-background: var(--color-base-800, #1f2937); + --ck-style-panel-button-hover-label-background: var(--color-base-700, #374151); + --ck-style-panel-button-hover-border-color: var(--color-base-600, #4b5563); + + /* editing area: CKEditor's content text is #000 by default */ + --ck-content-font-color: var(--color-font-important-dark, #f9fafb); + --ck-content-color-image-caption-background: var(--color-base-800, #1f2937); + --ck-content-color-image-caption-text: var(--color-font-default-dark, #d1d5db); + --ck-content-color-table-caption-background: var(--color-base-800, #1f2937); + --ck-content-color-table-caption-text: var(--color-font-default-dark, #d1d5db); + --ck-content-color-mention-text: #f472b6; + --ck-table-content-default-border-color: var(--color-base-600, #4b5563); + --ck-table-layout-default-border-color: var(--color-base-600, #4b5563); + --ck-table-border-none-helper-line-color: var(--color-base-600, #4b5563); +} + +/* native scrollbars and controls inside the editor; the page's color-scheme is unfold's */ +html.dark .ck { + color-scheme: dark; +} + +/* fixed content colors without a variable */ + +/* code blocks: #353535 text */ +html.dark .ck-content pre { + color: var(--color-font-default-dark, #d1d5db); + border-color: var(--color-base-700, #374151); +} + +/* todo list checkboxes: #333 border (checked ones keep CKEditor's green) */ +html.dark .ck-content .todo-list .todo-list__label > input::before, +html.dark .ck-content .todo-list .todo-list__label > span[contenteditable="false"] > input::before { + border-color: var(--color-base-400, #9ca3af); +} + +/* find & replace matches: --ck-color-text (light in dark mode) on yellow / #ff9633 */ +html.dark .ck-find-result { + color: var(--color-base-900, #111827); }