fix: Template variables are not applied on client (#8044)

* fix: Template variables are not applied on client

* test
This commit is contained in:
Tom Moor
2024-11-30 10:13:44 -05:00
committed by GitHub
parent 514a724d9d
commit d8fbe35455
9 changed files with 83 additions and 92 deletions
+2 -2
View File
@@ -3,6 +3,7 @@ import { DocumentIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { MenuButton, useMenuState } from "reakit/Menu";
import { TextHelper } from "@shared/utils/TextHelper";
import Document from "~/models/Document";
import Button from "~/components/Button";
import ContextMenu from "~/components/ContextMenu";
@@ -11,7 +12,6 @@ import Icon from "~/components/Icon";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import { MenuItem } from "~/types";
import { replaceTitleVariables } from "~/utils/date";
type Props = {
document: Document;
@@ -29,7 +29,7 @@ function TemplatesMenu({ onSelectTemplate, document }: Props) {
const templateToMenuItem = React.useCallback(
(tmpl: Document): MenuItem => ({
type: "button",
title: replaceTitleVariables(tmpl.titleWithDefault, user),
title: TextHelper.replaceTemplateVariables(tmpl.titleWithDefault, user),
icon: tmpl.icon ? (
<Icon value={tmpl.icon} color={tmpl.color ?? undefined} />
) : (
+10 -4
View File
@@ -26,6 +26,7 @@ import {
TeamPreference,
} from "@shared/types";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import { TextHelper } from "@shared/utils/TextHelper";
import { parseDomain } from "@shared/utils/domains";
import { determineIconType } from "@shared/utils/icon";
import RootStore from "~/stores/RootStore";
@@ -44,7 +45,6 @@ import withStores from "~/components/withStores";
import type { Editor as TEditor } from "~/editor";
import { SearchResult } from "~/editor/components/LinkEditor";
import { client } from "~/utils/ApiClient";
import { replaceTitleVariables } from "~/utils/date";
import { emojiToUrl } from "~/utils/emoji";
import { isModKey } from "~/utils/keyboard";
@@ -151,7 +151,13 @@ class DocumentScene extends React.Component<Props> {
}
const { view, schema } = editorRef;
const doc = Node.fromJSON(schema, template.data);
const doc = Node.fromJSON(
schema,
ProsemirrorHelper.replaceTemplateVariables(
template.data,
this.props.auth.user!
)
);
if (doc) {
view.dispatch(
@@ -168,9 +174,9 @@ class DocumentScene extends React.Component<Props> {
}
if (!this.title) {
const title = replaceTitleVariables(
const title = TextHelper.replaceTemplateVariables(
template.title,
this.props.auth.user || undefined
this.props.auth.user!
);
this.title = title;
this.props.document.title = title;
+1 -28
View File
@@ -10,16 +10,7 @@ import {
isPast,
} from "date-fns";
import { TFunction } from "i18next";
import startCase from "lodash/startCase";
import {
getCurrentDateAsString,
getCurrentDateTimeAsString,
getCurrentTimeAsString,
unicodeCLDRtoBCP47,
dateLocale,
locales,
} from "@shared/utils/date";
import User from "~/models/User";
import { dateLocale, locales } from "@shared/utils/date";
export function dateToHeading(
dateTime: string,
@@ -121,21 +112,3 @@ export function dateToExpiry(
date: formatDate(date, "MMM dd, yyyy", { locale }),
});
}
/**
* Replaces template variables in the given text with the current date and time.
*
* @param text The text to replace the variables in
* @param user The user to get the language/locale from
* @returns The text with the variables replaced
*/
export function replaceTitleVariables(text: string, user?: User) {
const locales = user?.language
? unicodeCLDRtoBCP47(user.language)
: undefined;
return text
.replace("{date}", startCase(getCurrentDateAsString(locales)))
.replace("{time}", startCase(getCurrentTimeAsString(locales)))
.replace("{datetime}", startCase(getCurrentDateTimeAsString(locales)));
}
+2 -2
View File
@@ -1,8 +1,8 @@
import { Optional } from "utility-types";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import { TextHelper } from "@shared/utils/TextHelper";
import { Document, Event, User } from "@server/models";
import { DocumentHelper } from "@server/models/helpers/DocumentHelper";
import { ProsemirrorHelper } from "@server/models/helpers/ProsemirrorHelper";
import { TextHelper } from "@server/models/helpers/TextHelper";
import { APIContext } from "@server/types";
type Props = Optional<
@@ -21,9 +21,7 @@ import { schema, parser } from "@server/editor";
import Logger from "@server/logging/Logger";
import { trace } from "@server/logging/tracing";
import Attachment from "@server/models/Attachment";
import User from "@server/models/User";
import FileStorage from "@server/storage/files";
import { TextHelper } from "./TextHelper";
export type HTMLOptions = {
/** A title, if it should be included */
@@ -264,29 +262,6 @@ export class ProsemirrorHelper {
return removeMarksInner(json);
}
/**
* Replaces all template variables in the node.
*
* @param data The ProsemirrorData object to replace variables in
* @param user The user to use for replacing variables
* @returns The content with variables replaced
*/
static replaceTemplateVariables(data: ProsemirrorData, user: User) {
function replace(node: ProsemirrorData) {
if (node.type === "text" && node.text) {
node.text = TextHelper.replaceTemplateVariables(node.text, user);
}
if (node.content) {
node.content.forEach(replace);
}
return node;
}
return replace(data);
}
static async replaceInternalUrls(
doc: Node | ProsemirrorData,
basePath: string
-26
View File
@@ -1,13 +1,6 @@
import chunk from "lodash/chunk";
import escapeRegExp from "lodash/escapeRegExp";
import startCase from "lodash/startCase";
import { AttachmentPreset } from "@shared/types";
import {
getCurrentDateAsString,
getCurrentDateTimeAsString,
getCurrentTimeAsString,
unicodeCLDRtoBCP47,
} from "@shared/utils/date";
import attachmentCreator from "@server/commands/attachmentCreator";
import env from "@server/env";
import { trace } from "@server/logging/tracing";
@@ -19,25 +12,6 @@ import parseImages from "@server/utils/parseImages";
@trace()
export class TextHelper {
/**
* Replaces template variables in the given text with the current date and time.
*
* @param text The text to replace the variables in
* @param user The user to get the language/locale from
* @returns The text with the variables replaced
*/
static replaceTemplateVariables(text: string, user: User) {
const locales = user.language
? unicodeCLDRtoBCP47(user.language)
: undefined;
return text
.replace(/{date}/g, startCase(getCurrentDateAsString(locales)))
.replace(/{time}/g, startCase(getCurrentTimeAsString(locales)))
.replace(/{datetime}/g, startCase(getCurrentDateTimeAsString(locales)))
.replace(/{author}/g, user.name);
}
/**
* Converts attachment urls in documents to signed equivalents that allow
* direct access without a session cookie
+29
View File
@@ -2,6 +2,7 @@ import { Node, Schema } from "prosemirror-model";
import headingToSlug from "../editor/lib/headingToSlug";
import textBetween from "../editor/lib/textBetween";
import { ProsemirrorData } from "../types";
import { TextHelper } from "./TextHelper";
export type Heading = {
/* The heading in plain text */
@@ -28,6 +29,11 @@ export type Task = {
completed: boolean;
};
interface User {
name: string;
language: string | null;
}
export const attachmentRedirectRegex =
/\/api\/attachments\.redirect\?id=(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;
@@ -307,4 +313,27 @@ export class ProsemirrorHelper {
});
return headings;
}
/**
* Replaces all template variables in the node.
*
* @param data The ProsemirrorData object to replace variables in
* @param user The user to use for replacing variables
* @returns The content with variables replaced
*/
static replaceTemplateVariables(data: ProsemirrorData, user: User) {
function replace(node: ProsemirrorData) {
if (node.type === "text" && node.text) {
node.text = TextHelper.replaceTemplateVariables(node.text, user);
}
if (node.content) {
node.content.forEach(replace);
}
return node;
}
return replace(data);
}
}
@@ -1,4 +1,3 @@
import { buildUser } from "@server/test/factories";
import { TextHelper } from "./TextHelper";
describe("TextHelper", () => {
@@ -12,18 +11,21 @@ describe("TextHelper", () => {
});
describe("replaceTemplateVariables", () => {
const user = {
name: "John Doe",
language: "en",
};
it("should replace {time} with current time", async () => {
const user = await buildUser();
const result = TextHelper.replaceTemplateVariables("Hello {time}", user);
expect(result).toBe("Hello 12 00 AM");
expect(result).toBe("Hello 12:00 AM");
});
it("should replace {date} with current date", async () => {
const user = await buildUser();
const result = TextHelper.replaceTemplateVariables("Hello {date}", user);
expect(result).toBe("Hello January 1 2021");
expect(result).toBe("Hello January 1, 2021");
});
});
});
+32
View File
@@ -0,0 +1,32 @@
import {
getCurrentDateAsString,
getCurrentDateTimeAsString,
getCurrentTimeAsString,
unicodeCLDRtoBCP47,
} from "./date";
interface User {
name: string;
language: string | null;
}
export class TextHelper {
/**
* Replaces template variables in the given text with the current date and time.
*
* @param text The text to replace the variables in
* @param user The user to get the language/locale from
* @returns The text with the variables replaced
*/
static replaceTemplateVariables(text: string, user: User) {
const locales = user.language
? unicodeCLDRtoBCP47(user.language)
: undefined;
return text
.replace(/{date}/g, getCurrentDateAsString(locales))
.replace(/{time}/g, getCurrentTimeAsString(locales))
.replace(/{datetime}/g, getCurrentDateTimeAsString(locales))
.replace(/{author}/g, user.name);
}
}