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+".
47 lines
2.0 KiB
Swift
47 lines
2.0 KiB
Swift
import Foundation
|
|
|
|
/// A comment (or reply, via `parentCommentId`) on a document. Backed by
|
|
/// `comments.*` — `create`/`info`/`update`/`delete`/`list` are in the
|
|
/// vendored OpenAPI spec; `resolve`/`unresolve`/`add_reaction`/
|
|
/// `remove_reaction` are not (confirmed against Outline's own server
|
|
/// source instead — same "spec mirror is incomplete" lesson as
|
|
/// `OutlinePin`). `data` is the comment body as a ProseMirror document,
|
|
/// not plain text — see `JSONValue.plainText()`.
|
|
public struct OutlineComment: Decodable, Identifiable, Sendable {
|
|
public let id: String
|
|
public let data: JSONValue
|
|
public let documentId: String
|
|
public let parentCommentId: String?
|
|
public let createdAt: Date
|
|
public let createdBy: OutlineUser?
|
|
public let updatedAt: Date?
|
|
public let resolvedAt: Date?
|
|
public let resolvedBy: OutlineUser?
|
|
public let reactions: [ReactionSummary]
|
|
/// The document text this comment is anchored to — only populated when
|
|
/// the request set `includeAnchorText: true`; `nil` for a document-level
|
|
/// (non-anchored) comment either way. No prefix/suffix comes back on
|
|
/// read (only accepted as create-time disambiguation input), so
|
|
/// re-finding this text in the rendered document is inherently
|
|
/// best-effort — first occurrence wins, same as Outline's own create
|
|
/// behavior when nothing else disambiguates.
|
|
public let anchorText: String?
|
|
|
|
public var isResolved: Bool { resolvedAt != nil }
|
|
public var bodyText: String {
|
|
data.plainText().trimmingCharacters(in: .whitespacesAndNewlines)
|
|
}
|
|
}
|
|
|
|
/// One emoji's worth of reactions on a comment — grouped by emoji server-side
|
|
/// (`ReactionSummary` in Outline's own source), not one object per reaction.
|
|
public struct ReactionSummary: Codable, Sendable, Equatable {
|
|
public let emoji: String
|
|
public let userIds: [String]
|
|
|
|
public init(emoji: String, userIds: [String]) {
|
|
self.emoji = emoji
|
|
self.userIds = userIds
|
|
}
|
|
}
|