FEATURE: Add visual doc category index editor#87
Draft
Conversation
Removes a lot of the visual noise from docs
Instead of hiding reply posts with CSS display:none and fighting the cloaking system, filter the post stream's ID array so replies are never rendered. Uses the topic-post-stream transformer to monkey-patch updateFromJson on doc category PostStream instances. All posts are stored in the identity map on initial load, then the stream and posts arrays are trimmed to the OP only. Toggling comments restores posts from the identity map synchronously with no network request.
When simple mode is enabled, the topic list should show only what matters for documentation: topic titles and last updated date. Three remaining visual elements were adding noise across all themes. - Delete the "views" column (was kept alongside posters/replies removal) - Hide inline category badge via CSS scoped to `.doc-simple-mode` (redundant when browsing within a single doc category) - Hide topic excerpts on pinned topics (documentation index should be a scannable list, not a reading view) Ref - t/179937 Requires discourse/discourse#38970
Adds a direct-edit index editor with auto-index, drag-and-drop, atomic save with category, and tab-switch state persistence.
Introduce a batch editing mode to `doc-category-index-editor`, allowing multiple items or sections to be selected, managed, and deleted at once. Batch actions include selecting all links in a section, inverting/clearing selection, and bulk deletion. A toolbar provides contextual feedback on selected items and offers controls to manage batch operations. This update also adds a "None" mode to the index editor, enabling users to disable indexing for a category. Additional refinements include improved state handling for edit mode toggles, enhanced UI responsiveness, and clarified editor interactions.
Use index_topic_id as an explicit discriminator for index mode: NULL = none, -1 = direct (visual editor), positive = topic. Adds mode helper methods (mode_none?, mode_direct?, mode_topic?) to the Index model, updates all consumers, and replaces the unique index with a partial unique index excluding -1.
- Add @Tracked doc_index_topic_id to Category via modifyClass - Use get() from @ember/object to read doc_index_topic_id in getters, ensuring Glimmer tracks EmberObject changes - Defer direct mode sentinel (-1) to validator on save - Dirty form on topic selection via form.set - Add system tests for topic chooser selection and save
Refactor how `doc_index_topic_id` and `doc_index_sections` are managed in the `doc-category-index-editor` and related components. Replace direct modifications of category properties with `args.form` updates to improve data flow consistency. Remove `@tracked` overrides and redundant transient state handling. This change reduces complexity by standardizing property interactions and ensures proper synchronization of form data, addressing potential inconsistencies in save payloads and mode transitions.
Add a confirmation dialog when switching to "None" mode in the `doc-category-index-tab` if category index data or topics exist. The dialog ensures users confirm the action to prevent accidental data loss. Introduce a private `#applyNoneMode` method to avoid duplicated cleanup logic and improve code clarity.
Introduce a confirmation dialog in `doc-category-index-tab` when switching from topic mode to direct mode. This ensures users acknowledge that the topic-based index will be disconnected, and editor changes won't sync back to the index topic. Update button styles in `doc-category-index-editor` for consistency and add localized warning messages to improve clarity for users transitioning between modes. Refine mode transitions to avoid accidental data loss and enhance user experience.
- Add drag handle in batch selection bar for bulk reordering - Items-only selection: drop between links in any section - Sections-only selection: drop between sections - Mixed selection: drag handle hidden - Show drop indicator inside empty sections for item drags - Disable batch edit when only one section with <= 1 link - Reset batch mode on form discard - Match apply button size to save button (btn-small)
- Only process drops when a visual indicator was shown - Enlarge last item drop target area for easier dragging - Align batch checkboxes and drag handles with section/item icons
- Validate pending edits, empty sections, empty titles, empty URLs - Block save with FormKit error feedback via registerValidator - Block apply with validation check before API call
- Send link type and topic_id to backend for proper validation - Auto-detect when title matches topic title and store as nil - Display "auto" badge on topic links using automatic titles - Show inline validation errors (danger border + message) on confirm - Use topic title as input placeholder for topic links
Update localized text and related system tests to rename "Visual editor" to "Editor" in sidebar index mode options. This simplifies the terminology, providing a clearer and more concise label for users while maintaining functionality. Adjust system test selectors to reflect the updated wording.
When switching to disabled mode, #applyNoneMode() sets doc_index_sections to "[]" on the form. But the editor's willDestroy() then overwrites it with the full serialized sections, causing the save to send stale data and revert back to editor mode.
- Wrap IndexSaver#save_sections! in a transaction so a mid-save failure rolls back instead of leaving partial data - Reject direct PUT updates to topic-mode indexes instead of silently converting them to direct mode (returns 403) - Validate links on focus-out before auto-committing edits - Clamp editingCount decrements to prevent negative drift from racing cancel/focus-out callbacks
- Raise Discourse::InvalidParameters when sections or links per section exceed limits instead of silently truncating - Remove unnecessary first_post load from serialize_index_structure (sidebar data comes from DB) - Use Number() coercion in willDestroy to handle FormKit storing doc_index_topic_id as a string - Inline _fetchCategoryTopics into its only call site - Add comprehensive IndexSaver spec covering limits, filtering, topic_id extraction, and auto-title detection - Add controller spec for limit validation and category save with doc_index_sections param
- Fix timer leak in apply() by storing and cancelling discourseLater in willDestroy - Fix editingCount getting stuck when sections are removed while links are being edited (add willDestroy to child components) - Add isDestroying guard to next() callback in onCardFocusOut - Clean up _autoExpandTimer on IndexEditorSection destroy - Handle empty sections after build_sections filtering in IndexSaver (treat as blank input) - Add JSON parse error handling in category callback - Add hard limit (5000) to topics endpoint and surface truncation warning in the UI - Remove unused :type param from strong params - Extract duplicated searchFilters getter to parent component - Add aria-hidden to collapsed section body - Add focus-visible styles to interactive elements (drag handles, section title label, topic href, batch drag handle) - Rename drag state CSS classes to BEM is- prefix convention - Add visibility: hidden to collapsed sections for a11y - Add explanatory comment to double-class specificity hack - Clean up --selected selectors to use & nesting - Annotate pill border-radius magic number
- Fix IndexSaver filtering tests to expect index destruction when all sections are filtered out (not just empty sections) - Add test for destroying existing index when all sections filter out - Add total_count assertion to topics endpoint test - Add test for invalid JSON in doc_index_sections returning 400
- Guard non-array JSON in IndexSaver#save_sections! - Extract duplicate cleanup logic to private destroy_index! - Clear stale direct-mode sidebar sections when switching to topic mode in CategoryIndexManager - Fix batch reorder drops on selected items causing misinsertion (guard with early return) - Set doc_index_topic_id to -1 explicitly on switchToDirectMode - Add isDestroying guards after await in all async methods (apply, _doIndexAllTopics, addMissingTopicsToSection, loadIndexTopic) - Clear existing _autoExpandTimer before creating new one on repeated sectionDragEnter - Replace clearTimeout with cancel() from @ember/runloop for discourseLater timers - CSS: merge danger-hover button selectors, merge --selected rules, fix cursor on section-title-label, add flex-shrink to batch-drag-handle, add prefers-reduced-motion media query - Add tests for non-array JSON input and direct-to-topic mode section cleanup
Give legacy category settings (enable_simplified_category_creation=false) the same mode selection (none, topic, editor) and visual editor that the new simplified flow already has. - Extract DocIndexModeSelector: shared dropdown component used by both the new-flow tab and legacy settings - Extract DocIndexModeState: shared state manager encapsulating mode tracking, switching with confirmation dialogs, topic loading, and topic validation - Refactor DocCategoryIndexEditor to use ConditionalInElement for toolbar and a new @footerElement arg for footer teleporting - Create DocIndexEditorModal: DModal wrapper with toolbar/footer targets and close guard for validation errors - Rework DocCategorySettings: mode selector, topic chooser, and editor modal trigger replacing the old topic-only UI - Add z-index overrides for DMenu dropdowns inside the modal - Add system specs and page object methods for legacy flow
The first section's header is suppressed in the sidebar, so an empty title is valid. This change: - Skips empty-title validation for the first section (client + server) - Shows a "Not collapsible (no title)" placeholder in the editor - Prevents the first section from auto-entering edit mode or being deleted on cancel when its title is intentionally empty - Disables the Apply button when validation errors exist - Adds JS unit tests for the validation function and system tests for the editor behavior
…rm fields Renames the transient form field from `_docIndexSections` to `_docIndexEditorState` to clearly distinguish it from `doc_index_sections` (the backend payload). Adds a JSDoc to `_saveToTransientData()` explaining the purpose of each field and why both must be committed after Apply.
Renames the auto-title badge from "auto" to "auto-title" to clearly distinguish it from "auto-indexed". Unifies both item badges under a single `__item-badge` CSS class with consistent pill styling.
- Align batch edit and wrench buttons to the right of the toolbar - Add select all, invert, clear, and close buttons to the batch bar with tooltips on all buttons - Select all/invert are context-dependent: operate on sections if any are selected, otherwise on items - Style delete and clear-index buttons with danger-red color and danger background on hover (matching post menu pattern) - Show confirmation dialog when exiting batch mode with active selection - Remove 8 abandoned locale keys
The modal page object's `add_section` was missing the fallback for the first section not auto-entering title edit mode. Also update the "flash error" test to check for a disabled Apply button instead, matching the current behavior and the non-legacy spec.
Topics that change archetype (e.g., banner <-> regular) or visibility (hidden <-> visible) were not automatically added/removed from the auto-index sidebar. - Listen to :topic_status_updated for visibility changes - Add after_save callback to detect archetype changes (make_banner!, remove_banner!, PM conversions) - Fix AddTopic#topic_qualifies to check archetype, consistent with Sync's qualifying query - Fix has_auto_index_for_category? to actually filter by category_id (parameter was accepted but unused)
Add tests for topic lifecycle events (created, recovered, trashed, destroyed), category filtering, and subcategory support. Fix has_auto_index_for_category? to check auto_index_include_subcategories when matching via parent category, avoiding no-op job enqueues for subcategory topics when the flag is disabled.
The auto_index_include_subcategories setting had no UI. Add a dropdown on the auto-index badge that reveals an "Include subcategories" checkbox. The badge label updates to reflect the current state. Also trigger a resync when the setting changes, with a confirmation dialog warning the user.
Add a "Resync topics on next save" button to the auto-index badge dropdown. The button toggles a pending state reflected in the badge label, and can be cancelled before saving. The resync button is hidden when the subcategory setting has been changed (since that already forces a resync). Toggling subcategories back to the original value skips the confirmation dialog.
Add IndexSaver specs for sync_auto_index_if_needed! with force param. Add controller specs for force_sync param and auto_index_include_subcategories triggering resync. Add system specs for the badge dropdown: toggling include subcategories, resync pending state, and subcategory topics being included after apply. Add page object helpers for badge dropdown interactions.
Convert IndexSaver, CategoryIndexManager, and IndexStructureRefresher from plain Ruby classes to Discourse's Service::Base pattern with the standard contract/model/policy/step/transaction DSL. This brings consistency with the auto-indexer services that already use Service::Base, simplifies the controller (result matchers replace manual orchestration), and standardizes error handling across all service entry points. Key changes: - IndexSaver: params contract, only_if for force_direct/sync, transaction-wrapped save, automatic auto-index sync - CategoryIndexManager: only_if branching for remove/direct/topic modes - IndexStructureRefresher: linear pipeline with policy guards - IndexesController#update: uses service runner with result matchers - All callers updated: category callbacks, initializers, job, rake task
Use #private fields where possible, extract shared isAboveElement utility, remove unnecessary optional chaining, simplify sidebar classNames getter, and reorder class members per ESLint sort-class-members rule.
Add `doc_categories_index_editor` site setting as an experimental upcoming change. When disabled, the "Editor" mode option is hidden from the mode selector and backend endpoints reject new direct-mode index creation. Categories already using the editor remain fully functional so admins can manage or switch away from them.
The sync service previously loaded ALL qualifying topic IDs into memory via an unbounded pluck, then used in-memory set operations for diffing. Since a section holds at most 200 links, this was wasteful for large categories. Replace with two DB-level queries: - compute_topics_to_add: uses a NOT IN subquery to find qualifying topics not already linked, capped at MAX_LINKS_PER_SECTION - find_stale_links: uses a LEFT JOIN against topics to detect auto-indexed links whose topics no longer qualify (deleted, invisible, moved, etc.)
Limit the number of topics loaded by the `topics` action from 5000 to 50 to improve resource utilization and reduce memory overhead. This change impacts categories with large volumes of topics, making the action faster and less memory-intensive. Assumption: The reduced limit aligns with expected use cases and does not disrupt workflows heavily relying on large topic loads.
Replace per-topic find_by with a single WHERE IN query in add_missing_topics, and skip cache clear + MessageBus publish when no changes were made. Also remove stray `For` token.
Move update_subcategory_setting inside the transaction block so it rolls back with save_sections on failure. Add safe navigation for first_post in both report files to handle hard-deleted posts. Reuse context[:sections_data] in determine_sync_needed instead of re-parsing params. Add missing invalid_sections locale key.
Add :topic to the sidebar_links preload so topics are eager-loaded for serialization. Remove unused Topic.clear_doc_categories_cache class method. Rename validate_topic to ensure_valid_topic in IndexStructureRefresher to reflect its destructive cleanup behavior.
Add tabindex="0" to drag handle spans for keyboard accessibility. Delete unused doc-category-settings-form.gjs component. Remove non-functional @computed decorator from sidebar panel. Deduplicate validation error messages with Set. Merge duplicate CSS rule for index tab header. Rename mismatched-category locale key to use underscores.
New links and sections auto-entering edit mode now correctly increment editingCount via onEditStateChange, preventing batch mode toggle during editing. Add missing topic_id, topicTitle, and autoTitle properties to links created by addMissingTopics and indexAllTopics. Commit the subcategory setting form field after successful apply to clear the dirty state.
Walk up the full ancestor chain in has_auto_index_for_category? instead of checking only the immediate parent, supporting sites with max_category_nesting > 2. Move auto-index remove before the early return in handle_category_change so stale links are cleaned up when a topic moves to a category where it is the index topic.
Nullify direct-mode sentinel values (-1) in migration rollback so the full unique index can be created. Simplify matching_category_ids to avoid duplicate root category ID. Use delete_all instead of destroy_all for stale link removal since SidebarLink has no destroy callbacks.
…tion Add controller auth tests for #update endpoint, job spec for DocCategoriesAutoIndex, sidebar_structure unit tests, PM edge case in refresher, auto-index category move tests, and JS validation tests for empty sections, links, and deduplication.
Replace the imperative `editingCount` counter + `onEditStateChange` callback with editing flags stored on section/link data objects and a derived getter. Child constructors were updating the parent's `editingCount` during render after `canToggleBatchMode` had already read it, causing an Ember autotracking assertion failure. Also adds a missing blank line before the `transaction` block in `IndexSaver` to fix the Rubocop lint.
… in specs When saving a category in topic mode, the frontend sends a null value for doc_index_sections which arrives as an empty string. The doc_index_sections callback then called IndexSaver, whose not_topic_managed policy failed because the index had just been created in topic mode by the preceding doc_index_topic_id callback. This raised Discourse::InvalidAccess (403), rolling back the entire transaction including the index creation. Also adds the missing doc_categories_index_editor site setting to auto_index_spec and legacy_spec, which were broken since the editor mode was gated behind this setting.
Change the `impact` setting from "staff" to "admins" and introduce a new `disallow_enabled_for_groups` option in `doc_categories_homepage` to ensure fine-grain control over features. This aligns configuration with intended permissions and improves clarity for administrators managing doc categories.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a visual editor for building doc category sidebar indexes directly in the admin UI, as an alternative to the existing topic-based index approach.
IndexesControllerwithtopicsandupdateendpointsIndexSaverservice for persisting editor-built sectionsindex_topic_idmade nullable with sentinel value of -1 for editor mode; partial unique index excludes NULL and -1doc_category_indexkey so MessageBus updates clear stale values