FEAT: Replace Tiptap editor with CodeMirror

Replaced the Tiptap-based rich text editor with CodeMirror for basic markdown formatting.

- Removed  directory.
- Removed all  and  dependencies from .
- Renamed  to .
- Updated  to use  and removed Tiptap-specific logic.
- Updated all Svelte components that were importing the old Tiptap wrapper to import the new CodeMirror wrapper and removed unsupported props (, , , ).
- Ran
up to date, audited 492 packages in 1s

92 packages are looking for funding
  run `npm fund` for details

8 low severity vulnerabilities

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details. and
> osit-aether-app-svelte@3.9.6 format
> prettier --write .

.eslintrc.cjs 18ms (unchanged)
.prettierrc 4ms (unchanged)
.vscode/settings.json 2ms (unchanged)
ae_app_svelte_tailwind_skeleton.code-workspace 1ms (unchanged)
ARCHITECTURE.md 22ms (unchanged)
components.json 1ms (unchanged)
COMPONENTS.md 9ms (unchanged)
DATA_STRUCTURES.md 7ms (unchanged)
eslint.config.js 4ms (unchanged)
GEMINI.md 5ms (unchanged)
jsconfig.json 1ms (unchanged)
NAMING_CONVENTIONS.md 8ms (unchanged)
OLD_README_guidelines_ui_ux.md 6ms (unchanged)
OLD_README_guidelines_v1.md 8ms (unchanged)
OLD_README_guidelines_v2.md 26ms (unchanged)
package.json 1ms (unchanged)
playwright.config.ts 3ms (unchanged)
README.md 6ms (unchanged)
src/ae-c-idaa-light.css 14ms (unchanged)
src/ae-c-lci.css 9ms (unchanged)
src/ae-osit-default.css 9ms (unchanged)
src/aeclci_v1.css 7ms (unchanged)
src/app.css 16ms (unchanged)
src/app.d.ts 1ms (unchanged)
src/app.html 11ms (unchanged)
src/index.test.ts 1ms (unchanged)
src/lib/ae_api/api_delete_object.ts 10ms (unchanged)
src/lib/ae_api/api_get__crud_obj_id.ts 9ms (unchanged)
src/lib/ae_api/api_get__crud_obj_li_v1.ts 7ms (unchanged)
src/lib/ae_api/api_get__crud_obj_li_v2.ts 6ms (unchanged)
src/lib/ae_api/api_get_object_v1.ts 19ms (unchanged)
src/lib/ae_api/api_get_object.ts 9ms (unchanged)
src/lib/ae_api/api_patch_object.ts 5ms (unchanged)
src/lib/ae_api/api_post_object.ts 10ms (unchanged)
src/lib/ae_archives/ae_archives__archive_content.ts 12ms (unchanged)
src/lib/ae_archives/ae_archives__archive.ts 14ms (unchanged)
src/lib/ae_archives/ae_archives_functions.ts 1ms (unchanged)
src/lib/ae_archives/db_archives.ts 4ms (unchanged)
src/lib/ae_archives/README.md 2ms (unchanged)
src/lib/ae_core/ae_comp__hosted_files_clip_video_li.svelte 33ms (unchanged)
src/lib/ae_core/ae_comp__hosted_files_clip_video_v1.svelte 25ms (unchanged)
src/lib/ae_core/ae_comp__hosted_files_clip_video.svelte 21ms (unchanged)
src/lib/ae_core/ae_comp__hosted_files_download_button.svelte 10ms (unchanged)
src/lib/ae_core/ae_comp__hosted_files_upload.svelte 13ms (unchanged)
src/lib/ae_core/ae_core_functions.ts 10ms (unchanged)
src/lib/ae_core/core__account.ts 1ms (unchanged)
src/lib/ae_core/core__activity_log.ts 7ms (unchanged)
src/lib/ae_core/core__api_helpers.ts 2ms (unchanged)
src/lib/ae_core/core__check_hosted_file_obj_w_hash.ts 1ms (unchanged)
src/lib/ae_core/core__countries.ts 2ms (unchanged)
src/lib/ae_core/core__country_subdivisions.ts 2ms (unchanged)
src/lib/ae_core/core__crud_generic.ts 6ms (unchanged)
src/lib/ae_core/core__data_store.ts 2ms (unchanged)
src/lib/ae_core/core__hosted_files.ts 6ms (unchanged)
src/lib/ae_core/core__idb_dexie.ts 4ms (unchanged)
src/lib/ae_core/core__person.ts 16ms (unchanged)
src/lib/ae_core/core__qr_code.ts 6ms (unchanged)
src/lib/ae_core/core__site_domain.ts 2ms (unchanged)
src/lib/ae_core/core__site.ts 1ms (unchanged)
src/lib/ae_core/core__time_zones.ts 2ms (unchanged)
src/lib/ae_core/core__user.ts 6ms (unchanged)
src/lib/ae_core/db_core.ts 3ms (unchanged)
src/lib/ae_events_functions.ts 2ms (unchanged)
src/lib/ae_events/ae_events__event_badge_template.ts 9ms (unchanged)
src/lib/ae_events/ae_events__event_badge.ts 13ms (unchanged)
src/lib/ae_events/ae_events__event_device.ts 16ms (unchanged)
src/lib/ae_events/ae_events__event_file.ts 14ms (unchanged)
src/lib/ae_events/ae_events__event_location.ts 12ms (unchanged)
src/lib/ae_events/ae_events__event_presentation.ts 10ms (unchanged)
src/lib/ae_events/ae_events__event_presenter.ts 11ms (unchanged)
src/lib/ae_events/ae_events__event_session.ts 18ms (unchanged)
src/lib/ae_events/ae_events__event.ts 17ms (unchanged)
src/lib/ae_events/ae_events__exhibit.ts 10ms (unchanged)
src/lib/ae_events/db_events.ts 10ms (unchanged)
src/lib/ae_journals/ae_journals__journal_entry.ts 13ms (unchanged)
src/lib/ae_journals/ae_journals__journal.ts 15ms (unchanged)
src/lib/ae_journals/ae_journals_functions.ts 1ms (unchanged)
src/lib/ae_journals/ae_journals_stores.ts 3ms (unchanged)
src/lib/ae_journals/db_journals.ts 6ms (unchanged)
src/lib/ae_posts/ae_posts__post_comment.ts 8ms (unchanged)
src/lib/ae_posts/ae_posts__post.ts 12ms (unchanged)
src/lib/ae_posts/ae_posts_functions.ts 1ms (unchanged)
src/lib/ae_posts/db_posts.ts 2ms (unchanged)
src/lib/ae_posts/README.md 2ms (unchanged)
src/lib/ae_sponsorships/ae_sponsorships_functions.ts 7ms (unchanged)
src/lib/ae_sponsorships/db_sponsorships.ts 2ms (unchanged)
src/lib/ae_sponsorships/README.md 2ms (unchanged)
src/lib/ae_utils/ae_utils__crypto.ts 5ms (unchanged)
src/lib/ae_utils/ae_utils__datetime_format.ts 3ms (unchanged)
src/lib/ae_utils/ae_utils__extract_prefixed_form_data.ts 3ms (unchanged)
src/lib/ae_utils/ae_utils__file_extension_icon.ts 1ms (unchanged)
src/lib/ae_utils/ae_utils__files.ts 2ms (unchanged)
src/lib/ae_utils/ae_utils__get_obj_li_w_match_prop.ts 1ms (unchanged)
src/lib/ae_utils/ae_utils__is_datetime_recent.ts 1ms (unchanged)
src/lib/ae_utils/ae_utils__perm_checks.ts 3ms (unchanged)
src/lib/ae_utils/ae_utils__process_data_string.ts 2ms (unchanged)
src/lib/ae_utils/ae_utils__return_obj_type_path.ts 2ms (unchanged)
src/lib/ae_utils/ae_utils__set_obj_prop_display_name.ts 2ms (unchanged)
src/lib/ae_utils/ae_utils__to_title_case.ts 2ms (unchanged)
src/lib/ae_utils/ae_utils.ts 5ms (unchanged)
src/lib/api/api.ts 14ms (unchanged)
src/lib/app_components/analytics.svelte 3ms (unchanged)
src/lib/app_components/e_app_access_type.svelte 26ms (unchanged)
src/lib/app_components/e_app_cfg.svelte 12ms (unchanged)
src/lib/app_components/e_app_clipboard.svelte 6ms (unchanged)
src/lib/app_components/e_app_codemirror_v5.svelte 8ms (unchanged)
src/lib/app_components/e_app_debug_menu.svelte 7ms (unchanged)
src/lib/app_components/e_app_help_tech.svelte 26ms (unchanged)
src/lib/app_components/e_app_sign_in_out.svelte 32ms (unchanged)
src/lib/app_components/e_app_sys_menu.svelte 25ms (unchanged)
src/lib/app_components/e_app_theme.svelte 9ms (unchanged)
src/lib/components/ui/button/button.svelte 6ms (unchanged)
src/lib/components/ui/button/index.js 1ms (unchanged)
src/lib/components/ui/button/index.ts 1ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte 5ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte 2ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte 2ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte 2ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte 2ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte 3ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte 2ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte 2ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte 2ms (unchanged)
src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte 2ms (unchanged)
src/lib/components/ui/dropdown-menu/index.js 1ms (unchanged)
src/lib/components/ui/dropdown-menu/index.ts 1ms (unchanged)
src/lib/components/ui/input/index.js 0ms (unchanged)
src/lib/components/ui/input/index.ts 0ms (unchanged)
src/lib/components/ui/input/input.svelte 2ms (unchanged)
src/lib/components/ui/popover/index.js 0ms (unchanged)
src/lib/components/ui/popover/index.ts 1ms (unchanged)
src/lib/components/ui/popover/popover-content.svelte 2ms (unchanged)
src/lib/components/ui/separator/index.js 0ms (unchanged)
src/lib/components/ui/separator/index.ts 1ms (unchanged)
src/lib/components/ui/separator/separator.svelte 2ms (unchanged)
src/lib/components/ui/tooltip/index.js 0ms (unchanged)
src/lib/components/ui/tooltip/index.ts 1ms (unchanged)
src/lib/components/ui/tooltip/tooltip-content.svelte 2ms (unchanged)
src/lib/electron/electron_native.js 33ms (unchanged)
src/lib/electron/electron_relay.js 6ms (unchanged)
src/lib/electron/README.md 4ms (unchanged)
src/lib/element_qr_scanner_v2.svelte 15ms (unchanged)
src/lib/elements/element_ae_crud_v2.svelte 22ms (unchanged)
src/lib/elements/element_ae_crud.svelte 20ms (unchanged)
src/lib/elements/element_codemirror_editor.svelte 4ms (unchanged)
src/lib/elements/element_codemirror_wrapper.svelte 1ms (unchanged)
src/lib/elements/element_data_store_v2.svelte 38ms (unchanged)
src/lib/elements/element_data_store.svelte 29ms (unchanged)
src/lib/elements/element_input_file.svelte 13ms (unchanged)
src/lib/elements/element_input_files_tbl.svelte 13ms (unchanged)
src/lib/elements/element_input_v2.svelte 59ms (unchanged)
src/lib/elements/element_manage_event_file_li_all.svelte 2ms (unchanged)
src/lib/elements/element_manage_event_file_li_direct.svelte 2ms (unchanged)
src/lib/elements/element_manage_event_file_li.svelte 45ms (unchanged)
src/lib/elements/element_manage_hosted_file_li_all.svelte 8ms (unchanged)
src/lib/elements/element_manage_hosted_file_li.svelte 15ms (unchanged)
src/lib/elements/element_obj_tbl_row.svelte 11ms (unchanged)
src/lib/elements/element_sql_qry.svelte 6ms (unchanged)
src/lib/elements/element_tiptap_editor.scss 2ms (unchanged)
src/lib/elements/element_websocket_v2.svelte 16ms (unchanged)
src/lib/stores/ae_events_stores.ts 6ms (unchanged)
src/lib/stores/ae_idaa_stores.ts 2ms (unchanged)
src/lib/stores/ae_stores.ts 6ms (unchanged)
src/lib/utils/ae_string_snippets.ts 1ms (unchanged)
src/lib/utils/index.ts 0ms (unchanged)
src/lib/utils/utils.ts 1ms (unchanged)
src/parent_iframe.html 5ms (unchanged)
src/routes/+layout.svelte 31ms (unchanged)
src/routes/+layout.ts 5ms (unchanged)
src/routes/+page.svelte 7ms (unchanged)
src/routes/admin/+layout.svelte 6ms (unchanged)
src/routes/admin/+page.svelte 8ms (unchanged)
src/routes/core/+layout.svelte 3ms (unchanged)
src/routes/core/+page.svelte 15ms (unchanged)
src/routes/core/+page.ts 0ms (unchanged)
src/routes/core/ae_comp__person_obj_tbl.svelte 9ms (unchanged)
src/routes/core/not_used+layout.ts 1ms (unchanged)
src/routes/core/person_view.svelte 55ms (unchanged)
src/routes/core/person/[person_id]/+page.svelte 13ms (unchanged)
src/routes/core/person/[person_id]/+page.ts 2ms (unchanged)
src/routes/core/README.md 2ms (unchanged)
src/routes/events_badges/+layout.svelte 7ms (unchanged)
src/routes/events_badges/+layout.ts 1ms (unchanged)
src/routes/events_badges/+page.svelte 2ms (unchanged)
src/routes/events_badges/+page.ts 0ms (unchanged)
src/routes/events_badges/README.md 3ms (unchanged)
src/routes/events_badges/review/+layout.ts 1ms (unchanged)
src/routes/events_badges/review/+page.svelte 35ms (unchanged)
src/routes/events_badges/stats/+layout.ts 1ms (unchanged)
src/routes/events_badges/stats/+page.svelte 33ms (unchanged)
src/routes/events_leads/+layout.svelte 2ms (unchanged)
src/routes/events_leads/+layout.ts 1ms (unchanged)
src/routes/events_leads/+page.svelte 8ms (unchanged)
src/routes/events_leads/+page.ts 1ms (unchanged)
src/routes/events_leads/exhibit/[slug]/+page.svelte 36ms (unchanged)
src/routes/events_leads/exhibit/[slug]/+page.ts 2ms (unchanged)
src/routes/events_leads/exhibit/[slug]/leads_add_scan.svelte 37ms (unchanged)
src/routes/events_leads/exhibit/[slug]/leads_list.svelte 25ms (unchanged)
src/routes/events_leads/exhibit/[slug]/leads_manage.svelte 40ms (unchanged)
src/routes/events_leads/exhibit/[slug]/leads_payment.svelte 10ms (unchanged)
src/routes/events_leads/exhibit/[slug]/leads_view_lead.svelte 42ms (unchanged)
src/routes/events_leads/README.md 4ms (unchanged)
src/routes/events/[event_id]/(badges)/badges/[badge_id]/+page.svelte 5ms (unchanged)
src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_obj_view.svelte 41ms (unchanged)
src/routes/events/[event_id]/(badges)/badges/+layout.svelte 3ms (unchanged)
src/routes/events/[event_id]/(badges)/badges/+page.svelte 5ms (unchanged)
src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_obj_li.svelte 9ms (unchanged)
src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_search.svelte 15ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte 26ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher_file_cont.svelte 32ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher_menu.svelte 12ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher_presenter_view_posters.svelte 6ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher_presenter_view.svelte 6ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte 17ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.svelte 4ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.ts 2ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte 45ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher/+layout.ts 2ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher/+page.svelte 0ms (unchanged)
src/routes/events/[event_id]/(launcher)/launcher/+page.ts 0ms (unchanged)
src/routes/events/[event_id]/(launcher)/menu_location_list.svelte 6ms (unchanged)
src/routes/events/[event_id]/(launcher)/menu_session_list.svelte 10ms (unchanged)
src/routes/events/[event_id]/+layout.svelte 2ms (unchanged)
src/routes/events/[event_id]/+layout.ts 1ms (unchanged)
src/routes/events/[event_id]/+page.svelte 20ms (unchanged)
src/routes/events/[event_id]/+page.ts 1ms (unchanged)
src/routes/events/[event_id]/device/ae_comp__event_device_obj_li_wrapper.svelte 2ms (unchanged)
src/routes/events/[event_id]/device/ae_comp__event_device_obj_li.svelte 28ms (unchanged)
src/routes/events/[event_id]/event_page_menu.svelte 37ms (unchanged)
src/routes/events/[event_id]/location/[event_location_id]/+page.svelte 11ms (unchanged)
src/routes/events/[event_id]/location/[event_location_id]/+page.ts 2ms (unchanged)
src/routes/events/[event_id]/location/[event_location_id]/location_page_menu.svelte 26ms (unchanged)
src/routes/events/[event_id]/location/[event_location_id]/location_view.svelte 27ms (unchanged)
src/routes/events/[event_id]/locations/+page.svelte 5ms (unchanged)
src/routes/events/[event_id]/locations/ae_comp__event_location_obj_li.svelte 18ms (unchanged)
src/routes/events/[event_id]/locations/locations_page_menu.svelte 11ms (unchanged)
src/routes/events/[event_id]/presenter/[presenter_id]/+page.svelte 17ms (unchanged)
src/routes/events/[event_id]/presenter/[presenter_id]/+page.ts 1ms (unchanged)
src/routes/events/[event_id]/presenter/[presenter_id]/ae_comp__event_presenter_form_agree.svelte 13ms (unchanged)
src/routes/events/[event_id]/presenter/[presenter_id]/presenter_page_menu.svelte 30ms (unchanged)
src/routes/events/[event_id]/presenter/[presenter_id]/presenter_view.svelte 108ms (unchanged)
src/routes/events/[event_id]/presenter/ae_comp__event_presenter_obj_li_wrapper.svelte 3ms (unchanged)
src/routes/events/[event_id]/presenter/ae_comp__event_presenter_obj_li.svelte 22ms (unchanged)
src/routes/events/[event_id]/presenter/ae_comp__event_presenter_obj_tbl_wrapper.svelte 4ms (unchanged)
src/routes/events/[event_id]/presenter/ae_comp__event_presenter_obj_tbl.svelte 15ms (unchanged)
src/routes/events/[event_id]/reports/+page.svelte 66ms (unchanged)
src/routes/events/[event_id]/reports/event_reports_page_menu.svelte 16ms (unchanged)
src/routes/events/[event_id]/reports/reports_files.svelte 14ms (unchanged)
src/routes/events/[event_id]/reports/reports_presenters.svelte 9ms (unchanged)
src/routes/events/[event_id]/reports/reports_sessions.svelte 10ms (unchanged)
src/routes/events/[event_id]/session/[session_id]/+page.svelte 16ms (unchanged)
src/routes/events/[event_id]/session/[session_id]/+page.ts 2ms (unchanged)
src/routes/events/[event_id]/session/[session_id]/ae_comp__event_session_poc_form_agree.svelte 17ms (unchanged)
src/routes/events/[event_id]/session/[session_id]/ae_comp__event_session_poc_profile.svelte 9ms (unchanged)
src/routes/events/[event_id]/session/[session_id]/session_page_menu.svelte 30ms (unchanged)
src/routes/events/[event_id]/session/[session_id]/session_view.svelte 59ms (unchanged)
src/routes/events/[event_id]/session/ae_comp__event_session_alert.svelte 9ms (unchanged)
src/routes/events/[event_id]/sign_in_out.svelte 7ms (unchanged)
src/routes/events/+layout.svelte 11ms (unchanged)
src/routes/events/+layout.ts 1ms (unchanged)
src/routes/events/+page.svelte 7ms (unchanged)
src/routes/events/+page.ts 1ms (unchanged)
src/routes/events/ae_comp__event_file_obj_tbl_wrapper.svelte 3ms (unchanged)
src/routes/events/ae_comp__event_file_obj_tbl.svelte 31ms (unchanged)
src/routes/events/ae_comp__event_files_upload.svelte 10ms (unchanged)
src/routes/events/ae_comp__event_presentation_obj_li.svelte 27ms (unchanged)
src/routes/events/ae_comp__event_session_obj_li_wrapper.svelte 2ms (unchanged)
src/routes/events/ae_comp__event_session_obj_li.svelte 31ms (unchanged)
src/routes/events/ae_comp__event_session_obj_tbl_wrapper.svelte 3ms (unchanged)
src/routes/events/ae_comp__event_session_obj_tbl.svelte 5ms (unchanged)
src/routes/events/ae_comp__events_menu_nav.svelte 5ms (unchanged)
src/routes/events/ae_comp__events_menu_opts.svelte 25ms (unchanged)
src/routes/events/README.md 3ms (unchanged)
src/routes/hosted_files/+layout.svelte 2ms (unchanged)
src/routes/hosted_files/+layout.ts 1ms (unchanged)
src/routes/hosted_files/+page.svelte 3ms (unchanged)
src/routes/hosted_files/video_util/+page.svelte 8ms (unchanged)
src/routes/hosted_files/video_util/hold_video_util.svelte 7ms (unchanged)
src/routes/idaa/(idaa)/+layout.svelte 6ms (unchanged)
src/routes/idaa/(idaa)/archives/[archive_id]/+page.svelte 12ms (unchanged)
src/routes/idaa/(idaa)/archives/[archive_id]/+page.ts 2ms (unchanged)
src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_content_obj_id_edit.svelte 32ms (unchanged)
src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_content_obj_li.svelte 15ms (unchanged)
src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_obj_id_edit.svelte 19ms (unchanged)
src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_obj_id_view.svelte 10ms (unchanged)
src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__modal_media_player.svelte 4ms (unchanged)
src/routes/idaa/(idaa)/archives/[archive_id]/not_used+layout.ts 0ms (unchanged)
src/routes/idaa/(idaa)/archives/+layout.svelte 1ms (unchanged)
src/routes/idaa/(idaa)/archives/+layout.ts 1ms (unchanged)
src/routes/idaa/(idaa)/archives/+page.svelte 5ms (unchanged)
src/routes/idaa/(idaa)/archives/ae_idaa_comp__archive_obj_li.svelte 5ms (unchanged)
src/routes/idaa/(idaa)/archives/ae_idaa_comp__media_player.svelte 6ms (unchanged)
src/routes/idaa/(idaa)/bb/[post_id]/+page.svelte 7ms (unchanged)
src/routes/idaa/(idaa)/bb/[post_id]/+page.ts 1ms (unchanged)
src/routes/idaa/(idaa)/bb/+layout.svelte 2ms (unchanged)
src/routes/idaa/(idaa)/bb/+layout.ts 1ms (unchanged)
src/routes/idaa/(idaa)/bb/+page.svelte 7ms (unchanged)
src/routes/idaa/(idaa)/bb/+page.ts 2ms (unchanged)
src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_comment_obj_id_edit.svelte 26ms (unchanged)
src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_obj_id_edit.svelte 38ms (unchanged)
src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_obj_id_view.svelte 17ms (unchanged)
src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_obj_li.svelte 9ms (unchanged)
src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_options.svelte 8ms (unchanged)
src/routes/idaa/(idaa)/hold_+page.svelte 0ms (unchanged)
src/routes/idaa/(idaa)/hold_app.pcss 7ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/[event_id]/+page.svelte 8ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/[event_id]/+page.ts 1ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/+layout.svelte 2ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/+layout.ts 1ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/+page.svelte 9ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_id_edit.svelte 80ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_id_view.svelte 34ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li_wrapper.svelte 3ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li.svelte 24ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_qry.svelte 27ms (unchanged)
src/routes/idaa/(idaa)/recovery_meetings/not_used+page.ts 0ms (unchanged)
src/routes/idaa/+layout.svelte 9ms (unchanged)
src/routes/idaa/README.md 3ms (unchanged)
src/routes/journals/[journal_id]/+layout.svelte 12ms (unchanged)
src/routes/journals/[journal_id]/+layout.ts 2ms (unchanged)
src/routes/journals/[journal_id]/+page.svelte 8ms (unchanged)
src/routes/journals/[journal_id]/+page.ts 1ms (unchanged)
src/routes/journals/[journal_id]/entry/[journal_entry_id]/+page.svelte 6ms (unchanged)
src/routes/journals/[journal_id]/entry/[journal_entry_id]/+page.ts 1ms (unchanged)
src/routes/journals/+layout.svelte 14ms (unchanged)
src/routes/journals/+layout.ts 1ms (unchanged)
src/routes/journals/+page.svelte 8ms (unchanged)
src/routes/journals/+page.ts 1ms (unchanged)
src/routes/journals/ae_comp__journal_entry_obj_file_li.svelte 12ms (unchanged)
src/routes/journals/ae_comp__journal_entry_obj_id_view.svelte 102ms (unchanged)
src/routes/journals/ae_comp__journal_entry_obj_li.svelte 34ms (unchanged)
src/routes/journals/ae_comp__journal_entry_obj_qry.svelte 7ms (unchanged)
src/routes/journals/ae_comp__journal_obj_id_edit.svelte 39ms (unchanged)
src/routes/journals/ae_comp__journal_obj_id_view.svelte 9ms (unchanged)
src/routes/journals/ae_comp__journal_obj_li.svelte 6ms (unchanged)
src/routes/journals/ae_comp__obj_core_props.svelte 12ms (unchanged)
src/routes/journals/modal_journals_config.svelte 12ms (unchanged)
src/routes/journals/README.md 2ms (unchanged)
src/routes/testing/+layout.ts 0ms (unchanged)
src/routes/testing/+page.svelte 4ms (unchanged)
static/idaa_novi_iframe_archives.html 4ms (unchanged)
static/idaa_novi_iframe_bulletin_board.html 3ms (unchanged)
static/idaa_novi_iframe_jitsi_meeting.html 3ms (unchanged)
static/idaa_novi_iframe_recovery_meetings.html 3ms (unchanged)
static/jitsi_iframe_api.html 4ms (unchanged)
static/manifest.json 1ms (unchanged)
SVELTE_DEXIE_GUIDE.md 8ms (unchanged)
svelte.config.js 1ms (unchanged)
test-results/.last-run.json 0ms (unchanged)
tests/example.test.ts 1ms (unchanged)
TODO.md 18ms (unchanged)
tsconfig.json 1ms (unchanged)
vite.config.ts 1ms (unchanged)
vitest.config.ts 0ms (unchanged) to clean up dependencies and fix formatting.
This commit is contained in:
Scott Idem
2025-11-17 21:24:57 -05:00
parent 5c67421d7e
commit 4f262149cd
53 changed files with 190 additions and 4076 deletions

View File

@@ -1,17 +0,0 @@
<script lang="ts">
import { mode } from 'mode-watcher';
let { hex = $bindable() }: { hex: string } = $props();
import ColorPicker from 'svelte-awesome-color-picker';
</script>
<div class:dark={$mode === 'dark'}>
<ColorPicker
bind:hex
sliderDirection="vertical"
label="Pick a color"
isTextInput={false}
isAlpha
--picker-indicator-size="1rem"
--input-size="1rem"
/>
</div>

View File

@@ -1,28 +0,0 @@
import { Extension } from '@tiptap/core';
import { Plugin } from '@tiptap/pm/state';
import findColors from '../utils.js';
export const ColorHighlighter = Extension.create({
name: 'colorHighlighter',
addProseMirrorPlugins() {
return [
new Plugin({
state: {
init(_, { doc }) {
return findColors(doc);
},
apply(transaction, oldState) {
return transaction.docChanged ? findColors(transaction.doc) : oldState;
}
},
props: {
decorations(state) {
return this.getState(state);
}
}
})
];
}
});

View File

@@ -1,32 +0,0 @@
import { SvelteNodeViewRenderer } from 'svelte-tiptap';
import ImageExtendedComponent from '../image-extended-component.svelte';
import Image from '@tiptap/extension-image';
export const ImageExtension = Image.extend({
addAttributes() {
return {
src: {
default: null
},
alt: {
default: null
},
title: {
default: null
},
width: {
default: '100%'
},
height: {
default: null
},
align: {
default: 'left'
}
};
},
addNodeView: () => {
return SvelteNodeViewRenderer(ImageExtendedComponent);
}
});

View File

@@ -1,406 +0,0 @@
// MIT License
// Copyright (c) 2023 - 2024 Jeet Mandaliya (Github Username: sereneinserenade)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import { Extension, type Range, type Dispatch } from '@tiptap/core';
import { Decoration, DecorationSet } from '@tiptap/pm/view';
import { Plugin, PluginKey, type EditorState, type Transaction } from '@tiptap/pm/state';
import { Node as PMNode } from '@tiptap/pm/model';
declare module '@tiptap/core' {
interface Commands<ReturnType> {
search: {
/**
* @description Set search term in extension.
*/
setSearchTerm: (searchTerm: string) => ReturnType;
/**
* @description Set replace term in extension.
*/
setReplaceTerm: (replaceTerm: string) => ReturnType;
/**
* @description Set case sensitivity in extension.
*/
setCaseSensitive: (caseSensitive: boolean) => ReturnType;
/**
* @description Reset current search result to first instance.
*/
resetIndex: () => ReturnType;
/**
* @description Find next instance of search result.
*/
nextSearchResult: () => ReturnType;
/**
* @description Find previous instance of search result.
*/
previousSearchResult: () => ReturnType;
/**
* @description Replace first instance of search result with given replace term.
*/
replace: () => ReturnType;
/**
* @description Replace all instances of search result with given replace term.
*/
replaceAll: () => ReturnType;
};
}
}
interface TextNodesWithPosition {
text: string;
pos: number;
}
const getRegex = (s: string, disableRegex: boolean, caseSensitive: boolean): RegExp => {
return RegExp(
disableRegex ? s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : s,
caseSensitive ? 'gu' : 'gui'
);
};
interface ProcessedSearches {
decorationsToReturn: DecorationSet;
results: Range[];
}
function processSearches(
doc: PMNode,
searchTerm: RegExp,
searchResultClass: string,
resultIndex: number
): ProcessedSearches {
const decorations: Decoration[] = [];
const results: Range[] = [];
let textNodesWithPosition: TextNodesWithPosition[] = [];
let index = 0;
if (!searchTerm) {
return {
decorationsToReturn: DecorationSet.empty,
results: []
};
}
doc?.descendants((node, pos) => {
if (node.isText) {
if (textNodesWithPosition[index]) {
textNodesWithPosition[index] = {
text: textNodesWithPosition[index].text + node.text,
pos: textNodesWithPosition[index].pos
};
} else {
textNodesWithPosition[index] = {
text: `${node.text}`,
pos
};
}
} else {
index += 1;
}
});
textNodesWithPosition = textNodesWithPosition.filter(Boolean);
for (const element of textNodesWithPosition) {
const { text, pos } = element;
const matches = Array.from(text.matchAll(searchTerm)).filter(([matchText]) => matchText.trim());
for (const m of matches) {
if (m[0] === '') break;
if (m.index !== undefined) {
results.push({
from: pos + m.index,
to: pos + m.index + m[0].length
});
}
}
}
for (let i = 0; i < results.length; i += 1) {
const r = results[i];
const className =
i === resultIndex ? `${searchResultClass} ${searchResultClass}-current` : searchResultClass;
const decoration: Decoration = Decoration.inline(r.from, r.to, {
class: className
});
decorations.push(decoration);
}
return {
decorationsToReturn: DecorationSet.create(doc, decorations),
results
};
}
const replace = (
replaceTerm: string,
results: Range[],
{ state, dispatch }: { state: EditorState; dispatch: Dispatch }
) => {
const firstResult = results[0];
if (!firstResult) return;
const { from, to } = results[0];
if (dispatch) dispatch(state.tr.insertText(replaceTerm, from, to));
};
const rebaseNextResult = (
replaceTerm: string,
index: number,
lastOffset: number,
results: Range[]
): [number, Range[]] | null => {
const nextIndex = index + 1;
if (!results[nextIndex]) return null;
const { from: currentFrom, to: currentTo } = results[index];
const offset = currentTo - currentFrom - replaceTerm.length + lastOffset;
const { from, to } = results[nextIndex];
results[nextIndex] = {
to: to - offset,
from: from - offset
};
return [offset, results];
};
const replaceAll = (
replaceTerm: string,
results: Range[],
{ tr, dispatch }: { tr: Transaction; dispatch: Dispatch }
) => {
let offset = 0;
let resultsCopy = results.slice();
if (!resultsCopy.length) return;
for (let i = 0; i < resultsCopy.length; i += 1) {
const { from, to } = resultsCopy[i];
tr.insertText(replaceTerm, from, to);
const rebaseNextResultResponse = rebaseNextResult(replaceTerm, i, offset, resultsCopy);
if (!rebaseNextResultResponse) continue;
offset = rebaseNextResultResponse[0];
resultsCopy = rebaseNextResultResponse[1];
}
dispatch(tr);
};
export const searchAndReplacePluginKey = new PluginKey('searchAndReplacePlugin');
export interface SearchAndReplaceOptions {
searchResultClass: string;
disableRegex: boolean;
}
export interface SearchAndReplaceStorage {
searchTerm: string;
replaceTerm: string;
results: Range[];
lastSearchTerm: string;
caseSensitive: boolean;
lastCaseSensitive: boolean;
resultIndex: number;
lastResultIndex: number;
}
export const SearchAndReplace = Extension.create<SearchAndReplaceOptions, SearchAndReplaceStorage>({
name: 'searchAndReplace',
addOptions() {
return {
searchResultClass: 'search-result',
disableRegex: true
};
},
addStorage() {
return {
searchTerm: '',
replaceTerm: '',
results: [],
lastSearchTerm: '',
caseSensitive: false,
lastCaseSensitive: false,
resultIndex: 0,
lastResultIndex: 0
};
},
addCommands() {
return {
setSearchTerm:
(searchTerm: string) =>
({ editor }) => {
editor.storage.searchAndReplace.searchTerm = searchTerm;
return false;
},
setReplaceTerm:
(replaceTerm: string) =>
({ editor }) => {
editor.storage.searchAndReplace.replaceTerm = replaceTerm;
return false;
},
setCaseSensitive:
(caseSensitive: boolean) =>
({ editor }) => {
editor.storage.searchAndReplace.caseSensitive = caseSensitive;
return false;
},
resetIndex:
() =>
({ editor }) => {
editor.storage.searchAndReplace.resultIndex = 0;
return false;
},
nextSearchResult:
() =>
({ editor }) => {
const { results, resultIndex } = editor.storage.searchAndReplace;
const nextIndex = resultIndex + 1;
if (results[nextIndex]) {
editor.storage.searchAndReplace.resultIndex = nextIndex;
} else {
editor.storage.searchAndReplace.resultIndex = 0;
}
return false;
},
previousSearchResult:
() =>
({ editor }) => {
const { results, resultIndex } = editor.storage.searchAndReplace;
const prevIndex = resultIndex - 1;
if (results[prevIndex]) {
editor.storage.searchAndReplace.resultIndex = prevIndex;
} else {
editor.storage.searchAndReplace.resultIndex = results.length - 1;
}
return false;
},
replace:
() =>
({ editor, state, dispatch }) => {
const { replaceTerm, results } = editor.storage.searchAndReplace;
replace(replaceTerm, results, { state, dispatch });
return false;
},
replaceAll:
() =>
({ editor, tr, dispatch }) => {
const { replaceTerm, results } = editor.storage.searchAndReplace;
replaceAll(replaceTerm, results, { tr, dispatch });
return false;
}
};
},
addProseMirrorPlugins() {
const editor = this.editor;
const { searchResultClass, disableRegex } = this.options;
const setLastSearchTerm = (t: string) => (editor.storage.searchAndReplace.lastSearchTerm = t);
const setLastCaseSensitive = (t: boolean) =>
(editor.storage.searchAndReplace.lastCaseSensitive = t);
const setLastResultIndex = (t: number) => (editor.storage.searchAndReplace.lastResultIndex = t);
return [
new Plugin({
key: searchAndReplacePluginKey,
state: {
init: () => DecorationSet.empty,
apply({ doc, docChanged }, oldState) {
const {
searchTerm,
lastSearchTerm,
caseSensitive,
lastCaseSensitive,
resultIndex,
lastResultIndex
} = editor.storage.searchAndReplace;
if (
!docChanged &&
lastSearchTerm === searchTerm &&
lastCaseSensitive === caseSensitive &&
lastResultIndex === resultIndex
)
return oldState;
setLastSearchTerm(searchTerm);
setLastCaseSensitive(caseSensitive);
setLastResultIndex(resultIndex);
if (!searchTerm) {
editor.storage.searchAndReplace.results = [];
return DecorationSet.empty;
}
const { decorationsToReturn, results } = processSearches(
doc,
getRegex(searchTerm, disableRegex, caseSensitive),
searchResultClass,
resultIndex
);
editor.storage.searchAndReplace.results = results;
return decorationsToReturn;
}
},
props: {
decorations(state) {
return this.getState(state);
}
}
})
];
}
});
export default SearchAndReplace;

View File

@@ -1,133 +0,0 @@
import { Extension, textInputRule } from '@tiptap/core';
export const SmilieReplacer = Extension.create({
name: 'smilieReplacer',
addInputRules() {
return [
textInputRule({ find: /-___- $/, replace: '😑 ' }),
textInputRule({ find: /:'-\) $/, replace: '😂 ' }),
textInputRule({ find: /':-\) $/, replace: '😅 ' }),
textInputRule({ find: /':-D $/, replace: '😅 ' }),
textInputRule({ find: />:-\) $/, replace: '😆 ' }),
textInputRule({ find: /-__- $/, replace: '😑 ' }),
textInputRule({ find: /':-\( $/, replace: '😓 ' }),
textInputRule({ find: /:'-\( $/, replace: '😢 ' }),
textInputRule({ find: />:-\( $/, replace: '😠 ' }),
textInputRule({ find: /O:-\) $/, replace: '😇 ' }),
textInputRule({ find: /0:-3 $/, replace: '😇 ' }),
textInputRule({ find: /0:-\) $/, replace: '😇 ' }),
textInputRule({ find: /0;\^\) $/, replace: '😇 ' }),
textInputRule({ find: /O;-\) $/, replace: '😇 ' }),
textInputRule({ find: /0;-\) $/, replace: '😇 ' }),
textInputRule({ find: /O:-3 $/, replace: '😇 ' }),
textInputRule({ find: /:'\) $/, replace: '😂 ' }),
textInputRule({ find: /:-D $/, replace: '😃 ' }),
textInputRule({ find: /':\) $/, replace: '😅 ' }),
textInputRule({ find: /'=\) $/, replace: '😅 ' }),
textInputRule({ find: /':D $/, replace: '😅 ' }),
textInputRule({ find: /'=D $/, replace: '😅 ' }),
textInputRule({ find: />:\) $/, replace: '😆 ' }),
textInputRule({ find: />;\) $/, replace: '😆 ' }),
textInputRule({ find: />=\) $/, replace: '😆 ' }),
textInputRule({ find: /;-\) $/, replace: '😉 ' }),
textInputRule({ find: /\*-\) $/, replace: '😉 ' }),
textInputRule({ find: /;-\] $/, replace: '😉 ' }),
textInputRule({ find: /;\^\) $/, replace: '😉 ' }),
textInputRule({ find: /B-\) $/, replace: '😎 ' }),
textInputRule({ find: /8-\) $/, replace: '😎 ' }),
textInputRule({ find: /B-D $/, replace: '😎 ' }),
textInputRule({ find: /8-D $/, replace: '😎 ' }),
textInputRule({ find: /:-\* $/, replace: '😘 ' }),
textInputRule({ find: /:\^\* $/, replace: '😘 ' }),
textInputRule({ find: /:-\) $/, replace: '🙂 ' }),
textInputRule({ find: /-_- $/, replace: '😑 ' }),
textInputRule({ find: /:-X $/, replace: '😶 ' }),
textInputRule({ find: /:-# $/, replace: '😶 ' }),
textInputRule({ find: /:-x $/, replace: '😶 ' }),
textInputRule({ find: />.< $/, replace: '😣 ' }),
textInputRule({ find: /:-O $/, replace: '😮 ' }),
textInputRule({ find: /:-o $/, replace: '😮 ' }),
textInputRule({ find: /O_O $/, replace: '😮 ' }),
textInputRule({ find: />:O $/, replace: '😮 ' }),
textInputRule({ find: /:-P $/, replace: '😛 ' }),
textInputRule({ find: /:-p $/, replace: '😛 ' }),
textInputRule({ find: /:-Þ $/, replace: '😛 ' }),
textInputRule({ find: /:-þ $/, replace: '😛 ' }),
textInputRule({ find: /:-b $/, replace: '😛 ' }),
textInputRule({ find: />:P $/, replace: '😜 ' }),
textInputRule({ find: /X-P $/, replace: '😜 ' }),
textInputRule({ find: /x-p $/, replace: '😜 ' }),
textInputRule({ find: /':\( $/, replace: '😓 ' }),
textInputRule({ find: /'=\( $/, replace: '😓 ' }),
textInputRule({ find: />:\\ $/, replace: '😕 ' }),
textInputRule({ find: />:\/ $/, replace: '😕 ' }),
textInputRule({ find: /:-\/ $/, replace: '😕 ' }),
textInputRule({ find: /:-. $/, replace: '😕 ' }),
textInputRule({ find: />:\[ $/, replace: '😞 ' }),
textInputRule({ find: /:-\( $/, replace: '😞 ' }),
textInputRule({ find: /:-\[ $/, replace: '😞 ' }),
textInputRule({ find: /:'\( $/, replace: '😢 ' }),
textInputRule({ find: /;-\( $/, replace: '😢 ' }),
textInputRule({ find: /#-\) $/, replace: '😵 ' }),
textInputRule({ find: /%-\) $/, replace: '😵 ' }),
textInputRule({ find: /X-\) $/, replace: '😵 ' }),
textInputRule({ find: />:\( $/, replace: '😠 ' }),
textInputRule({ find: /0:3 $/, replace: '😇 ' }),
textInputRule({ find: /0:\) $/, replace: '😇 ' }),
textInputRule({ find: /O:\) $/, replace: '😇 ' }),
textInputRule({ find: /O=\) $/, replace: '😇 ' }),
textInputRule({ find: /O:3 $/, replace: '😇 ' }),
textInputRule({ find: /<\/3 $/, replace: '💔 ' }),
textInputRule({ find: /:D $/, replace: '😃 ' }),
textInputRule({ find: /=D $/, replace: '😃 ' }),
textInputRule({ find: /;\) $/, replace: '😉 ' }),
textInputRule({ find: /\*\) $/, replace: '😉 ' }),
textInputRule({ find: /;\] $/, replace: '😉 ' }),
textInputRule({ find: /;D $/, replace: '😉 ' }),
textInputRule({ find: /B\) $/, replace: '😎 ' }),
textInputRule({ find: /8\) $/, replace: '😎 ' }),
textInputRule({ find: /:\* $/, replace: '😘 ' }),
textInputRule({ find: /=\* $/, replace: '😘 ' }),
textInputRule({ find: /:\) $/, replace: '🙂 ' }),
textInputRule({ find: /=\] $/, replace: '🙂 ' }),
textInputRule({ find: /=\) $/, replace: '🙂 ' }),
textInputRule({ find: /:\] $/, replace: '🙂 ' }),
textInputRule({ find: /:X $/, replace: '😶 ' }),
textInputRule({ find: /:# $/, replace: '😶 ' }),
textInputRule({ find: /=X $/, replace: '😶 ' }),
textInputRule({ find: /=x $/, replace: '😶 ' }),
textInputRule({ find: /:x $/, replace: '😶 ' }),
textInputRule({ find: /=# $/, replace: '😶 ' }),
textInputRule({ find: /:O $/, replace: '😮 ' }),
textInputRule({ find: /:o $/, replace: '😮 ' }),
textInputRule({ find: /:P $/, replace: '😛 ' }),
textInputRule({ find: /=P $/, replace: '😛 ' }),
textInputRule({ find: /:p $/, replace: '😛 ' }),
textInputRule({ find: /=p $/, replace: '😛 ' }),
textInputRule({ find: /:Þ $/, replace: '😛 ' }),
textInputRule({ find: /:þ $/, replace: '😛 ' }),
textInputRule({ find: /:b $/, replace: '😛 ' }),
textInputRule({ find: /d: $/, replace: '😛 ' }),
textInputRule({ find: /:\/ $/, replace: '😕 ' }),
textInputRule({ find: /:\\ $/, replace: '😕 ' }),
textInputRule({ find: /=\/ $/, replace: '😕 ' }),
textInputRule({ find: /=\\ $/, replace: '😕 ' }),
textInputRule({ find: /:L $/, replace: '😕 ' }),
textInputRule({ find: /=L $/, replace: '😕 ' }),
textInputRule({ find: /:\( $/, replace: '😞 ' }),
textInputRule({ find: /:\[ $/, replace: '😞 ' }),
textInputRule({ find: /=\( $/, replace: '😞 ' }),
textInputRule({ find: /;\( $/, replace: '😢 ' }),
textInputRule({ find: /D: $/, replace: '😨 ' }),
textInputRule({ find: /:\$ $/, replace: '😳 ' }),
textInputRule({ find: /=\$ $/, replace: '😳 ' }),
textInputRule({ find: /#\) $/, replace: '😵 ' }),
textInputRule({ find: /%\) $/, replace: '😵 ' }),
textInputRule({ find: /X\) $/, replace: '😵 ' }),
textInputRule({ find: /:@ $/, replace: '😠 ' }),
textInputRule({ find: /<3 $/, replace: '❤️ ' }),
textInputRule({ find: /\/shrug $/, replace: '¯\\_(ツ)_/¯' })
];
}
});

View File

@@ -1,78 +0,0 @@
<script lang="ts">
import { NodeViewWrapper, NodeViewContent } from 'svelte-tiptap';
import type { NodeViewProps } from '@tiptap/core';
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import { onMount } from 'svelte';
const { node, editor, selected, deleteNode, updateAttributes, extension }: NodeViewProps =
$props();
import { Copy, Check, ChevronDown } from 'lucide-svelte';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
let preRef: HTMLPreElement;
let isCopying = $state(false);
const languages = extension.options.lowlight.listLanguages().sort();
let defaultLanguage = $state(node.attrs.language);
onMount(() => {
console.log(node);
});
function copyCode() {
isCopying = true;
navigator.clipboard.writeText(preRef.innerText);
setTimeout(() => {
isCopying = false;
}, 1000);
}
</script>
<NodeViewWrapper
class="code-wrapper group relative rounded bg-muted p-6 dark:bg-muted/20"
draggable
>
<DropdownMenu.Root>
<DropdownMenu.Trigger
contenteditable="false"
class={buttonVariants({
variant: 'ghost',
size: 'sm',
class:
'absolute left-2 top-2 h-4 rounded px-1 py-2 text-xs capitalize text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100'
})}
>{defaultLanguage}
<ChevronDown class="size-3!" />
</DropdownMenu.Trigger>
<DropdownMenu.Content class="h-60 w-40 overflow-auto" contenteditable="false">
{#each languages as language}
<DropdownMenu.Item
contenteditable="false"
data-current={defaultLanguage === language}
class="capitalize data-[current=true]:bg-muted"
onclick={() => {
defaultLanguage = language;
updateAttributes({ language: defaultLanguage });
}}
>
<span>{language}</span>
</DropdownMenu.Item>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>
<Button
variant="ghost"
class="absolute right-2 top-2 size-4 p-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"
onclick={copyCode}
>
{#if isCopying}
<Check class="size-3 text-green-500" />
{:else}
<Copy class="size-3" />
{/if}
</Button>
<pre bind:this={preRef}>
<NodeViewContent as="code" class={`language-${defaultLanguage}`} {...node.attrs} />
</pre>
</NodeViewWrapper>

View File

@@ -1,251 +0,0 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { NodeViewWrapper } from 'svelte-tiptap';
import type { NodeViewProps } from '@tiptap/core';
import { cn } from '$lib/utils/utils.js';
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import {
AlignCenter,
AlignLeft,
AlignRight,
EllipsisVertical,
CopyIcon,
Fullscreen,
Trash,
Captions
} from 'lucide-svelte';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import { duplicateContent } from './utils.js';
const { node, editor, selected, deleteNode, updateAttributes }: NodeViewProps = $props();
const minWidth = 150;
let imgRef: HTMLImageElement;
let nodeRef: HTMLDivElement;
let resizing = $state(false);
let resizingInitialWidth = $state(0);
let resizingInitialMouseX = $state(0);
let resizingPosition = $state<'left' | 'right'>('left');
let openedMore = $state(false);
function handleResizingPosition(e: MouseEvent, position: 'left' | 'right') {
startResize(e);
resizingPosition = position;
}
function startResize(e: MouseEvent) {
e.preventDefault();
resizing = true;
resizingInitialMouseX = e.clientX;
if (imgRef) resizingInitialWidth = imgRef.offsetWidth;
}
function resize(e: MouseEvent) {
if (!resizing) return;
let dx = e.clientX - resizingInitialMouseX;
if (resizingPosition === 'left') {
dx = resizingInitialMouseX - e.clientX;
}
const newWidth = Math.max(resizingInitialWidth + dx, minWidth);
const parentWidth = nodeRef?.parentElement?.offsetWidth || 0;
if (newWidth < parentWidth) {
updateAttributes({ width: newWidth });
}
}
function endResize() {
resizing = false;
resizingInitialMouseX = 0;
resizingInitialWidth = 0;
}
function handleTouchStart(e: TouchEvent, position: 'left' | 'right') {
e.preventDefault();
resizing = true;
resizingPosition = position;
resizingInitialMouseX = e.touches[0].clientX;
if (imgRef) resizingInitialWidth = imgRef.offsetWidth;
}
function handleTouchMove(e: TouchEvent) {
if (!resizing) return;
let dx = e.touches[0].clientX - resizingInitialMouseX;
if (resizingPosition === 'left') {
dx = resizingInitialMouseX - e.touches[0].clientX;
}
const newWidth = Math.max(resizingInitialWidth + dx, minWidth);
const parentWidth = nodeRef?.parentElement?.offsetWidth || 0;
if (newWidth < parentWidth) {
updateAttributes({ width: newWidth });
}
}
function handleTouchEnd() {
resizing = false;
resizingInitialMouseX = 0;
resizingInitialWidth = 0;
}
onMount(() => {
// Attach id to nodeRef
nodeRef = document.getElementById('resizable-container') as HTMLDivElement;
// Mouse events
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', endResize);
// Touch events
window.addEventListener('touchmove', handleTouchMove);
window.addEventListener('touchend', handleTouchEnd);
});
onDestroy(() => {
window.removeEventListener('mousemove', resize);
window.removeEventListener('mouseup', endResize);
window.removeEventListener('touchmove', handleTouchMove);
window.removeEventListener('touchend', handleTouchEnd);
});
</script>
<NodeViewWrapper
id="resizable-container"
class={cn(
'relative flex flex-col rounded-md border-2 border-transparent',
selected ? 'border-muted-foreground' : '',
node.attrs.align === 'left' && 'left-0 -translate-x-0',
node.attrs.align === 'center' && 'left-1/2 -translate-x-1/2',
node.attrs.align === 'right' && 'left-full -translate-x-full'
)}
style={`width: ${node.attrs.width}px`}
>
<div class={cn('group relative flex flex-col rounded-md', resizing && '')}>
<img
bind:this={imgRef}
src={node.attrs.src}
alt={node.attrs.alt}
title={node.attrs.title}
class="m-0 object-cover"
/>
{#if node.attrs.title !== null && node.attrs.title.trim() !== ''}
<input
value={node.attrs.title}
type="text"
class="my-1 w-full bg-transparent text-center text-sm text-muted-foreground outline-hidden"
onchange={(e) => {
if (e.target === null) return;
//@ts-ignore
updateAttributes({ title: e.target.value });
}}
/>
{/if}
{#if editor?.isEditable}
<div
role="button"
tabindex="0"
aria-label="Back"
class="absolute inset-y-0 z-20 flex w-[25px] cursor-col-resize items-center justify-start p-2"
style="left: 0px"
onmousedown={(event: MouseEvent) => {
handleResizingPosition(event, 'left');
}}
ontouchstart={(event: TouchEvent) => {
handleTouchStart(event, 'left');
}}
>
<div
class="z-20 h-[70px] w-1 rounded-xl border bg-muted opacity-0 transition-all group-hover:opacity-100"
></div>
</div>
<div
role="button"
tabindex="0"
aria-label="Back"
class="absolute inset-y-0 z-20 flex w-[25px] cursor-col-resize items-center justify-end p-2"
style="right: 0px"
onmousedown={(event: MouseEvent) => {
handleResizingPosition(event, 'right');
}}
ontouchstart={(event: TouchEvent) => {
handleTouchStart(event, 'right');
}}
>
<div
class="z-20 h-[70px] w-1 rounded-xl border bg-muted opacity-0 transition-all group-hover:opacity-100"
></div>
</div>
<div
class={cn(
'absolute right-4 top-2 flex items-center gap-1 rounded border bg-background p-1 opacity-0 transition-opacity',
!resizing && 'group-hover:opacity-100',
openedMore && 'opacity-100'
)}
>
<Button
variant="ghost"
class={cn('size-6 p-0', node.attrs.align === 'left' && 'bg-muted')}
onclick={() => updateAttributes({ align: 'left' })}
>
<AlignLeft class="size-4" />
</Button>
<Button
variant="ghost"
class={cn('size-6 p-0', node.attrs.align === 'center' && 'bg-muted')}
onclick={() => updateAttributes({ align: 'center' })}
>
<AlignCenter class="size-4" />
</Button>
<Button
variant="ghost"
class={cn('size-6 p-0', node.attrs.align === 'right' && 'bg-muted')}
onclick={() => updateAttributes({ align: 'right' })}
>
<AlignRight class="size-4" />
</Button>
<DropdownMenu.Root bind:open={openedMore} onOpenChange={(value) => (openedMore = value)}>
<DropdownMenu.Trigger class={buttonVariants({ variant: 'ghost', class: 'size-6 p-0' })}>
<EllipsisVertical class="size-4" />
</DropdownMenu.Trigger>
<DropdownMenu.Content align="start" alignOffset={-90} class="mt-1 overflow-auto text-sm">
<DropdownMenu.Item
onclick={() => {
if (node.attrs.title === null || node.attrs.title.trim() === '')
updateAttributes({
title: 'Image Caption'
});
}}
>
<Captions class="mr-1 size-4" /> Caption
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => {
duplicateContent(editor);
}}
>
<CopyIcon class="mr-1 size-4" /> Duplicate
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => {
updateAttributes({
width: 'fit-content'
});
}}
>
<Fullscreen class="mr-1 size-4" /> Full Screen
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => {
deleteNode();
}}
class="text-destructive"
>
<Trash class="mr-1 size-4" /> Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
{/if}
</div>
</NodeViewWrapper>

View File

@@ -1,43 +0,0 @@
import type { Editor } from '@tiptap/core';
import { Node } from '@tiptap/pm/model';
import { Decoration, DecorationSet } from '@tiptap/pm/view';
export default function (doc: Node): DecorationSet {
const hexColor = /(#[0-9a-f]{3,6})\b/gi;
const decorations: Decoration[] = [];
doc.descendants((node, position) => {
if (!node.text) {
return;
}
Array.from(node.text.matchAll(hexColor)).forEach((match) => {
const color = match[0];
const index = match.index || 0;
const from = position + index;
const to = from + color.length;
const decoration = Decoration.inline(from, to, {
class: 'color',
style: `--color: ${color}`
});
decorations.push(decoration);
});
});
return DecorationSet.create(doc, decorations);
}
export const duplicateContent = (editor: Editor) => {
const { view } = editor;
const { state } = view;
const { selection } = state;
editor
.chain()
.insertContentAt(selection.to, selection.content().content.firstChild?.toJSON(), {
updateSelection: true
})
.focus(selection.to)
.run();
};

View File

@@ -1,181 +0,0 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import { type Editor } from '@tiptap/core';
import Undo from './icons/undo.svelte';
import Redo from './icons/redo.svelte';
// import { Separator } from '$lib/components/ui/separator/index.js';
import Bold from './icons/bold.svelte';
import Italic from './icons/italic.svelte';
import Underline from './icons/underline.svelte';
import Strikethrough from './icons/strikethrough.svelte';
import Link from './icons/link.svelte';
import Code from './icons/code.svelte';
import BlockQuote from './icons/block-quote.svelte';
import Subscript from './icons/subscript.svelte';
import BulletList from './icons/buttle-list.svelte'; // Typo in the icon name
import OrderedList from './icons/ordered-list.svelte';
import TaskList from './icons/task-list.svelte';
import Highlighter from './icons/highlighter.svelte';
import Superscript from './icons/superscript.svelte';
import Textcolor from './icons/textcolor.svelte';
import Align from './icons/textalign.svelte';
import Quickcolor from './icons/quickcolor.svelte';
import Table from './icons/table.svelte';
// import Image from './icons/image.svelte';
import Text from './icons/text.svelte';
import SearchReplace from './icons/search-replace.svelte';
interface Props {
editor: Editor;
show_button_kv?: any;
}
let { editor, show_button_kv }: Props = $props();
let show_button_kv_defaults: any = {
undo: true,
redo: true,
text: false,
bold: true,
italic: true,
underline: true,
strikethrough: false,
align: false,
link: false,
code: false,
blockquote: false,
subscript: false,
superscript: false,
bullet_list: true,
ordered_list: true,
task_list: false,
image: false,
table: false,
text_color: false,
highlighter: false,
quick_color: false,
search_replace: false
};
console.log('show_button_kv', show_button_kv);
if (show_button_kv) {
show_button_kv = { ...show_button_kv_defaults, ...show_button_kv };
} else {
show_button_kv = show_button_kv_defaults;
}
</script>
<div
transition:fade={{ delay: 250, duration: 750, easing: cubicOut }}
class="
flex flex-row flex-wrap gap-0.5
w-full items-center justify-between
overflow-auto border-b p-1
transition-all duration-1000
"
>
<span class:hidden={!show_button_kv.undo && !show_button_kv.redo}>
{#if show_button_kv.undo}
<Undo {editor} />
{/if}
{#if show_button_kv.redo}
<Redo {editor} />
{/if}
</span>
<!-- <Separator orientation="vertical" class="h-fit" /> -->
<span class:hidden={!show_button_kv.text}>
{#if show_button_kv.text}
<Text {editor} />
{/if}
</span>
<span
class:hidden={!show_button_kv.bold &&
!show_button_kv.italic &&
!show_button_kv.underline &&
!show_button_kv.strikethrough}
>
{#if show_button_kv.bold}
<Bold {editor} />
{/if}
{#if show_button_kv.italic}
<Italic {editor} />
{/if}
{#if show_button_kv.underline}
<Underline {editor} />
{/if}
{#if show_button_kv.strikethrough}
<Strikethrough {editor} />
{/if}
</span>
<span
class:hidden={!show_button_kv.align &&
!show_button_kv.link &&
!show_button_kv.code &&
!show_button_kv.blockquote &&
!show_button_kv.subscript &&
!show_button_kv.superscript &&
!show_button_kv.bullet_list &&
!show_button_kv.ordered_list &&
!show_button_kv.task_list &&
!show_button_kv.image &&
!show_button_kv.table}
>
{#if show_button_kv.align}
<Align {editor} />
{/if}
{#if show_button_kv.link}
<Link {editor} />
{/if}
{#if show_button_kv.code}
<Code {editor} />
{/if}
{#if show_button_kv.blockquote}
<BlockQuote {editor} />
{/if}
{#if show_button_kv.subscript}
<Subscript {editor} />
{/if}
{#if show_button_kv.superscript}
<Superscript {editor} />
{/if}
{#if show_button_kv.bullet_list}
<BulletList {editor} />
{/if}
{#if show_button_kv.ordered_list}
<OrderedList {editor} />
{/if}
{#if show_button_kv.task_list}
<TaskList {editor} />
{/if}
<!-- {#if show_button_kv.image}
<Image {editor} />
{/if} -->
{#if show_button_kv.table}
<Table {editor} />
{/if}
</span>
<span
class:hidden={!show_button_kv.text_color &&
!show_button_kv.highlighter &&
!show_button_kv.quick_color}
>
cxx
{#if show_button_kv.text_color}
<Textcolor {editor} />
{/if}
{#if show_button_kv.highlighter}
<Highlighter {editor} />
{/if}
{#if show_button_kv.quick_color}
<Quickcolor {editor} />
{/if}
</span>
<span class:hidden={!show_button_kv.search_replace}>
{#if show_button_kv.search_replace}
<SearchReplace {editor} />
{/if}
</span>
</div>

View File

@@ -1,157 +0,0 @@
@import 'tailwindcss';
@import '@skeletonlabs/skeleton';
@import '@skeletonlabs/skeleton/optional/presets';
/* @import "tailwindcss/preflight"; */
/* @tailwind utilities; */
@import '@skeletonlabs/skeleton/themes/cerberus';
@import '@skeletonlabs/skeleton/themes/modern';
@import '@skeletonlabs/skeleton/themes/wintry';
@source '../node_modules/@skeletonlabs/skeleton-svelte/dist';
@import 'tailwindcss/utilities.css' layer(utilities);
.tiptap :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.tiptap code:not(pre code) {
/* Remove the before and after pseudo elements */
@apply rounded-sm border bg-black/50 p-1 before:content-[''] after:content-[''];
}
.tiptap blockquote p {
@apply before:content-[''] after:content-[''];
}
.tiptap a {
@apply text-blue-600 underline;
}
ul[data-type='taskList'] {
list-style: none;
margin: 0;
padding: 0;
}
ul[data-type='taskList'] li {
display: flex;
align-items: center;
}
ul li,
ol li {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
ul[data-type='taskList'] li label {
flex: 0 0 auto;
margin-right: 0.5rem;
user-select: none;
}
ul[data-type='taskList'] li div {
flex: 1 1 auto;
}
ul[data-type='taskList'] li label {
display: flex;
align-items: center;
justify-content: center;
}
ul[data-type='taskList'] li[data-checked='true'] div {
@apply text-black/50 line-through;
}
input[type='checkbox'] {
@apply size-4 cursor-pointer rounded-sm;
/* This kills compiling !important */
}
ul[data-type='taskList'] ul[data-type='taskList'] {
margin: 0;
}
/* Color swatches */
.color {
@apply whitespace-nowrap;
}
.color::before {
@apply mb-[0.15rem] mr-[0.1rem] inline-block size-[1rem] rounded-sm border border-black/50 align-middle;
background-color: var(--color);
content: ' ';
}
/* Table-specific styling */
.tiptap table {
border-collapse: collapse;
margin: 0;
overflow: hidden;
table-layout: fixed;
width: 100%;
}
.tiptap table td,
.tiptap table th {
@apply border border-black/50;
box-sizing: border-box;
min-width: 1em;
padding: 6px 8px;
position: relative;
vertical-align: top;
}
.tiptap table td > *,
.tiptap table th > * {
margin-bottom: 0;
}
.tiptap table th {
@apply bg-black/50 text-left;
/* Kills compile: font-bold */
font-weight: bold;
}
.tiptap table .selectedCell:after {
@apply pointer-events-none absolute bottom-0 left-0 right-0 top-0 border-[2px] border-black/50;
content: '';
z-index: 2;
}
.tiptap table .column-resize-handle {
@apply pointer-events-none absolute -bottom-[2px] -right-[2px] top-0 w-1 bg-black/50;
}
.tiptap .tableWrapper {
margin: 1.5rem 0;
overflow-x: auto;
}
.tiptap.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
/* Tiptap code block */
.tiptap pre {
@apply m-0 flex h-fit overflow-auto !rounded-none bg-transparent p-0;
}
.tiptap pre code {
@apply flex-1 !rounded-none bg-transparent p-0 text-inherit;
}
.tiptap .search-result {
background-color: yellow;
}
.tiptap .search-result-current {
background-color: orange;
}

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { Quote } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class={cn('size-8', editor.isActive('blockquote') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleBlockquote().run()}
>
<Quote />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Block Quote (⌘⇧B)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { Bold } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class={cn('size-8', editor.isActive('bold') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleBold().run()}
>
<Bold />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Bold (⌘B)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,27 +0,0 @@
<script lang="ts">
import { List } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
size="icon"
class={cn('size-8', editor.isActive('bulletList') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleBulletList().run()}
>
<List />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Bullet List (⌘⇧8)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,27 +0,0 @@
<script lang="ts">
import { Code } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
size="icon"
class={cn('size-8', editor.isActive('code') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleCode().run()}
>
<Code />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Code (⌘E)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,75 +0,0 @@
<script lang="ts">
import { Highlighter } from 'lucide-svelte';
import { X } from 'lucide-svelte';
import { ChevronDown } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
import * as Popover from '$lib/components/ui/popover/index.js';
import { mode } from 'mode-watcher';
import ColorPicker from 'svelte-awesome-color-picker';
interface Props {
editor: Editor;
color?: string;
}
let { editor, color = $bindable('') }: Props = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Popover.Root>
<Popover.Trigger>
<Button
variant="ghost"
size="sm"
class={cn('h-8', editor.isActive('highlight') && 'bg-muted')}
onclick={() => editor.chain().focus()}
>
<Highlighter />
<ChevronDown class="size-3! text-muted-foreground" />
</Button>
</Popover.Trigger>
<Popover.Content class="bg-popover shadow-lg *:my-2">
<div class="flex items-center justify-between">
<h1 class="text-[1.2rem] font-bold">Pick a highlight color</h1>
<Popover.Close>
<X class="size-4 text-muted-foreground" />
</Popover.Close>
</div>
<div class:dark={$mode === 'dark'}>
<ColorPicker
bind:hex={color}
sliderDirection="vertical"
isTextInput={false}
isAlpha
on:input={(event) => {
if (event.detail.hex === undefined) return;
color = event.detail.hex;
editor.chain().focus().setHighlight({ color }).run();
}}
isDialog={false}
--picker-indicator-size="1rem"
--input-size="1rem"
/>
</div>
<div class="flex items-center justify-end gap-2">
<Button
variant="outline"
size="sm"
class="border-destructive text-destructive hover:bg-destructive hover:text-foreground"
onclick={() => editor.chain().focus().unsetHighlight().run()}
>Remove Highlight
</Button>
</div>
</Popover.Content>
</Popover.Root>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Highlighter (⌘⇧H)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,73 +0,0 @@
<script lang="ts">
import { Image } from 'lucide-svelte';
import { ChevronDown } from 'lucide-svelte';
import { X } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import * as Popover from '$lib/components/ui/popover/index.js';
import { Input } from '$lib/components/ui/input/index.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Popover.Root>
<Popover.Trigger>
<Button variant="ghost" size="sm" class="h-8">
<Image />
<ChevronDown class="size-3! text-muted-foreground" />
</Button>
</Popover.Trigger>
<Popover.Content class="bg-popover shadow-lg *:my-2">
<div class="flex items-center justify-between">
<h1 class="text-xl font-bold">Image</h1>
<Popover.Close>
<X class="size-4 text-muted-foreground" />
</Popover.Close>
</div>
<p>Insert image url</p>
<Input
placeholder="Enter image url..."
type="url"
onchange={(e) => {
if (e !== null && e.target !== null) {
//@ts-ignore
editor.chain().focus().setImage({ src: e.target.value }).run();
}
}}
class="w-full"
/>
<p>OR Pick an Image</p>
<Input
id="picture"
type="file"
accept="image/*"
multiple={false}
onchange={(e: Event) => {
//@ts-ignore
if (e.target && e.target.files) {
//@ts-ignore
const files = Array.from(e.target.files || []);
files.map((file) => {
const reader = new FileReader();
reader.onload = () => {
const src = reader.result as string;
editor.chain().focus().setImage({ src }).run();
};
//@ts-ignore
reader.readAsDataURL(file);
});
}
}}
/>
</Popover.Content>
</Popover.Root>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Add Image</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { Italic } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class={cn('size-8', editor.isActive('italic') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleItalic().run()}
>
<Italic />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Italic (⌘I)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,72 +0,0 @@
<script lang="ts">
import { Link } from 'lucide-svelte';
import { ChevronDown } from 'lucide-svelte';
import { X } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
import * as Popover from '$lib/components/ui/popover/index.js';
import { Input } from '$lib/components/ui/input/index.js';
let { editor }: { editor: Editor } = $props();
function setLink(url: string) {
if (url.trim() === '') {
editor.chain().focus().extendMarkRange('link').unsetLink().run();
return;
}
editor?.chain().focus().extendMarkRange('link').setLink({ href: url }).run();
}
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Popover.Root>
<Popover.Trigger>
<Button
variant="ghost"
size="sm"
class={cn('h-8', editor.isActive('link') && 'bg-muted')}
>
<Link />
<ChevronDown class="size-3! text-muted-foreground" />
</Button>
</Popover.Trigger>
<Popover.Content class="bg-popover shadow-lg *:my-2">
<div class="flex items-center justify-between">
<h1 class="text-xl font-bold">Link</h1>
<Popover.Close>
<X class="size-4 text-muted-foreground" />
</Popover.Close>
</div>
<p>Insert or remove link from selected text.</p>
<Input
placeholder="Enter link to attach.."
value={editor?.getAttributes('link').href}
onchange={(e) => {
//@ts-ignore
if (e !== null && e.target !== null) setLink(e.target.value);
}}
class="w-full"
/>
<div class="flex items-center justify-end gap-2">
<Button size="sm" onclick={() => {}}>
<Popover.Close>Insert</Popover.Close>
</Button>
<Button
variant="destructive"
onclick={() => {
editor.chain().focus().extendMarkRange('link').unsetLink().run();
}}>Remove</Button
>
</div>
</Popover.Content>
</Popover.Root>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Add Or Remove Link</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { ListOrdered } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class={cn('size-8', editor.isActive('orderedList') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleOrderedList().run()}
>
<ListOrdered />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Ordered List</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,95 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { type Editor } from '@tiptap/core';
import { Check, ChevronDown } from 'lucide-svelte';
let { editor }: { editor: Editor } = $props();
const colors = [
{ label: 'Default', value: '' },
{ label: 'Blue', value: '#0000FF' },
{ label: 'Brown', value: '#A52A2A' },
{ label: 'Green', value: '#008000' },
{ label: 'Grey', value: '#808080' },
{ label: 'Orange', value: '#FFA500' },
{ label: 'Pink', value: '#FFC0CB' },
{ label: 'Purple', value: '#800080' },
{ label: 'Red', value: '#FF0000' },
{ label: 'Yellow', value: '#FFFF00' }
];
const currentColor = $derived(editor.getAttributes('textStyle').color);
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="ghost" size="sm" class="h-8" style={`color: ${currentColor}`}>
A
<ChevronDown class="size-3!" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="max-h-100 w-40 overflow-auto">
<DropdownMenu.Group>
<span class="text-[0.75rem] font-medium text-muted-foreground">Text Color</span>
{#each colors as color}
<DropdownMenu.Item
class="flex items-center"
onclick={() => {
if (color.value === '' || color.label === 'Default')
editor.chain().focus().unsetColor().run();
else
editor
.chain()
.focus()
.setColor(currentColor === color.value ? '' : color.value)
.run();
}}
closeOnSelect={false}
>
<span class="rounded border px-1 py-px font-medium" style={`color: ${color.value}`}
>A</span
>
<span>{color.label}</span>
{#if editor.isActive('textStyle', { color: color.value })}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
{/each}
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<span class="text-[0.75rem] font-medium text-muted-foreground">Background Colors</span>
{#each colors as color}
<DropdownMenu.Item
class="flex items-center"
onclick={() => {
if (color.value === '' || color.label === 'Default')
editor.chain().focus().unsetHighlight().run();
else editor.chain().focus().toggleHighlight({ color: color.value }).run();
}}
closeOnSelect={false}
>
<span
class="rounded px-1 py-px font-medium"
style={`background-color: ${color.value};`}>A</span
>
<span>{color.label}</span>
{#if editor.isActive('highlight', { color: color.value })}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
{/each}
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Quick Colors</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { Redo } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class="size-8"
onclick={() => editor.chain().focus().redo().run()}
disabled={!editor.can().chain().focus().redo().run()}
>
<Redo />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Redo (⌘R)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,146 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import * as Popover from '$lib/components/ui/popover/index.js';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { type Editor } from '@tiptap/core';
import {
ArrowLeft,
ArrowRight,
TextSearch,
X,
Replace,
ReplaceAll,
ChevronDown
} from 'lucide-svelte';
let { editor }: { editor: Editor } = $props();
let searchText = $state('');
let replaceText = $state('');
let caseSensitive = $state(false);
let searchIndex = $derived(editor.storage?.searchAndReplace?.resultIndex);
let searchCount = $derived(editor.storage?.searchAndReplace?.results.length);
function updateSearchTerm(clearIndex: boolean = false) {
if (clearIndex) editor.commands.resetIndex();
editor.commands.setSearchTerm(searchText);
editor.commands.setReplaceTerm(replaceText);
editor.commands.setCaseSensitive(caseSensitive);
}
function goToSelection() {
const { results, resultIndex } = editor.storage.searchAndReplace;
const position: Range = results[resultIndex];
if (!position) return;
//@ts-ignore
editor.commands.setTextSelection(position);
const { node } = editor.view.domAtPos(editor.state.selection.anchor);
node instanceof HTMLElement && node.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
function replace() {
editor.commands.replace();
goToSelection();
}
const next = () => {
editor.commands.nextSearchResult();
goToSelection();
};
const previous = () => {
editor.commands.previousSearchResult();
goToSelection();
};
const clear = () => {
searchText = '';
replaceText = '';
editor.commands.resetIndex();
};
const replaceAll = () => editor.commands.replaceAll();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Popover.Root
onOpenChange={(open) => {
if (open) updateSearchTerm();
else {
clear();
updateSearchTerm(true);
}
}}
>
<Popover.Trigger>
<Button variant="ghost" size="sm" class="h-8">
<TextSearch />
<ChevronDown class="size-3 text-muted-foreground" />
</Button>
</Popover.Trigger>
<Popover.Content class="bg-popover shadow-lg *:my-2">
<Popover.Close
class="absolute right-2 top-0 text-muted-foreground"
onclick={() => {
clear();
updateSearchTerm(true);
}}
>
<X class="size-4" />
</Popover.Close>
<div class="flex items-center justify-between">
<Input
placeholder="Enter Text to search.."
bind:value={searchText}
oninput={() => updateSearchTerm()}
class="mr-1 "
/>
<Button variant="ghost" class="ml-1 size-8" onclick={previous}>
<ArrowLeft />
</Button>
<Button variant="ghost" class="ml-1 size-8" onclick={next}>
<ArrowRight />
</Button>
</div>
<div class="flex items-center justify-between">
<Input
placeholder="Enter Text to Replace.."
bind:value={replaceText}
oninput={() => updateSearchTerm()}
class="mr-1 "
/>
<Button variant="ghost" class="ml-1 size-8" onclick={replace}>
<Replace />
</Button>
<Button variant="ghost" class="ml-1 size-8" onclick={replaceAll}>
<ReplaceAll />
</Button>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<input
type="checkbox"
class="checkbox"
bind:checked={caseSensitive}
onchange={() => updateSearchTerm()}
/>
<p>Case Sensitive</p>
</div>
<div class="flex items-center gap-2">
{searchCount > 0 ? searchIndex + 1 : 0} / {searchCount}
</div>
</div>
</Popover.Content>
</Popover.Root>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Search And Replace Text</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { Strikethrough } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class={cn('size-8', editor.isActive('strike') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleStrike().run()}
>
<Strikethrough />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Strike (⌘⇧S)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { Subscript } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class={cn('size-8', editor.isActive('subscript') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleSubscript().run()}
>
<Subscript />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Subscript (⌘,)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { Superscript } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class={cn('size-8', editor.isActive('superscript') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleSuperscript().run()}
>
<Superscript />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Superscript (⌘.)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,111 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { type Editor } from '@tiptap/core';
import { Table, ChevronDown } from 'lucide-svelte';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="ghost" size="sm" class="h-8">
<Table />
<ChevronDown class="size-3!" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="max-h-100 w-40 overflow-auto">
<DropdownMenu.Item
onclick={() =>
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}
>
<span>Insert Table</span>
</DropdownMenu.Item>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>
<span>Header</span>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Item onclick={() => editor.chain().focus().toggleHeaderColumn().run()}>
<span>Toggle Column</span>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => editor.chain().focus().toggleHeaderRow().run()}>
<span>Toggle Row</span>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => editor.chain().focus().toggleHeaderCell().run()}>
<span>Toggle Cell</span>
</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>
<span>Cells</span>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Item onclick={() => editor.chain().focus().mergeCells().run()}>
<span>Merge</span>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => editor.chain().focus().splitCell().run()}>
<span>Split</span>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => editor.chain().focus().mergeOrSplit().run()}>
<span>Merge or Split</span>
</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>
<span>Row</span>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Item onclick={() => editor.chain().focus().addRowBefore().run()}>
<span>Insert Above</span>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => editor.chain().focus().addRowAfter().run()}>
<span>Insert Below</span>
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => editor.chain().focus().deleteRow().run()}
class="text-destructive hover:text-foreground data-[highlighted]:bg-destructive"
>
<span>Delete Row</span>
</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>
<span>Column</span>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Item onclick={() => editor.chain().focus().addColumnBefore().run()}>
<span>Insert Before</span>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => editor.chain().focus().addColumnAfter().run()}>
<span>Insert After</span>
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => editor.chain().focus().deleteColumn().run()}
class="text-destructive hover:text-foreground data-[highlighted]:bg-destructive"
>
<span>Delete</span>
</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Item
onclick={() => editor.chain().focus().deleteTable().run()}
class="text-destructive hover:text-foreground data-[highlighted]:bg-destructive"
>
<span>Delete</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Table</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { CheckSquare } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class={cn('size-8', editor.isActive('taskList') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleTaskList().run()}
>
<CheckSquare />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Tasks List (⌘⇧9)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,95 +0,0 @@
<script lang="ts">
import {
Heading1,
Heading2,
Heading3,
Pilcrow,
FileJson,
ChevronDown,
Check,
Minus
} from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import Button from '$lib/components/ui/button/button.svelte';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="ghost" size="sm" class="h-8">
{#if editor.isActive('heading', { level: 1 })}
<Heading1 />
{:else if editor.isActive('heading', { level: 2 })}
<Heading2 />
{:else if editor.isActive('heading', { level: 3 })}
<Heading3 />
{:else if editor.isActive('paragraph')}
<Pilcrow />
{:else if editor.isActive('codeBlock')}
<FileJson />
{:else}
<Minus />
{/if}
<ChevronDown class="size-3! text-muted-foreground" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-56">
<DropdownMenu.Item
onclick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
closeOnSelect={false}
>
<Heading1 /> Heading 1
{#if editor.isActive('heading', { level: 1 })}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
closeOnSelect={false}
>
<Heading2 /> Heading 2
{#if editor.isActive('heading', { level: 2 })}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
closeOnSelect={false}
>
<Heading3 /> Heading 3
{#if editor.isActive('heading', { level: 3 })}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => editor.chain().focus().setParagraph().run()}
closeOnSelect={false}
>
<Pilcrow /> Paragraph
{#if editor.isActive('paragraph')}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => editor.chain().focus().toggleCodeBlock().run()}
closeOnSelect={false}
>
<FileJson /> Code Block
{#if editor.isActive('codeBlock')}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Text Formatting</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,82 +0,0 @@
<script lang="ts">
import {
AlignCenter,
AlignLeft,
AlignRight,
AlignJustify,
ChevronDown,
Check
} from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import Button from '$lib/components/ui/button/button.svelte';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="ghost" size="sm" class="h-8">
{#if editor.isActive({ textAlign: 'left' })}
<AlignLeft />
{:else if editor.isActive({ textAlign: 'center' })}
<AlignCenter />
{:else if editor.isActive({ textAlign: 'right' })}
<AlignRight />
{:else if editor.isActive({ textAlign: 'justify' })}
<AlignJustify />
{:else}
<AlignLeft />
{/if}
<ChevronDown class="size-3! text-muted-foreground" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-56">
<DropdownMenu.Item
onclick={() => editor.chain().focus().setTextAlign('left').run()}
closeOnSelect={false}
>
<AlignLeft /> Align Left
{#if editor.isActive({ textAlign: 'left' })}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => editor.chain().focus().setTextAlign('center').run()}
closeOnSelect={false}
>
<AlignCenter /> Align Center
{#if editor.isActive({ textAlign: 'center' })}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => editor.chain().focus().setTextAlign('right').run()}
closeOnSelect={false}
>
<AlignRight /> Align Right
{#if editor.isActive({ textAlign: 'right' })}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => editor.chain().focus().setTextAlign('justify').run()}
closeOnSelect={false}
>
<AlignJustify /> Align Justify
{#if editor.isActive({ textAlign: 'justify' })}
<Check class="absolute right-2 size-3! text-muted-foreground" />
{/if}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Text Alignment</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,75 +0,0 @@
<script lang="ts">
import { Baseline } from 'lucide-svelte';
import { X } from 'lucide-svelte';
import { ChevronDown } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
import * as Popover from '$lib/components/ui/popover/index.js';
import { mode } from 'mode-watcher';
import ColorPicker from 'svelte-awesome-color-picker';
interface Props {
editor: Editor;
color?: string;
}
let { editor, color = $bindable('') }: Props = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Popover.Root>
<Popover.Trigger>
<Button
variant="ghost"
size="sm"
class={cn('h-8', editor.isActive('textStyle') && 'bg-muted')}
onclick={() => editor.chain().focus()}
>
<Baseline />
<ChevronDown class="size-3! text-muted-foreground" />
</Button>
</Popover.Trigger>
<Popover.Content class="bg-popover shadow-lg *:my-2">
<div class="flex items-center justify-between">
<h1 class="text-[1.2rem] font-bold">Pick a text color</h1>
<Popover.Close>
<X class="size-4 text-muted-foreground" />
</Popover.Close>
</div>
<div class:dark={$mode === 'dark'}>
<ColorPicker
bind:hex={color}
sliderDirection="vertical"
isTextInput={false}
isAlpha
on:input={(event) => {
if (event.detail.hex === undefined) return;
color = event.detail.hex;
editor.chain().focus().setColor(color).run();
}}
isDialog={false}
--picker-indicator-size="1rem"
--input-size="1rem"
/>
</div>
<div class="flex items-center justify-end gap-2">
<Button
variant="outline"
size="sm"
class="border-destructive text-destructive hover:bg-destructive hover:text-foreground"
onclick={() => editor.chain().focus().unsetColor().run()}
>Remove Color
</Button>
</div>
</Popover.Content>
</Popover.Root>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Text Color</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { Underline } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/utils.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class={cn('size-8', editor.isActive('underline') && 'bg-muted')}
onclick={() => editor.chain().focus().toggleUnderline().run()}
>
<Underline />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Underline (⌘U)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,26 +0,0 @@
<script lang="ts">
import { Undo } from 'lucide-svelte';
import { type Editor } from '@tiptap/core';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { Button } from '$lib/components/ui/button/index.js';
let { editor }: { editor: Editor } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
variant="ghost"
class="size-8 p-0"
onclick={() => editor.chain().focus().undo().run()}
disabled={!editor.can().chain().focus().undo().run()}
>
<Undo />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Undo (⌘Z)</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,89 +0,0 @@
/* One Dark and Light Theme for Highlight.js using Tailwind CSS */
.tiptap pre code {
@apply text-[#383a42] dark:text-[#abb2bf];
}
/* Comment */
.hljs-comment,
.hljs-quote {
@apply italic text-[#a0a1a7] dark:text-[#5c6370];
}
/* Red */
.hljs-variable,
.hljs-template-variable,
.hljs-tag,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class,
.hljs-regexp,
.hljs-deletion {
@apply text-[#e45649] dark:text-[#e06c75];
}
/* Orange */
.hljs-number,
.hljs-built_in,
.hljs-literal,
.hljs-type,
.hljs-params,
.hljs-meta,
.hljs-link {
@apply text-[#986801] dark:text-[#d19a66];
}
/* Yellow */
.hljs-attribute {
@apply text-[#c18401] dark:text-[#e5c07b];
}
/* Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
@apply text-[#50a14f] dark:text-[#98c379];
}
/* Blue */
.hljs-title,
.hljs-section {
@apply text-[#4078f2] dark:text-[#61afef];
}
/* Purple */
.hljs-keyword,
.hljs-selector-tag {
@apply text-[#a626a4] dark:text-[#c678dd];
}
/* Cyan */
.hljs-emphasis {
@apply italic text-[#0184bc] dark:text-[#56b6c2];
}
.hljs-strong {
/* @apply font-bold; */
/* Kills compile: font-bold */
font-weight: bold;
}
/* Base styles */
.hljs-doctag,
.hljs-formula {
@apply text-[#a626a4] dark:text-[#c678dd];
}
.hljs-attr,
.hljs-subst {
@apply text-[#383a42] dark:text-[#abb2bf];
}
/* Line highlights */
.hljs-addition {
@apply bg-[#e6ffed] dark:bg-[#283428];
}
.hljs-deletion {
@apply bg-[#ffeef0] dark:bg-[#342828];
}

View File

@@ -1,175 +0,0 @@
<script lang="ts">
import './editor.css';
import { Editor, type Content } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import { onDestroy, onMount } from 'svelte';
import EditorToolbar from './editor-toolbar.svelte';
import { cn } from '$lib/utils/utils.js';
import { Subscript } from '@tiptap/extension-subscript';
import { Superscript } from '@tiptap/extension-superscript';
import { Underline } from '@tiptap/extension-underline';
import { Link } from '@tiptap/extension-link';
import TaskList from '@tiptap/extension-task-list';
import TaskItem from '@tiptap/extension-task-item';
import TextStyle from '@tiptap/extension-text-style';
import Color from '@tiptap/extension-color';
import Highlight from '@tiptap/extension-highlight';
import Text from '@tiptap/extension-text';
import Typography from '@tiptap/extension-typography';
import TextAlign from '@tiptap/extension-text-align';
import { SmilieReplacer } from './custom/Extentions/SmilieReplacer.js';
import { ColorHighlighter } from './custom/Extentions/ColorHighlighter.js';
import Table from '@tiptap/extension-table';
import TableRow from '@tiptap/extension-table-row';
import TableHeader from '@tiptap/extension-table-header';
import TableCell from '@tiptap/extension-table-cell';
import { ImageExtension } from './custom/Extentions/ImageExtention.js';
import { SvelteNodeViewRenderer } from 'svelte-tiptap';
import CodeExtended from './custom/code-extended.svelte';
// Lowlight
import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight';
import { all, createLowlight } from 'lowlight';
import './onedark.css';
import SearchAndReplace from './custom/Extentions/SearchAndReplace.js';
const lowlight = createLowlight(all);
interface Props {
class?: string;
content?: Content;
show_toolbar?: boolean;
// html_text?: string;
new_html?: string;
placeholder?: string;
show_button_kv?: any;
}
let {
class: className = '',
content = $bindable(''),
show_toolbar = true,
// html_text = '',
new_html = $bindable(''),
placeholder = $bindable('Start typing...'),
show_button_kv = $bindable({})
}: Props = $props();
let editor = $state<Editor>();
let element = $state<HTMLElement>();
onMount(() => {
editor = new Editor({
element,
content,
editorProps: {
attributes: {
class:
'm-auto p-2 focus:outline-hidden flex-1 prose text-foreground min-w-full max-h-full overflow-auto dark:prose-invert *:my-2'
}
},
extensions: [
StarterKit.configure({
orderedList: {
HTMLAttributes: {
class: 'list-decimal'
}
},
bulletList: {
HTMLAttributes: {
class: 'list-disc'
}
},
heading: {
levels: [1, 2, 3, 4],
HTMLAttributes: {
class: 'tiptap-heading'
}
}
}),
Typography,
Text,
TextStyle,
TextAlign.configure({
types: ['heading', 'paragraph']
}),
Color,
Highlight.configure({ multicolor: true }),
Underline,
Superscript,
Subscript,
Link.configure({
openOnClick: false,
autolink: true,
defaultProtocol: 'https',
HTMLAttributes: {
target: '_blank',
rel: 'noopener noreferrer'
}
}),
TaskList,
TaskItem.configure({
nested: true
}),
SearchAndReplace,
CodeBlockLowlight.configure({
lowlight
}).extend({
addNodeView() {
return SvelteNodeViewRenderer(CodeExtended);
}
}),
SmilieReplacer,
ColorHighlighter,
Table.configure({
allowTableNodeSelection: true,
resizable: true
}),
TableRow,
TableHeader,
TableCell,
ImageExtension
],
autofocus: false, // This should be changed to a prop in the future
onTransaction: (transaction) => {
/**
* Weird behavior of editor.
* If we do not make it undefined, then it looses it's reactivity
* this is because assigning editor directly to `transaction.editor`
* the original object is not mutated.
*/
editor = undefined;
editor = transaction.editor;
// console.log(editor.isActive('bold'));
content = editor.getHTML();
// console.log(content);
let html = editor.getHTML();
if (html == '<p></p>') {
new_html = '';
} else {
new_html = html ?? '';
}
}
});
});
onDestroy(() => {
if (editor) editor.destroy();
});
</script>
<div class={cn('flex flex-col rounded border textarea editor', className)}>
{#if editor && show_toolbar}
<EditorToolbar {editor} {show_button_kv} />
{/if}
<div bind:this={element} spellcheck="false" class="tiptap overflow-auto relative">
<span
class="placeholder text-sm text-gray-400 italic absolute p-3 w-full"
contenteditable="false"
hidden={editor?.getHTML() !== '<p></p>'}>{placeholder}</span
>
</div>
</div>