Commit Graph

146 Commits

Author SHA1 Message Date
Philipinho 911bf53924 Merge branch 'main' into base-formula 2026-05-24 01:47:59 +01:00
Philip Okugbe 92c0e36e46 fix(a11y): WCAG 2.1 AA fixes (#2219) 2026-05-20 16:47:25 +01:00
Olivier Lambert 1c166c4736 feat(editor): add alt text support for images (#2097)
* 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>
2026-05-20 16:45:59 +01:00
Philip Okugbe b7b99cb3b2 fix: code splitting and editor fixes (#2211)
* fix table

* fix code splitting

* fix: editor ready check

* fix codeblock/mermaid gap cursor

* fix callout
2026-05-15 02:46:54 +01:00
Philip Okugbe f4af4c3fc0 feat(editor): add page break node (#2202) 2026-05-14 03:48:13 +01:00
Philip Okugbe 299a9ca3c8 fix: bug fixes (#2201)
* 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
2026-05-14 02:54:00 +01:00
Philip Okugbe cea9be7926 feat: table enhancement (#2191) 2026-05-14 00:37:44 +01:00
Philip Okugbe 31ed0df3f7 feat(tree): replace sidebar tree (react-aborist) with custom tree implementation (#2199)
* 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
2026-05-13 23:01:04 +01:00
Philip Okugbe bdc369fce0 feat(editor): fixed toolbar preference (#2185)
* feat(editor): fixed toolbar preference

* remove key

* cleanup translation strings

* update axios
2026-05-09 13:27:03 +01:00
David Gallardo 6046d04375 feat(editor): replace emoji picker with browse + search (#2171)
* 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>
2026-05-08 21:33:43 +01:00
Philip Okugbe de60aa7e61 feat: synced blocks (transclusion) (#2163)
* feat: synced blocks (transclusion)

* fix:remove name

* make placeholders smaller

* feat: enforce strict transclusion schema

* fix: scope synced blocks to workspace, gate unsync on edit permission

* fix collab module error
2026-05-08 13:23:16 +01:00
Peter Tripp c9fa6e20b3 Add alias: /toc and /ol (#2161) 2026-05-08 01:20:27 +01:00
Philip Okugbe dbe6c2d6ba feat: A11y fixes (#2148) 2026-05-04 21:21:37 +01:00
Philip Okugbe 24be90b95f fix: duplicate PDF uploads (#2139) 2026-04-29 10:01:47 +01:00
Philipinho 5069573e8a refactor(base): fold /bases/inline-embed into /bases/create with parentPageId
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.
2026-04-28 20:29:07 +01:00
Philipinho edc7143f77 fix(base): match inline-embed placeholder skeleton to seeded 3 col / 1 row shape
The placeholder rendered a default 10×6 BaseTableSkeleton while
waiting on the create-base API, then swapped to the real table once
the response landed. Because the inline-embed flow now seeds
Title + Text 1 + Text 2 with one default row, the real table is 3×1
— the swap visibly collapsed a large fake table down to a small
empty one. The scroll didn't jump (initialOffset takes care of that)
but the flicker was jarring.

Re-introduce rows + columns props on BaseTableSkeleton (default still
10 / 6 so other call sites are unaffected) and pass rows=1 columns=3
from the inline-embed placeholder so the swap is visually stable.
2026-04-28 11:34:08 +01:00
Philipinho dac65914ed chore(base): drop pre-virtualizer-fix scroll-jump workarounds
Two pieces of code added under the wrong height-mismatch theory of
the inline-embed scroll jump are no longer load-bearing now that the
real cause (virtual-core's _willUpdate calling _scrollToOffset(NaN))
is fixed by the initialOffset seed in grid-container.tsx:

  - BaseTableSkeleton's `rows` prop, whose only consumer was the
    "creating database" placeholder passing rows=0 to "match" the
    eventual empty-base height. Reverted to a fixed 10-row skeleton.
  - The Database slash command's React Query cache prefill of
    ["bases", id] and ["base-rows", id, …] (~30 lines), which
    existed to skip BaseTable's own loading skeleton on swap. The
    create endpoint return type is back to { id }.

The placeholder approach (pendingKey + setNodeMarkup patch) stays —
that's what gives the user visible state during the create request,
unrelated to the scroll jump.
2026-04-28 11:29:28 +01:00
Philipinho b411f52c18 fix(base): keep editor scroll stable when inline-embed creation completes
The placeholder rendered the full 10-row BaseTableSkeleton (~440px)
while waiting for the create response, then BaseTable mounted, ran its
own queries, and rendered the same 10-row skeleton again until rows
loaded. The actual content for a freshly-created empty base is ~112px
— so the swap shrank the doc by ~330px and on a short page the
browser clamped scrollY past the new doc bottom, manifesting as a
"jump to top of editor."

Two changes to keep the height constant end-to-end:

1. BaseTableSkeleton now accepts a `rows` prop (default 10). The
   placeholder in BaseEmbedView passes `rows={0}` so the skeleton
   matches the height of the eventual empty base shell — header row +
   AddRow button, no fake body rows.

2. The Database slash command now seeds `["bases", id]` and the
   `["base-rows", id, undefined, undefined, undefined]` infinite-query
   cache from the create response (the endpoint already returns the
   full base with properties + views; the typed return was just too
   narrow). BaseTable mounts with baseLoading/rowsLoading already
   false and skips its own skeleton — no transient grow-then-shrink
   between placeholder and final content.

End state: placeholder height ≈ rendered-empty-base height, and no
intermediate skeleton appears while BaseTable is "loading." The
scrollY clamp can't fire because the doc never shrinks.
2026-04-27 22:33:08 +01:00
Philipinho c8086b33e4 fix(base): show skeleton while inline embed is being created, anchor at slash position
The Database slash command deleted the trigger text and then awaited
the create-base API before inserting the embed. During the wait the
editor sat empty (no visible state), and by the time the embed was
inserted the editor's selection had often drifted — so the new node
landed in the wrong place and the page appeared to "jump up" when the
response arrived.

Insert a placeholder baseEmbed node synchronously at the slash position
with pageId: null and a unique pendingKey. BaseEmbedView renders
BaseTableSkeleton while pendingKey is set — same skeleton BaseTable
shows on its own initial load, so the swap to the real table is a
visual no-op. Once the API responds, look up the placeholder by its
pendingKey and patch in the real pageId via setNodeMarkup. On API
failure, remove the placeholder and surface a toast. The pendingKey
attribute is non-serializing (parseHTML returns null, renderHTML
returns {}) so a placeholder can't survive a page reload as orphan.
2026-04-27 22:02:42 +01:00
Philipinho cd8d1e0ed8 feat(base): make column headers sticky in standalone and inline contexts
Split the unified .gridWrapper into a sticky band (containing column
headers, plus banner + toolbar in inline) and a body grid that owns
horizontal scroll. The band's vertical sticky anchor is automatic CSS:
the table's scrollport in standalone, the page in inline. A small
useHorizontalScrollSync hook mirrors body scrollLeft onto the header
and turns wheel-on-header into pan-on-body.
2026-04-27 20:25:13 +01:00
Philipinho 00e9d1d724 feat(base): add right-side scroll headroom on inline embed grid
Mirror the leftward padding with --embed-grid-pad-right = extendRight,
applied as padding-right on .grid. The user can now pan past the last
column into empty space — same shape as Notion's behavior. Standalone
full-page bases are unaffected (var unset → 0).
2026-04-27 04:44:47 +01:00
Philipinho 678c6f5f3f fix(base): drop outer panel border on inline embed grid
The .grid had a 1px outer border + small radius drawing a rounded
rectangle around the whole table. In standalone full-page mode that
reads as a panel and is fine. In an inline embed it looked like a
floating box, especially with the leftward padded area inside the
border.

Make the outer border and radius CSS vars; the embed wrapper sets
them to none/0 so inline databases get only cell-level gridlines,
matching Notion's document-style rendering. Standalone bases get
the default 1px panel frame as before.
2026-04-27 04:20:58 +01:00
Philipinho 030d3e878a feat(base): extend inline embed scroll viewport leftward, anchor first cell with grid padding
Bring back the leftward extension via negative margin-left so the
scroll viewport reaches AppShell.Main's left edge, then offset the
.grid with padding-left = extendLeft so the first cell still lines
up with page-content on load. The extended area becomes scrollable
empty space the user can pan into — same shape as Notion's inline
databases.

Done with one new CSS var (--embed-grid-pad-left) consumed by the
.grid in grid.module.css. Standalone full-page bases never set the
var, so it's a no-op there.
2026-04-27 04:13:34 +01:00
Philipinho a7105267ed fix(base): keep inline embed left edge at page-content, extend right only
Removing the leftward extension. With negative margin-left, the
grid grew leftward past page-content and the first column appeared
near the sidebar edge on load — wrong; Notion keeps the first column
aligned with page text. Drop margin-left and the extendLeft
calculation; only extend rightward toward AppShell.Main's right edge.

Leftward viewport extension (so frozen columns can lock at the
sidebar edge during scroll) becomes meaningful once we add frozen
columns. Deferred until that feature lands.
2026-04-27 04:05:18 +01:00
Philipinho 67f45ee61b fix(base): anchor inline embed extension to AppShell main, not first wider parent
The findWiderAncestor walk often stopped at a slightly-larger
intermediate (NodeViewWrapper, drag-handle wrapper, etc.) whose
left edge was almost the same as the wrapper's, so the leftward
extension came out as ~0 and the grid only grew to the right.

Use closest('main') instead — that's AppShell.Main, the layout
container whose bounds already reflect the navbar width and
sidebar collapse state. Both sides now extend to its edges.
2026-04-27 03:57:45 +01:00
Philipinho d73ac010af feat(base): use negative margin to extend inline grid past parent
The previous approach (position: relative + explicit width +
inset-inline-start) didn't physically grow the box in some flex
contexts, so the grid stayed at the parent's width. Switch to
negative margin-left / margin-right on the grid wrapper only —
with width: auto, the rendered width becomes parent + |margin|,
extending the box past the parent without any positioning hacks.

The toolbar keeps its natural parent-constrained width (no extension)
so it stays aligned with the page text above. Two CSS vars,
--embed-extend-l / --embed-extend-r, are computed on mount + on
ResizeObserver from the wrapper and the closest wider ancestor.
2026-04-27 03:51:21 +01:00
Philipinho 8132b171f4 fix(base): apply embed width-extension after base loads, not just on mount
useEffect ran once on mount with empty deps, but the wrapper div was
inside a conditional branch that only renders after the base query
resolves. Result: the ref was null when the effect ran, so nothing
extended. Move the wrapper outside the conditional so the ref is
always set, and re-run the effect when isLoading / isError / pageId
change so the extension applies once the table mounts.
2026-04-27 03:43:38 +01:00
Philipinho e0e87329f4 feat(base): inline embed extends grid + toolbar beyond page width
When a base is embedded inline in a doc page, measure the parent
container's available area and extend toolbar + grid sections to fill
it via CSS variables (--embed-width / --embed-shift / --embed-pad).
Inner content is re-padded so toolbar buttons and the first column
visually align with the page text, while the box itself reaches the
viewport edges for horizontal scroll headroom on wide databases.
Sticky inset-inline-start keeps the toolbar pinned to the page-content
edge during horizontal scroll. Standalone full-page bases are
unaffected (the embedded prop defaults to false).
2026-04-27 03:39:03 +01:00
Philipinho 7f8ed733a3 feat(base): inline base embed — node registration, slash command, and renderer view 2026-04-27 01:54:59 +01:00
Philip Okugbe d42091ccb1 feat: favorites (#2103)
* feat: favorites and templates(ee)

* rename migrations

* fix sidebar

* cleanup tabs

* fix

* turn off templates

* cleanup

* uuid validation
2026-04-12 22:06:25 +01:00
Philip Okugbe 57efb91bd3 feat(ee): ai chat (#2098)
* feat: ai chat

* feat: ai chat

* sync

* cleanup

* view space button
2026-04-10 19:23:47 +01:00
Philip Okugbe 879aa2c3d8 feat: page update notifications (#2074)
* feat: watchers notification and email preferences

* fix: email copy

* digests

* clean up

* fix

* clean up

* move backlinks queue-up to history processor

* fix

* fix keys

* feat: group notifications

* filter

* adjust email digest window
2026-03-31 16:03:59 +01:00
Philip Okugbe a062f7a165 fix: enhance confluence importer (#2072)
* fix placeholder

* min resize dimensions

* fix media links

* fix
2026-03-30 13:16:40 +01:00
Philip Okugbe 3829b6cbef feat(ee): viewer comments (#2060) 2026-03-28 19:32:52 +00:00
Philipinho 859f16740b tooltip portal 2026-03-28 19:19:00 +00:00
Philip Okugbe 7981ef462e feat(editor): audio and PDF nodes (#2064)
* use local resizable

* feat: aduio

* support audio imports

* feat: use confluence real file names

* cleanup

* error handling

* hide notice

* add audio

* fix pulse

* Fix import and export

* unify pulse

* hide in readonly mode

* keywords

* keyword

* translations

* better sort

* feat: PDF embed

* cleanup

* remove audio menu

* open active

* hide focus on readonly mode

* increase iframe default dimension
2026-03-28 17:33:29 +00:00
Philip Okugbe fa4872e89e fix(deps): package updates (#2041)
* update
* overrides
* override
* fix page update mutation
* fix
* cleanup
* loader
* fix excalidraw package
* override
* fix regex
2026-03-25 10:07:01 +00:00
Philipinho 2678c4e279 fix 2026-03-16 00:32:30 +00:00
Philipinho b0bde4b375 feat: replace link popover with dedicated bubble menu 2026-03-16 00:26:03 +00:00
Philipinho 724e37d5b7 revert 2026-03-15 23:03:32 +00:00
Philip Okugbe 7520c329d0 fix notion importer (#2027)
* fix notion importer

* fix link selector on mobile
2026-03-15 22:06:40 +00:00
Philip Okugbe 89b94e5d19 feat: refactor link menu (#2025)
* link markview - WIP

* WIP

* feat: refactor links

* cleanup
2026-03-15 17:08:59 +00:00
Philip Okugbe 1fdee33206 feat(editor): add auto-save and unsaved changes protection for diagrams (#2011)
* feat(editor): add auto-save and unsaved changes protection for diagrams
* 30 seconds
2026-03-13 17:58:29 +00:00
Philip Okugbe 7b69727a30 fix shared page mention view for non-logged in users (#2008) 2026-03-11 19:25:27 +00:00
Philipinho fcffa3dfa0 fix media 2026-03-04 12:08:08 +00:00
Philip Okugbe bea1637519 fix: image fallback regression (#1989)
* fix: image fallback regression

* fix image preview on upload

* fix image loading
2026-03-04 11:51:43 +00:00
Olivier Lambert f5d794220e fix: resolve keystroke input being swallowed after link in Firefox (#1922)
* fix: resolve keystroke input being swallowed after link in Firefox

In Firefox, when the cursor is at the right boundary of a link mark,
contenteditable inserts new text inside the <a> element. ProseMirror
then rejects the DOM mutation because the link mark has inclusive: false,
causing keystrokes to be silently swallowed. Unlike Chrome, Firefox also
does not fire ProseMirror's handleTextInput callback in this state.

This adds a ProseMirror plugin that intercepts printable character
keydowns at link mark boundaries and programmatically inserts the text
without the link mark, bypassing Firefox's native contenteditable
behavior entirely.

Fixes #1773

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve keystroke input being swallowed before a link in Firefox

Extend the linkBoundaryInput plugin to also handle the left boundary
of links, where the cursor is just before a link (e.g. at the start
of a line). Firefox inserts text inside the <a> element in this case
too, causing ProseMirror to reject the mutation.

Fixes #1748
2026-03-03 17:19:03 +00:00
Philip Okugbe a3fd79dae8 fix: spreadsheets paste (#1982) 2026-03-02 17:37:56 +00:00
Philipinho cf43e2b4fe feat: enhance embed resizer 2026-03-02 02:45:13 +00:00
Philipinho 614baf153b fix: show resize handle if node is selected 2026-03-02 01:57:06 +00:00