diff --git a/backend/src/api/model/realm/mod.rs b/backend/src/api/model/realm/mod.rs index 6d2788769..f37e6d9a2 100644 --- a/backend/src/api/model/realm/mod.rs +++ b/backend/src/api/model/realm/mod.rs @@ -155,7 +155,14 @@ impl_from_db!( impl Realm { pub(crate) async fn root(context: &Context) -> ApiResult { - let (selection, mapping) = select!(child_order, moderator_roles, admin_roles); + let (selection, mapping) = select!( + child_order, + moderator_roles, + admin_roles, + name, + name_from_block, + resolved_name: "realms.resolved_name", + ); let row = context.db .query_one(&format!("select {selection} from realms where id = 0"), &[]) .await?; @@ -163,9 +170,9 @@ impl Realm { Ok(Self { key: Key(0), parent_key: None, - plain_name: None, - resolved_name: None, - name_from_block: None, + plain_name: mapping.name.of(&row), + resolved_name: mapping.resolved_name.of(&row), + name_from_block: mapping.name_from_block.of(&row), path_segment: String::new(), full_path: String::new(), index: 0, @@ -322,8 +329,8 @@ impl Realm { } /// The raw information about the name of the realm, showing where the name - /// is coming from and if there is no name, why that is. Is `null` for the - /// root realm, non-null for all other realms. + /// is coming from and if there is no name, why that is. Can be `null` only for the + /// root realm, must be non-null for all other realms. fn name_source(&self) -> Option { if let Some(name) = &self.plain_name { Some(RealmNameSource::Plain(PlainRealmName { diff --git a/backend/src/api/model/realm/mutations.rs b/backend/src/api/model/realm/mutations.rs index e6e248101..91a738e53 100644 --- a/backend/src/api/model/realm/mutations.rs +++ b/backend/src/api/model/realm/mutations.rs @@ -204,7 +204,7 @@ impl Realm { realm.require_moderator_rights(context)?; let db = &context.db; - if name.plain.is_some() == name.block.is_some() { + if !realm.is_main_root() && name.plain.is_some() == name.block.is_some() { return Err(invalid_input!("exactly one of name.block and name.plain has to be set")); } let block = name.block diff --git a/backend/src/db/migrations.rs b/backend/src/db/migrations.rs index 812802c9a..2eb9baeef 100644 --- a/backend/src/db/migrations.rs +++ b/backend/src/db/migrations.rs @@ -371,4 +371,5 @@ static MIGRATIONS: Lazy> = include_migrations![ 36: "playlist-blocks", 37: "redo-search-triggers-and-listed", 38: "event-texts", + 39: "realm-names-constraint-revision", ]; diff --git a/backend/src/db/migrations/39-realm-names-constraint-revision.sql b/backend/src/db/migrations/39-realm-names-constraint-revision.sql new file mode 100644 index 000000000..96d6b65c2 --- /dev/null +++ b/backend/src/db/migrations/39-realm-names-constraint-revision.sql @@ -0,0 +1,10 @@ +-- Adjusts name_source constraint to allow custom names for root, including null. + +alter table realms + drop constraint valid_name_source, + add constraint valid_name_source check ( + -- Root is allowed to have no name. + (id = 0 and name is null or name_from_block is null) + -- All other realms have either a plain or derived name. + or (id <> 0 and (name is null) != (name_from_block is null)) + ); diff --git a/frontend/src/i18n/locales/de.yaml b/frontend/src/i18n/locales/de.yaml index 401bc2b9b..26d2be74a 100644 --- a/frontend/src/i18n/locales/de.yaml +++ b/frontend/src/i18n/locales/de.yaml @@ -483,6 +483,7 @@ manage: name-from-block-description: > Video/Serie-Element von dieser Seite verknüpfen, sodass der Seitenname immer dem Titel des verknüpften Elements entspricht. + no-name: Keinen Namen anzeigen no-blocks: Auf dieser Seite befinden sich keine verknüpfbaren Videos/Serien. rename-failed: Änderung des Namen fehlgeschlagen. no-rename-root: > diff --git a/frontend/src/i18n/locales/en.yaml b/frontend/src/i18n/locales/en.yaml index 3dd91daf6..46a014540 100644 --- a/frontend/src/i18n/locales/en.yaml +++ b/frontend/src/i18n/locales/en.yaml @@ -462,6 +462,7 @@ manage: name-from-block: Derive name from video or series name-from-block-description: > Link a video/series-element from this page: the page name will always be the title of the linked element. + no-name: Omit name no-blocks: There are no linkable video/series on this page. rename-failed: Failed to change the name. no-rename-root: > diff --git a/frontend/src/routes/Realm.tsx b/frontend/src/routes/Realm.tsx index d0faed771..f74a748d7 100644 --- a/frontend/src/routes/Realm.tsx +++ b/frontend/src/routes/Realm.tsx @@ -13,8 +13,7 @@ import { RootLoader } from "../layout/Root"; import { NotFound } from "./NotFound"; import { Nav } from "../layout/Navigation"; import { LinkList, LinkWithIcon } from "../ui"; -import CONFIG from "../config"; -import { characterClass, useTitle, useTranslatedConfig } from "../util"; +import { characterClass, useTitle } from "../util"; import { makeRoute } from "../rauta"; import { MissingRealmName } from "./util"; import { realmBreadcrumbs } from "../util/realm"; @@ -143,17 +142,15 @@ type Props = { const RealmPage: React.FC = ({ realm }) => { const { t } = useTranslation(); - const siteTitle = useTranslatedConfig(CONFIG.siteTitle); const breadcrumbs = realmBreadcrumbs(t, realm.ancestors); - const title = realm.isMainRoot ? siteTitle : realm.name; - useTitle(title, realm.isMainRoot); + useTitle(realm.name); return <> {!realm.isMainRoot && ( } /> )} - {title && ( + {realm.name && (
= ({ realm }) => { columnGap: 12, rowGap: 6, }}> -

{title}

+

{realm.name}

{realm.isUserRealm && }
)} diff --git a/frontend/src/routes/manage/Realm/General.tsx b/frontend/src/routes/manage/Realm/General.tsx index 7d8a99142..4031a8b13 100644 --- a/frontend/src/routes/manage/Realm/General.tsx +++ b/frontend/src/routes/manage/Realm/General.tsx @@ -60,16 +60,10 @@ type Props = { }; export const General: React.FC = ({ fragRef }) => { - const { t } = useTranslation(); const realm = useFragment(fragment, fragRef); - // We do not allow changing the name of the root realm. - if (realm.isMainRoot) { - return

{t("manage.realm.general.no-rename-root")}

; - } - const { nameSource, ...rest } = realm; - if (nameSource == null) { + if (!realm.isMainRoot && nameSource == null) { return bug("name source is null for non-root realm"); } @@ -77,29 +71,29 @@ export const General: React.FC = ({ fragRef }) => { }; type NameFormProps = { - realm: GeneralRealmData$data & { - nameSource: NonNullable; - }; + realm: GeneralRealmData$data; }; export const NameForm: React.FC = ({ realm }) => { type FormData = { name: string | null; block: string | null; - nameSource: "plain-name" | "name-from-block"; + nameSource: "plain-name" | "name-from-block" | "no-name"; }; const initial = { - name: realm.nameSource.__typename === "PlainRealmName" + name: realm.nameSource?.__typename === "PlainRealmName" ? realm.name : null, - block: realm.nameSource.__typename === "RealmNameFromBlock" + block: realm.nameSource?.__typename === "RealmNameFromBlock" // TODO: this breaks when we add new block types ? realm.nameSource.block.id ?? null : null, - nameSource: realm.nameSource.__typename === "PlainRealmName" - ? "plain-name" - : "name-from-block", + nameSource: !realm.nameSource ? "no-name" : ( + realm.nameSource.__typename === "PlainRealmName" + ? "plain-name" + : "name-from-block" + ), } as const; const { t } = useTranslation(); @@ -135,9 +129,11 @@ export const NameForm: React.FC = ({ realm }) => { const block = watch("block"); const nameSource = watch("nameSource"); const isPlain = nameSource === "plain-name"; + const fromBlock = nameSource === "name-from-block"; const canSave = match(nameSource, { "name-from-block": () => block != null && block !== initial.block, "plain-name": () => !!name && name !== initial.name, + "no-name": () => initial.nameSource !== "no-name", }); const suitableBlocks = realm.blocks @@ -223,7 +219,7 @@ export const NameForm: React.FC = ({ realm }) => { - {!isPlain &&
+ {fromBlock &&
{suitableBlocks.length === 0 &&
{t("manage.realm.general.no-blocks")}
} @@ -239,6 +235,12 @@ export const NameForm: React.FC = ({ realm }) => { }
}
+ {realm.isMainRoot &&
+ +
}
diff --git a/frontend/src/schema.graphql b/frontend/src/schema.graphql index 204743161..656cff794 100644 --- a/frontend/src/schema.graphql +++ b/frontend/src/schema.graphql @@ -397,8 +397,8 @@ type Realm implements Node { name: String """ The raw information about the name of the realm, showing where the name - is coming from and if there is no name, why that is. Is `null` for the - root realm, non-null for all other realms. + is coming from and if there is no name, why that is. Can be `null` only for the + root realm, must be non-null for all other realms. """ nameSource: RealmNameSource "Returns `true` if this is the root of the public realm tree (with path = \"/\")." diff --git a/util/dummy-realms.yaml b/util/dummy-realms.yaml index a1b537a65..6487cf08a 100644 --- a/util/dummy-realms.yaml +++ b/util/dummy-realms.yaml @@ -1,7 +1,7 @@ # A realm tree definition for development. Can be imported with # `cargo run -- import-realm-tree dummy-realms.yaml` -name: "" +name: "Tobira Videoportal" path: "" blocks: - text: |