share: add allowIndexing (#7896)

* share: add `allowIndexing`

## Ticket
Closes 7486

* i18n: follow existing no-punctuation style
This commit is contained in:
infinite-persistence
2024-11-09 09:28:30 +08:00
committed by GitHub
parent 9747c6ba5d
commit ca17b41c53
10 changed files with 85 additions and 4 deletions
@@ -1,12 +1,13 @@
import debounce from "lodash/debounce";
import isEmpty from "lodash/isEmpty";
import { observer } from "mobx-react";
import { CopyIcon, GlobeIcon, InfoIcon } from "outline-icons";
import { CopyIcon, GlobeIcon, InfoIcon, QuestionMarkIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { toast } from "sonner";
import styled, { useTheme } from "styled-components";
import Flex from "@shared/components/Flex";
import Squircle from "@shared/components/Squircle";
import { s } from "@shared/styles";
import { UrlHelper } from "@shared/utils/UrlHelper";
@@ -50,6 +51,19 @@ function PublicAccess({ document, share, sharedParent }: Props) {
setUrlId(share?.urlId);
}, [share?.urlId]);
const handleIndexingChanged = React.useCallback(
async (event) => {
try {
await share?.save({
allowIndexing: event.currentTarget.checked,
});
} catch (err) {
toast.error(err.message);
}
},
[share]
);
const handlePublishedChange = React.useCallback(
async (event) => {
try {
@@ -175,6 +189,32 @@ function PublicAccess({ document, share, sharedParent }: Props) {
</ShareLinkInput>
) : null}
{share?.published && (
<ListItem
title={
<Flex>
{t("Search engine indexing")}&nbsp;
<Tooltip
content={t(
"Disable this setting to discourage search engines from indexing the page"
)}
>
<QuestionMarkIcon size={18} />
</Tooltip>
</Flex>
}
actions={
<Switch
aria-label={t("Search engine indexing")}
checked={share?.allowIndexing ?? false}
onChange={handleIndexingChanged}
width={26}
height={14}
/>
}
/>
)}
{share?.published && !share.includeChildDocuments ? (
<Text as="p" type="tertiary" size="xsmall">
<StyledInfoIcon size={18} />
+4
View File
@@ -55,6 +55,10 @@ class Share extends Model {
@observable
url: string;
@Field
@observable
allowIndexing: boolean;
/** The user that shared the document. */
@Relation(() => User, { onDelete: "null" })
createdBy: User;
@@ -0,0 +1,15 @@
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn("shares", "allowIndexing", {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: true,
});
},
down: async (queryInterface) => {
await queryInterface.removeColumn("shares", "allowIndexing");
},
};
+4
View File
@@ -184,6 +184,10 @@ class Share extends IdModel<
@Column(DataType.UUID)
documentId: string;
@Default(true)
@Column
allowIndexing: boolean;
revoke(userId: string) {
this.revokedAt = new Date();
this.revokedById = userId;
+1
View File
@@ -12,6 +12,7 @@ export default function presentShare(share: Share, isAdmin = false) {
urlId: share.urlId,
createdBy: presentUser(share.user),
includeChildDocuments: share.includeChildDocuments,
allowIndexing: share.allowIndexing,
lastAccessedAt: share.lastAccessedAt || undefined,
views: share.views || 0,
domain: share.domain,
+1
View File
@@ -51,6 +51,7 @@ export const SharesUpdateSchema = BaseSchema.extend({
id: z.string().uuid(),
includeChildDocuments: z.boolean().optional(),
published: z.boolean().optional(),
allowIndexing: z.boolean().optional(),
urlId: z
.string()
.regex(UrlHelper.SHARE_URL_SLUG_REGEX, {
+6 -1
View File
@@ -230,7 +230,8 @@ router.post(
auth(),
validate(T.SharesUpdateSchema),
async (ctx: APIContext<T.SharesUpdateReq>) => {
const { id, includeChildDocuments, published, urlId } = ctx.input.body;
const { id, includeChildDocuments, published, urlId, allowIndexing } =
ctx.input.body;
const { user } = ctx.state.auth;
authorize(user, "share", user.team);
@@ -257,6 +258,10 @@ router.post(
share.urlId = urlId;
}
if (allowIndexing !== undefined) {
share.allowIndexing = allowIndexing;
}
await share.save();
await Event.createFromContext(ctx, {
name: "shares.update",
+10 -2
View File
@@ -54,6 +54,7 @@ export const renderApp = async (
rootShareId?: string;
isShare?: boolean;
analytics?: Integration<IntegrationType.Analytics>[];
allowIndexing?: boolean;
} = {}
) => {
const {
@@ -61,6 +62,7 @@ export const renderApp = async (
description = "A modern team knowledge base for your internal documentation, product specs, support answers, meeting notes, onboarding, &amp; more…",
canonical = "",
shortcutIcon = `${env.CDN_URL || ""}/images/favicon-32.png`,
allowIndexing = true,
} = options;
if (ctx.request.path === "/realtime/") {
@@ -91,6 +93,10 @@ export const renderApp = async (
</script>
`;
const noIndexTag = allowIndexing
? ""
: '<meta name="robots" content="noindex, nofollow">';
const scriptTags = env.isProduction
? `<script type="module" nonce="${ctx.state.cspNonce}" src="${
env.CDN_URL || ""
@@ -112,6 +118,7 @@ export const renderApp = async (
.replace(/\{lang\}/g, unicodeCLDRtoISO639(env.DEFAULT_LANGUAGE))
.replace(/\{title\}/g, escape(title))
.replace(/\{description\}/g, escape(description))
.replace(/\{noindex\}/g, noIndexTag)
.replace(
/\{manifest-url\}/g,
options.isShare ? "" : "/static/manifest.webmanifest"
@@ -131,8 +138,8 @@ export const renderShare = async (ctx: Context, next: Next) => {
const documentSlug = ctx.params.documentSlug;
// Find the share record if publicly published so that the document title
// can be be returned in the server-rendered HTML. This allows it to appear in
// unfurls with more reliablity
// can be returned in the server-rendered HTML. This allows it to appear in
// unfurls with more reliability
let share, document, team;
let analytics: Integration<IntegrationType.Analytics>[] = [];
@@ -188,5 +195,6 @@ export const renderShare = async (ctx: Context, next: Next) => {
canonical: share
? `${share.canonicalUrl}${documentSlug && document ? document.url : ""}`
: undefined,
allowIndexing: share?.allowIndexing,
});
};
+1
View File
@@ -9,6 +9,7 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="description" content="{description}" />
<meta name="darkreader-lock" />
{noindex}
<link rel="manifest" href="{manifest-url}" />
<link rel="canonical" href="{canonical-url}" data-react-helmet="true" />
{prefetch}
@@ -350,6 +350,8 @@
"Anyone with the link can access because the parent document, <2>{{documentTitle}}</2>, is shared": "Anyone with the link can access because the parent document, <2>{{documentTitle}}</2>, is shared",
"Allow anyone with the link to access": "Allow anyone with the link to access",
"Publish to internet": "Publish to internet",
"Search engine indexing": "Search engine indexing",
"Disable this setting to discourage search engines from indexing the page": "Disable this setting to discourage search engines from indexing the page",
"Nested documents are not shared on the web. Toggle sharing to enable access, this will be the default behavior in the future": "Nested documents are not shared on the web. Toggle sharing to enable access, this will be the default behavior in the future",
"{{ userName }} was added to the document": "{{ userName }} was added to the document",
"{{ count }} people added to the document": "{{ count }} people added to the document",