Files
Outpost/Vendor/swift-markdown-engine/Sources/MarkdownEngine/TextView/CommentAnchor.swift
T
Puranjay Savar Mattas b31d49bb7f feat: drafts, publish flow, and full comment threading
Drafts + Publish:
- New Home tab backed by documents.drafts (undocumented request shape
  confirmed from a live network capture, not the OpenAPI spec).
- Reader toolbar menu now shows Publish...  for an unpublished
  document instead of an unconditional Unpublish (which could
  previously be tapped on a draft at all). Publish opens a
  MoveDocumentSheet-style collection/parent picker, pre-filled from
  the draft's own collectionId/parentDocumentId when it already has
  one. Publishing itself is documents.update(publish: true,
  collectionId:) for the collection placement, plus a second
  documents.move call only when a specific parent document was also
  picked (documents.update has no parentDocumentId field).
- New Document defaults to Draft (collectionId/publish both now
  optional on CreateDocumentRequest, previously collectionId was
  required so a draft couldn't be created from this sheet at all).
  Contextual entry points (right-click a collection/document) still
  pre-fill that location, but now show a warning that doing so
  auto-publishes.
- Fixed onDeleted only popping the reader's nav path without telling
  the sidebar to refresh - Delete/Archive/Unpublish/Move all left the
  sidebar showing stale state until an unrelated trigger (the 45s
  poll, navigating away and back) happened to catch it up.

Comments:
- Replies (comments.create with parentCommentId, one level of nesting
  same as Outline's own limit) and emoji reactions
  (comments.add_reaction/remove_reaction, confirmed against Outline's
  server source - not in the spec, and return {success: true} rather
  than the updated comment, so a toggle refetches via comments.info
  for the real post-toggle state) plus a document-level "new comment"
  composer, since replying needs something to reply to.
- Inline anchor markers: a new engine-side mechanism
  (CommentAnchorQuery/CommentAnchorRect/onCommentAnchorRectsChange)
  resolves an anchored comment's anchorText to an on-screen rect via
  the same viewRect utility the code-block copy button uses, kept in
  sync on typing/resize/reflow the same way the code-block and image
  positioning fixes earlier this session are. Renders as a thin blue
  bar next to the commented text; tapping it opens the comments sheet
  scrolled and highlighted to that thread. First-occurrence text
  search only (Outline's API returns no position data, and no
  prefix/suffix on read) - creating new anchored comments from this
  app still isn't supported.
- Toolbar badge: tighter offset so the count doesn't clip past the
  icon, caps at "10+".
2026-08-20 21:31:36 +01:00

45 lines
1.6 KiB
Swift

//
// CommentAnchor.swift
// MarkdownEngine
//
// Lets an embedder ask "where on screen is this text?" for arbitrary
// plain-text needles (e.g. an anchored comment's `anchorText`) that the
// engine has no concept of on its own — it doesn't know what a comment is,
// it just resolves a search term to a rect the same way it already
// resolves code-block token ranges.
//
import SwiftUI
/// A plain-text search term the embedder wants a screen rect for, tagged
/// with an opaque id so results can be matched back to whatever the
/// embedder cares about (e.g. a comment id). Engine does a first-occurrence
/// substring search against the current display text — no fuzzier
/// disambiguation than that, since the engine has no positional data to
/// work from beyond the text itself.
public struct CommentAnchorQuery: Sendable, Equatable {
public let id: String
public let anchorText: String
public init(id: String, anchorText: String) {
self.id = id
self.anchorText = anchorText
}
}
/// Resolved screen rect for one ``CommentAnchorQuery``. Delivered through
/// ``NativeTextViewWrapper/onCommentAnchorRectsChange`` — queries whose
/// `anchorText` isn't found in the current text (deleted, edited past
/// recognition) simply don't appear in the result.
public struct CommentAnchorRect: Identifiable, Sendable {
public let id: String
/// Frame of the matched text in the text view's coordinate space —
/// same coordinate system as ``CodeBlockSelection/rect``.
public let rect: CGRect
public init(id: String, rect: CGRect) {
self.id = id
self.rect = rect
}
}