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+".
134 lines
9.1 KiB
Swift
134 lines
9.1 KiB
Swift
import Foundation
|
|
|
|
/// REST-layer boundary. The CRDT/sync layer and editor UI layer depend only on this
|
|
/// protocol, never on `LiveOutlineAPIClient`, so the transport can be swapped or mocked
|
|
/// without touching callers.
|
|
public protocol OutlineAPIClient: Sendable {
|
|
/// Validates the current token and identifies the signed-in user/workspace. Backed by `auth.info`.
|
|
func authInfo() async throws -> OutlineAuthInfo
|
|
|
|
func documentInfo(id: String) async throws -> OutlineDocument
|
|
func listDocuments(collectionId: String?, parentDocumentId: String?, offset: Int, limit: Int) async throws -> [OutlineDocument]
|
|
/// Richer filtering (sort/direction/userId) for the Home page's tabs. See `DocumentsListRequest`.
|
|
func documentsList(_ request: DocumentsListRequest) async throws -> [OutlineDocument]
|
|
/// Documents the current user has recently viewed. Backed by `documents.viewed`.
|
|
func listViewedDocuments(offset: Int, limit: Int) async throws -> [OutlineDocument]
|
|
/// Draft (unpublished) documents belonging to the current user. Backed
|
|
/// by `documents.drafts`.
|
|
func listDrafts(_ request: ListDraftsRequest) async throws -> [OutlineDocument]
|
|
/// Full-text search with snippets/ranking. Backed by `documents.search`.
|
|
func searchDocuments(_ request: DocumentSearchRequest) async throws -> [OutlineDocumentSearchResult]
|
|
/// Title-only search — faster, no snippets. Backed by `documents.search_titles`.
|
|
func searchDocumentTitles(_ request: DocumentSearchTitlesRequest) async throws -> [OutlineDocument]
|
|
func createDocument(_ request: CreateDocumentRequest) async throws -> OutlineDocument
|
|
func updateDocument(_ request: UpdateDocumentRequest) async throws -> OutlineDocument
|
|
func starDocument(_ request: StarDocumentRequest) async throws -> OutlineStar
|
|
func templatizeDocument(_ request: TemplatizeDocumentRequest) async throws -> OutlineTemplate
|
|
func duplicateDocument(_ request: DuplicateDocumentRequest) async throws -> [OutlineDocument]
|
|
func unpublishDocument(_ request: UnpublishDocumentRequest) async throws -> OutlineDocument
|
|
func archiveDocument(id: String) async throws -> OutlineDocument
|
|
func moveDocument(_ request: MoveDocumentRequest) async throws
|
|
func deleteDocument(_ request: DeleteDocumentRequest) async throws
|
|
/// Requires insights to be enabled on the document server-side. Backed by `documents.insights`.
|
|
func documentInsights(_ request: DocumentInsightsRequest) async throws -> [OutlineDocumentInsight]
|
|
/// Backed by `revisions.list` — omits full body content for performance.
|
|
func listRevisions(_ request: ListRevisionsRequest) async throws -> [OutlineRevision]
|
|
/// Returns the document's markdown source directly (not a file operation job). Backed by `documents.export`.
|
|
func exportDocument(id: String) async throws -> String
|
|
func createShare(_ request: CreateShareRequest) async throws -> OutlineShare
|
|
func shareInfo(documentId: String) async throws -> OutlineShare?
|
|
func updateShare(_ request: UpdateShareRequest) async throws -> OutlineShare
|
|
func listShares(_ request: ListSharesRequest) async throws -> [OutlineShare]
|
|
func revokeShare(id: String) async throws
|
|
/// See `OutlinePin` — best-effort, not in the vendored spec.
|
|
func createPin(_ request: CreatePinRequest) async throws -> OutlinePin
|
|
func listPins(_ request: ListPinsRequest) async throws -> [OutlinePin]
|
|
func deletePin(id: String) async throws
|
|
/// See `OutlineSubscription` — best-effort, not in the vendored spec.
|
|
func createSubscription(_ request: CreateSubscriptionRequest) async throws -> OutlineSubscription
|
|
func listSubscriptions(_ request: ListSubscriptionsRequest) async throws -> [OutlineSubscription]
|
|
func deleteSubscription(id: String) async throws
|
|
/// Historical view records, not live presence. Backed by `views.list`.
|
|
func listViews(_ request: ListViewsRequest) async throws -> [OutlineView]
|
|
|
|
/// See `OutlineComment`. `resolve`/`unresolve`/`add_reaction`/
|
|
/// `remove_reaction` are confirmed real against Outline's own server
|
|
/// source but aren't in the vendored spec.
|
|
func listComments(_ request: ListCommentsRequest) async throws -> [OutlineComment]
|
|
func commentInfo(id: String) async throws -> OutlineComment
|
|
func createComment(_ request: CreateCommentRequest) async throws -> OutlineComment
|
|
func resolveComment(id: String) async throws -> OutlineComment
|
|
func unresolveComment(id: String) async throws -> OutlineComment
|
|
/// Reaction endpoints return `{success: true}`, not the updated
|
|
/// comment — callers refetch via `commentInfo` for the fresh
|
|
/// `reactions` array.
|
|
func addReaction(commentId: String, emoji: String) async throws
|
|
func removeReaction(commentId: String, emoji: String) async throws
|
|
|
|
/// See `OutlineMembership`/`OutlineDocumentMember` — `add` is confirmed
|
|
/// from Outline's official docs, the rest are best-effort.
|
|
func addDocumentUser(_ request: AddDocumentUserRequest) async throws -> OutlineMembership
|
|
func removeDocumentUser(_ request: RemoveDocumentUserRequest) async throws
|
|
func documentUsers(_ request: ListDocumentUsersRequest) async throws -> [OutlineDocumentMember]
|
|
/// For searching workspace members to invite. Backed by `users.list`.
|
|
func listUsers(_ request: ListUsersRequest) async throws -> [OutlineUser]
|
|
|
|
func listCollections(offset: Int, limit: Int) async throws -> [OutlineCollection]
|
|
func collectionInfo(id: String) async throws -> OutlineCollection
|
|
func updateCollection(_ request: UpdateCollectionRequest) async throws -> OutlineCollection
|
|
func deleteCollection(id: String) async throws
|
|
/// Kicks off an async export job — this only wraps the trigger, not polling
|
|
/// for completion or downloading the resulting file.
|
|
func exportCollection(_ request: ExportCollectionRequest) async throws -> OutlineFileOperation
|
|
func starCollection(_ request: StarCollectionRequest) async throws -> OutlineStar
|
|
/// Every star across both documents and collections for the current user. Backed by `stars.list`.
|
|
func listStars(_ request: ListStarsRequest) async throws -> [OutlineStar]
|
|
func deleteStar(id: String) async throws
|
|
|
|
func currentUser() async throws -> OutlineUser
|
|
|
|
/// Two-step presigned upload: this requests where/how to upload,
|
|
/// `uploadAttachmentFile` performs the actual multipart POST to that
|
|
/// target. See `OutlineAttachment`/`CreateAttachmentResult`.
|
|
func createAttachment(_ request: CreateAttachmentRequest) async throws -> CreateAttachmentResult
|
|
func uploadAttachmentFile(_ result: CreateAttachmentResult, fileData: Data) async throws
|
|
/// Fetches raw bytes from an authenticated, server-relative GET path —
|
|
/// e.g. `/api/attachments.redirect?id=<uuid>`, the reference Outline's
|
|
/// own editor embeds for uploaded images in document Markdown. Unlike
|
|
/// `post`'s RPC endpoints, this is a GET that 302-redirects to the
|
|
/// actual (often presigned, cross-host) storage URL; `path` is resolved
|
|
/// against the client's base URL, same as `uploadAttachmentFile`'s
|
|
/// `uploadUrl` handling.
|
|
func fetchAuthenticatedFile(path: String) async throws -> Data
|
|
/// Best-effort — matches the shape every other simple `id`-only delete
|
|
/// in this API uses (`pins.delete`, `stars.delete`, …), not confirmed
|
|
/// against a live server specifically for attachments yet.
|
|
func deleteAttachment(id: String) async throws
|
|
/// `users.update`, avatar only. See `UpdateUserAvatarRequest`.
|
|
func updateUserAvatar(_ request: UpdateUserAvatarRequest) async throws -> OutlineUser
|
|
/// `users.update`, name only. See `UpdateUserNameRequest`.
|
|
func updateUserName(_ request: UpdateUserNameRequest) async throws -> OutlineUser
|
|
/// `users.update`, language only. See `UpdateUserLanguageRequest`.
|
|
func updateUserLanguage(_ request: UpdateUserLanguageRequest) async throws -> OutlineUser
|
|
/// `users.update`, preferences only. See `UpdateUserPreferencesRequest`.
|
|
func updateUserPreferences(_ request: UpdateUserPreferencesRequest) async throws -> OutlineUser
|
|
/// Backed by `users.delete` — self-service account deletion, no
|
|
/// confirmation code param confirmed live, matches every other simple
|
|
/// no-body delete in this API.
|
|
func deleteAccount() async throws
|
|
/// `nil` targets every notification event. See `NotificationEventType`.
|
|
func subscribeToNotifications(eventType: NotificationEventType?) async throws -> OutlineUser
|
|
func unsubscribeFromNotifications(eventType: NotificationEventType?) async throws -> OutlineUser
|
|
|
|
/// Settings → API & Access.
|
|
func listApiKeys(_ request: ListApiKeysRequest) async throws -> [OutlineAPIKey]
|
|
/// The returned `OutlineAPIKey.value` is the only time the full
|
|
/// plaintext key is ever available — the caller is responsible for
|
|
/// displaying it once and then discarding it.
|
|
func createApiKey(_ request: CreateApiKeyRequest) async throws -> OutlineAPIKey
|
|
func deleteApiKey(id: String) async throws
|
|
|
|
/// Settings → Installation. Self-hosted server version info.
|
|
func installationInfo() async throws -> OutlineInstallationInfo
|
|
}
|