diff --git a/firestore.rules b/firestore.rules index ec439466a..ffe3db1e5 100644 --- a/firestore.rules +++ b/firestore.rules @@ -149,6 +149,8 @@ service cloud.firestore { function isRequestorViewer(uid) { return + "scopeOverwrite" in resource.data && + !resource.data.scopeOverwrite && "viewersFlat" in resource.data && uid in resource.data.viewersFlat; } diff --git a/src/db/howtos/HowToDatabase.svelte.ts b/src/db/howtos/HowToDatabase.svelte.ts index 92ccea808..71982d232 100644 --- a/src/db/howtos/HowToDatabase.svelte.ts +++ b/src/db/howtos/HowToDatabase.svelte.ts @@ -90,7 +90,9 @@ const HowToSchemaV1 = z.object({ viewers: z.record(z.string(), z.array(z.string())), /** Flat version of viewers, calculated in a firestore function, for firestore rule queries */ viewersFlat: z.array(z.string()), - /** If the user wants to overwrite the scope set by the gallery curator */ + /** True if the user restricts access to the how-to to only those who have direct access to the gallery + * I.e., overwrites the gallery curator "expanding" how-to viewing permissions + */ scopeOverwrite: z.boolean(), /** Locales that the how-to depends on All ISO 639-1 languaage codes, followed by a -, followed by ISO 3166-2 region code: https://en.wikipedia.org/wiki/ISO_3166-2 */ locales: z.array(z.string()), @@ -239,6 +241,10 @@ export default class HowTo { return this.data.social.viewCount; } + getScopeOverwrite() { + return this.data.scopeOverwrite; + } + getData() { return { ...this.data }; } @@ -345,6 +351,7 @@ export class HowToDatabase { locales: string[], reactionTypes: Record, notify: boolean, + overwriteAccessScope: boolean, ): Promise { if (firestore === undefined) return undefined; const user = this.db.getUser()?.uid; @@ -384,7 +391,7 @@ export class HowToDatabase { collaborators: collaborators, viewers: {} as Record, viewersFlat: [] as string[], - scopeOverwrite: false, + scopeOverwrite: overwriteAccessScope, locales: locales, social: newHowToSocial, }; @@ -488,7 +495,10 @@ export class HowToDatabase { or( or(...creatorOrCollaborator), or(...editorGalleryConstraints), - where('viewersFlat', 'array-contains', userId), + and( + where('scopeOverwrite', '==', false), + where('viewersFlat', 'array-contains', userId) + ), ), ); diff --git a/src/locale/en-US.json b/src/locale/en-US.json index c9ef240e1..9bb76d297 100644 --- a/src/locale/en-US.json +++ b/src/locale/en-US.json @@ -4559,10 +4559,29 @@ "placeholder": "Title" }, "titlePlaceholder": "Untitled how-to", - "collaboratorsPrompt": "Add a collaborator, who can edit this how-to", + "collaborators": { + "header": "👥 collaborators", + "explanation": "Collaborators can edit this how-to and view it in drafts." + }, "collaboratorsToggle": { "on": "collapse the collaborators panel", "off": "expand the collaborators panel" + }, + "access": { + "header": "↗ share", + "explanation": "The curator of this gallery can let creators and curators of other galleries they curate view this how-to. If you don't want them to be able to view it, you can restrict who can view this how-to to only the curators and creators in this gallery." + }, + "accessToggle": { + "on": "collapse the access settings panel", + "off": "expand the access settings panel" + }, + "accessMode": { + "label": "restrict who can view this how-to", + "labels": ["restricted", "expanded"], + "tips": [ + "only creators in this gallery can view", + "creators and curators in any gallery the curator curates can view" + ] } }, "viewer": { diff --git a/src/routes/gallery/[galleryid]/howto/HowToForm.svelte b/src/routes/gallery/[galleryid]/howto/HowToForm.svelte index 1a91cbe12..016ea522b 100644 --- a/src/routes/gallery/[galleryid]/howto/HowToForm.svelte +++ b/src/routes/gallery/[galleryid]/howto/HowToForm.svelte @@ -6,7 +6,6 @@ import MarkupHTMLView from '@components/concepts/MarkupHTMLView.svelte'; import { getUser } from '@components/project/Contexts'; import CreatorList from '@components/project/CreatorList.svelte'; - import { TileKind } from '@components/project/Tile'; import Button from '@components/widgets/Button.svelte'; import ConfirmButton from '@components/widgets/ConfirmButton.svelte'; import Dialog from '@components/widgets/Dialog.svelte'; @@ -21,7 +20,6 @@ import type Gallery from '@db/galleries/Gallery'; import HowTo from '@db/howtos/HowToDatabase.svelte'; import type { ButtonText } from '@locale/UITexts'; - import { COLLABORATE_SYMBOL } from '@parser/Symbols'; import type { Snippet } from 'svelte'; import type { SvelteMap } from 'svelte/reactivity'; import { movePermitted } from './HowToMovement'; @@ -232,6 +230,7 @@ ['en-US'], gallery ? gallery.getHowToReactions() : {}, notify, + overwriteAccess, ); // pan the camera to the new how-to @@ -261,6 +260,7 @@ xcoord: writeX, ycoord: writeY, collaborators: allCollaborators, + scopeOverwrite: overwriteAccess, social: { ...howTo.getSocial(), notifySubscribers: notify, @@ -433,6 +433,12 @@ }); } } + + // allowing the user to overwrite the gallery's expanded viewing permissions + let accessToggle: boolean = $state(false); + let overwriteAccess: boolean = $derived( + howTo ? howTo.getScopeOverwrite() : false, + );