* feat(editor): add alt text support for images
* feat: extend alt text support to videos and diagrams
---------
Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
* Better trash
I recently lost a bunch of time editing and searching for pages that were actually in the Trash. Docmost intentionally tries to not link to Trashed pages, but the url of that Trashed page and any inbound links still work. This makes it clearer when a page you are interacting with is in the Trash.
- /trash
- Refactored banner into `trash-banner.tsx`
- Refactored "Restore" modal into `use-restore-page-modal.tsx`
- Page (when isDeleted)
- Add: `trash-banner.tsx`
- Add breadcrumbs: `Parent / Child / Page (Deleted)`
- Change: Deleted Pages are read-only
- Replace "Move to Trash" with "Restore" in page menu (invokes `use-restore-page-modal`)
I tried very hard to keep this simple and re-use existing translation strings wherever possible.
* cleanup
---------
Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
The header edit/read toggle now controls only the current session's mode
without saving it as the user's preference. The saved preference (set in
profile settings) is applied once on initial load and sticks across page
navigations within the session, so navigating to a new page no longer
resets the mode mid-session.
Fixes#1693
* fix(editor): hide transclusion borders and reset spacing in read-only mode
* feat(share): add full width toggle for shared pages
* feat(share): support resizing sidebar on shared pages
* fix: auto redirect if there is only one SSO provider.
- fix tighten sso redirect
- fix share tree margin
* sync
* package overrides
* feat(tree): replace react-arborist with custom tree implementation
* feat(tree): keyboard arrow navigation between rows
* feat(emoji-picker): focus search input on open
* refactor(emoji): switch to @slidoapp/emoji-mart fork for accessibility
* feat(tree): Home/End and typeahead keyboard navigation
* feat(tree): roving tabindex and * to expand sibling subtrees
* feat(tree): Space activation and ARIA refinements
* fix(tree): move treeitem role to focusable row + aria-current
* feat(editor): show emoji name in suggestion list
Replace the fixed-column emoji grid with a vertical list that displays
each emoji alongside its :shortcode: name. This makes the picker more
discoverable—users can see and learn shortcodes without prior knowledge.
Changes:
- EmojiList: switch from SimpleGrid/ActionIcon to UnstyledButton list
rows showing emoji glyph + monospace 🆔 label
- Navigation simplified to ArrowUp/ArrowDown (list has no columns)
- Results capped at 8 items for a focused, scannable dropdown
- CSS module: rename menuBtn -> menuItem, tighten padding
* feat(editor): replace SearchIndex with name/id includes search
Port the exact search algorithm from the original extension:
- Build a flat index from @emoji-mart/data: { id, name (lowercase), native }
- Filter with name.includes(q) || id.includes(q) — predictable, no
keyword indirection
- Results capped at 5 (same as extension)
- Frequently-used emojis (sorted by usage) shown when query is empty
- Remove emoji-mart init() / SearchIndex / getEmojiDataFromNative
dependencies; index is built lazily and cached in memory
- Remove unused GRID_COLUMNS constant
* feat(editor): emoji picker with browse and search modes
When the query is empty the picker shows a category bar with 8 tabs
(people, nature, food…) and a scrollable emoji grid. Typing after ':'
switches to a compact list that shows the glyph and :shortcode: side by
side, making it easy to discover emoji names while you type.
- Category data is loaded lazily from @emoji-mart/data and cached, so
opening the picker more than once has no overhead
- Grid keyboard nav: arrow keys move by cell/row, Enter picks
- List keyboard nav: up/down through results, Enter picks
- Mouse hover syncs the keyboard selection index in both modes
- incrementEmojiUsage tracks picks so frequently used ones bubble up
in future sessions
* fix(editor): polish emoji picker copy and loading
* feat: add emoji to slash command
* Add keyboard support to emoji group navigation
---------
Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
* Improve cmd-k / ctrl-k behavior
Use cmd-k on macOS/iOS for search and keep ctrl-k everywhere else.
Fixes a bug where ctrl-k on macOS, which cuts to the end of the line,
was also triggering the search prompt.
* comment submit: cmd-enter (mac) / ctrl-enter (win/linux)
The sticky-headers refactor (cd8d1e0e) moved the header row's sticky
behavior up to .stickyBand and dropped the position rule from
.headerCell entirely — but the cell still has two absolutely-positioned
children that need it as their containing block:
- .resizeHandle (right: 0) — without a per-cell containing block, all
handles resolve up to .stickyBand and stack at the band's right
edge, so only one is visible.
- Popover.Target (inset: 0) — every header's popover anchor collapses
to the same band-relative box, so the property menu opens at one
fixed spot regardless of which header was clicked.
.headerCellPinned still establishes a containing block via position:
sticky, which is why the primary Title column kept working. Re-adding
position: relative on the base .headerCell fixes both the resize handle
and the property-menu popover for non-pinned columns; pinned cells are
unaffected because position: sticky later in the cascade still wins.
The "People with access" list in the page share modal used
<ScrollArea mah={250}>, which caps the container height but does not
make the inner viewport scroll (no fixed height is given to the
viewport). Items beyond ~4 entries were rendered correctly but clipped
out of view.
Switches to <ScrollArea.Autosize mah={400}>, which is Mantine's
dedicated primitive for "grow with content up to a max, then scroll".
Closes#2135
The inline-embed endpoint was a thin wrapper around baseService.create:
its only differences from /bases/create were that it derived the
spaceId/workspaceId from a parent page, gated permission via
pageAccessService.validateCanEdit on that parent, and seeded inline
defaults (Title + Text 1 + Text 2 + one row). All of that fits
naturally on /bases/create as a parentPageId branch.
- CreateBaseDto: spaceId is now optional. Either spaceId or
parentPageId must be supplied; the controller validates the
cross-field requirement and 400s otherwise.
- /bases/create: with parentPageId, derive workspaceId/spaceId from
the parent, validateCanEdit on the parent, apply inline defaults.
Without parentPageId, gate on space-level Create, Page (the
standalone path).
- InlineEmbedBaseDto + createInlineEmbed deleted.
- Slash command in the editor now POSTs /bases/create with
{ parentPageId } — the server picks up the inline branch.
Bases are pages (isBase=true) and the casl rules already granted the
exact same Manage/Read level on the Base subject as on Page for every
space role (admin, writer, reader). The `Base` subject was therefore
pure duplication: any caller that needed to check base access either
went through pageAccessService (which uses Page internally) or did a
direct Page-equivalent ability.cannot(..., Base) check that produced
the same outcome as Page would have.
Drop SpaceCaslSubject.Base entirely — server enum, server union,
server factory rules, client enum, client union — and switch the two
remaining direct callers to Page:
- base.controller.ts `create` and list checks now use Page (matching
page.controller.ts's create/list).
- base-table.tsx's `canSave` now reads Page edit ability.
Net effect: one source of truth for "can this user view/edit/manage
content in this space," whether the content is a regular page or a
base. Existing role assignments behave identically; no migration
needed because permissions are computed per-request from the role,
not stored.