Files
Copilot 7ed41eadc6 Add per-share branding: title and logoUrl overrides (#12003)
* feat: add title and logoUrl to Share model

Agent-Logs-Url: https://github.com/outline/outline/sessions/9bc9d438-6892-4903-9d32-6b6868f4fd97

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* fix: use STRING(4096) for logoUrl column in migration

Agent-Logs-Url: https://github.com/outline/outline/sessions/9bc9d438-6892-4903-9d32-6b6868f4fd97

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* feat: use share title and logoUrl to override team branding on shared page

Agent-Logs-Url: https://github.com/outline/outline/sessions/854d6d22-e80b-4673-b3b2-0f9cf43a3246

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* refactor: use ShareValidation class constants for title/logoUrl max lengths

Agent-Logs-Url: https://github.com/outline/outline/sessions/ea462d6a-d4d3-4882-ab8e-88060bf64877

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* fix: use ShareValidation constants in @Length msg template literals

Agent-Logs-Url: https://github.com/outline/outline/sessions/694116c2-47e8-4001-a103-c8a62c7ac71e

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* feat: add display settings popover with custom title and icon for shares

Move share toggles (search indexing, email subscriptions, show last
modified, show TOC) into a popover triggered by a settings cog. The
popover also includes inputs for a custom site title and icon upload
to override team branding on shared pages. Rename logoUrl to iconUrl,
loosen URL validation to allow relative attachment paths, and surface
the popover in the shared page header for users with edit permission.

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

* styling

* Display branding on single shared pages

* Review comments

* refactor

* PR feedback

* Lose 'Remove icon' button

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 21:23:13 -04:00

137 lines
2.6 KiB
TypeScript

import { computed, observable } from "mobx";
import type { NavigationNode, PublicTeam } from "@shared/types";
import type SharesStore from "~/stores/SharesStore";
import Collection from "./Collection";
import Document from "./Document";
import User from "./User";
import Model from "./base/Model";
import Field from "./decorators/Field";
import Relation from "./decorators/Relation";
import type { Searchable } from "./interfaces/Searchable";
class Share extends Model implements Searchable {
static modelName = "Share";
store: SharesStore;
@Field
@observable
published: boolean;
@Field
@observable
includeChildDocuments: boolean;
/** The document ID that is shared. */
@Field
@observable
documentId: string;
/** The document that is shared. */
@Relation(() => Document, { onDelete: "cascade" })
document: Document;
/** The collection ID that is shared. */
@Field
@observable
collectionId: string;
/** The collection that is shared. */
@Relation(() => Collection, { onDelete: "cascade" })
collection: Collection;
@Field
@observable
urlId: string;
@Field
@observable
domain: string;
@observable
sourceTitle: string;
@observable
sourcePath: string;
@observable
documentTitle: string;
@observable
documentUrl: string;
@observable
lastAccessedAt: string | null | undefined;
@observable
url: string;
@Field
@observable
allowIndexing: boolean;
@Field
@observable
allowSubscriptions: boolean;
@Field
@observable
showLastUpdated: boolean;
@Field
@observable
showTOC: boolean;
/** Custom branding title to display on the shared page, supersedes team name. */
@Field
@observable
title: string | null;
/** Custom branding icon URL to display on the shared page, supersedes team avatar. */
@Field
@observable
iconUrl: string | null;
@observable
views: number;
/** The user that shared the document. */
@Relation(() => User, { onDelete: "null" })
createdBy: User;
@computed
get sourcePathWithFallback(): string {
return this.sourcePath ?? this.documentUrl;
}
@computed
get searchContent(): string[] {
return [this.sourceTitle ?? this.documentTitle];
}
@computed
get searchSuppressed(): boolean {
return false;
}
@computed
get sharedCache() {
return (
this.store.sharedCache.get(this.id) ??
this.store.sharedCache.get(this.urlId)
);
}
@computed
get team(): PublicTeam | undefined {
return this.sharedCache?.team;
}
@computed
get tree(): NavigationNode | undefined {
return this.sharedCache?.sharedTree ?? undefined;
}
}
export default Share;