From: Matthias Schmidt Date: Fri, 9 Apr 2021 11:54:30 +0000 (+0200) Subject: Add `Ui/Message/Share/Dialog` as reusable share dialog module (#4108) X-Git-Tag: 5.4.0_Alpha_1~111 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=c4a8692233513b171025ec5b4c4a703b7690e248;p=GitHub%2FWoltLab%2FWCF.git Add `Ui/Message/Share/Dialog` as reusable share dialog module (#4108) * Add `Ui/Message/Share/Dialog` as reusable share dialog module Close #4026 * Apply suggestions from code review * Apply suggestions from code review --- diff --git a/com.woltlab.wcf/templates/article.tpl b/com.woltlab.wcf/templates/article.tpl index 536662ae5b..93f0acb7c0 100644 --- a/com.woltlab.wcf/templates/article.tpl +++ b/com.woltlab.wcf/templates/article.tpl @@ -180,9 +180,13 @@ {/if} -
-{if ENABLE_SHARE_BUTTONS} - {capture assign='footerBoxes'} -
-

{lang}wcf.message.share{/lang}

- -
- {include file='shareButtons'} -
-
- {/capture} -{/if} - -{if $page->showShareButtons()} - {capture assign='footerBoxes'} -
-

{lang}wcf.message.share{/lang}

- -
- {include file='shareButtons'} -
-
- {/capture} -{/if} - {include file='footer'} diff --git a/com.woltlab.wcf/templates/headIncludeJavaScript.tpl b/com.woltlab.wcf/templates/headIncludeJavaScript.tpl index 9b70fc5a07..64560c01dc 100644 --- a/com.woltlab.wcf/templates/headIncludeJavaScript.tpl +++ b/com.woltlab.wcf/templates/headIncludeJavaScript.tpl @@ -133,7 +133,21 @@ window.addEventListener('pageshow', function(event) { 'wcf.date.datePicker.hour': '{jslang}wcf.date.datePicker.hour{/jslang}', 'wcf.date.datePicker.minute': '{jslang}wcf.date.datePicker.minute{/jslang}', 'wcf.global.form.password.button.hide': '{jslang}wcf.global.form.password.button.hide{/jslang}', - 'wcf.global.form.password.button.show': '{jslang}wcf.global.form.password.button.show{/jslang}' + 'wcf.global.form.password.button.show': '{jslang}wcf.global.form.password.button.show{/jslang}', + 'wcf.message.share': '{jslang}wcf.message.share{/jslang}', + 'wcf.message.share.facebook': '{jslang}wcf.message.share.facebook{/jslang}', + 'wcf.message.share.twitter': '{jslang}wcf.message.share.twitter{/jslang}', + 'wcf.message.share.reddit': '{jslang}wcf.message.share.reddit{/jslang}', + 'wcf.message.share.whatsApp': '{jslang}wcf.message.share.whatsApp{/jslang}', + 'wcf.message.share.linkedIn': '{jslang}wcf.message.share.linkedIn{/jslang}', + 'wcf.message.share.pinterest': '{jslang}wcf.message.share.pinterest{/jslang}', + 'wcf.message.share.xing': '{jslang}wcf.message.share.xing{/jslang}', + 'wcf.message.share.permalink': '{jslang}wcf.message.share.permalink{/jslang}', + 'wcf.message.share.permalink.bbcode': '{jslang}wcf.message.share.permalink.bbcode{/jslang}', + 'wcf.message.share.permalink.html': '{jslang}wcf.message.share.permalink.html{/jslang}', + 'wcf.message.share.socialMedia': '{jslang}wcf.message.share.socialMedia{/jslang}', + 'wcf.message.share.copy': '{jslang}wcf.message.share.copy{/jslang}', + 'wcf.message.share.copy.success': '{jslang}wcf.message.share.copy.success{/jslang}' {if MODULE_LIKE} ,'wcf.like.button.like': '{jslang}wcf.like.button.like{/jslang}', 'wcf.like.button.dislike': '{jslang}wcf.like.button.dislike{/jslang}', @@ -153,6 +167,9 @@ window.addEventListener('pageshow', function(event) { }, enableUserPopover: {if $__wcf->getSession()->getPermission('user.profile.canViewUserProfile')}true{else}false{/if}, executeCronjobs: {if $executeCronjobs}true{else}false{/if}, + {if ENABLE_SHARE_BUTTONS} + shareButtonProviders: [{implode from="\n"|explode:SHARE_BUTTONS_PROVIDERS item=shareButtonProvider}'{$shareButtonProvider}'{/implode}], + {/if} styleChanger: {if $__wcf->getStyleHandler()->showStyleChanger()}true{else}false{/if} }); diff --git a/ts/WoltLabSuite/Core/BootstrapFrontend.ts b/ts/WoltLabSuite/Core/BootstrapFrontend.ts index 658626f7e1..d58f13ce66 100644 --- a/ts/WoltLabSuite/Core/BootstrapFrontend.ts +++ b/ts/WoltLabSuite/Core/BootstrapFrontend.ts @@ -15,6 +15,8 @@ import * as UiUserIgnore from "./Ui/User/Ignore"; import * as UiPageHeaderMenu from "./Ui/Page/Header/Menu"; import * as UiMessageUserConsent from "./Ui/Message/UserConsent"; import * as Ajax from "./Ajax"; +import * as UiMessageShareDialog from "./Ui/Message/Share/Dialog"; +import * as UiMessageShareProviders from "./Ui/Message/Share/Providers"; interface BoostrapOptions { backgroundQueue: { @@ -23,6 +25,7 @@ interface BoostrapOptions { }; enableUserPopover: boolean; executeCronjobs: boolean; + shareButtonProviders?: string[]; styleChanger: boolean; } @@ -85,4 +88,7 @@ export function setup(options: BoostrapOptions): void { } UiMessageUserConsent.init(); + + UiMessageShareProviders.enableShareProviders(options.shareButtonProviders || []); + UiMessageShareDialog.setup(); } diff --git a/ts/WoltLabSuite/Core/Ui/Message/Share/Dialog.ts b/ts/WoltLabSuite/Core/Ui/Message/Share/Dialog.ts new file mode 100644 index 0000000000..16e2f7734c --- /dev/null +++ b/ts/WoltLabSuite/Core/Ui/Message/Share/Dialog.ts @@ -0,0 +1,170 @@ +/** + * Shows the share dialog when clicking on the share button of a message. + * + * @author Matthias Schmidt + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Message/Share/Dialog + */ + +import UiDialog from "../../Dialog"; +import DomUtil from "../../../Dom/Util"; +import * as DomTraverse from "../../../Dom/Traverse"; +import * as Language from "../../../Language"; +import * as Clipboard from "../../../Clipboard"; +import * as UiNotification from "../../Notification"; +import * as StringUtil from "../../../StringUtil"; +import DomChangeListener from "../../../Dom/Change/Listener"; +import * as UiMessageShare from "../Share"; +import * as UiMessageShareProviders from "./Providers"; + +const shareButtons = new WeakSet(); + +/** + * Copies the contents of one of the share dialog's input elements to the clipboard. + */ +async function copy(event: Event): Promise { + event.preventDefault(); + + const target = event.currentTarget as HTMLElement; + const input = DomTraverse.prevBySel(target, 'input[type="text"]') as HTMLInputElement; + + await Clipboard.copyTextToClipboard(input.value); + + UiNotification.show(Language.get("wcf.message.share.copy.success")); +} + +/** + * Returns all of the dialog elements shown in the dialog. + */ +function getDialogElements(shareButton: HTMLElement): string { + let dialogOptions = ""; + + let permalink = ""; + if (shareButton instanceof HTMLAnchorElement && shareButton.href) { + permalink = shareButton.href; + } + + if (permalink) { + dialogOptions += getDialogElement("wcf.message.share.permalink", permalink); + } + if (shareButton.dataset.bbcode) { + dialogOptions += getDialogElement("wcf.message.share.permalink.bbcode", shareButton.dataset.bbcode); + } + if (permalink && shareButton.dataset.linkTitle) { + if (!shareButton.dataset.bbcode) { + dialogOptions += getDialogElement( + "wcf.message.share.permalink.bbcode", + `[url='${permalink}']${shareButton.dataset.linkTitle}[/url]`, + ); + } + + dialogOptions += getDialogElement( + "wcf.message.share.permalink.html", + `
${StringUtil.escapeHTML(shareButton.dataset.linkTitle)}`, + ); + } + + return dialogOptions; +} + +/** + * Returns a dialog element with the given label and input field value. + */ +function getDialogElement(label: string, value: string): string { + return ` +
+
${Language.get(label)}
+
+
+ + +
+
+
+ `; +} + +function getProviderButtons(): string { + const providerButtons = Array.from(UiMessageShareProviders.getEnabledProviders()) + .map((provider) => { + const label = Language.get(provider.label); + + return ` +
  • + + + ${label} + +
  • + `; + }) + .join("\n"); + + if (providerButtons) { + return ``; + } + + return ""; +} + +/** + * Opens the share dialog after clicking on the share button. + */ +function openDialog(event: MouseEvent): void { + event.preventDefault(); + + const target = event.currentTarget as HTMLElement; + const dialogId = `shareContentDialog_${DomUtil.identify(target)}`; + if (!UiDialog.getDialog(dialogId)) { + const providerButtons = getProviderButtons(); + let providerElement = ""; + if (providerButtons) { + providerElement = ` +
    +
    ${Language.get("wcf.message.share.socialMedia")}
    +
    ${providerButtons}
    +
    + `; + } + + const dialogContent = ` +
    + ${getDialogElements(target)} + ${providerElement} +
    + `; + + const dialogData = UiDialog.openStatic(dialogId, dialogContent, { + title: Language.get("wcf.message.share"), + }); + + dialogData.content.style.maxWidth = "600px"; + dialogData.dialog + .querySelectorAll(".shareDialogCopyButton") + .forEach((el) => el.addEventListener("click", (ev) => copy(ev))); + + if (providerButtons) { + UiMessageShare.init(); + } + } else { + UiDialog.openStatic(dialogId, null); + } +} + +function registerButtons(): void { + document.querySelectorAll(".shareButton").forEach((shareButton: HTMLElement) => { + if (!shareButtons.has(shareButton)) { + shareButton.addEventListener("click", (ev) => openDialog(ev)); + + shareButtons.add(shareButton); + } + }); +} + +export function setup(): void { + registerButtons(); + DomChangeListener.add("WoltLabSuite/Core/Ui/Message/Share/Dialog", () => registerButtons()); +} diff --git a/ts/WoltLabSuite/Core/Ui/Message/Share/Providers.ts b/ts/WoltLabSuite/Core/Ui/Message/Share/Providers.ts new file mode 100644 index 0000000000..0a4a1ae1e7 --- /dev/null +++ b/ts/WoltLabSuite/Core/Ui/Message/Share/Providers.ts @@ -0,0 +1,98 @@ +/** + * Manages the share providers shown in the share dialogs. + * + * @author Matthias Schmidt + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Message/Share/Providers + */ + +export interface ShareProvider { + cssClass: string; + iconClassName: string; + label: string; +} + +const enabledProviders = new Set(); +const providers = new Map([ + [ + "Facebook", + { + cssClass: "jsShareFacebook", + iconClassName: "fa-facebook-official", + label: "wcf.message.share.facebook", + }, + ], + [ + "Twitter", + { + cssClass: "jsShareTwitter", + iconClassName: "fa-twitter", + label: "wcf.message.share.twitter", + }, + ], + [ + "Reddit", + { + cssClass: "jsShareReddit", + iconClassName: "fa-reddit", + label: "wcf.message.share.reddit", + }, + ], + [ + "WhatsApp", + { + cssClass: "jsShareWhatsApp", + iconClassName: "fa-whatsapp", + label: "wcf.message.share.whatsApp", + }, + ], + [ + "LinkedIn", + { + cssClass: "jsShareLinkedIn", + iconClassName: "fa-linkedin", + label: "wcf.message.share.linkedIn", + }, + ], + [ + "Pinterest", + { + cssClass: "jsSharePinterest", + iconClassName: "fa-pinterest-p", + label: "wcf.message.share.pinterest", + }, + ], + [ + "XING", + { + cssClass: "jsShareXing", + iconClassName: "fa-xing", + label: "wcf.message.share.xing", + }, + ], +]); + +export function addShareProvider(providerName: string, provider: ShareProvider): void { + if (providers.has(providerName)) { + throw new Error(`A share provider with name "${providerName}" already exists.`); + } + + providers.set(providerName, provider); +} + +export function enableShareProviders(providerNames: string[]): void { + providerNames.forEach((providerName) => { + if (providers.has(providerName)) { + enabledProviders.add(providers.get(providerName)!); + } + }); +} + +export function getProviders(): ReadonlyMap { + return providers; +} + +export function getEnabledProviders(): ReadonlySet { + return enabledProviders; +} diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js b/wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js index 381c737dc7..b5623a30e2 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js @@ -6,7 +6,7 @@ * @license GNU Lesser General Public License * @module WoltLabSuite/Core/BootstrapFrontend */ -define(["require", "exports", "tslib", "./BackgroundQueue", "./Bootstrap", "./Controller/Style/Changer", "./Controller/Popover", "./Ui/User/Ignore", "./Ui/Page/Header/Menu", "./Ui/Message/UserConsent", "./Ajax"], function (require, exports, tslib_1, BackgroundQueue, Bootstrap, ControllerStyleChanger, ControllerPopover, UiUserIgnore, UiPageHeaderMenu, UiMessageUserConsent, Ajax) { +define(["require", "exports", "tslib", "./BackgroundQueue", "./Bootstrap", "./Controller/Style/Changer", "./Controller/Popover", "./Ui/User/Ignore", "./Ui/Page/Header/Menu", "./Ui/Message/UserConsent", "./Ajax", "./Ui/Message/Share/Dialog", "./Ui/Message/Share/Providers"], function (require, exports, tslib_1, BackgroundQueue, Bootstrap, ControllerStyleChanger, ControllerPopover, UiUserIgnore, UiPageHeaderMenu, UiMessageUserConsent, Ajax, UiMessageShareDialog, UiMessageShareProviders) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.setup = void 0; @@ -18,6 +18,8 @@ define(["require", "exports", "tslib", "./BackgroundQueue", "./Bootstrap", "./Co UiPageHeaderMenu = tslib_1.__importStar(UiPageHeaderMenu); UiMessageUserConsent = tslib_1.__importStar(UiMessageUserConsent); Ajax = tslib_1.__importStar(Ajax); + UiMessageShareDialog = tslib_1.__importStar(UiMessageShareDialog); + UiMessageShareProviders = tslib_1.__importStar(UiMessageShareProviders); /** * Initializes user profile popover. */ @@ -68,6 +70,8 @@ define(["require", "exports", "tslib", "./BackgroundQueue", "./Bootstrap", "./Co UiUserIgnore.init(); } UiMessageUserConsent.init(); + UiMessageShareProviders.enableShareProviders(options.shareButtonProviders || []); + UiMessageShareDialog.setup(); } exports.setup = setup; }); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Share/Dialog.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Share/Dialog.js new file mode 100644 index 0000000000..d70c839f7d --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Share/Dialog.js @@ -0,0 +1,144 @@ +/** + * Shows the share dialog when clicking on the share button of a message. + * + * @author Matthias Schmidt + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Message/Share/Dialog + */ +define(["require", "exports", "tslib", "../../Dialog", "../../../Dom/Util", "../../../Dom/Traverse", "../../../Language", "../../../Clipboard", "../../Notification", "../../../StringUtil", "../../../Dom/Change/Listener", "../Share", "./Providers"], function (require, exports, tslib_1, Dialog_1, Util_1, DomTraverse, Language, Clipboard, UiNotification, StringUtil, Listener_1, UiMessageShare, UiMessageShareProviders) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.setup = void 0; + Dialog_1 = tslib_1.__importDefault(Dialog_1); + Util_1 = tslib_1.__importDefault(Util_1); + DomTraverse = tslib_1.__importStar(DomTraverse); + Language = tslib_1.__importStar(Language); + Clipboard = tslib_1.__importStar(Clipboard); + UiNotification = tslib_1.__importStar(UiNotification); + StringUtil = tslib_1.__importStar(StringUtil); + Listener_1 = tslib_1.__importDefault(Listener_1); + UiMessageShare = tslib_1.__importStar(UiMessageShare); + UiMessageShareProviders = tslib_1.__importStar(UiMessageShareProviders); + const shareButtons = new WeakSet(); + /** + * Copies the contents of one of the share dialog's input elements to the clipboard. + */ + async function copy(event) { + event.preventDefault(); + const target = event.currentTarget; + const input = DomTraverse.prevBySel(target, 'input[type="text"]'); + await Clipboard.copyTextToClipboard(input.value); + UiNotification.show(Language.get("wcf.message.share.copy.success")); + } + /** + * Returns all of the dialog elements shown in the dialog. + */ + function getDialogElements(shareButton) { + let dialogOptions = ""; + let permalink = ""; + if (shareButton instanceof HTMLAnchorElement && shareButton.href) { + permalink = shareButton.href; + } + if (permalink) { + dialogOptions += getDialogElement("wcf.message.share.permalink", permalink); + } + if (shareButton.dataset.bbcode) { + dialogOptions += getDialogElement("wcf.message.share.permalink.bbcode", shareButton.dataset.bbcode); + } + if (permalink && shareButton.dataset.linkTitle) { + if (!shareButton.dataset.bbcode) { + dialogOptions += getDialogElement("wcf.message.share.permalink.bbcode", `[url='${permalink}']${shareButton.dataset.linkTitle}[/url]`); + } + dialogOptions += getDialogElement("wcf.message.share.permalink.html", `${StringUtil.escapeHTML(shareButton.dataset.linkTitle)}`); + } + return dialogOptions; + } + /** + * Returns a dialog element with the given label and input field value. + */ + function getDialogElement(label, value) { + return ` +
    +
    ${Language.get(label)}
    +
    +
    + + +
    +
    +
    + `; + } + function getProviderButtons() { + const providerButtons = Array.from(UiMessageShareProviders.getEnabledProviders()) + .map((provider) => { + const label = Language.get(provider.label); + return ` +
  • + + + ${label} + +
  • + `; + }) + .join("\n"); + if (providerButtons) { + return `
      ${providerButtons}
    `; + } + return ""; + } + /** + * Opens the share dialog after clicking on the share button. + */ + function openDialog(event) { + event.preventDefault(); + const target = event.currentTarget; + const dialogId = `shareContentDialog_${Util_1.default.identify(target)}`; + if (!Dialog_1.default.getDialog(dialogId)) { + const providerButtons = getProviderButtons(); + let providerElement = ""; + if (providerButtons) { + providerElement = ` +
    +
    ${Language.get("wcf.message.share.socialMedia")}
    +
    ${providerButtons}
    +
    + `; + } + const dialogContent = ` +
    + ${getDialogElements(target)} + ${providerElement} +
    + `; + const dialogData = Dialog_1.default.openStatic(dialogId, dialogContent, { + title: Language.get("wcf.message.share"), + }); + dialogData.content.style.maxWidth = "600px"; + dialogData.dialog + .querySelectorAll(".shareDialogCopyButton") + .forEach((el) => el.addEventListener("click", (ev) => copy(ev))); + if (providerButtons) { + UiMessageShare.init(); + } + } + else { + Dialog_1.default.openStatic(dialogId, null); + } + } + function registerButtons() { + document.querySelectorAll(".shareButton").forEach((shareButton) => { + if (!shareButtons.has(shareButton)) { + shareButton.addEventListener("click", (ev) => openDialog(ev)); + shareButtons.add(shareButton); + } + }); + } + function setup() { + registerButtons(); + Listener_1.default.add("WoltLabSuite/Core/Ui/Message/Share/Dialog", () => registerButtons()); + } + exports.setup = setup; +}); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Share/Providers.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Share/Providers.js new file mode 100644 index 0000000000..bad89cf6ee --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Share/Providers.js @@ -0,0 +1,95 @@ +/** + * Manages the share providers shown in the share dialogs. + * + * @author Matthias Schmidt + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Message/Share/Providers + */ +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.getEnabledProviders = exports.getProviders = exports.enableShareProviders = exports.addShareProvider = void 0; + const enabledProviders = new Set(); + const providers = new Map([ + [ + "Facebook", + { + cssClass: "jsShareFacebook", + iconClassName: "fa-facebook-official", + label: "wcf.message.share.facebook", + }, + ], + [ + "Twitter", + { + cssClass: "jsShareTwitter", + iconClassName: "fa-twitter", + label: "wcf.message.share.twitter", + }, + ], + [ + "Reddit", + { + cssClass: "jsShareReddit", + iconClassName: "fa-reddit", + label: "wcf.message.share.reddit", + }, + ], + [ + "WhatsApp", + { + cssClass: "jsShareWhatsApp", + iconClassName: "fa-whatsapp", + label: "wcf.message.share.whatsApp", + }, + ], + [ + "LinkedIn", + { + cssClass: "jsShareLinkedIn", + iconClassName: "fa-linkedin", + label: "wcf.message.share.linkedIn", + }, + ], + [ + "Pinterest", + { + cssClass: "jsSharePinterest", + iconClassName: "fa-pinterest-p", + label: "wcf.message.share.pinterest", + }, + ], + [ + "XING", + { + cssClass: "jsShareXing", + iconClassName: "fa-xing", + label: "wcf.message.share.xing", + }, + ], + ]); + function addShareProvider(providerName, provider) { + if (providers.has(providerName)) { + throw new Error(`A share provider with name "${providerName}" already exists.`); + } + providers.set(providerName, provider); + } + exports.addShareProvider = addShareProvider; + function enableShareProviders(providerNames) { + providerNames.forEach((providerName) => { + if (providers.has(providerName)) { + enabledProviders.add(providers.get(providerName)); + } + }); + } + exports.enableShareProviders = enableShareProviders; + function getProviders() { + return providers; + } + exports.getProviders = getProviders; + function getEnabledProviders() { + return enabledProviders; + } + exports.getEnabledProviders = getEnabledProviders; +}); diff --git a/wcfsetup/install/files/lib/data/page/content/PageContent.class.php b/wcfsetup/install/files/lib/data/page/content/PageContent.class.php index d79ff58ca0..c9d4e14968 100644 --- a/wcfsetup/install/files/lib/data/page/content/PageContent.class.php +++ b/wcfsetup/install/files/lib/data/page/content/PageContent.class.php @@ -3,7 +3,7 @@ namespace wcf\data\page\content; use wcf\data\DatabaseObject; -use wcf\data\ILinkableObject; +use wcf\data\ITitledLinkObject; use wcf\system\html\output\HtmlOutputProcessor; use wcf\system\html\simple\HtmlSimpleParser; use wcf\system\message\embedded\object\MessageEmbeddedObjectManager; @@ -28,7 +28,7 @@ use wcf\system\WCF; * @property-read string $customURL custom url of the page in the associated language * @property-read int $hasEmbeddedObjects is `1` if the page content contains embedded objects, otherwise `0` */ -class PageContent extends DatabaseObject implements ILinkableObject +class PageContent extends DatabaseObject implements ITitledLinkObject { /** * @inheritDoc @@ -138,4 +138,12 @@ class PageContent extends DatabaseObject implements ILinkableObject { return LinkHandler::getInstance()->getCmsLink($this->pageID, $this->languageID); } + + /** + * @inheritDoc + */ + public function getTitle() + { + return $this->title; + } } diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index d5781d4afb..216b280fe7 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -4188,6 +4188,9 @@ Dateianhänge: + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 718052e023..2c9055d426 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -4135,6 +4135,9 @@ Attachments: + + +