Add dialog with anonymous/personalized links for RSS feeds (#4123)
authorMatthias Schmidt <gravatronics@live.com>
Mon, 19 Apr 2021 12:34:32 +0000 (14:34 +0200)
committerGitHub <noreply@github.com>
Mon, 19 Apr 2021 12:34:32 +0000 (14:34 +0200)
Close #3661

com.woltlab.wcf/templates/articleList.tpl
com.woltlab.wcf/templates/categoryArticleList.tpl
com.woltlab.wcf/templates/headIncludeJavaScript.tpl
com.woltlab.wcf/templates/notificationList.tpl
ts/WoltLabSuite/Core/BootstrapFrontend.ts
ts/WoltLabSuite/Core/Ui/Feed/Dialog.ts [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Feed/Dialog.js [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 18849b803a3277f8aca4aac90ccb4bfcd482514e..c27c753972593fb104751ad067454f41134c034c 100644 (file)
@@ -14,7 +14,7 @@
 {/capture}
 
 {capture assign='headerNavigation'}
-       <li><a rel="alternate" href="{if $__wcf->getUser()->userID}{link controller='ArticleFeed'}at={@$__wcf->getUser()->userID}-{@$__wcf->getUser()->accessToken}{/link}{else}{link controller='ArticleFeed'}{/link}{/if}" title="{lang}wcf.global.button.rss{/lang}" class="jsTooltip"><span class="icon icon16 fa-rss"></span> <span class="invisible">{lang}wcf.global.button.rss{/lang}</span></a></li>
+       <li><a rel="alternate" href="{if $__wcf->getUser()->userID}{link controller='ArticleFeed'}at={@$__wcf->getUser()->userID}-{@$__wcf->getUser()->accessToken}{/link}{else}{link controller='ArticleFeed'}{/link}{/if}" title="{lang}wcf.global.button.rss{/lang}" class="rssFeed jsTooltip"><span class="icon icon16 fa-rss"></span> <span class="invisible">{lang}wcf.global.button.rss{/lang}</span></a></li>
        {if ARTICLE_ENABLE_VISIT_TRACKING}
                <li class="jsOnly"><a href="#" title="{lang}wcf.article.markAllAsRead{/lang}" class="markAllAsReadButton jsTooltip"><span class="icon icon16 fa-check"></span> <span class="invisible">{lang}wcf.article.markAllAsRead{/lang}</span></a></li>
        {/if}
index 60b3e32636355329e17f13f5e5a0f8078a5d9e37..33b310afec758ece401edeed569326fbaede53e1 100644 (file)
@@ -19,7 +19,7 @@
 {/capture}
 
 {capture assign='headerNavigation'}
-       <li><a rel="alternate" href="{if $__wcf->getUser()->userID}{link controller='ArticleFeed' id=$categoryID}at={@$__wcf->getUser()->userID}-{@$__wcf->getUser()->accessToken}{/link}{else}{link controller='ArticleFeed' id=$categoryID}{/link}{/if}" title="{lang}wcf.global.button.rss{/lang}" class="jsTooltip"><span class="icon icon16 fa-rss"></span> <span class="invisible">{lang}wcf.global.button.rss{/lang}</span></a></li>
+       <li><a rel="alternate" href="{if $__wcf->getUser()->userID}{link controller='ArticleFeed' id=$categoryID}at={@$__wcf->getUser()->userID}-{@$__wcf->getUser()->accessToken}{/link}{else}{link controller='ArticleFeed' id=$categoryID}{/link}{/if}" title="{lang}wcf.global.button.rss{/lang}" class="rssFeed jsTooltip"><span class="icon icon16 fa-rss"></span> <span class="invisible">{lang}wcf.global.button.rss{/lang}</span></a></li>
        {if $__wcf->user->userID}
                <li class="jsOnly"><a href="#" title="{lang}wcf.user.objectWatch.manageSubscription{/lang}" class="jsSubscribeButton jsTooltip" data-object-type="com.woltlab.wcf.article.category" data-object-id="{@$category->categoryID}"><span class="icon icon16 fa-bookmark{if !$category->isSubscribed()}-o{/if}"></span> <span class="invisible">{lang}wcf.user.objectWatch.manageSubscription{/lang}</span></a></li>
        {/if}
index 64560c01dc550aae1e36e6c0e7dcf785f1373e82..27d2649b7f60387605acc17140a5d0b728b9019a 100644 (file)
@@ -147,7 +147,13 @@ window.addEventListener('pageshow', function(event) {
                        '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}'
+                       'wcf.message.share.copy.success': '{jslang}wcf.message.share.copy.success{/jslang}',
+                       'wcf.global.button.rss': '{jslang}wcf.global.button.rss{/jslang}',
+                       'wcf.global.rss.copy': '{jslang}wcf.global.rss.copy{/jslang}',
+                       'wcf.global.rss.copy.success': '{jslang}wcf.global.rss.copy.success{/jslang}',
+                       'wcf.global.rss.accessToken.info': '{jslang}wcf.global.rss.accessToken.info{/jslang}',
+                       'wcf.global.rss.withoutAccessToken': '{jslang}wcf.global.rss.withoutAccessToken{/jslang}',
+                       'wcf.global.rss.withAccessToken': '{jslang}wcf.global.rss.withAccessToken{/jslang}'
                        {if MODULE_LIKE}
                                ,'wcf.like.button.like': '{jslang}wcf.like.button.like{/jslang}',
                                'wcf.like.button.dislike': '{jslang}wcf.like.button.dislike{/jslang}',
index 33338e7f138bf9646c37636f4fca017cf9401221..d2a661f54ccfb0115fa49d09533813dc8fd21bfe 100644 (file)
@@ -22,7 +22,7 @@
 {/capture}
 
 {capture assign='headerNavigation'}
-       <li><a rel="alternate" href="{link controller='NotificationFeed'}at={@$__wcf->getUser()->userID}-{@$__wcf->getUser()->accessToken}{/link}" title="{lang}wcf.global.button.rss{/lang}" class="jsTooltip"><span class="icon icon16 fa-rss"></span> <span class="invisible">{lang}wcf.global.button.rss{/lang}</span></a></li>
+       <li><a rel="alternate" href="{link controller='NotificationFeed'}at={@$__wcf->getUser()->userID}-{@$__wcf->getUser()->accessToken}{/link}" title="{lang}wcf.global.button.rss{/lang}" class="rssFeed jsTooltip"><span class="icon icon16 fa-rss"></span> <span class="invisible">{lang}wcf.global.button.rss{/lang}</span></a></li>
 {/capture}
 
 {include file='userMenuSidebar'}
index d58f13ce660b18c5a49f725d91956677c529433a..61c5f8f27014a8e2ae405821d739a6477fed3bf3 100644 (file)
@@ -17,6 +17,8 @@ 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";
+import * as UiFeedDialog from "./Ui/Feed/Dialog";
+import User from "./User";
 
 interface BoostrapOptions {
   backgroundQueue: {
@@ -91,4 +93,8 @@ export function setup(options: BoostrapOptions): void {
 
   UiMessageShareProviders.enableShareProviders(options.shareButtonProviders || []);
   UiMessageShareDialog.setup();
+
+  if (User.userId) {
+    UiFeedDialog.setup();
+  }
 }
diff --git a/ts/WoltLabSuite/Core/Ui/Feed/Dialog.ts b/ts/WoltLabSuite/Core/Ui/Feed/Dialog.ts
new file mode 100644 (file)
index 0000000..925ed6a
--- /dev/null
@@ -0,0 +1,83 @@
+/**
+ * Shows a dialog with an anonymous and personalized link for RSS feeds with access tokens.
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2021 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/Ui/Feed/Dialog
+ */
+
+import UiDialog from "../Dialog";
+import * as StringUtil from "../../StringUtil";
+import * as Language from "../../Language";
+import * as Clipboard from "../../Clipboard";
+import * as UiNotification from "../Notification";
+
+/**
+ * Copies one of links to the clipboard.
+ */
+async function copy(event: Event): Promise<void> {
+  event.preventDefault();
+
+  const target = event.currentTarget as HTMLElement;
+  const input = target.parentNode!.querySelector('input[type="text"]') as HTMLInputElement;
+
+  await Clipboard.copyTextToClipboard(input.value);
+
+  UiNotification.show(Language.get("wcf.global.rss.copy.success"));
+}
+
+/**
+ * Opens the dialog with an anonymous and personalized link after clicking on the RSS feed link.
+ */
+function openDialog(event: Event): void {
+  event.preventDefault();
+
+  const alternative = event.currentTarget as HTMLAnchorElement;
+  const linkWithAccessToken = alternative.href;
+
+  const linkWithoutAccessToken = linkWithAccessToken.replace(/(\\?|&)at=[^&]*&?/, "$1").replace(/(\?|&)$/, "");
+
+  UiDialog.openStatic(
+    "feedLinkDialog",
+    `
+<p class="info">${Language.get("wcf.global.rss.accessToken.info")}</p>
+<dl>
+  <dt>${Language.get("wcf.global.rss.withoutAccessToken")}</dt>
+  <dd>
+    <div class="inputAddon">
+      <input type="text" class="long" readonly value="${StringUtil.escapeHTML(linkWithoutAccessToken)}">
+      <a href="#" class="inputSuffix button jsTooltip feedLinkDialogCopyButton" title="${Language.get(
+        "wcf.global.rss.copy",
+      )}"><span class="icon icon16 fa-files-o pointer"></span></a>
+    </div>
+  </dd>
+</dl>
+<dl>
+  <dt>${Language.get("wcf.global.rss.withAccessToken")}</dt>
+  <dd>
+    <div class="inputAddon">
+      <input type="text" class="long" readonly value="${StringUtil.escapeHTML(linkWithAccessToken)}">
+      <a href="#" class="inputSuffix button jsTooltip feedLinkDialogCopyButton" title="${Language.get(
+        "wcf.global.rss.copy",
+      )}"><span class="icon icon16 fa-files-o pointer"></span></a>
+    </div>
+  </dd>
+</dl>
+`,
+    {
+      onSetup(content: HTMLElement) {
+        content
+          .querySelectorAll(".feedLinkDialogCopyButton")
+          .forEach((el) => el.addEventListener("click", (ev) => copy(ev)));
+      },
+      title: alternative.title || Language.get("wcf.global.button.rss"),
+    },
+  );
+}
+
+export function setup(): void {
+  document.querySelectorAll("a.rssFeed").forEach((link: HTMLAnchorElement) => {
+    link.addEventListener("click", (ev) => openDialog(ev));
+  });
+}
index b5623a30e2bf3ba7b3e6b353af7e2180767924dd..474c1893f9734c8e6a31995679da8bd6838aafb3 100644 (file)
@@ -6,7 +6,7 @@
  * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @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", "./Ui/Message/Share/Dialog", "./Ui/Message/Share/Providers"], function (require, exports, tslib_1, BackgroundQueue, Bootstrap, ControllerStyleChanger, ControllerPopover, UiUserIgnore, UiPageHeaderMenu, UiMessageUserConsent, Ajax, UiMessageShareDialog, UiMessageShareProviders) {
+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", "./Ui/Feed/Dialog", "./User"], function (require, exports, tslib_1, BackgroundQueue, Bootstrap, ControllerStyleChanger, ControllerPopover, UiUserIgnore, UiPageHeaderMenu, UiMessageUserConsent, Ajax, UiMessageShareDialog, UiMessageShareProviders, UiFeedDialog, User_1) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.setup = void 0;
@@ -20,6 +20,8 @@ define(["require", "exports", "tslib", "./BackgroundQueue", "./Bootstrap", "./Co
     Ajax = tslib_1.__importStar(Ajax);
     UiMessageShareDialog = tslib_1.__importStar(UiMessageShareDialog);
     UiMessageShareProviders = tslib_1.__importStar(UiMessageShareProviders);
+    UiFeedDialog = tslib_1.__importStar(UiFeedDialog);
+    User_1 = tslib_1.__importDefault(User_1);
     /**
      * Initializes user profile popover.
      */
@@ -72,6 +74,9 @@ define(["require", "exports", "tslib", "./BackgroundQueue", "./Bootstrap", "./Co
         UiMessageUserConsent.init();
         UiMessageShareProviders.enableShareProviders(options.shareButtonProviders || []);
         UiMessageShareDialog.setup();
+        if (User_1.default.userId) {
+            UiFeedDialog.setup();
+        }
     }
     exports.setup = setup;
 });
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Feed/Dialog.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Feed/Dialog.js
new file mode 100644 (file)
index 0000000..70d4077
--- /dev/null
@@ -0,0 +1,71 @@
+/**
+ * Shows a dialog with an anonymous and personalized link for RSS feeds with access tokens.
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2021 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/Ui/Feed/Dialog
+ */
+define(["require", "exports", "tslib", "../Dialog", "../../StringUtil", "../../Language", "../../Clipboard", "../Notification"], function (require, exports, tslib_1, Dialog_1, StringUtil, Language, Clipboard, UiNotification) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.setup = void 0;
+    Dialog_1 = tslib_1.__importDefault(Dialog_1);
+    StringUtil = tslib_1.__importStar(StringUtil);
+    Language = tslib_1.__importStar(Language);
+    Clipboard = tslib_1.__importStar(Clipboard);
+    UiNotification = tslib_1.__importStar(UiNotification);
+    /**
+     * Copies one of links to the clipboard.
+     */
+    async function copy(event) {
+        event.preventDefault();
+        const target = event.currentTarget;
+        const input = target.parentNode.querySelector('input[type="text"]');
+        await Clipboard.copyTextToClipboard(input.value);
+        UiNotification.show(Language.get("wcf.global.rss.copy.success"));
+    }
+    /**
+     * Opens the dialog with an anonymous and personalized link after clicking on the RSS feed link.
+     */
+    function openDialog(event) {
+        event.preventDefault();
+        const alternative = event.currentTarget;
+        const linkWithAccessToken = alternative.href;
+        const linkWithoutAccessToken = linkWithAccessToken.replace(/(\\?|&)at=[^&]*&?/, "$1").replace(/(\?|&)$/, "");
+        Dialog_1.default.openStatic("feedLinkDialog", `
+<p class="info">${Language.get("wcf.global.rss.accessToken.info")}</p>
+<dl>
+  <dt>${Language.get("wcf.global.rss.withoutAccessToken")}</dt>
+  <dd>
+    <div class="inputAddon">
+      <input type="text" class="long" readonly value="${StringUtil.escapeHTML(linkWithoutAccessToken)}">
+      <a href="#" class="inputSuffix button jsTooltip feedLinkDialogCopyButton" title="${Language.get("wcf.global.rss.copy")}"><span class="icon icon16 fa-files-o pointer"></span></a>
+    </div>
+  </dd>
+</dl>
+<dl>
+  <dt>${Language.get("wcf.global.rss.withAccessToken")}</dt>
+  <dd>
+    <div class="inputAddon">
+      <input type="text" class="long" readonly value="${StringUtil.escapeHTML(linkWithAccessToken)}">
+      <a href="#" class="inputSuffix button jsTooltip feedLinkDialogCopyButton" title="${Language.get("wcf.global.rss.copy")}"><span class="icon icon16 fa-files-o pointer"></span></a>
+    </div>
+  </dd>
+</dl>
+`, {
+            onSetup(content) {
+                content
+                    .querySelectorAll(".feedLinkDialogCopyButton")
+                    .forEach((el) => el.addEventListener("click", (ev) => copy(ev)));
+            },
+            title: alternative.title || Language.get("wcf.global.button.rss"),
+        });
+    }
+    function setup() {
+        document.querySelectorAll("a.rssFeed").forEach((link) => {
+            link.addEventListener("click", (ev) => openDialog(ev));
+        });
+    }
+    exports.setup = setup;
+});
index 61114990b920469a04bd5817a4dac27d76779c28..6e8bdb397614a3639e16191f9c73b04b75a2e681 100644 (file)
@@ -3962,6 +3962,11 @@ Dateianhänge:
                <item name="wcf.global.button.showSidebarRight"><![CDATA[Rechte Sidebar]]></item>
                <item name="wcf.global.button.showMenu"><![CDATA[Menü anzeigen]]></item>
                <item name="wcf.global.button.hideMenu"><![CDATA[Menü verbergen]]></item>
+               <item name="wcf.global.rss.copy"><![CDATA[Link kopieren]]></item>
+               <item name="wcf.global.rss.copy.success"><![CDATA[Der Link wurde erfolgreich kopiert.]]></item>
+               <item name="wcf.global.rss.accessToken.info"><![CDATA[Der Link zum anonymen RSS-Feed enthält nur Inhalte, auf die Gäste Zugriff haben. Der Link zum personalisierten RSS-Feed enthält alle Inhalte, auf die {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} Zugriff {if LANGUAGE_USE_INFORMAL_VARIANT}hast{else}haben{/if}.]]></item>
+               <item name="wcf.global.rss.withAccessToken"><![CDATA[Personalisierter RSS-Feed]]></item>
+               <item name="wcf.global.rss.withoutAccessToken"><![CDATA[Anonymer RSS-Feed]]></item>
        </category>
        <category name="wcf.global.form">
                <item name="wcf.global.form.boolean.no"><![CDATA[Nein]]></item>
index 7d6850b3a9d73c5fa9a1d95ee9a4a732285d4e22..48b22a08528d9636805370d367fdf02a053d9bb3 100644 (file)
@@ -3909,6 +3909,11 @@ Attachments:
                <item name="wcf.global.button.showSidebarRight"><![CDATA[Sidebar Right]]></item>
                <item name="wcf.global.button.showMenu"><![CDATA[Show Menu]]></item>
                <item name="wcf.global.button.hideMenu"><![CDATA[Hide Menu]]></item>
+               <item name="wcf.global.rss.copy"><![CDATA[Copy Link]]></item>
+               <item name="wcf.global.rss.copy.success"><![CDATA[The link has been copied successfully.]]></item>
+               <item name="wcf.global.rss.accessToken.info"><![CDATA[The link to the anonymous RSS feed only contains contents that guests can access. The link to the personalized RSS feed contains all contents that you can access.]]></item>
+               <item name="wcf.global.rss.withAccessToken"><![CDATA[Personalized RSS Feed]]></item>
+               <item name="wcf.global.rss.withoutAccessToken"><![CDATA[Anonymous RSS Feed]]></item>
        </category>
        <category name="wcf.global.form">
                <item name="wcf.global.form.boolean.no"><![CDATA[No]]></item>