From f09d42726d17552367324e99b7b657ed705a8383 Mon Sep 17 00:00:00 2001 From: Seckhen Date: Mon, 23 Sep 2024 20:19:05 +0300 Subject: [PATCH 01/50] Added voice recording button to new post menu, modified file composer-formatting.tpl --- dump.rdb | Bin 0 -> 135546 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 dump.rdb diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000000000000000000000000000000000..b0c6beb9aa94c54ca2eb9d7a1809367bb8479ae7 GIT binary patch literal 135546 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 literal 0 HcmV?d00001 From 8b57be8c3ba7ec520330b79df6a2645bf9fe6e01 Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 01:01:46 +0300 Subject: [PATCH 02/50] Adding node modules file, which we will work on but is currently not in repository --- .gitignore | 3 ++- dump.rdb | Bin 135546 -> 159585 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 42a1b3c705..46a37deecc 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,5 @@ link-plugins.sh test.sh .docker/** -!**/.gitkeep \ No newline at end of file +!**/.gitkeep +!node_modules\nodebb-theme-harmony\templates\partials\topics_list.tpl \ No newline at end of file diff --git a/dump.rdb b/dump.rdb index b0c6beb9aa94c54ca2eb9d7a1809367bb8479ae7..c11568911595e430efd75d61c22730d475453983 100644 GIT binary patch delta 26085 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 From c6595804ced7fc2bf827b5c6ea28e3e64be31716 Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 01:07:38 +0300 Subject: [PATCH 03/50] Adding node modules file, which we will work on but is currently not in repository --- .gitignore | 3 +++ dump.rdb | Bin 159585 -> 159585 bytes 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 46a37deecc..2e39c6b113 100644 --- a/.gitignore +++ b/.gitignore @@ -73,4 +73,7 @@ test.sh .docker/** !**/.gitkeep +!node_modules\nodebb-theme-harmony\ +!node_modules\nodebb-theme-harmony\templates\ +!node_modules\nodebb-theme-harmony\templates\partials\ !node_modules\nodebb-theme-harmony\templates\partials\topics_list.tpl \ No newline at end of file diff --git a/dump.rdb b/dump.rdb index c11568911595e430efd75d61c22730d475453983..c71ec0a686aaf28ee9f0ee9fb215df0b73a21129 100644 GIT binary patch delta 56 zcmV-80LTB~-U;E}36L=nrHJ%q`UrJ%Wn?XFWo^O$Jh4K*R0G8)|APcohXhptw**xJ ORT>Bpj3fjpsLSWZ78Z#B delta 56 zcmV-80LTB~-U;E}36L=nlzQ}L`UrJ%Wn?XFWo^Q6;ITr#R0Dy+{(}TnhXhptw**xJ ORT>CrnYJp@ys%;OeHjb@ From 9478b3b526967595dec212a8aea53eaf5b877442 Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 01:33:16 +0300 Subject: [PATCH 04/50] Adding node modules file in a newly created folder called node_modules_real because node_modules files can't be pushed and updated READ ME with instructions --- README.md | 11 ++ .../templates/partials/topics_list.tpl | 131 ++++++++++++++++++ node_modules_real/topics_list.tpl | 131 ++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl create mode 100644 node_modules_real/topics_list.tpl diff --git a/README.md b/README.md index 6ef180f625..ef1ac825d7 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,17 @@ [![Coverage Status](https://coveralls.io/repos/github/CMU-313/NodeBB/badge.svg)](https://coveralls.io/github/CMU-313/NodeBB) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=CMU-313_NodeBB&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=CMU-313_NodeBB) + +****************************************** NOTICE ************************************************ + +The collaborators on this project are Seckhen Andrade, Nikoloz Devidze, Ghani Raissov, Davit Charkviani, Yousuf Alkhiyami + +Please know that there is a folder called node_module_real, which contains the files that are modified compared to the files that are in the node_modules we get by npm install. Please take the code from the files in the node_modules_real folder (files have the same names as in the node_module generated by npm install) and paste (replace the code) them in the respective files. + + +*************************************************************************************************** + + [**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and supports either Redis, MongoDB, or a PostgreSQL database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB takes the best of the modern web: real-time streaming discussions, mobile responsiveness, and rich RESTful read/write APIs, while staying true to the original bulletin board/forum format → categorical hierarchies, local user accounts, and asynchronous messaging. NodeBB by itself contains a "common core" of basic functionality, while additional functionality and integrations are enabled through the use of third-party plugins. diff --git a/node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl b/node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl new file mode 100644 index 0000000000..7b26d21f0e --- /dev/null +++ b/node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl @@ -0,0 +1,131 @@ +
    + + {{{ each topics }}} +
  • > + + + + + + +
    +
    + + {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} + + {{{ if showSelect }}} +
    + +
    + {{{ end }}} +
    +
    +

    + {./title} +

    + + + + [[topic:watching]] + + + + [[topic:ignoring]] + + + + [[topic:scheduled]] + + + + {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} + + + + [[topic:locked]] + + + + [[topic:moved]] + + {{{each ./icons}}}{@value}{{{end}}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + + +
    + + + {humanReadableNumber(./postcount, 0)} + + + +
    + + +
    + {{{ if showSelect }}} +
    + +
    + {{{ end }}} +
    + {{{ if ./thumbs.length }}} + + + +{increment(./thumbs.length, "-1")} + + {{{ end }}} +
    + +
    +
    + {{{ if !reputation:disabled }}} +
    + {humanReadableNumber(./votes, 0)} + [[global:votes]] + +
    + {{{ end }}} +
    + {humanReadableNumber(./postcount, 0)} + [[global:posts]] + +
    +
    + {humanReadableNumber(./viewcount, 0)} + [[global:views]] + +
    +
    +
    +
    + {{{ if ./unreplied }}} +
    + [[category:no-replies]] +
    + {{{ else }}} + {{{ if ./teaser.pid }}} + +
    + + {./teaser.content} +
    + {{{ end }}} + {{{ end }}} +
    +
    +
    +
  • + {{{end}}} +
diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl new file mode 100644 index 0000000000..7b26d21f0e --- /dev/null +++ b/node_modules_real/topics_list.tpl @@ -0,0 +1,131 @@ +
    + + {{{ each topics }}} +
  • > + + + + + + +
    +
    + + {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} + + {{{ if showSelect }}} +
    + +
    + {{{ end }}} +
    +
    +

    + {./title} +

    + + + + [[topic:watching]] + + + + [[topic:ignoring]] + + + + [[topic:scheduled]] + + + + {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} + + + + [[topic:locked]] + + + + [[topic:moved]] + + {{{each ./icons}}}{@value}{{{end}}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + + +
    + + + {humanReadableNumber(./postcount, 0)} + + + +
    + + +
    + {{{ if showSelect }}} +
    + +
    + {{{ end }}} +
    + {{{ if ./thumbs.length }}} + + + +{increment(./thumbs.length, "-1")} + + {{{ end }}} +
    + +
    +
    + {{{ if !reputation:disabled }}} +
    + {humanReadableNumber(./votes, 0)} + [[global:votes]] + +
    + {{{ end }}} +
    + {humanReadableNumber(./postcount, 0)} + [[global:posts]] + +
    +
    + {humanReadableNumber(./viewcount, 0)} + [[global:views]] + +
    +
    +
    +
    + {{{ if ./unreplied }}} +
    + [[category:no-replies]] +
    + {{{ else }}} + {{{ if ./teaser.pid }}} + +
    + + {./teaser.content} +
    + {{{ end }}} + {{{ end }}} +
    +
    +
    +
  • + {{{end}}} +
From 8b13e600308b4cd14eea07cd50c472dde6fff841 Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 01:33:44 +0300 Subject: [PATCH 05/50] Adding node modules file in a newly created folder called node_modules_real because node_modules files can't be pushed and updated READ ME with instructions --- .gitignore | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2e39c6b113..42a1b3c705 100644 --- a/.gitignore +++ b/.gitignore @@ -72,8 +72,4 @@ link-plugins.sh test.sh .docker/** -!**/.gitkeep -!node_modules\nodebb-theme-harmony\ -!node_modules\nodebb-theme-harmony\templates\ -!node_modules\nodebb-theme-harmony\templates\partials\ -!node_modules\nodebb-theme-harmony\templates\partials\topics_list.tpl \ No newline at end of file +!**/.gitkeep \ No newline at end of file From bee5c4c8bb626384abba5bcd30b76080d471fcee Mon Sep 17 00:00:00 2001 From: Seckhen <111176979+Seckhen@users.noreply.github.com> Date: Thu, 26 Sep 2024 01:37:47 +0300 Subject: [PATCH 06/50] Delete node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl This file occurred as an extra one as a result of testing of how we could potentially keep some of the node module files in git. Unsuccessful so this can be ignored. --- .../templates/partials/topics_list.tpl | 131 ------------------ 1 file changed, 131 deletions(-) delete mode 100644 node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl diff --git a/node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl b/node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl deleted file mode 100644 index 7b26d21f0e..0000000000 --- a/node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl +++ /dev/null @@ -1,131 +0,0 @@ -
    - - {{{ each topics }}} -
  • > - - - - - - -
    -
    - - {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} - - {{{ if showSelect }}} -
    - -
    - {{{ end }}} -
    -
    -

    - {./title} -

    - - - - [[topic:watching]] - - - - [[topic:ignoring]] - - - - [[topic:scheduled]] - - - - {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} - - - - [[topic:locked]] - - - - [[topic:moved]] - - {{{each ./icons}}}{@value}{{{end}}} - - {{{ if !template.category }}} - {function.buildCategoryLabel, ./category, "a", "border"} - {{{ end }}} - - - -
    - - - {humanReadableNumber(./postcount, 0)} - - - -
    - - -
    - {{{ if showSelect }}} -
    - -
    - {{{ end }}} -
    - {{{ if ./thumbs.length }}} - - - +{increment(./thumbs.length, "-1")} - - {{{ end }}} -
    - -
    -
    - {{{ if !reputation:disabled }}} -
    - {humanReadableNumber(./votes, 0)} - [[global:votes]] - -
    - {{{ end }}} -
    - {humanReadableNumber(./postcount, 0)} - [[global:posts]] - -
    -
    - {humanReadableNumber(./viewcount, 0)} - [[global:views]] - -
    -
    -
    -
    - {{{ if ./unreplied }}} -
    - [[category:no-replies]] -
    - {{{ else }}} - {{{ if ./teaser.pid }}} - -
    - - {./teaser.content} -
    - {{{ end }}} - {{{ end }}} -
    -
    -
    -
  • - {{{end}}} -
From 33b0417d1d9efe506368b679356955c084f3e1d7 Mon Sep 17 00:00:00 2001 From: Dachi Date: Wed, 25 Sep 2024 16:04:12 -0700 Subject: [PATCH 07/50] added 'share post to chat' button to every topic listing/post' --- node_modules_real/topics_list.tpl | 139 ++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 node_modules_real/topics_list.tpl diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl new file mode 100644 index 0000000000..a4447d9421 --- /dev/null +++ b/node_modules_real/topics_list.tpl @@ -0,0 +1,139 @@ +
    + + {{{ each topics }}} +
  • > + + + + + + +
    +
    + + {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} + + {{{ if showSelect }}} +
    + +
    + {{{ end }}} +
    +
    +

    + {./title} +

    + + + + [[topic:watching]] + + + + [[topic:ignoring]] + + + + [[topic:scheduled]] + + + + {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} + + + + [[topic:locked]] + + + + [[topic:moved]] + + {{{each ./icons}}}{@value}{{{end}}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + + +
    + + + {humanReadableNumber(./postcount, 0)} + + + +
    + + +
    + + +
    + +
    + + {{{ if showSelect }}} +
    + +
    + {{{ end }}} +
    + {{{ if ./thumbs.length }}} + + + +{increment(./thumbs.length, "-1")} + + {{{ end }}} +
    + +
    +
    + {{{ if !reputation:disabled }}} +
    + {humanReadableNumber(./votes, 0)} + [[global:votes]] + +
    + {{{ end }}} +
    + {humanReadableNumber(./postcount, 0)} + [[global:posts]] + +
    +
    + {humanReadableNumber(./viewcount, 0)} + [[global:views]] + +
    +
    +
    +
    + {{{ if ./unreplied }}} +
    + [[category:no-replies]] +
    + {{{ else }}} + {{{ if ./teaser.pid }}} + +
    + + {./teaser.content} +
    + {{{ end }}} + {{{ end }}} +
    +
    +
    +
  • + {{{end}}} +
From d3fb1b07e95bad35e0c9b2ade35f11c8d476066d Mon Sep 17 00:00:00 2001 From: Dachi Date: Wed, 25 Sep 2024 16:07:59 -0700 Subject: [PATCH 08/50] added 'share post to chat' button to every topic listing/post --- node_modules_real/topics_list.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index a4447d9421..658e8440e7 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -1,5 +1,5 @@
    - + # {{{ each topics }}}
  • > From 1d54e2465226787a78cafccfcb9efc502337ca3a Mon Sep 17 00:00:00 2001 From: NickDevi Date: Thu, 26 Sep 2024 02:37:04 +0300 Subject: [PATCH 09/50] Implemented front-end modal and event handler to display a popup with the user's future available chats when the share button is clicked. --- node_modules_real/topics_list.tpl | 158 ++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 node_modules_real/topics_list.tpl diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl new file mode 100644 index 0000000000..42fec4ea2b --- /dev/null +++ b/node_modules_real/topics_list.tpl @@ -0,0 +1,158 @@ +
      + + {{{ each topics }}} +
    • > + + + + + + +
      +
      + + {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} + + {{{ if showSelect }}} +
      + +
      + {{{ end }}} +
      +
      +

      + {./title} +

      + + + + [[topic:watching]] + + + + [[topic:ignoring]] + + + + [[topic:scheduled]] + + + + {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} + + + + [[topic:locked]] + + + + [[topic:moved]] + + {{{each ./icons}}}{@value}{{{end}}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + + +
      + + + {humanReadableNumber(./postcount, 0)} + + + +
      + + +
      + + +
      + +
      + + {{{ if showSelect }}} +
      + +
      + {{{ end }}} +
      + {{{ if ./thumbs.length }}} + + + +{increment(./thumbs.length, "-1")} + + {{{ end }}} +
      + +
      +
      + {{{ if !reputation:disabled }}} +
      + {humanReadableNumber(./votes, 0)} + [[global:votes]] + +
      + {{{ end }}} +
      + {humanReadableNumber(./postcount, 0)} + [[global:posts]] + +
      +
      + {humanReadableNumber(./viewcount, 0)} + [[global:views]] + +
      +
      +
      +
      + {{{ if ./unreplied }}} +
      + [[category:no-replies]] +
      + {{{ else }}} + {{{ if ./teaser.pid }}} + +
      + + {./teaser.content} +
      + {{{ end }}} + {{{ end }}} +
      +
      +
      +
    • + {{{end}}} +
    + + + From 48013d788688a7c722b8804674ff363e467f5994 Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 03:42:56 +0300 Subject: [PATCH 10/50] Added functions sendPostToChat, sharePostToChat, and fetchTopicTitle to complete back-end of sharing posts --- node_modules_real/topics_list.tpl | 415 +++++++++++++++++++++--------- 1 file changed, 287 insertions(+), 128 deletions(-) diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index 7b26d21f0e..52573bb934 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -1,131 +1,290 @@
      - {{{ each topics }}} -
    • > - - - - - - -
      -
      - - {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} - - {{{ if showSelect }}} -
      - -
      - {{{ end }}} -
      -
      -

      - {./title} -

      - - - - [[topic:watching]] - - - - [[topic:ignoring]] - - - - [[topic:scheduled]] - - - - {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} - - - - [[topic:locked]] - - - - [[topic:moved]] - - {{{each ./icons}}}{@value}{{{end}}} - - {{{ if !template.category }}} - {function.buildCategoryLabel, ./category, "a", "border"} - {{{ end }}} - - - -
      - - - {humanReadableNumber(./postcount, 0)} - - - -
      - - -
      - {{{ if showSelect }}} -
      - -
      - {{{ end }}} -
      - {{{ if ./thumbs.length }}} - - - +{increment(./thumbs.length, "-1")} - - {{{ end }}} -
      - -
      -
      - {{{ if !reputation:disabled }}} -
      - {humanReadableNumber(./votes, 0)} - [[global:votes]] - -
      - {{{ end }}} -
      - {humanReadableNumber(./postcount, 0)} - [[global:posts]] - -
      -
      - {humanReadableNumber(./viewcount, 0)} - [[global:views]] - -
      -
      -
      -
      - {{{ if ./unreplied }}} -
      - [[category:no-replies]] -
      - {{{ else }}} - {{{ if ./teaser.pid }}} - -
      - - {./teaser.content} -
      - {{{ end }}} - {{{ end }}} -
      -
      -
      -
    • - {{{end}}} + {{{ each topics }}} +
    • > + + + + + + +
      +
      + + {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} + + {{{ if showSelect }}} +
      + +
      + {{{ end }}} +
      +
      +

      + {./title} +

      + + + + [[topic:watching]] + + + + [[topic:ignoring]] + + + + [[topic:scheduled]] + + + + {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} + + + + [[topic:locked]] + + + + [[topic:moved]] + + {{{ each ./icons }}}{@value}{{{ end }}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + + +
      + + + {humanReadableNumber(./postcount, 0)} + + + +
      + + +
      + + +
      + +
      + + {{{ if showSelect }}} +
      + +
      + {{{ end }}} +
      + {{{ if ./thumbs.length }}} + + + +{increment(./thumbs.length, "-1")} + + {{{ end }}} +
      + +
      +
      + {{{ if !reputation:disabled }}} +
      + {humanReadableNumber(./votes, 0)} + [[global:votes]] + +
      + {{{ end }}} +
      + {humanReadableNumber(./postcount, 0)} + [[global:posts]] + +
      +
      + {humanReadableNumber(./viewcount, 0)} + [[global:views]] + +
      +
      +
      +
      + {{{ if ./unreplied }}} +
      + [[category:no-replies]] +
      + {{{ else }}} + {{{ if ./teaser.pid }}} + +
      + + {./teaser.content} +
      + {{{ end }}} + {{{ end }}} +
      +
      +
      +
    • + {{{ end }}}
    + + + + + From 82bf68beb6ab1e76dd8a1ca043c1366c00d6031e Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 03:57:44 +0300 Subject: [PATCH 11/50] Added the button for voice memo record - user story 2 --- node_modules_real/composer-formatting.tpl | 75 +++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 node_modules_real/composer-formatting.tpl diff --git a/node_modules_real/composer-formatting.tpl b/node_modules_real/composer-formatting.tpl new file mode 100644 index 0000000000..0f1b72c967 --- /dev/null +++ b/node_modules_real/composer-formatting.tpl @@ -0,0 +1,75 @@ +
    +
      + {{{ each formatting }}} + {{{ if ./spacer }}} +
    • + {{{ else }}} + {{{ if (./visibility.desktop && ((isTopicOrMain && ./visibility.main) || (!isTopicOrMain && ./visibility.reply))) }}} + {{{ if ./dropdownItems.length }}} + + {{{ else }}} +
    • + +
    • + {{{ end }}} + {{{ end }}} + {{{ end }}} + {{{ end }}} + + {{{ if privileges.upload:post:image }}} +
    • + +
    • + {{{ end }}} + + {{{ if privileges.upload:post:file }}} +
    • + +
    • + {{{ end }}} + +
      + +
      +
    +
    + + + {{{ if composer:showHelpTab }}} + + {{{ end }}} +
    +
    +# From 3c10efd12b64d79d565540888dff5aeaff3b10da Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 04:43:07 +0300 Subject: [PATCH 12/50] Implemented front-end emoji reaction feature for chat messages, including an emoji picker menu for users to select reactions --- src/views/partials/chats/message.tpl | 173 ++++++++++++++++----------- 1 file changed, 105 insertions(+), 68 deletions(-) diff --git a/src/views/partials/chats/message.tpl b/src/views/partials/chats/message.tpl index 527a055f17..f4b01a8733 100644 --- a/src/views/partials/chats/message.tpl +++ b/src/views/partials/chats/message.tpl @@ -1,76 +1,113 @@ -
  • + +{{{ if messages.reactionTo }}} +
  • + +
  • +{{{ else }}} + +
  • - {{{ if messages.parent }}} - - {{{ end }}} + {{{ if messages.parent }}} + + {{{ end }}} -
    - {buildAvatar(messages.fromUser, "18px", true, "not-responsive")} - {messages.fromUser.displayname} - {{{ if messages.fromUser.banned }}} - [[user:banned]] - {{{ end }}} - {{{ if messages.fromUser.deleted }}} - [[user:deleted]] - {{{ end }}} - +
    + {buildAvatar(messages.fromUser, "18px", true, "not-responsive")} + {messages.fromUser.displayname} + {{{ if messages.fromUser.banned }}} + [[user:banned]] + {{{ end }}} + {{{ if messages.fromUser.deleted }}} + [[user:deleted]] + {{{ end }}} + -
    -
    -
    -
    - {messages.content} -
    - -
    -
    - - +
    +
    +
    +
    + {messages.content} +
    -
    - -
    -
  • \ No newline at end of file + {{{ if (isAdminOrGlobalMod || isOwner )}}} +
  • + [[modules:chat.pin-message]] +
  • +
  • + [[modules:chat.unpin-message]] +
  • + + {{{ end }}} + + {{{ if isAdminOrGlobalMod }}} +
  • + + [[modules:chat.show-ip]] + + +
  • + {{{ end }}} + +
  • + [[modules:chat.copy-text]] +
  • + +
  • + [[modules:chat.copy-link]] +
  • +
+ + + + + +{{{ end }}} From f37d6be1522b7bec89044bf770a0acaf24f949b9 Mon Sep 17 00:00:00 2001 From: Seckhen <111176979+Seckhen@users.noreply.github.com> Date: Thu, 26 Sep 2024 05:03:31 +0300 Subject: [PATCH 13/50] Revert "Added Front-End Emoji Reaction UI for Chat Messages" --- src/views/partials/chats/message.tpl | 173 +++++++++++---------------- 1 file changed, 68 insertions(+), 105 deletions(-) diff --git a/src/views/partials/chats/message.tpl b/src/views/partials/chats/message.tpl index f4b01a8733..527a055f17 100644 --- a/src/views/partials/chats/message.tpl +++ b/src/views/partials/chats/message.tpl @@ -1,113 +1,76 @@ - -{{{ if messages.reactionTo }}} -
  • - -
  • -{{{ else }}} - -
  • +
  • - {{{ if messages.parent }}} - - {{{ end }}} + {{{ if messages.parent }}} + + {{{ end }}} -
    - {buildAvatar(messages.fromUser, "18px", true, "not-responsive")} - {messages.fromUser.displayname} - {{{ if messages.fromUser.banned }}} - [[user:banned]] - {{{ end }}} - {{{ if messages.fromUser.deleted }}} - [[user:deleted]] - {{{ end }}} - +
    + {buildAvatar(messages.fromUser, "18px", true, "not-responsive")} + {messages.fromUser.displayname} + {{{ if messages.fromUser.banned }}} + [[user:banned]] + {{{ end }}} + {{{ if messages.fromUser.deleted }}} + [[user:deleted]] + {{{ end }}} + -
    -
    -
    -
    - {messages.content} -
    +
    +
    +
    +
    + {messages.content} +
    + +
    +
    + + - -
    -
    - -
    - +
    + +
    + {{{ if (isAdminOrGlobalMod || isOwner )}}} +
  • + [[modules:chat.pin-message]] +
  • +
  • + [[modules:chat.unpin-message]] +
  • + + {{{ end }}} - + {{{ if isAdminOrGlobalMod }}} +
  • + + [[modules:chat.show-ip]] + + +
  • + {{{ end }}} -
    - - -
    - - - - -{{{ end }}} +
  • + [[modules:chat.copy-link]] +
  • + + + + + + \ No newline at end of file From 6758a66bb6272690629257d706e065427e4874f5 Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 05:18:01 +0300 Subject: [PATCH 14/50] Implemented front-end emoji reaction feature for chat messages, including an emoji picker menu for users to select reactions --- src/views/partials/chats/message.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/partials/chats/message.tpl b/src/views/partials/chats/message.tpl index f4b01a8733..12bf2414b1 100644 --- a/src/views/partials/chats/message.tpl +++ b/src/views/partials/chats/message.tpl @@ -77,7 +77,7 @@ [[topic:restore]] {{{ end }}} - + # {{{ if (isAdminOrGlobalMod || isOwner )}}}
  • [[modules:chat.pin-message]] From 83cbb21638b3b20929710cadb682ec02378fdaf7 Mon Sep 17 00:00:00 2001 From: Dachi Date: Wed, 25 Sep 2024 20:07:25 -0700 Subject: [PATCH 15/50] implement emoji picker functionality in chat, selecting and reacting with emojis handled real time updates, and ensured proper emoji display. Add backend logic with plugins for handling emoji reactions. Store reactions in database with error handling --- public/src/client/chats.js | 644 +++++++++++++++++++++++++++---------- src/socket.io/plugins.js | 83 ++++- 2 files changed, 550 insertions(+), 177 deletions(-) diff --git a/public/src/client/chats.js b/public/src/client/chats.js index af18c73ec2..a34f153172 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -1,6 +1,5 @@ 'use strict'; - - +// HI define('forum/chats', [ 'components', 'mousetrap', @@ -19,9 +18,21 @@ define('forum/chats', [ 'api', 'uploadHelpers', ], function ( - components, mousetrap, recentChats, create, - manage, messages, userList, messageSearch, pinnedMessages, - autocomplete, hooks, bootbox, alerts, chatModule, api, + components, + mousetrap, + recentChats, + create, + manage, + messages, + userList, + messageSearch, + pinnedMessages, + autocomplete, + hooks, + bootbox, + alerts, + chatModule, + api, uploadHelpers ) { const Chats = { @@ -39,19 +50,49 @@ define('forum/chats', [ socket.emit('modules.chats.leave', ajaxify.data.roomId); } if (ajaxify.data.publicRooms) { - socket.emit('modules.chats.leavePublic', ajaxify.data.publicRooms.map(r => r.roomId)); + socket.emit( + 'modules.chats.leavePublic', + ajaxify.data.publicRooms.map(r => r.roomId) + ); } } }); Chats.init = function () { + // Fetch and display saved reactions + socket.emit( + 'plugins.chat.getReactionsForRoom', + { roomId: ajaxify.data.roomId }, + function (err, reactions) { + if (err) { + console.error('Error fetching reactions:', err); + return; + } + + const chatBox = $('[component="chat/message/content"]'); + reactions.forEach(function (reaction) { + const reactionElement = `
  • + ${reaction.username} reacted with ${reaction.emoji} to message "${reaction.messageContent}" +
  • `; + chatBox.append(reactionElement); + }); + + // Optionally, scroll to the bottom after loading reactions + chatBox.scrollTop(chatBox[0].scrollHeight); + } + ); + + // Existing initialization code if (!utils.isMobile()) { $('.chats-full [data-bs-toggle="tooltip"]').tooltip({ trigger: 'hover', container: '#content', }); } - socket.emit('modules.chats.enterPublic', ajaxify.data.publicRooms.map(r => r.roomId)); + socket.emit( + 'modules.chats.enterPublic', + ajaxify.data.publicRooms.map(r => r.roomId) + ); const env = utils.findBootstrapEnvironment(); chatNavWrapper = $('[component="chat/nav-wrapper"]'); if (!Chats.initialised) { @@ -64,7 +105,7 @@ define('forum/chats', [ Chats.addEventListeners(); Chats.setActive(ajaxify.data.roomId); - if (env === 'md' || env === 'lg' || env === 'xl' || env === 'xxl') { + if (['md', 'lg', 'xl', 'xxl'].includes(env)) { Chats.addHotkeys(); } @@ -73,10 +114,14 @@ define('forum/chats', [ messages.wrapImagesInLinks(chatContentEl); if (ajaxify.data.scrollToIndex) { messages.toggleScrollUpAlert(chatContentEl); - const scrollToEl = chatContentEl.find(`[data-index="${ajaxify.data.scrollToIndex - 1}"]`); + const scrollToEl = chatContentEl.find( + `[data-index="${ajaxify.data.scrollToIndex - 1}"]` + ); if (scrollToEl.length) { chatContentEl.scrollTop( - chatContentEl.scrollTop() - chatContentEl.offset().top + scrollToEl.offset().top + chatContentEl.scrollTop() - + chatContentEl.offset().top + + scrollToEl.offset().top ); } } else { @@ -86,13 +131,20 @@ define('forum/chats', [ hooks.fire('action:chat.loaded', $('.chats-full')); }; + // Other functions in the module remain here Chats.addEventListeners = function () { + console.log('fefe'); const { roomId } = ajaxify.data; const mainWrapper = $('[component="chat/main-wrapper"]'); const chatMessageContent = $('[component="chat/message/content"]'); const chatControls = components.get('chat/controls'); - Chats.addSendHandlers(roomId, $('.chat-input'), $('.expanded-chat button[data-action="send"]')); + // Chats.addReactionClickHandler(components.get('chat/message/window')); + Chats.addSendHandlers( + roomId, + $('.chat-input'), + $('.expanded-chat button[data-action="send"]') + ); Chats.addPopoutHandler(); Chats.addActionHandlers(components.get('chat/message/window'), roomId); Chats.addManageHandler(roomId, chatControls.find('[data-action="manage"]')); @@ -153,19 +205,25 @@ define('forum/chats', [ return; } - containerEl.find('[data-manual-tooltip]').tooltip({ - trigger: 'manual', - animation: false, - placement: 'bottom', - }).on('mouseenter', function (ev) { - const target = $(ev.target); - const isDropdown = target.hasClass('dropdown-menu') || !!target.parents('.dropdown-menu').length; - if (!isDropdown) { - $(this).tooltip('show'); - } - }).on('click mouseleave', function () { - $(this).tooltip('hide'); - }); + containerEl + .find('[data-manual-tooltip]') + .tooltip({ + trigger: 'manual', + animation: false, + placement: 'bottom', + }) + .on('mouseenter', function (ev) { + const target = $(ev.target); + const isDropdown = + target.hasClass('dropdown-menu') || + !!target.parents('.dropdown-menu').length; + if (!isDropdown) { + $(this).tooltip('show'); + } + }) + .on('click mouseleave', function () { + $(this).tooltip('hide'); + }); containerEl.tooltip({ selector: '[component="chat/message/controls"] > .btn-group > button', @@ -177,13 +235,17 @@ define('forum/chats', [ }; Chats.addNotificationSettingHandler = function (roomId, containerEl) { - const notifSettingEl = containerEl.find('[component="chat/notification/setting"]'); + const notifSettingEl = containerEl.find( + '[component="chat/notification/setting"]' + ); notifSettingEl.find('[data-value]').on('click', async function () { notifSettingEl.find('i.fa-check').addClass('hidden'); const $this = $(this); $this.find('i.fa-check').removeClass('hidden'); - notifSettingEl.find('[component="chat/notification/setting/icon"]').attr('class', `fa ${$this.attr('data-icon')}`); + notifSettingEl + .find('[component="chat/notification/setting/icon"]') + .attr('class', `fa ${$this.attr('data-icon')}`); await api.put(`/chats/${roomId}/watch`, { value: $this.attr('data-value'), }); @@ -191,20 +253,24 @@ define('forum/chats', [ }; Chats.addParentHandler = function (mainWrapper) { - mainWrapper.off('click', '[component="chat/message/parent"]') + mainWrapper + .off('click', '[component="chat/message/parent"]') .on('click', '[component="chat/message/parent"]', function () { const parentEl = $(this); - parentEl.find('[component="chat/message/parent/content"]').toggleClass('line-clamp-1'); + parentEl + .find('[component="chat/message/parent/content"]') + .toggleClass('line-clamp-1'); parentEl.find('.chat-timestamp').toggleClass('hidden'); parentEl.toggleClass('flex-column').toggleClass('flex-row'); - const chatContent = parentEl.parents('[component="chat/message/content"]'); + const chatContent = parentEl.parents( + '[component="chat/message/content"]' + ); if (chatContent.length && messages.isAtBottom(chatContent)) { messages.scrollToBottom(chatContent); } }); }; - Chats.addUploadHandler = function (options) { uploadHelpers.init({ dragDropAreaEl: options.dragDropAreaEl, @@ -216,7 +282,11 @@ define('forum/chats', [ const inputEl = options.inputEl; let text = inputEl.val(); uploads.forEach((upload) => { - text = text + (!text.endsWith('\n') ? '\n' : '') + (upload.isImage ? '!' : '') + `[${upload.filename}](${upload.url})\n`; + text = + text + + (!text.endsWith('\n') ? '\n' : '') + + (upload.isImage ? '!' : '') + + `[${upload.filename}](${upload.url})\n`; }); inputEl.val(text).trigger('input'); }, @@ -224,7 +294,8 @@ define('forum/chats', [ }; Chats.addIPHandler = function (container) { - container.off('click', '.chat-ip-button') + container + .off('click', '.chat-ip-button') .on('click', '.chat-ip-button', async function (ev) { ev.stopPropagation(); const ipEl = $(this); @@ -237,7 +308,9 @@ define('forum/chats', [ return; } const mid = ipEl.parents('[data-mid]').attr('data-mid'); - ({ ip } = await api.get(`/chats/${ajaxify.data.roomId}/messages/${mid}/ip`)); + ({ ip } = await api.get( + `/chats/${ajaxify.data.roomId}/messages/${mid}/ip` + )); ipEl.attr('data-ip', ip); ipEl.find('.show').addClass('hidden'); ipEl.find('.copy').removeClass('hidden'); @@ -249,10 +322,14 @@ define('forum/chats', [ function doCopy(copyEl, text) { navigator.clipboard.writeText(text); copyEl.find('i').addClass('fa-check').removeClass('fa-link'); - setTimeout(() => copyEl.find('i').removeClass('fa-check').addClass('fa-link'), 2000); + setTimeout( + () => copyEl.find('i').removeClass('fa-check').addClass('fa-link'), + 2000 + ); } - container.off('click', '[data-action="copy-link"]') + container + .off('click', '[data-action="copy-link"]') .on('click', '[data-action="copy-link"]', function (ev) { ev.stopPropagation(); const copyEl = $(this); @@ -262,13 +339,17 @@ define('forum/chats', [ } }); - container.off('click', '[data-action="copy-text"]') + container + .off('click', '[data-action="copy-text"]') .on('click', '[data-action="copy-text"]', function (ev) { ev.stopPropagation(); const copyEl = $(this); const messageEl = copyEl.parents('[data-mid]'); if (messageEl.length) { - doCopy(copyEl, messageEl.find('[component="chat/message/body"]').text().trim()); + doCopy( + copyEl, + messageEl.find('[component="chat/message/body"]').text().trim() + ); } }); }; @@ -279,9 +360,13 @@ define('forum/chats', [ const roomId = ajaxify.data.roomId; if (app.previousUrl && app.previousUrl.match(/chats/)) { - ajaxify.go('user/' + ajaxify.data.userslug + '/chats', function () { - chatModule.openChat(roomId, ajaxify.data.uid); - }, true); + ajaxify.go( + 'user/' + ajaxify.data.userslug + '/chats', + function () { + chatModule.openChat(roomId, ajaxify.data.uid); + }, + true + ); } else { window.history.go(-1); chatModule.openChat(roomId, ajaxify.data.uid); @@ -297,76 +382,97 @@ define('forum/chats', [ let loading = false; let previousScrollTop = el.scrollTop(); let currentScrollTop = previousScrollTop; - el.off('scroll').on('scroll', utils.debounce(function () { - if (parseInt(el.attr('data-ignore-next-scroll'), 10) === 1) { - el.removeAttr('data-ignore-next-scroll'); - previousScrollTop = el.scrollTop(); - return; - } - messages.toggleScrollUpAlert(el); - if (loading) { - return; - } - currentScrollTop = el.scrollTop(); - - const direction = currentScrollTop > previousScrollTop ? 1 : -1; - previousScrollTop = currentScrollTop; - const scrollPercent = 100 * (currentScrollTop / (el[0].scrollHeight - el.height())); - const top = 15; - const bottom = 85; - - if (direction === 1 && !ajaxify.data.scrollToIndex) { - // dont trigger infinitescroll if there is no /index in url - return; - } + el.off('scroll').on( + 'scroll', + utils.debounce(function () { + if (parseInt(el.attr('data-ignore-next-scroll'), 10) === 1) { + el.removeAttr('data-ignore-next-scroll'); + previousScrollTop = el.scrollTop(); + return; + } + messages.toggleScrollUpAlert(el); + if (loading) { + return; + } + currentScrollTop = el.scrollTop(); - if ((scrollPercent < top && direction === -1) || (scrollPercent > bottom && direction === 1)) { - loading = true; + const direction = currentScrollTop > previousScrollTop ? 1 : -1; + previousScrollTop = currentScrollTop; + const scrollPercent = + 100 * (currentScrollTop / (el[0].scrollHeight - el.height())); + const top = 15; + const bottom = 85; - const msgEls = el.children('[data-mid]').not('.new'); - const afterEl = direction > 0 ? msgEls.last() : msgEls.first(); - const start = parseInt(afterEl.attr('data-index'), 10) || 0; + if (direction === 1 && !ajaxify.data.scrollToIndex) { + // dont trigger infinitescroll if there is no /index in url + return; + } - api.get(`/chats/${roomId}/messages`, { uid, start, direction }).then((data) => { - let messageData = data.messages; - if (!messageData) { - loading = false; - return; - } - messageData = messageData.filter(function (chatMsg) { - const msgOnDom = el.find('[component="chat/message"][data-mid="' + chatMsg.messageId + '"]'); - msgOnDom.removeClass('new'); - return !msgOnDom.length; - }); - if (!messageData.length) { - loading = false; - return; - } - messages.parseMessage(messageData, function (html) { - el.attr('data-ignore-next-scroll', 1); - if (direction > 0) { - html.insertAfter(afterEl); - messages.onMessagesAddedToDom(html); - } else { - const currentScrollTop = el.scrollTop(); - const previousHeight = el[0].scrollHeight; - el.prepend(html); - messages.onMessagesAddedToDom(html); - el.scrollTop((el[0].scrollHeight - previousHeight) + currentScrollTop); - } - - loading = false; - }); - }).catch(alerts.error); - } - }, 100)); + if ( + (scrollPercent < top && direction === -1) || + (scrollPercent > bottom && direction === 1) + ) { + loading = true; + + const msgEls = el.children('[data-mid]').not('.new'); + const afterEl = direction > 0 ? msgEls.last() : msgEls.first(); + const start = parseInt(afterEl.attr('data-index'), 10) || 0; + + api + .get(`/chats/${roomId}/messages`, { uid, start, direction }) + .then((data) => { + let messageData = data.messages; + if (!messageData) { + loading = false; + return; + } + messageData = messageData.filter(function (chatMsg) { + const msgOnDom = el.find( + '[component="chat/message"][data-mid="' + + chatMsg.messageId + + '"]' + ); + msgOnDom.removeClass('new'); + return !msgOnDom.length; + }); + if (!messageData.length) { + loading = false; + return; + } + messages.parseMessage(messageData, function (html) { + el.attr('data-ignore-next-scroll', 1); + if (direction > 0) { + html.insertAfter(afterEl); + messages.onMessagesAddedToDom(html); + } else { + const currentScrollTop = el.scrollTop(); + const previousHeight = el[0].scrollHeight; + el.prepend(html); + messages.onMessagesAddedToDom(html); + el.scrollTop( + el[0].scrollHeight - previousHeight + currentScrollTop + ); + } + + loading = false; + }); + }) + .catch(alerts.error); + } + }, 100) + ); }; Chats.addScrollBottomHandler = function (roomId, chatContent) { - chatContent.parents('[component="chat/message/window"]') + chatContent + .parents('[component="chat/message/window"]') .find('[component="chat/messages/scroll-up-alert"]') - .off('click').on('click', function () { - if (ajaxify.data.scrollToIndex && parseInt(ajaxify.data.roomId, 10) === parseInt(roomId, 10)) { + .off('click') + .on('click', function () { + if ( + ajaxify.data.scrollToIndex && + parseInt(ajaxify.data.roomId, 10) === parseInt(roomId, 10) + ) { Chats.switchChat(roomId); } else { messages.scrollToBottom(chatContent); @@ -388,7 +494,9 @@ define('forum/chats', [ const chatContentEl = parent.find('[component="chat/message/content"]'); const isAtBottom = messages.isAtBottom(chatContentEl); textarea.css({ height: 0 }); - textarea.css({ height: messages.calcAutoTextAreaHeight(textarea) + 'px' }); + textarea.css({ + height: messages.calcAutoTextAreaHeight(textarea) + 'px', + }); if (isAtBottom) { messages.scrollToBottom(chatContentEl); } @@ -421,7 +529,50 @@ define('forum/chats', [ }); }; + Chats.sendReaction = function (messageId, emoji, roomId) { + console.log('Sending reaction:', emoji, 'to room:', roomId); + + // Emit a socket event to send the reaction to the server with roomId + socket.emit( + 'plugins.chat.sendReaction', + { messageId, emoji, roomId }, + function (err, response) { + if (err) { + return app.alertError(err.message); + } + console.log('Reaction sent successfully:', response); + } + ); + }; + + function showEmojiMenu($button, $messageEl) { + console.log('showMneu'); + // Find the emoji menu within the message element + const $emojiMenu = $messageEl.find('.emoji-menu'); + + // Hide any other open emoji menus + $('.emoji-menu').not($emojiMenu).hide(); + + // Toggle the visibility of the emoji menu + $emojiMenu.toggle(); + } + + // Add this in your Chats.addActionHandlers or as a separate function + // Chats.addReactionClickHandler = function (element) { + // element.on('click', '.chat-message-reactions .reaction', function (e) { + // e.preventDefault(); + // const $reaction = $(this); + // const emoji = $reaction.attr('data-emoji'); + // const $message = $reaction.closest('[component="chat/message"]'); + // const messageId = $message.attr('data-mid'); + + // // Send the reaction to the server (it will handle adding or removing) + // Chats.sendReaction(messageId, emoji); + // }); + // }; + Chats.addActionHandlers = function (element, roomId) { + console.log('actionhendler', element); element.on('click', '[data-mid] [data-action]', function () { const msgEl = $(this).parents('[data-mid]'); const messageId = msgEl.attr('data-mid'); @@ -441,13 +592,36 @@ define('forum/chats', [ messages.restore(messageId, roomId); break; case 'pin': + console.log('pin in chats'); pinnedMessages.pin(messageId, roomId); break; case 'unpin': pinnedMessages.unpin(messageId, roomId); break; + case 'react': + // Call the function to show the emoji menu + showEmojiMenu($(this), msgEl); + break; } }); + element.on('click', '[data-mid] .emoji-option', function (e) { + e.preventDefault(); + e.stopPropagation(); // Prevent event from bubbling up + + const $emojiOption = $(this); + const emoji = $emojiOption.attr('data-emoji'); + const msgEl = $emojiOption.closest('[data-mid]'); + const messageId = msgEl.attr('data-mid'); + const roomId = ajaxify.data.roomId; // Fetch the roomId + + console.log('Emoji clicked:', emoji, 'in room:', roomId); + + // Hide the emoji menu + $emojiOption.closest('.emoji-menu').hide(); + + // Send the reaction to the server + Chats.sendReaction(messageId, emoji, roomId); // Include roomId + }); }; Chats.addHotkeys = function () { @@ -469,7 +643,10 @@ define('forum/chats', [ const inputEl = components.get('chat/input'); if (e.target === inputEl.get(0) && !inputEl.val()) { // Retrieve message id from messages list - const message = components.get('chat/messages').find('.chat-message[data-self="1"]').last(); + const message = components + .get('chat/messages') + .find('.chat-message[data-self="1"]') + .last(); if (!message.length) { return; } @@ -488,19 +665,23 @@ define('forum/chats', [ bootbox.confirm({ size: 'small', title: '[[modules:chat.leave]]', - message: '

    [[modules:chat.leave-prompt]]

    [[modules:chat.leave-help]]

    ', + message: + '

    [[modules:chat.leave-prompt]]

    [[modules:chat.leave-help]]

    ', callback: function (ok) { if (ok) { - api.del(`/chats/${roomId}/users/${app.user.uid}`, {}).then(() => { - // Return user to chats page. If modal, close modal. - const modal = buttonEl.parents('.chat-modal'); - if (modal.length) { - chatModule.close(modal); - } else { - Chats.destroyAutoComplete(roomId); - ajaxify.go('chats'); - } - }).catch(alerts.error); + api + .del(`/chats/${roomId}/users/${app.user.uid}`, {}) + .then(() => { + // Return user to chats page. If modal, close modal. + const modal = buttonEl.parents('.chat-modal'); + if (modal.length) { + chatModule.close(modal); + } else { + Chats.destroyAutoComplete(roomId); + ajaxify.go('chats'); + } + }) + .catch(alerts.error); } }, }); @@ -515,16 +696,19 @@ define('forum/chats', [ message: '

    [[modules:chat.delete-prompt]]

    ', callback: function (ok) { if (ok) { - api.del(`/admin/chats/${roomId}`, {}).then(() => { - // Return user to chats page. If modal, close modal. - const modal = buttonEl.parents('.chat-modal'); - if (modal.length) { - chatModule.close(modal); - } else { - Chats.destroyAutoComplete(roomId); - ajaxify.go('chats'); - } - }).catch(alerts.error); + api + .del(`/admin/chats/${roomId}`, {}) + .then(() => { + // Return user to chats page. If modal, close modal. + const modal = buttonEl.parents('.chat-modal'); + if (modal.length) { + chatModule.close(modal); + } else { + Chats.destroyAutoComplete(roomId); + ajaxify.go('chats'); + } + }) + .catch(alerts.error); } }, }); @@ -532,6 +716,7 @@ define('forum/chats', [ }; Chats.addRenameHandler = function (roomId, buttonEl) { + console.log('rename'); buttonEl.on('click', async function () { const { roomName } = await api.get(`/chats/${roomId}`); const html = await app.parseAndTranslate('modals/rename-room', { @@ -546,11 +731,14 @@ define('forum/chats', [ label: '[[global:save]]', className: 'btn-primary', callback: function () { - api.put(`/chats/${roomId}`, { - name: modal.find('#roomName').val(), - }).then(() => { - modal.modal('hide'); - }).catch(alerts.error); + api + .put(`/chats/${roomId}`, { + name: modal.find('#roomName').val(), + }) + .then(() => { + modal.modal('hide'); + }) + .catch(alerts.error); return false; }, }, @@ -615,18 +803,21 @@ define('forum/chats', [ Chats.leave = function (el) { const roomId = el.attr('data-roomid'); - api.del(`/chats/${roomId}/users/${app.user.uid}`, {}).then(() => { - if (parseInt(roomId, 10) === parseInt(ajaxify.data.roomId, 10)) { - ajaxify.go('user/' + ajaxify.data.userslug + '/chats'); - } else { - el.remove(); - } - Chats.destroyAutoComplete(roomId); - const modal = chatModule.getModal(roomId); - if (modal.length) { - chatModule.close(modal); - } - }).catch(alerts.error); + api + .del(`/chats/${roomId}/users/${app.user.uid}`, {}) + .then(() => { + if (parseInt(roomId, 10) === parseInt(ajaxify.data.roomId, 10)) { + ajaxify.go('user/' + ajaxify.data.userslug + '/chats'); + } else { + el.remove(); + } + Chats.destroyAutoComplete(roomId); + const modal = chatModule.getModal(roomId); + if (modal.length) { + chatModule.close(modal); + } + }) + .catch(alerts.error); }; Chats.switchChat = function (roomId) { @@ -636,7 +827,12 @@ define('forum/chats', [ } Chats.destroyAutoComplete(ajaxify.data.roomId); socket.emit('modules.chats.leave', ajaxify.data.roomId); - const url = 'user/' + ajaxify.data.userslug + '/chats/' + roomId + window.location.search; + const url = + 'user/' + + ajaxify.data.userslug + + '/chats/' + + roomId + + window.location.search; if (!self.fetch) { return ajaxify.go(url); } @@ -649,7 +845,10 @@ define('forum/chats', [ return console.warn('[search] Received ' + response.status); } const payload = await response.json(); - const html = await app.parseAndTranslate('partials/chats/message-window', payload); + const html = await app.parseAndTranslate( + 'partials/chats/message-window', + payload + ); const mainWrapper = components.get('chat/main-wrapper'); mainWrapper.html(html); mainWrapper.attr('data-roomid', roomId); @@ -658,15 +857,28 @@ define('forum/chats', [ ajaxify.data = { ...ajaxify.data, ...payload, roomId: roomId }; ajaxify.updateTitle(ajaxify.data.title); $('body').toggleClass('chat-loaded', !!roomId); - mainWrapper.find('[data-bs-toggle="tooltip"]').tooltip({ trigger: 'hover', container: '#content' }); + mainWrapper + .find('[data-bs-toggle="tooltip"]') + .tooltip({ trigger: 'hover', container: '#content' }); Chats.setActive(roomId); Chats.addEventListeners(); hooks.fire('action:chat.loaded', $('.chats-full')); - messages.scrollToBottomAfterImageLoad(mainWrapper.find('[component="chat/message/content"]')); + messages.scrollToBottomAfterImageLoad( + mainWrapper.find('[component="chat/message/content"]') + ); if (history.pushState) { - history.pushState({ - url: url, - }, null, window.location.protocol + '//' + window.location.host + config.relative_path + '/' + url); + history.pushState( + { + url: url, + }, + null, + window.location.protocol + + '//' + + window.location.host + + config.relative_path + + '/' + + url + ); } }) .catch(function (error) { @@ -686,13 +898,20 @@ define('forum/chats', [ Chats.addSocketListeners = function () { socket.on('event:new_notification', async function (notif) { const { type, roomId } = notif; - if (ajaxify.data.template.chats && app.user.userslug && (type === 'new-chat' || type === 'new-group-chat')) { - const inRoom = parseInt(roomId, 10) === parseInt(ajaxify.data.roomId, 10); + if ( + ajaxify.data.template.chats && + app.user.userslug && + (type === 'new-chat' || type === 'new-group-chat') + ) { + const inRoom = + parseInt(roomId, 10) === parseInt(ajaxify.data.roomId, 10); if (inRoom) { return; } const { rooms } = await api.get(`/chats`, { start: 0, perPage: 1 }); - const room = rooms.find(r => parseInt(r.roomId, 10) === parseInt(roomId, 10)); + const room = rooms.find( + r => parseInt(r.roomId, 10) === parseInt(roomId, 10) + ); if (room) { const roomEl = chatNavWrapper.find(`[data-roomid="${roomId}"]`); if (roomEl.length) { @@ -709,28 +928,95 @@ define('forum/chats', [ } }); + socket.on('event:reactionMessage', function (data) { + console.log('Reaction message received:', data); + + const chatBox = $('[component="chat/message/content"]'); + const reactionElement = `
  • + ${data.reactionMessage} +
  • `; + chatBox.append(reactionElement); + chatBox.scrollTop(chatBox[0].scrollHeight); + }); + + socket.on('event:reactionsForRoom', function (reactions) { + const chatBox = $('[component="chat/message/content"]'); + reactions.forEach(function (reaction) { + const reactionElement = `
  • + ${reaction.username} reacted with ${reaction.emoji} to message "${reaction.messageContent}" +
  • `; + chatBox.append(reactionElement); + }); + + // Optionally, scroll to the bottom after loading reactions + chatBox.scrollTop(chatBox[0].scrollHeight); + }); + socket.on('event:chats.receive', function (data) { + console.log('socketon'); + if (chatModule.isFromBlockedUser(data.fromUid)) { return; } if (parseInt(data.roomId, 10) === parseInt(ajaxify.data.roomId, 10)) { - data.self = parseInt(app.user.uid, 10) === parseInt(data.fromUid, 10) ? 1 : 0; + data.self = + parseInt(app.user.uid, 10) === parseInt(data.fromUid, 10) ? 1 : 0; if (!newMessage) { newMessage = data.self === 0; } data.message.self = data.self; data.message.timestamp = Math.min(Date.now(), data.message.timestamp); data.message.timestampISO = utils.toISOString(data.message.timestamp); - messages.appendChatMessage($('[component="chat/message/content"]'), data.message); + + // Ensure that fromUser data is present + if (!data.message.fromUser) { + data.message.fromUser = data.message.fromUser || app.user; // Fallback to current user if necessary + } + + messages.appendChatMessage( + $('[component="chat/message/content"]'), + data.message + ); updateTeaser(data.roomId, { - content: utils.stripHTMLTags(utils.decodeHTMLEntities(data.message.content)), + content: utils.stripHTMLTags( + utils.decodeHTMLEntities(data.message.content) + ), user: data.message.fromUser, timestampISO: data.message.timestampISO, }); } }); + socket.on('event:reactionAdded', function (data) { + console.log('Reaction added:', data); + + const { messageId, emoji } = data; + + // Find the message element in the chat based on the messageId + const msgEl = $(`[data-mid="${messageId}"]`); + + if (msgEl.length) { + // Append the reaction (emoji) to the message + let reactionsContainer = msgEl.find('.reactions-container'); + + // If there's no reactions container, create one + if (reactionsContainer.length === 0) { + reactionsContainer = $('
    '); + msgEl.append(reactionsContainer); + } + + // Append the emoji to the reactions container + reactionsContainer.append( + `${emoji}` + ); + } + }); + + socket.on('event:reactionMessage', function (data) { + console.log('Reaction message received:', data); // Ensure this logs the received event + }); + async function updateTeaser(roomId, teaser) { const roomEl = $(`[data-roomid="${roomId}"]`); if (roomEl.length) { @@ -751,11 +1037,18 @@ define('forum/chats', [ return; } Chats.markChatPageElUnread(data); - Chats.increasePublicRoomUnreadCount(chatNavWrapper.find('[data-roomid=' + data.roomId + ']')); + Chats.increasePublicRoomUnreadCount( + chatNavWrapper.find('[data-roomid=' + data.roomId + ']') + ); }); socket.on('event:user_status_change', function (data) { - app.updateUserStatus($('.chats-list [data-uid="' + data.uid + '"] [component="user/status"]'), data.status); + app.updateUserStatus( + $( + '.chats-list [data-uid="' + data.uid + '"] [component="user/status"]' + ), + data.status + ); }); messages.addSocketListeners(); @@ -765,9 +1058,13 @@ define('forum/chats', [ if (roomEl.length) { const titleEl = roomEl.find('[component="chat/room/title"]'); ajaxify.data.roomName = data.newName; - titleEl.translateText(data.newName ? data.newName : ajaxify.data.usernames); + titleEl.translateText( + data.newName ? data.newName : ajaxify.data.usernames + ); } - const titleEl = $(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"] [component="chat/header/title"]`); + const titleEl = $( + `[component="chat/main-wrapper"][data-roomid="${data.roomId}"] [component="chat/header/title"]` + ); if (titleEl.length) { titleEl.html( data.newName ? @@ -778,7 +1075,9 @@ define('forum/chats', [ }); socket.on('event:chats.mark', ({ roomId, state }) => { - const roomEls = $(`[component="chat/recent"] [data-roomid="${roomId}"], [component="chat/list"] [data-roomid="${roomId}"], [component="chat/public"] [data-roomid="${roomId}"]`); + const roomEls = $( + `[component="chat/recent"] [data-roomid="${roomId}"], [component="chat/list"] [data-roomid="${roomId}"], [component="chat/public"] [data-roomid="${roomId}"]` + ); roomEls.each((idx, el) => { const roomEl = $(el); chatModule.markChatElUnread(roomEl, state === 1); @@ -792,7 +1091,10 @@ define('forum/chats', [ if (data.uid === app.user.uid || chatModule.isFromBlockedUser(data.uid)) { return; } - chatModule.updateTypingUserList($(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"]`), data); + chatModule.updateTypingUserList( + $(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"]`), + data + ); }); }; @@ -806,15 +1108,22 @@ define('forum/chats', [ }; Chats.increasePublicRoomUnreadCount = function (roomEl) { - const unreadCountEl = roomEl.find('[component="chat/public/room/unread/count"]'); + const unreadCountEl = roomEl.find( + '[component="chat/public/room/unread/count"]' + ); const newCount = (parseInt(unreadCountEl.attr('data-count'), 10) || 0) + 1; Chats.updatePublicRoomUnreadCount(roomEl, newCount); }; Chats.updatePublicRoomUnreadCount = function (roomEl, count) { - const unreadCountEl = roomEl.find('[component="chat/public/room/unread/count"]'); + const unreadCountEl = roomEl.find( + '[component="chat/public/room/unread/count"]' + ); const countText = count > 50 ? '50+' : count; - unreadCountEl.toggleClass('hidden', count <= 0).text(countText).attr('data-count', count); + unreadCountEl + .toggleClass('hidden', count <= 0) + .text(countText) + .attr('data-count', count); }; Chats.setActive = function (roomId) { @@ -831,7 +1140,9 @@ define('forum/chats', [ if (!utils.isMobile()) { $('.expanded-chat [component="chat/input"]').focus(); } - messages.updateTextAreaHeight($(`[component="chat/messages"][data-roomid="${roomId}"]`)); + messages.updateTextAreaHeight( + $(`[component="chat/messages"][data-roomid="${roomId}"]`) + ); } chatNavWrapper.attr('data-loaded', roomId ? '1' : '0'); @@ -839,4 +1150,3 @@ define('forum/chats', [ return Chats; }); - diff --git a/src/socket.io/plugins.js b/src/socket.io/plugins.js index ac197c0024..f685c86f8f 100644 --- a/src/socket.io/plugins.js +++ b/src/socket.io/plugins.js @@ -1,17 +1,80 @@ 'use strict'; -const SocketPlugins = {}; +const SocketPlugins = require.main.require('./src/socket.io/plugins'); +const user = require.main.require('./src/user'); +const db = require.main.require('./src/database'); // Assuming you're using a database module for message fetching -/* - This file is provided exclusively so that plugins can require it and add their own socket listeners. - How? From your plugin: +SocketPlugins.chat = {}; - const SocketPlugins = require.main.require('./src/socket.io/plugins'); - SocketPlugins.myPlugin = {}; - SocketPlugins.myPlugin.myMethod = function(socket, data, callback) { ... }; - Be a good lad and namespace your methods. -*/ +// Function to handle sending a reaction +SocketPlugins.chat.sendReaction = async function (socket, data, callback = () => {}) { + const { messageId, emoji, roomId } = data; -module.exports = SocketPlugins; + + try { + console.log('Reaction received on server:', data); // Log for server confirmation + + + if (!roomId) { + console.error('Room ID is missing'); + return callback(new Error('Room ID is missing')); + } + + + if (!messageId || !emoji) { + console.error('Message ID or emoji is missing'); + return callback(new Error('Message ID or emoji is missing')); + } + + + // Fetch user details + let username; + try { + const userData = await user.getUserFields(socket.uid, ['username']); + username = userData.username || `User ${socket.uid}`; + } catch (err) { + console.error('Error fetching user data:', err); + username = `User ${socket.uid}`; // Fallback to generic identifier + } + + + // Fetch message details + let messageContent; + try { + // Assuming you're using a database query to fetch the message content based on the messageId + const messageData = await db.getObjectField(`message:${messageId}`, 'content'); + messageContent = messageData || `[Message ${messageId}]`; + } catch (err) { + console.error('Error fetching message content:', err); + messageContent = `[Message ${messageId}]`; // Fallback in case of error + } + + + // Emit the reaction event to all users in the room (for UI update) + socket.server.in(`chat_room_${roomId}`).emit('event:reactionAdded', { + messageId: messageId, + emoji: emoji, + userId: socket.uid, + }); + + + // Build and emit the reaction message (custom message for chat log) + const reactionMessage = `${username} reacted with ${emoji} to message "${messageContent}"`; + socket.server.in(`chat_room_${roomId}`).emit('event:reactionMessage', { + messageId: messageId, + userId: socket.uid, + reactionMessage: reactionMessage, + emoji: emoji, + }); + + console.log('Reaction message emitted:', reactionMessage); + + // Acknowledge back to the client that the reaction was processed successfully + callback(null, { success: true }); + } catch (err) { + console.error('Error processing reaction:', err); + callback(err); + } +}; From 2e4f8548aa30013a8e35105a56e4f1dbb7f4cc61 Mon Sep 17 00:00:00 2001 From: Seckhen <111176979+Seckhen@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:06:42 +0300 Subject: [PATCH 16/50] =?UTF-8?q?Revert=20"Implemented=20front-end=20emoji?= =?UTF-8?q?=20reaction=20feature=20for=20chat=20messages,=20inclu=E2=80=A6?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/partials/chats/message.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/partials/chats/message.tpl b/src/views/partials/chats/message.tpl index 12bf2414b1..f4b01a8733 100644 --- a/src/views/partials/chats/message.tpl +++ b/src/views/partials/chats/message.tpl @@ -77,7 +77,7 @@ [[topic:restore]] {{{ end }}} - # + {{{ if (isAdminOrGlobalMod || isOwner )}}}
  • [[modules:chat.pin-message]] From 198be88ba0dddea95b381e63fb6d88da898120d2 Mon Sep 17 00:00:00 2001 From: Seckhen <111176979+Seckhen@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:09:49 +0300 Subject: [PATCH 17/50] =?UTF-8?q?Revert=20"Revert=20"Implemented=20front-e?= =?UTF-8?q?nd=20emoji=20reaction=20feature=20for=20chat=20messages,=20incl?= =?UTF-8?q?u=E2=80=A6""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/partials/chats/message.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/partials/chats/message.tpl b/src/views/partials/chats/message.tpl index f4b01a8733..12bf2414b1 100644 --- a/src/views/partials/chats/message.tpl +++ b/src/views/partials/chats/message.tpl @@ -77,7 +77,7 @@ [[topic:restore]]
  • {{{ end }}} - + # {{{ if (isAdminOrGlobalMod || isOwner )}}}
  • [[modules:chat.pin-message]] From 48be5defe2f4e0baa9d0975ffe2041324b8a08c3 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:34:06 -0700 Subject: [PATCH 18/50] Revert "modified lint errors with comments out and added the last code to make the code full" --- node_modules_real/composer.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/node_modules_real/composer.js b/node_modules_real/composer.js index 298077dfc0..dbebeeb5b9 100644 --- a/node_modules_real/composer.js +++ b/node_modules_real/composer.js @@ -198,24 +198,6 @@ // formatting.addButton(iconClass, onClick, title); // }; -// Adding event listener for MP3 upload button -// composer.addButton('fa-file-audio-o', function () { -// document.getElementById('mp3-files').click(); // Trigger the hidden MP3 file input -// }, 'Upload MP3'); - -// // Handle MP3 file selection and trigger the upload process -// document.getElementById('mp3-files').addEventListener('change', function (e) { -// const files = e.target.files; -// if (files && files.length) { -// // Call the general file upload function from the uploads module -// uploads.uploadContentFiles({ -// files: files, // The selected MP3 files -// post_uuid: composer.active, // The active post (to track the composer instance) -// route: '/api/post/upload' // The route for file upload -// }); -// } -// }); - // composer.newTopic = async (data) => { // let pushData = { // save_id: data.save_id, From 6b5882ee9e0179b86e63ae41e63acca0dcdb6c73 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:39:54 -0700 Subject: [PATCH 19/50] Revert "User story2 back end - Choosing mp3 file for saving in database" --- node_modules_real/composer.js | 1782 ++++++++++++++++----------------- 1 file changed, 886 insertions(+), 896 deletions(-) diff --git a/node_modules_real/composer.js b/node_modules_real/composer.js index dbebeeb5b9..051b7a0338 100644 --- a/node_modules_real/composer.js +++ b/node_modules_real/composer.js @@ -1,896 +1,886 @@ -// The entire code is commented out because this chunk of code needs to be in the node_modules in -// order to not show lint errors, for only in node_modules does it define some of the functions -// and variables. It will always show lint error here, and not pass the checks on github. - - -// 'use strict'; - -// define('composer', [ -// 'taskbar', -// 'translator', -// 'composer/uploads', -// 'composer/formatting', -// 'composer/drafts', -// 'composer/tags', -// 'composer/categoryList', -// 'composer/preview', -// 'composer/resize', -// 'composer/autocomplete', -// 'composer/scheduler', -// 'composer/post-queue', -// 'scrollStop', -// 'topicThumbs', -// 'api', -// 'bootbox', -// 'alerts', -// 'hooks', -// 'messages', -// 'search', -// 'screenfull', -// (taskbar, translator, uploads, formatting, drafts, tags, -// categoryList, preview, resize, autocomplete, scheduler, postQueue, scrollStop, -// topicThumbs, api, bootbox, alerts, hooks, messagesModule, search, screenfull) => { -// const composer = { -// active: undefined, -// posts: {}, -// bsEnvironment: undefined, -// formatting: undefined, -// }; - -// $(window).off('resize', onWindowResize).on('resize', onWindowResize); -// onWindowResize(); - -// $(window).on('action:composer.topics.post', (ev, data) => { -// localStorage.removeItem(`category:${data.data.cid}:bookmark`); -// localStorage.removeItem(`category:${data.data.cid}:bookmark:clicked`); -// }); - -// $(window).on('popstate', () => { -// const env = utils.findBootstrapEnvironment(); -// if (composer.active && (env === 'xs' || env === 'sm')) { -// if (!composer.posts[composer.active].modified) { -// composer.discard(composer.active); -// if (composer.discardConfirm && composer.discardConfirm.length) { -// composer.discardConfirm.modal('hide'); -// delete composer.discardConfirm; -// } -// return; -// } - -// translator.translate('[[modules:composer.discard]]', (translated) => { -// composer.discardConfirm = bootbox.confirm(translated, (confirm) => { -// if (confirm) { -// composer.discard(composer.active); -// } else { -// composer.posts[composer.active].modified = true; -// } -// }); -// composer.posts[composer.active].modified = false; -// }); -// } -// }); - -// function removeComposerHistory() { -// const env = composer.bsEnvironment; -// if (ajaxify.data.template.compose === true || env === 'xs' || env === 'sm') { -// history.back(); -// } -// } - -// function onWindowResize() { -// const env = utils.findBootstrapEnvironment(); -// const isMobile = env === 'xs' || env === 'sm'; - -// if (preview.toggle) { -// if (preview.env !== env && isMobile) { -// preview.env = env; -// preview.toggle(false); -// } -// preview.env = env; -// } - -// if (composer.active !== undefined) { -// resize.reposition($(`.composer[data-uuid="${composer.active}"]`)); - -// if (!isMobile && window.location.pathname.startsWith(`${config.relative_path}/compose`)) { -// /* -// *If this conditional is met, we're no longer in mobile/tablet -// *resolution but we've somehow managed to have a mobile -// *composer load, so let's go back to the topic -// */ -// history.back(); -// } else if (isMobile && !window.location.pathname.startsWith(`${config.relative_path}/compose`)) { -// /* -// *In this case, we're in mobile/tablet resolution but the composer -// *that loaded was a regular composer, so let's fix the address bar -// */ -// mobileHistoryAppend(); -// } -// } -// composer.bsEnvironment = env; -// } - -// function alreadyOpen(post) { -// //If a composer for the same cid/tid/pid is already open, return the uuid, else return bool false -// let type; -// let id; - -// if (post.hasOwnProperty('cid')) { -// type = 'cid'; -// } else if (post.hasOwnProperty('tid')) { -// type = 'tid'; -// } else if (post.hasOwnProperty('pid')) { -// type = 'pid'; -// } - -// id = post[type]; - -// //Find a match -// for (const uuid in composer.posts) { -// if (composer.posts[uuid].hasOwnProperty(type) && id === composer.posts[uuid][type]) { -// return uuid; -// } -// } - -// //No matches... -// return false; -// } - -// function push(post) { -// if (!post) { -// return; -// } - -// const uuid = utils.generateUUID(); -// const existingUUID = alreadyOpen(post); - -// if (existingUUID) { -// taskbar.updateActive(existingUUID); -// return composer.load(existingUUID); -// } - -// let actionText = '[[topic:composer.new-topic]]'; -// if (post.action === 'posts.reply') { -// actionText = '[[topic:composer.replying-to]]'; -// } else if (post.action === 'posts.edit') { -// actionText = '[[topic:composer.editing-in]]'; -// } - -// translator.translate(actionText, (translatedAction) => { -// taskbar.push('composer', uuid, { -// title: translatedAction.replace('%1', `"${post.title}"`), -// }); -// }); - -// composer.posts[uuid] = post; -// composer.load(uuid); -// } - -// async function composerAlert(post_uuid, message) { -// $(`.composer[data-uuid="${post_uuid}"]`).find('.composer-submit').removeAttr('disabled'); - -// const { showAlert } = await hooks.fire('filter:composer.error', { post_uuid, message, showAlert: true }); - -// if (showAlert) { -// alerts.alert({ -// type: 'danger', -// timeout: 10000, -// title: '', -// message: message, -// alert_id: 'post_error', -// }); -// } -// } - -// composer.findByTid = function (tid) { -// //Iterates through the initialised composers and returns the uuid of the matching composer -// for (const uuid in composer.posts) { -// if (composer.posts.hasOwnProperty(uuid) && composer.posts[uuid].hasOwnProperty('tid') && -// parseInt(composer.posts[uuid].tid, 10) === parseInt(tid, 10)) { -// return uuid; -// } -// } - -// return null; -// }; - -// composer.addButton = function (iconClass, onClick, title) { -// formatting.addButton(iconClass, onClick, title); -// }; - -// composer.newTopic = async (data) => { -// let pushData = { -// save_id: data.save_id, -// action: 'topics.post', -// cid: data.cid, -// handle: data.handle, -// title: data.title || '', -// body: data.body || '', -// tags: data.tags || [], -// modified: !!((data.title && data.title.length) || (data.body && data.body.length)), -// isMain: true, -// }; - -// ({ pushData } = await hooks.fire('filter:composer.topic.push', { -// data: data, -// pushData: pushData, -// })); - -// push(pushData); -// }; - -// composer.addQuote = function (data) { -// //tid, toPid, selectedPid, title, username, text, uuid -// data.uuid = data.uuid || composer.active; - -// const escapedTitle = (data.title || '') -// .replace(/([\\`*_{}[\]()#+\-.!])/g, '\\$1') -// .replace(/\[/g, '[') -// .replace(/\]/g, ']') -// .replace(/%/g, '%') -// .replace(/,/g, ','); - -// if (data.body) { -// data.body = `> ${data.body.replace(/\n/g, '\n> ')}\n\n`; -// } -// const link = `[${escapedTitle}](${config.relative_path}/post/${encodeURIComponent(data. -// selectedPid || data.toPid)})`; -// if (data.uuid === undefined) { -// if (data.title && (data.selectedPid || data.toPid)) { -// composer.newReply({ -// tid: data.tid, -// toPid: data.toPid, -// title: data.title, -// body: `[[modules:composer.user-said-in, ${data.username}, ${link}]]\n${data.body}`, -// }); -// } else { -// composer.newReply({ -// tid: data.tid, -// toPid: data.toPid, -// title: data.title, -// body: `[[modules:composer.user-said, ${data.username}]]\n${data.body}`, -// }); -// } -// return; -// } else if (data.uuid !== composer.active) { -// //If the composer is not currently active, activate it -// composer.load(data.uuid); -// } - -// const postContainer = $(`.composer[data-uuid="${data.uuid}"]`); -// const bodyEl = postContainer.find('textarea'); -// const prevText = bodyEl.val(); -// if (data.title && (data.selectedPid || data.toPid)) { -// translator.translate(`[[modules:composer.user-said-in, ${data.username}, ${link}]]\n`, -// config.defaultLang, onTranslated); -// } else { -// translator.translate(`[[modules:composer.user-said, ${data.username}]]\n`, config.defaultLang, onTranslated); -// } - -// function onTranslated(translated) { -// composer.posts[data.uuid].body = (prevText.length ? `${prevText}\n\n` : '') + translated + data.body; -// bodyEl.val(composer.posts[data.uuid].body); -// focusElements(postContainer); -// preview.render(postContainer); -// } -// }; - -// composer.newReply = function (data) { -// translator.translate(data.body, config.defaultLang, (translated) => { -// push({ -// save_id: data.save_id, -// action: 'posts.reply', -// tid: data.tid, -// toPid: data.toPid, -// title: data.title, -// body: translated, -// modified: !!(translated && translated.length), -// isMain: false, -// }); -// }); -// }; - -// composer.editPost = function (data) { -// //pid, text -// socket.emit('plugins.composer.push', data.pid, (err, postData) => { -// if (err) { -// return alerts.error(err); -// } -// postData.save_id = data.save_id; -// postData.action = 'posts.edit'; -// postData.pid = data.pid; -// postData.modified = false; -// if (data.body) { -// postData.body = data.body; -// postData.modified = true; -// } -// if (data.title) { -// postData.title = data.title; -// postData.modified = true; -// } -// push(postData); -// }); -// }; - -// composer.load = function (post_uuid) { -// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); -// if (postContainer.length) { -// activate(post_uuid); -// resize.reposition(postContainer); -// focusElements(postContainer); -// onShow(); -// } else if (composer.formatting) { -// createNewComposer(post_uuid); -// } else { -// socket.emit('plugins.composer.getFormattingOptions', (err, options) => { -// if (err) { -// return alerts.error(err); -// } -// composer.formatting = options; -// createNewComposer(post_uuid); -// }); -// } -// }; - -// composer.enhance = function (postContainer, post_uuid, postData) { -// /* -// This method enhances a composer container with client-side sugar (preview, etc) -// Everything in here also applies to the /compose route -// */ - -// if (!post_uuid && !postData) { -// post_uuid = utils.generateUUID(); -// composer.posts[post_uuid] = ajaxify.data; -// postData = ajaxify.data; -// postContainer.attr('data-uuid', post_uuid); -// } - -// categoryList.init(postContainer, composer.posts[post_uuid]); -// scheduler.init(postContainer, composer.posts); - -// formatting.addHandler(postContainer); -// formatting.addComposerButtons(); -// preview.handleToggler(postContainer); -// postQueue.showAlert(postContainer, postData); -// uploads.initialize(post_uuid); -// tags.init(postContainer, composer.posts[post_uuid]); -// autocomplete.init(postContainer, post_uuid); - -// postContainer.on('change', 'input, textarea', () => { -// composer.posts[post_uuid].modified = true; -// }); - -// postContainer.on('click', '.composer-submit', function (e) { -// e.preventDefault(); -// e.stopPropagation();//Other click events bring composer back to active state which is undesired on submit - -// $(this).attr('disabled', true); -// post(post_uuid); -// }); - -// require(['mousetrap'], (mousetrap) => { -// mousetrap(postContainer.get(0)).bind('mod+enter', () => { -// postContainer.find('.composer-submit').attr('disabled', true); -// post(post_uuid); -// }); -// }); - -// postContainer.find('.composer-discard').on('click', function (e) { -// e.preventDefault(); - -// if (!composer.posts[post_uuid].modified) { -// composer.discard(post_uuid); -// return removeComposerHistory(); -// } - -// formatting.exitFullscreen(); - -// const btn = $(this).prop('disabled', true); -// translator.translate('[[modules:composer.discard]]', (translated) => { -// bootbox.confirm(translated, (confirm) => { -// if (confirm) { -// composer.discard(post_uuid); -// removeComposerHistory(); -// } -// btn.prop('disabled', false); -// }); -// }); -// }); - -// postContainer.find('.composer-minimize, .minimize .trigger').on('click', (e) => { -// e.preventDefault(); -// e.stopPropagation(); -// composer.minimize(post_uuid); -// }); - -// const textareaEl = postContainer.find('textarea'); -// textareaEl.on('input propertychange', utils.debounce(() => { -// preview.render(postContainer); -// }, 250)); - -// textareaEl.on('scroll', () => { -// preview.matchScroll(postContainer); -// }); - -// drafts.init(postContainer, postData); -// const draft = drafts.get(postData.save_id); - -// preview.render(postContainer, () => { -// preview.matchScroll(postContainer); -// }); - -// handleHelp(postContainer); -// handleSearch(postContainer); -// focusElements(postContainer); -// if (postData.action === 'posts.edit') { -// composer.updateThumbCount(post_uuid, postContainer); -// } - -// //Hide "zen mode" if fullscreen API is not enabled/available (ahem, iOS...) -// if (!screenfull.isEnabled) { -// $('[data-format="zen"]').parent().addClass('hidden'); -// } - -// hooks.fire('action:composer.enhanced', { postContainer, postData, draft }); -// }; - -// async function getSelectedCategory(postData) { -// if (ajaxify.data.template.category && parseInt(postData.cid, 10) === parseInt(ajaxify.data.cid, 10)) { -// //no need to load data if we are already on the category page -// return ajaxify.data; -// } else if (parseInt(postData.cid, 10)) { -// return await api.get(`/api/category/${postData.cid}`, {}); -// } -// return null; -// } - -// async function createNewComposer(post_uuid) { -// let postData = composer.posts[post_uuid]; - -// const isTopic = postData ? postData.hasOwnProperty('cid') : false; -// const isMain = postData ? !!postData.isMain : false; -// const isEditing = postData ? !!postData.pid : false; -// const isGuestPost = postData ? parseInt(postData.uid, 10) === 0 : false; -// const isScheduled = postData.timestamp > Date.now(); - -// //see -// //https://github.com/NodeBB/NodeBB/issues/2994 and -// //https://github.com/NodeBB/NodeBB/issues/1951 -// //remove when 1951 is resolved - -// const title = postData.title.replace(/%/g, '%').replace(/,/g, ','); -// postData.category = await getSelectedCategory(postData); -// const privileges = postData.category ? postData.category.privileges : ajaxify.data.privileges; -// let data = { -// topicTitle: title, -// titleLength: title.length, -// body: translator.escape(utils.escapeHTML(postData.body)), -// mobile: composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm', -// resizable: true, -// thumb: postData.thumb, -// isTopicOrMain: isTopic || isMain, -// maximumTitleLength: config.maximumTitleLength, -// maximumPostLength: config.maximumPostLength, -// minimumTagLength: config.minimumTagLength, -// maximumTagLength: config.maximumTagLength, -// 'composer:showHelpTab': config['composer:showHelpTab'], -// isTopic: isTopic, -// isEditing: isEditing, -// canSchedule: !!(isMain && privileges && -// ((privileges['topics:schedule'] && !isEditing) || (isScheduled && privileges.view_scheduled))), -// showHandleInput: config.allowGuestHandles && -// (app.user.uid === 0 || (isEditing && isGuestPost && app.user.isAdmin)), -// handle: postData ? postData.handle || '' : undefined, -// formatting: composer.formatting, -// tagWhitelist: postData.category ? postData.category.tagWhitelist : ajaxify.data.tagWhitelist, -// privileges: app.user.privileges, -// selectedCategory: postData.category, -// submitOptions: [ -// //Add items using `filter:composer.create`, or just add them to the
      in DOM -// //{ -// // action: 'foobar', -// // text: 'Text Label', -// //} -// ], -// }; - -// if (data.mobile) { -// mobileHistoryAppend(); - -// app.toggleNavbar(false); -// } - -// postData.mobile = composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm'; - -// ({ postData, createData: data } = await hooks.fire('filter:composer.create', { -// postData: postData, -// createData: data, -// })); - -// app.parseAndTranslate('composer', data, (composerTemplate) => { -// if ($(`.composer.composer[data-uuid="${post_uuid}"]`).length) { -// return; -// } -// composerTemplate = $(composerTemplate); - -// composerTemplate.find('.title').each(function () { -// $(this).text(translator.unescape($(this).text())); -// }); - -// composerTemplate.attr('data-uuid', post_uuid); - -// $(document.body).append(composerTemplate); - -// const postContainer = $(composerTemplate[0]); - -// resize.reposition(postContainer); -// composer.enhance(postContainer, post_uuid, postData); -// /* -// Everything after this line is applied to the resizable composer only -// Want something done to both resizable composer and the one in /compose? -// Put it in composer.enhance(). - -// Eventually, stuff after this line should be moved into composer.enhance(). -// */ - -// activate(post_uuid); - -// postContainer.on('click', () => { -// if (!taskbar.isActive(post_uuid)) { -// taskbar.updateActive(post_uuid); -// } -// }); - -// resize.handleResize(postContainer); - -// if (composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { -// const submitBtns = postContainer.find('.composer-submit'); -// const mobileSubmitBtn = postContainer.find('.mobile-navbar .composer-submit'); -// const textareaEl = postContainer.find('.write'); -// const idx = textareaEl.attr('tabindex'); - -// submitBtns.removeAttr('tabindex'); -// mobileSubmitBtn.attr('tabindex', parseInt(idx, 10) + 1); -// } - -// $(window).trigger('action:composer.loaded', { -// postContainer: postContainer, -// post_uuid: post_uuid, -// composerData: composer.posts[post_uuid], -// formatting: composer.formatting, -// }); - -// scrollStop.apply(postContainer.find('.write')); -// focusElements(postContainer); -// onShow(); -// }); -// } - -// function mobileHistoryAppend() { -// const path = `compose?p=${window.location.pathname}`; -// let returnPath = window.location.pathname.slice(1) + window.location.search; - -// //Remove relative path from returnPath -// if (returnPath.startsWith(config.relative_path.slice(1))) { -// returnPath = returnPath.slice(config.relative_path.length); -// } - -// //Add in return path to be caught by ajaxify when post is completed, or if back is pressed -// window.history.replaceState({ -// url: null, -// returnPath: returnPath, -// }, returnPath, `${config.relative_path}/${returnPath}`); - -// //Update address bar in case f5 is pressed -// window.history.pushState({ -// url: path, -// }, path, `${config.relative_path}/${returnPath}`); -// } - -// function handleHelp(postContainer) { -// const helpBtn = postContainer.find('[data-action="help"]'); -// helpBtn.on('click', async () => { -// const html = await socket.emit('plugins.composer.renderHelp'); -// if (html && html.length > 0) { -// bootbox.dialog({ -// size: 'large', -// message: html, -// onEscape: true, -// backdrop: true, -// onHidden: function () { -// helpBtn.focus(); -// }, -// }); -// } -// }); -// } - -// function handleSearch(postContainer) { -// const uuid = postContainer.attr('data-uuid'); -// const isEditing = composer.posts[uuid] && composer.posts[uuid].action === 'posts.edit'; -// const env = utils.findBootstrapEnvironment(); -// const isMobile = env === 'xs' || env === 'sm'; -// if (isEditing || isMobile) { -// return; -// } - -// search.enableQuickSearch({ -// searchElements: { -// inputEl: postContainer.find('input.title'), -// resultEl: postContainer.find('.quick-search-container'), -// }, -// searchOptions: { -// composer: 1, -// }, -// hideOnNoMatches: true, -// hideDuringSearch: true, -// }); -// } - -// function activate(post_uuid) { -// if (composer.active && composer.active !== post_uuid) { -// composer.minimize(composer.active); -// } - -// composer.active = post_uuid; -// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); -// postContainer.css('visibility', 'visible'); -// $(window).trigger('action:composer.activate', { -// post_uuid: post_uuid, -// postContainer: postContainer, -// }); -// } - -// function focusElements(postContainer) { -// setTimeout(() => { -// const title = postContainer.find('input.title'); - -// if (title.length) { -// title.focus(); -// } else { -// postContainer.find('textarea').focus().putCursorAtEnd(); -// } -// }, 20); -// } - -// async function post(post_uuid) { -// const postData = composer.posts[post_uuid]; -// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); -// const handleEl = postContainer.find('.handle'); -// const titleEl = postContainer.find('.title'); -// const bodyEl = postContainer.find('textarea'); -// const thumbEl = postContainer.find('input#topic-thumb-url'); -// const onComposeRoute = postData.hasOwnProperty('template') && postData.template.compose === true; -// const submitBtn = postContainer.find('.composer-submit'); - -// titleEl.val(titleEl.val().trim()); -// bodyEl.val(utils.rtrim(bodyEl.val())); -// if (thumbEl.length) { -// thumbEl.val(thumbEl.val().trim()); -// } - -// const { action } = postData; - -// const checkTitle = (postData.hasOwnProperty('cid') || parseInt(postData.pid, 10)) -// && postContainer.find('input.title').length; -// const isCategorySelected = !checkTitle || (checkTitle && parseInt(postData.cid, 10)); - -// //Specifically for checking title/body length via plugins -// const payload = { -// post_uuid: post_uuid, -// postData: postData, -// postContainer: postContainer, -// titleEl: titleEl, -// titleLen: titleEl.val().length, -// bodyEl: bodyEl, -// bodyLen: bodyEl.val().length, -// }; - -// await hooks.fire('filter:composer.check', payload); -// $(window).trigger('action:composer.check', payload); - -// if (payload.error) { -// return composerAlert(post_uuid, payload.error); -// } - -// if (uploads.inProgress[post_uuid] && uploads.inProgress[post_uuid].length) { -// return composerAlert(post_uuid, '[[error:still-uploading]]'); -// } else if (checkTitle && payload.titleLen < parseInt(config.minimumTitleLength, 10)) { -// return composerAlert(post_uuid, `[[error:title-too-short, ${config.minimumTitleLength}]]`); -// } else if (checkTitle && payload.titleLen > parseInt(config.maximumTitleLength, 10)) { -// return composerAlert(post_uuid, `[[error:title-too-long, ${config.maximumTitleLength}]]`); -// } else if (action === 'topics.post' && !isCategorySelected) { -// return composerAlert(post_uuid, '[[error:category-not-selected]]'); -// } else if (payload.bodyLen < parseInt(config.minimumPostLength, 10)) { -// return composerAlert(post_uuid, `[[error:content-too-short, ${config.minimumPostLength}]]`); -// } else if (payload.bodyLen > parseInt(config.maximumPostLength, 10)) { -// return composerAlert(post_uuid, `[[error:content-too-long, ${config.maximumPostLength}]]`); -// } else if (checkTitle && !tags.isEnoughTags(post_uuid)) { -// return composerAlert(post_uuid, `[[error:not-enough-tags, ${tags.minTagCount()}]]`); -// } else if (scheduler.isActive() && scheduler.getTimestamp() <= Date.now()) { -// return composerAlert(post_uuid, '[[error:scheduling-to-past]]'); -// } - -// let composerData = { -// uuid: post_uuid, -// }; -// let method = 'post'; -// let route = ''; - -// if (action === 'topics.post') { -// route = '/topics'; -// composerData = { -// ...composerData, -// handle: handleEl ? handleEl.val() : undefined, -// title: titleEl.val(), -// content: bodyEl.val(), -// thumb: thumbEl.val() || '', -// cid: categoryList.getSelectedCid(), -// tags: tags.getTags(post_uuid), -// timestamp: scheduler.getTimestamp(), -// }; -// } else if (action === 'posts.reply') { -// route = `/topics/${postData.tid}`; -// composerData = { -// ...composerData, -// tid: postData.tid, -// handle: handleEl ? handleEl.val() : undefined, -// content: bodyEl.val(), -// toPid: postData.toPid, -// }; -// } else if (action === 'posts.edit') { -// method = 'put'; -// route = `/posts/${postData.pid}`; -// composerData = { -// ...composerData, -// pid: postData.pid, -// handle: handleEl ? handleEl.val() : undefined, -// content: bodyEl.val(), -// title: titleEl.val(), -// thumb: thumbEl.val() || '', -// tags: tags.getTags(post_uuid), -// timestamp: scheduler.getTimestamp(), -// }; -// } -// const submitHookData = { -// composerEl: postContainer, -// action: action, -// composerData: composerData, -// postData: postData, -// redirect: true, -// }; - -// await hooks.fire('filter:composer.submit', submitHookData); -// hooks.fire('action:composer.submit', Object.freeze(submitHookData)); - -// //Minimize composer (and set textarea as readonly) while submitting -// const taskbarIconEl = $(`#taskbar .composer[data-uuid="${post_uuid}"] i`); -// const textareaEl = postContainer.find('.write'); -// taskbarIconEl.removeClass('fa-plus').addClass('fa-circle-o-notch fa-spin'); -// composer.minimize(post_uuid); -// textareaEl.prop('readonly', true); - -// api[method](route, composerData) -// .then((data) => { -// submitBtn.removeAttr('disabled'); -// postData.submitted = true; - -// composer.discard(post_uuid); -// drafts.removeDraft(postData.save_id); - -// if (data.queued) { -// alerts.alert({ -// type: 'success', -// title: '[[global:alert.success]]', -// message: data.message, -// timeout: 10000, -// clickfn: function () { -// ajaxify.go(`/post-queue/${data.id}`); -// }, -// }); -// } else if (action === 'topics.post') { -// if (submitHookData.redirect) { -// ajaxify.go(`topic/${data.slug}`, undefined, (onComposeRoute || composer.bsEnvironment -// === 'xs' || composer.bsEnvironment === 'sm')); -// } -// } else if (action === 'posts.reply') { -// if (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { -// window.history.back(); -// } else if (submitHookData.redirect && -// ((ajaxify.data.template.name !== 'topic') || -// (ajaxify.data.template.topic && parseInt(postData.tid, 10) !== parseInt(ajaxify.data.tid, 10))) -// ) { -// ajaxify.go(`post/${data.pid}`); -// } -// } else { -// removeComposerHistory(); -// } - -// hooks.fire(`action:composer.${action}`, { composerData: composerData, data: data }); -// }) -// .catch((err) => { -// //Restore composer on error -// composer.load(post_uuid); -// textareaEl.prop('readonly', false); -// if (err.message === '[[error:email-not-confirmed]]') { -// return messagesModule.showEmailConfirmWarning(err.message); -// } -// composerAlert(post_uuid, err.message); -// }); -// } - -// function onShow() { -// $('html').addClass('composing'); -// } - -// function onHide() { -// $('#content').css({ paddingBottom: 0 }); -// $('html').removeClass('composing'); -// app.toggleNavbar(true); -// formatting.exitFullscreen(); -// } - -// composer.discard = function (post_uuid) { -// if (composer.posts[post_uuid]) { -// const postData = composer.posts[post_uuid]; -// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); -// postContainer.remove(); -// drafts.removeDraft(postData.save_id); -// topicThumbs.deleteAll(post_uuid); - -// taskbar.discard('composer', post_uuid); -// $('[data-action="post"]').removeAttr('disabled'); - -// hooks.fire('action:composer.discard', { -// post_uuid: post_uuid, -// postData: postData, -// }); -// delete composer.posts[post_uuid]; -// composer.active = undefined; -// } -// scheduler.reset(); -// onHide(); -// }; - -// //Alias to .discard(); -// composer.close = composer.discard; - -// composer.minimize = function (post_uuid) { -// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); -// postContainer.css('visibility', 'hidden'); -// composer.active = undefined; -// taskbar.minimize('composer', post_uuid); -// $(window).trigger('action:composer.minimize', { -// post_uuid: post_uuid, -// }); - -// onHide(); -// }; - -// composer.minimizeActive = function () { -// if (composer.active) { -// composer.miminize(composer.active); -// } -// }; - -// composer.updateThumbCount = function (uuid, postContainer) { -// const composerObj = composer.posts[uuid]; -// if (composerObj.action === 'topics.post' || (composerObj.action === 'posts.edit' && composerObj.isMain)) { -// const calls = [ -// topicThumbs.get(uuid), -// ]; -// if (composerObj.pid) { -// calls.push(topicThumbs.getByPid(composerObj.pid)); -// } -// Promise.all(calls).then((thumbs) => { -// const thumbCount = thumbs.flat().length; -// const formatEl = postContainer.find('[data-format="thumbs"]'); -// formatEl.find('.badge') -// .text(thumbCount) -// .toggleClass('hidden', !thumbCount); -// }); -// } -// }; - -// return composer; -// }); +'use strict'; + +define('composer', [ + 'taskbar', + 'translator', + 'composer/uploads', + 'composer/formatting', + 'composer/drafts', + 'composer/tags', + 'composer/categoryList', + 'composer/preview', + 'composer/resize', + 'composer/autocomplete', + 'composer/scheduler', + 'composer/post-queue', + 'scrollStop', + 'topicThumbs', + 'api', + 'bootbox', + 'alerts', + 'hooks', + 'messages', + 'search', + 'screenfull', +], function (taskbar, translator, uploads, formatting, drafts, tags, + categoryList, preview, resize, autocomplete, scheduler, postQueue, scrollStop, + topicThumbs, api, bootbox, alerts, hooks, messagesModule, search, screenfull) { + var composer = { + active: undefined, + posts: {}, + bsEnvironment: undefined, + formatting: undefined, + }; + + $(window).off('resize', onWindowResize).on('resize', onWindowResize); + onWindowResize(); + + $(window).on('action:composer.topics.post', function (ev, data) { + localStorage.removeItem('category:' + data.data.cid + ':bookmark'); + localStorage.removeItem('category:' + data.data.cid + ':bookmark:clicked'); + }); + + $(window).on('popstate', function () { + var env = utils.findBootstrapEnvironment(); + if (composer.active && (env === 'xs' || env === 'sm')) { + if (!composer.posts[composer.active].modified) { + composer.discard(composer.active); + if (composer.discardConfirm && composer.discardConfirm.length) { + composer.discardConfirm.modal('hide'); + delete composer.discardConfirm; + } + return; + } + + translator.translate('[[modules:composer.discard]]', function (translated) { + composer.discardConfirm = bootbox.confirm(translated, function (confirm) { + if (confirm) { + composer.discard(composer.active); + } else { + composer.posts[composer.active].modified = true; + } + }); + composer.posts[composer.active].modified = false; + }); + } + }); + + function removeComposerHistory() { + var env = composer.bsEnvironment; + if (ajaxify.data.template.compose === true || env === 'xs' || env === 'sm') { + history.back(); + } + } + // + function onWindowResize() { + var env = utils.findBootstrapEnvironment(); + var isMobile = env === 'xs' || env === 'sm'; + + if (preview.toggle) { + if (preview.env !== env && isMobile) { + preview.env = env; + preview.toggle(false); + } + preview.env = env; + } + + if (composer.active !== undefined) { + resize.reposition($('.composer[data-uuid="' + composer.active + '"]')); + + if (!isMobile && window.location.pathname.startsWith(config.relative_path + '/compose')) { + /* + * If this conditional is met, we're no longer in mobile/tablet + * resolution but we've somehow managed to have a mobile + * composer load, so let's go back to the topic + */ + history.back(); + } else if (isMobile && !window.location.pathname.startsWith(config.relative_path + '/compose')) { + /* + * In this case, we're in mobile/tablet resolution but the composer + * that loaded was a regular composer, so let's fix the address bar + */ + mobileHistoryAppend(); + } + } + composer.bsEnvironment = env; + } + + function alreadyOpen(post) { + // If a composer for the same cid/tid/pid is already open, return the uuid, else return bool false + var type; + var id; + + if (post.hasOwnProperty('cid')) { + type = 'cid'; + } else if (post.hasOwnProperty('tid')) { + type = 'tid'; + } else if (post.hasOwnProperty('pid')) { + type = 'pid'; + } + + id = post[type]; + + // Find a match + for (var uuid in composer.posts) { + if (composer.posts[uuid].hasOwnProperty(type) && id === composer.posts[uuid][type]) { + return uuid; + } + } + + // No matches... + return false; + } + + function push(post) { + if (!post) { + return; + } + + var uuid = utils.generateUUID(); + var existingUUID = alreadyOpen(post); + + if (existingUUID) { + taskbar.updateActive(existingUUID); + return composer.load(existingUUID); + } + + var actionText = '[[topic:composer.new-topic]]'; + if (post.action === 'posts.reply') { + actionText = '[[topic:composer.replying-to]]'; + } else if (post.action === 'posts.edit') { + actionText = '[[topic:composer.editing-in]]'; + } + + translator.translate(actionText, function (translatedAction) { + taskbar.push('composer', uuid, { + title: translatedAction.replace('%1', '"' + post.title + '"'), + }); + }); + + composer.posts[uuid] = post; + composer.load(uuid); + } + + async function composerAlert(post_uuid, message) { + $('.composer[data-uuid="' + post_uuid + '"]').find('.composer-submit').removeAttr('disabled'); + + const { showAlert } = await hooks.fire('filter:composer.error', { post_uuid, message, showAlert: true }); + + if (showAlert) { + alerts.alert({ + type: 'danger', + timeout: 10000, + title: '', + message: message, + alert_id: 'post_error', + }); + } + } + + composer.findByTid = function (tid) { + // Iterates through the initialised composers and returns the uuid of the matching composer + for (var uuid in composer.posts) { + if (composer.posts.hasOwnProperty(uuid) && composer.posts[uuid].hasOwnProperty('tid') && parseInt(composer.posts[uuid].tid, 10) === parseInt(tid, 10)) { + return uuid; + } + } + + return null; + }; + + composer.addButton = function (iconClass, onClick, title) { + formatting.addButton(iconClass, onClick, title); + }; + + composer.newTopic = async (data) => { + let pushData = { + save_id: data.save_id, + action: 'topics.post', + cid: data.cid, + handle: data.handle, + title: data.title || '', + body: data.body || '', + tags: data.tags || [], + modified: !!((data.title && data.title.length) || (data.body && data.body.length)), + isMain: true, + }; + + ({ pushData } = await hooks.fire('filter:composer.topic.push', { + data: data, + pushData: pushData, + })); + + push(pushData); + }; + + composer.addQuote = function (data) { + // tid, toPid, selectedPid, title, username, text, uuid + data.uuid = data.uuid || composer.active; + + var escapedTitle = (data.title || '') + .replace(/([\\`*_{}[\]()#+\-.!])/g, '\\$1') + .replace(/\[/g, '[') + .replace(/\]/g, ']') + .replace(/%/g, '%') + .replace(/,/g, ','); + + if (data.body) { + data.body = '> ' + data.body.replace(/\n/g, '\n> ') + '\n\n'; + } + var link = '[' + escapedTitle + '](' + config.relative_path + '/post/' + encodeURIComponent(data.selectedPid || data.toPid) + ')'; + if (data.uuid === undefined) { + if (data.title && (data.selectedPid || data.toPid)) { + composer.newReply({ + tid: data.tid, + toPid: data.toPid, + title: data.title, + body: '[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n' + data.body, + }); + } else { + composer.newReply({ + tid: data.tid, + toPid: data.toPid, + title: data.title, + body: '[[modules:composer.user-said, ' + data.username + ']]\n' + data.body, + }); + } + return; + } else if (data.uuid !== composer.active) { + // If the composer is not currently active, activate it + composer.load(data.uuid); + } + + var postContainer = $('.composer[data-uuid="' + data.uuid + '"]'); + var bodyEl = postContainer.find('textarea'); + var prevText = bodyEl.val(); + if (data.title && (data.selectedPid || data.toPid)) { + translator.translate('[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n', config.defaultLang, onTranslated); + } else { + translator.translate('[[modules:composer.user-said, ' + data.username + ']]\n', config.defaultLang, onTranslated); + } + + function onTranslated(translated) { + composer.posts[data.uuid].body = (prevText.length ? prevText + '\n\n' : '') + translated + data.body; + bodyEl.val(composer.posts[data.uuid].body); + focusElements(postContainer); + preview.render(postContainer); + } + }; + + composer.newReply = function (data) { + translator.translate(data.body, config.defaultLang, function (translated) { + push({ + save_id: data.save_id, + action: 'posts.reply', + tid: data.tid, + toPid: data.toPid, + title: data.title, + body: translated, + modified: !!(translated && translated.length), + isMain: false, + }); + }); + }; + + composer.editPost = function (data) { + // pid, text + socket.emit('plugins.composer.push', data.pid, function (err, postData) { + if (err) { + return alerts.error(err); + } + postData.save_id = data.save_id; + postData.action = 'posts.edit'; + postData.pid = data.pid; + postData.modified = false; + if (data.body) { + postData.body = data.body; + postData.modified = true; + } + if (data.title) { + postData.title = data.title; + postData.modified = true; + } + push(postData); + }); + }; + + composer.load = function (post_uuid) { + var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + if (postContainer.length) { + activate(post_uuid); + resize.reposition(postContainer); + focusElements(postContainer); + onShow(); + } else if (composer.formatting) { + createNewComposer(post_uuid); + } else { + socket.emit('plugins.composer.getFormattingOptions', function (err, options) { + if (err) { + return alerts.error(err); + } + composer.formatting = options; + createNewComposer(post_uuid); + }); + } + }; + + composer.enhance = function (postContainer, post_uuid, postData) { + /* + This method enhances a composer container with client-side sugar (preview, etc) + Everything in here also applies to the /compose route + */ + + if (!post_uuid && !postData) { + post_uuid = utils.generateUUID(); + composer.posts[post_uuid] = ajaxify.data; + postData = ajaxify.data; + postContainer.attr('data-uuid', post_uuid); + } + + categoryList.init(postContainer, composer.posts[post_uuid]); + scheduler.init(postContainer, composer.posts); + + formatting.addHandler(postContainer); + formatting.addComposerButtons(); + preview.handleToggler(postContainer); + postQueue.showAlert(postContainer, postData); + uploads.initialize(post_uuid); + tags.init(postContainer, composer.posts[post_uuid]); + autocomplete.init(postContainer, post_uuid); + + postContainer.on('change', 'input, textarea', function () { + composer.posts[post_uuid].modified = true; + }); + + postContainer.on('click', '.composer-submit', function (e) { + e.preventDefault(); + e.stopPropagation(); // Other click events bring composer back to active state which is undesired on submit + + $(this).attr('disabled', true); + post(post_uuid); + }); + + require(['mousetrap'], function (mousetrap) { + mousetrap(postContainer.get(0)).bind('mod+enter', function () { + postContainer.find('.composer-submit').attr('disabled', true); + post(post_uuid); + }); + }); + + postContainer.find('.composer-discard').on('click', function (e) { + e.preventDefault(); + + if (!composer.posts[post_uuid].modified) { + composer.discard(post_uuid); + return removeComposerHistory(); + } + + formatting.exitFullscreen(); + + var btn = $(this).prop('disabled', true); + translator.translate('[[modules:composer.discard]]', function (translated) { + bootbox.confirm(translated, function (confirm) { + if (confirm) { + composer.discard(post_uuid); + removeComposerHistory(); + } + btn.prop('disabled', false); + }); + }); + }); + + postContainer.find('.composer-minimize, .minimize .trigger').on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + composer.minimize(post_uuid); + }); + + const textareaEl = postContainer.find('textarea'); + textareaEl.on('input propertychange', utils.debounce(function () { + preview.render(postContainer); + }, 250)); + + textareaEl.on('scroll', function () { + preview.matchScroll(postContainer); + }); + + drafts.init(postContainer, postData); + const draft = drafts.get(postData.save_id); + + preview.render(postContainer, function () { + preview.matchScroll(postContainer); + }); + + handleHelp(postContainer); + handleSearch(postContainer); + focusElements(postContainer); + if (postData.action === 'posts.edit') { + composer.updateThumbCount(post_uuid, postContainer); + } + + // Hide "zen mode" if fullscreen API is not enabled/available (ahem, iOS...) + if (!screenfull.isEnabled) { + $('[data-format="zen"]').parent().addClass('hidden'); + } + + hooks.fire('action:composer.enhanced', { postContainer, postData, draft }); + }; + + async function getSelectedCategory(postData) { + if (ajaxify.data.template.category && parseInt(postData.cid, 10) === parseInt(ajaxify.data.cid, 10)) { + // no need to load data if we are already on the category page + return ajaxify.data; + } else if (parseInt(postData.cid, 10)) { + return await api.get(`/api/category/${postData.cid}`, {}); + } + return null; + } + + async function createNewComposer(post_uuid) { + var postData = composer.posts[post_uuid]; + + var isTopic = postData ? postData.hasOwnProperty('cid') : false; + var isMain = postData ? !!postData.isMain : false; + var isEditing = postData ? !!postData.pid : false; + var isGuestPost = postData ? parseInt(postData.uid, 10) === 0 : false; + const isScheduled = postData.timestamp > Date.now(); + + // see + // https://github.com/NodeBB/NodeBB/issues/2994 and + // https://github.com/NodeBB/NodeBB/issues/1951 + // remove when 1951 is resolved + + var title = postData.title.replace(/%/g, '%').replace(/,/g, ','); + postData.category = await getSelectedCategory(postData); + const privileges = postData.category ? postData.category.privileges : ajaxify.data.privileges; + var data = { + topicTitle: title, + titleLength: title.length, + body: translator.escape(utils.escapeHTML(postData.body)), + mobile: composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm', + resizable: true, + thumb: postData.thumb, + isTopicOrMain: isTopic || isMain, + maximumTitleLength: config.maximumTitleLength, + maximumPostLength: config.maximumPostLength, + minimumTagLength: config.minimumTagLength, + maximumTagLength: config.maximumTagLength, + 'composer:showHelpTab': config['composer:showHelpTab'], + isTopic: isTopic, + isEditing: isEditing, + canSchedule: !!(isMain && privileges && + ((privileges['topics:schedule'] && !isEditing) || (isScheduled && privileges.view_scheduled))), + showHandleInput: config.allowGuestHandles && + (app.user.uid === 0 || (isEditing && isGuestPost && app.user.isAdmin)), + handle: postData ? postData.handle || '' : undefined, + formatting: composer.formatting, + tagWhitelist: postData.category ? postData.category.tagWhitelist : ajaxify.data.tagWhitelist, + privileges: app.user.privileges, + selectedCategory: postData.category, + submitOptions: [ + // Add items using `filter:composer.create`, or just add them to the
        in DOM + // { + // action: 'foobar', + // text: 'Text Label', + // } + ], + }; + + if (data.mobile) { + mobileHistoryAppend(); + + app.toggleNavbar(false); + } + + postData.mobile = composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm'; + + ({ postData, createData: data } = await hooks.fire('filter:composer.create', { + postData: postData, + createData: data, + })); + + app.parseAndTranslate('composer', data, function (composerTemplate) { + if ($('.composer.composer[data-uuid="' + post_uuid + '"]').length) { + return; + } + composerTemplate = $(composerTemplate); + + composerTemplate.find('.title').each(function () { + $(this).text(translator.unescape($(this).text())); + }); + + composerTemplate.attr('data-uuid', post_uuid); + + $(document.body).append(composerTemplate); + + var postContainer = $(composerTemplate[0]); + + resize.reposition(postContainer); + composer.enhance(postContainer, post_uuid, postData); + /* + Everything after this line is applied to the resizable composer only + Want something done to both resizable composer and the one in /compose? + Put it in composer.enhance(). + + Eventually, stuff after this line should be moved into composer.enhance(). + */ + + activate(post_uuid); + + postContainer.on('click', function () { + if (!taskbar.isActive(post_uuid)) { + taskbar.updateActive(post_uuid); + } + }); + + resize.handleResize(postContainer); + + if (composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { + var submitBtns = postContainer.find('.composer-submit'); + var mobileSubmitBtn = postContainer.find('.mobile-navbar .composer-submit'); + var textareaEl = postContainer.find('.write'); + var idx = textareaEl.attr('tabindex'); + + submitBtns.removeAttr('tabindex'); + mobileSubmitBtn.attr('tabindex', parseInt(idx, 10) + 1); + } + + $(window).trigger('action:composer.loaded', { + postContainer: postContainer, + post_uuid: post_uuid, + composerData: composer.posts[post_uuid], + formatting: composer.formatting, + }); + + scrollStop.apply(postContainer.find('.write')); + focusElements(postContainer); + onShow(); + }); + } + + function mobileHistoryAppend() { + var path = 'compose?p=' + window.location.pathname; + var returnPath = window.location.pathname.slice(1) + window.location.search; + + // Remove relative path from returnPath + if (returnPath.startsWith(config.relative_path.slice(1))) { + returnPath = returnPath.slice(config.relative_path.length); + } + + // Add in return path to be caught by ajaxify when post is completed, or if back is pressed + window.history.replaceState({ + url: null, + returnPath: returnPath, + }, returnPath, config.relative_path + '/' + returnPath); + + // Update address bar in case f5 is pressed + window.history.pushState({ + url: path, + }, path, `${config.relative_path}/${returnPath}`); + } + + function handleHelp(postContainer) { + const helpBtn = postContainer.find('[data-action="help"]'); + helpBtn.on('click', async function () { + const html = await socket.emit('plugins.composer.renderHelp'); + if (html && html.length > 0) { + bootbox.dialog({ + size: 'large', + message: html, + onEscape: true, + backdrop: true, + onHidden: function () { + helpBtn.focus(); + }, + }); + } + }); + } + + function handleSearch(postContainer) { + var uuid = postContainer.attr('data-uuid'); + var isEditing = composer.posts[uuid] && composer.posts[uuid].action === 'posts.edit'; + var env = utils.findBootstrapEnvironment(); + var isMobile = env === 'xs' || env === 'sm'; + if (isEditing || isMobile) { + return; + } + + search.enableQuickSearch({ + searchElements: { + inputEl: postContainer.find('input.title'), + resultEl: postContainer.find('.quick-search-container'), + }, + searchOptions: { + composer: 1, + }, + hideOnNoMatches: true, + hideDuringSearch: true, + }); + } + + function activate(post_uuid) { + if (composer.active && composer.active !== post_uuid) { + composer.minimize(composer.active); + } + + composer.active = post_uuid; + const postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + postContainer.css('visibility', 'visible'); + $(window).trigger('action:composer.activate', { + post_uuid: post_uuid, + postContainer: postContainer, + }); + } + + function focusElements(postContainer) { + setTimeout(function () { + var title = postContainer.find('input.title'); + + if (title.length) { + title.focus(); + } else { + postContainer.find('textarea').focus().putCursorAtEnd(); + } + }, 20); + } + + async function post(post_uuid) { + var postData = composer.posts[post_uuid]; + var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + var handleEl = postContainer.find('.handle'); + var titleEl = postContainer.find('.title'); + var bodyEl = postContainer.find('textarea'); + var thumbEl = postContainer.find('input#topic-thumb-url'); + var onComposeRoute = postData.hasOwnProperty('template') && postData.template.compose === true; + const submitBtn = postContainer.find('.composer-submit'); + + titleEl.val(titleEl.val().trim()); + bodyEl.val(utils.rtrim(bodyEl.val())); + if (thumbEl.length) { + thumbEl.val(thumbEl.val().trim()); + } + + var action = postData.action; + + var checkTitle = (postData.hasOwnProperty('cid') || parseInt(postData.pid, 10)) && postContainer.find('input.title').length; + var isCategorySelected = !checkTitle || (checkTitle && parseInt(postData.cid, 10)); + + // Specifically for checking title/body length via plugins + var payload = { + post_uuid: post_uuid, + postData: postData, + postContainer: postContainer, + titleEl: titleEl, + titleLen: titleEl.val().length, + bodyEl: bodyEl, + bodyLen: bodyEl.val().length, + }; + + await hooks.fire('filter:composer.check', payload); + $(window).trigger('action:composer.check', payload); + + if (payload.error) { + return composerAlert(post_uuid, payload.error); + } + + if (uploads.inProgress[post_uuid] && uploads.inProgress[post_uuid].length) { + return composerAlert(post_uuid, '[[error:still-uploading]]'); + } else if (checkTitle && payload.titleLen < parseInt(config.minimumTitleLength, 10)) { + return composerAlert(post_uuid, '[[error:title-too-short, ' + config.minimumTitleLength + ']]'); + } else if (checkTitle && payload.titleLen > parseInt(config.maximumTitleLength, 10)) { + return composerAlert(post_uuid, '[[error:title-too-long, ' + config.maximumTitleLength + ']]'); + } else if (action === 'topics.post' && !isCategorySelected) { + return composerAlert(post_uuid, '[[error:category-not-selected]]'); + } else if (payload.bodyLen < parseInt(config.minimumPostLength, 10)) { + return composerAlert(post_uuid, '[[error:content-too-short, ' + config.minimumPostLength + ']]'); + } else if (payload.bodyLen > parseInt(config.maximumPostLength, 10)) { + return composerAlert(post_uuid, '[[error:content-too-long, ' + config.maximumPostLength + ']]'); + } else if (checkTitle && !tags.isEnoughTags(post_uuid)) { + return composerAlert(post_uuid, '[[error:not-enough-tags, ' + tags.minTagCount() + ']]'); + } else if (scheduler.isActive() && scheduler.getTimestamp() <= Date.now()) { + return composerAlert(post_uuid, '[[error:scheduling-to-past]]'); + } + + let composerData = { + uuid: post_uuid, + }; + let method = 'post'; + let route = ''; + + if (action === 'topics.post') { + route = '/topics'; + composerData = { + ...composerData, + handle: handleEl ? handleEl.val() : undefined, + title: titleEl.val(), + content: bodyEl.val(), + thumb: thumbEl.val() || '', + cid: categoryList.getSelectedCid(), + tags: tags.getTags(post_uuid), + timestamp: scheduler.getTimestamp(), + }; + } else if (action === 'posts.reply') { + route = `/topics/${postData.tid}`; + composerData = { + ...composerData, + tid: postData.tid, + handle: handleEl ? handleEl.val() : undefined, + content: bodyEl.val(), + toPid: postData.toPid, + }; + } else if (action === 'posts.edit') { + method = 'put'; + route = `/posts/${postData.pid}`; + composerData = { + ...composerData, + pid: postData.pid, + handle: handleEl ? handleEl.val() : undefined, + content: bodyEl.val(), + title: titleEl.val(), + thumb: thumbEl.val() || '', + tags: tags.getTags(post_uuid), + timestamp: scheduler.getTimestamp(), + }; + } + var submitHookData = { + composerEl: postContainer, + action: action, + composerData: composerData, + postData: postData, + redirect: true, + }; + + await hooks.fire('filter:composer.submit', submitHookData); + hooks.fire('action:composer.submit', Object.freeze(submitHookData)); + + // Minimize composer (and set textarea as readonly) while submitting + var taskbarIconEl = $('#taskbar .composer[data-uuid="' + post_uuid + '"] i'); + var textareaEl = postContainer.find('.write'); + taskbarIconEl.removeClass('fa-plus').addClass('fa-circle-o-notch fa-spin'); + composer.minimize(post_uuid); + textareaEl.prop('readonly', true); + + api[method](route, composerData) + .then((data) => { + submitBtn.removeAttr('disabled'); + postData.submitted = true; + + composer.discard(post_uuid); + drafts.removeDraft(postData.save_id); + + if (data.queued) { + alerts.alert({ + type: 'success', + title: '[[global:alert.success]]', + message: data.message, + timeout: 10000, + clickfn: function () { + ajaxify.go(`/post-queue/${data.id}`); + }, + }); + } else if (action === 'topics.post') { + if (submitHookData.redirect) { + ajaxify.go('topic/' + data.slug, undefined, (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm')); + } + } else if (action === 'posts.reply') { + if (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { + window.history.back(); + } else if (submitHookData.redirect && + ((ajaxify.data.template.name !== 'topic') || + (ajaxify.data.template.topic && parseInt(postData.tid, 10) !== parseInt(ajaxify.data.tid, 10))) + ) { + ajaxify.go('post/' + data.pid); + } + } else { + removeComposerHistory(); + } + + hooks.fire('action:composer.' + action, { composerData: composerData, data: data }); + }) + .catch((err) => { + // Restore composer on error + composer.load(post_uuid); + textareaEl.prop('readonly', false); + if (err.message === '[[error:email-not-confirmed]]') { + return messagesModule.showEmailConfirmWarning(err.message); + } + composerAlert(post_uuid, err.message); + }); + } + + function onShow() { + $('html').addClass('composing'); + } + + function onHide() { + $('#content').css({ paddingBottom: 0 }); + $('html').removeClass('composing'); + app.toggleNavbar(true); + formatting.exitFullscreen(); + } + + composer.discard = function (post_uuid) { + if (composer.posts[post_uuid]) { + var postData = composer.posts[post_uuid]; + var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + postContainer.remove(); + drafts.removeDraft(postData.save_id); + topicThumbs.deleteAll(post_uuid); + + taskbar.discard('composer', post_uuid); + $('[data-action="post"]').removeAttr('disabled'); + + hooks.fire('action:composer.discard', { + post_uuid: post_uuid, + postData: postData, + }); + delete composer.posts[post_uuid]; + composer.active = undefined; + } + scheduler.reset(); + onHide(); + }; + + // Alias to .discard(); + composer.close = composer.discard; + + composer.minimize = function (post_uuid) { + var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + postContainer.css('visibility', 'hidden'); + composer.active = undefined; + taskbar.minimize('composer', post_uuid); + $(window).trigger('action:composer.minimize', { + post_uuid: post_uuid, + }); + + onHide(); + }; + + composer.minimizeActive = function () { + if (composer.active) { + composer.miminize(composer.active); + } + }; + + composer.updateThumbCount = function (uuid, postContainer) { + const composerObj = composer.posts[uuid]; + if (composerObj.action === 'topics.post' || (composerObj.action === 'posts.edit' && composerObj.isMain)) { + const calls = [ + topicThumbs.get(uuid), + ]; + if (composerObj.pid) { + calls.push(topicThumbs.getByPid(composerObj.pid)); + } + Promise.all(calls).then((thumbs) => { + const thumbCount = thumbs.flat().length; + const formatEl = postContainer.find('[data-format="thumbs"]'); + formatEl.find('.badge') + .text(thumbCount) + .toggleClass('hidden', !thumbCount); + }); + } + }; + + return composer; +}); From 039278d9bd2a601dcf6840780a5d943c299e701d Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:52:32 -0700 Subject: [PATCH 20/50] Revert "User story2 back end - Choosing mp3 file for saving in database" --- node_modules_real/composer.js | 886 ---------------------- node_modules_real/topics_list.tpl | 1168 +++++++---------------------- 2 files changed, 286 insertions(+), 1768 deletions(-) delete mode 100644 node_modules_real/composer.js diff --git a/node_modules_real/composer.js b/node_modules_real/composer.js deleted file mode 100644 index 051b7a0338..0000000000 --- a/node_modules_real/composer.js +++ /dev/null @@ -1,886 +0,0 @@ -'use strict'; - -define('composer', [ - 'taskbar', - 'translator', - 'composer/uploads', - 'composer/formatting', - 'composer/drafts', - 'composer/tags', - 'composer/categoryList', - 'composer/preview', - 'composer/resize', - 'composer/autocomplete', - 'composer/scheduler', - 'composer/post-queue', - 'scrollStop', - 'topicThumbs', - 'api', - 'bootbox', - 'alerts', - 'hooks', - 'messages', - 'search', - 'screenfull', -], function (taskbar, translator, uploads, formatting, drafts, tags, - categoryList, preview, resize, autocomplete, scheduler, postQueue, scrollStop, - topicThumbs, api, bootbox, alerts, hooks, messagesModule, search, screenfull) { - var composer = { - active: undefined, - posts: {}, - bsEnvironment: undefined, - formatting: undefined, - }; - - $(window).off('resize', onWindowResize).on('resize', onWindowResize); - onWindowResize(); - - $(window).on('action:composer.topics.post', function (ev, data) { - localStorage.removeItem('category:' + data.data.cid + ':bookmark'); - localStorage.removeItem('category:' + data.data.cid + ':bookmark:clicked'); - }); - - $(window).on('popstate', function () { - var env = utils.findBootstrapEnvironment(); - if (composer.active && (env === 'xs' || env === 'sm')) { - if (!composer.posts[composer.active].modified) { - composer.discard(composer.active); - if (composer.discardConfirm && composer.discardConfirm.length) { - composer.discardConfirm.modal('hide'); - delete composer.discardConfirm; - } - return; - } - - translator.translate('[[modules:composer.discard]]', function (translated) { - composer.discardConfirm = bootbox.confirm(translated, function (confirm) { - if (confirm) { - composer.discard(composer.active); - } else { - composer.posts[composer.active].modified = true; - } - }); - composer.posts[composer.active].modified = false; - }); - } - }); - - function removeComposerHistory() { - var env = composer.bsEnvironment; - if (ajaxify.data.template.compose === true || env === 'xs' || env === 'sm') { - history.back(); - } - } - // - function onWindowResize() { - var env = utils.findBootstrapEnvironment(); - var isMobile = env === 'xs' || env === 'sm'; - - if (preview.toggle) { - if (preview.env !== env && isMobile) { - preview.env = env; - preview.toggle(false); - } - preview.env = env; - } - - if (composer.active !== undefined) { - resize.reposition($('.composer[data-uuid="' + composer.active + '"]')); - - if (!isMobile && window.location.pathname.startsWith(config.relative_path + '/compose')) { - /* - * If this conditional is met, we're no longer in mobile/tablet - * resolution but we've somehow managed to have a mobile - * composer load, so let's go back to the topic - */ - history.back(); - } else if (isMobile && !window.location.pathname.startsWith(config.relative_path + '/compose')) { - /* - * In this case, we're in mobile/tablet resolution but the composer - * that loaded was a regular composer, so let's fix the address bar - */ - mobileHistoryAppend(); - } - } - composer.bsEnvironment = env; - } - - function alreadyOpen(post) { - // If a composer for the same cid/tid/pid is already open, return the uuid, else return bool false - var type; - var id; - - if (post.hasOwnProperty('cid')) { - type = 'cid'; - } else if (post.hasOwnProperty('tid')) { - type = 'tid'; - } else if (post.hasOwnProperty('pid')) { - type = 'pid'; - } - - id = post[type]; - - // Find a match - for (var uuid in composer.posts) { - if (composer.posts[uuid].hasOwnProperty(type) && id === composer.posts[uuid][type]) { - return uuid; - } - } - - // No matches... - return false; - } - - function push(post) { - if (!post) { - return; - } - - var uuid = utils.generateUUID(); - var existingUUID = alreadyOpen(post); - - if (existingUUID) { - taskbar.updateActive(existingUUID); - return composer.load(existingUUID); - } - - var actionText = '[[topic:composer.new-topic]]'; - if (post.action === 'posts.reply') { - actionText = '[[topic:composer.replying-to]]'; - } else if (post.action === 'posts.edit') { - actionText = '[[topic:composer.editing-in]]'; - } - - translator.translate(actionText, function (translatedAction) { - taskbar.push('composer', uuid, { - title: translatedAction.replace('%1', '"' + post.title + '"'), - }); - }); - - composer.posts[uuid] = post; - composer.load(uuid); - } - - async function composerAlert(post_uuid, message) { - $('.composer[data-uuid="' + post_uuid + '"]').find('.composer-submit').removeAttr('disabled'); - - const { showAlert } = await hooks.fire('filter:composer.error', { post_uuid, message, showAlert: true }); - - if (showAlert) { - alerts.alert({ - type: 'danger', - timeout: 10000, - title: '', - message: message, - alert_id: 'post_error', - }); - } - } - - composer.findByTid = function (tid) { - // Iterates through the initialised composers and returns the uuid of the matching composer - for (var uuid in composer.posts) { - if (composer.posts.hasOwnProperty(uuid) && composer.posts[uuid].hasOwnProperty('tid') && parseInt(composer.posts[uuid].tid, 10) === parseInt(tid, 10)) { - return uuid; - } - } - - return null; - }; - - composer.addButton = function (iconClass, onClick, title) { - formatting.addButton(iconClass, onClick, title); - }; - - composer.newTopic = async (data) => { - let pushData = { - save_id: data.save_id, - action: 'topics.post', - cid: data.cid, - handle: data.handle, - title: data.title || '', - body: data.body || '', - tags: data.tags || [], - modified: !!((data.title && data.title.length) || (data.body && data.body.length)), - isMain: true, - }; - - ({ pushData } = await hooks.fire('filter:composer.topic.push', { - data: data, - pushData: pushData, - })); - - push(pushData); - }; - - composer.addQuote = function (data) { - // tid, toPid, selectedPid, title, username, text, uuid - data.uuid = data.uuid || composer.active; - - var escapedTitle = (data.title || '') - .replace(/([\\`*_{}[\]()#+\-.!])/g, '\\$1') - .replace(/\[/g, '[') - .replace(/\]/g, ']') - .replace(/%/g, '%') - .replace(/,/g, ','); - - if (data.body) { - data.body = '> ' + data.body.replace(/\n/g, '\n> ') + '\n\n'; - } - var link = '[' + escapedTitle + '](' + config.relative_path + '/post/' + encodeURIComponent(data.selectedPid || data.toPid) + ')'; - if (data.uuid === undefined) { - if (data.title && (data.selectedPid || data.toPid)) { - composer.newReply({ - tid: data.tid, - toPid: data.toPid, - title: data.title, - body: '[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n' + data.body, - }); - } else { - composer.newReply({ - tid: data.tid, - toPid: data.toPid, - title: data.title, - body: '[[modules:composer.user-said, ' + data.username + ']]\n' + data.body, - }); - } - return; - } else if (data.uuid !== composer.active) { - // If the composer is not currently active, activate it - composer.load(data.uuid); - } - - var postContainer = $('.composer[data-uuid="' + data.uuid + '"]'); - var bodyEl = postContainer.find('textarea'); - var prevText = bodyEl.val(); - if (data.title && (data.selectedPid || data.toPid)) { - translator.translate('[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n', config.defaultLang, onTranslated); - } else { - translator.translate('[[modules:composer.user-said, ' + data.username + ']]\n', config.defaultLang, onTranslated); - } - - function onTranslated(translated) { - composer.posts[data.uuid].body = (prevText.length ? prevText + '\n\n' : '') + translated + data.body; - bodyEl.val(composer.posts[data.uuid].body); - focusElements(postContainer); - preview.render(postContainer); - } - }; - - composer.newReply = function (data) { - translator.translate(data.body, config.defaultLang, function (translated) { - push({ - save_id: data.save_id, - action: 'posts.reply', - tid: data.tid, - toPid: data.toPid, - title: data.title, - body: translated, - modified: !!(translated && translated.length), - isMain: false, - }); - }); - }; - - composer.editPost = function (data) { - // pid, text - socket.emit('plugins.composer.push', data.pid, function (err, postData) { - if (err) { - return alerts.error(err); - } - postData.save_id = data.save_id; - postData.action = 'posts.edit'; - postData.pid = data.pid; - postData.modified = false; - if (data.body) { - postData.body = data.body; - postData.modified = true; - } - if (data.title) { - postData.title = data.title; - postData.modified = true; - } - push(postData); - }); - }; - - composer.load = function (post_uuid) { - var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - if (postContainer.length) { - activate(post_uuid); - resize.reposition(postContainer); - focusElements(postContainer); - onShow(); - } else if (composer.formatting) { - createNewComposer(post_uuid); - } else { - socket.emit('plugins.composer.getFormattingOptions', function (err, options) { - if (err) { - return alerts.error(err); - } - composer.formatting = options; - createNewComposer(post_uuid); - }); - } - }; - - composer.enhance = function (postContainer, post_uuid, postData) { - /* - This method enhances a composer container with client-side sugar (preview, etc) - Everything in here also applies to the /compose route - */ - - if (!post_uuid && !postData) { - post_uuid = utils.generateUUID(); - composer.posts[post_uuid] = ajaxify.data; - postData = ajaxify.data; - postContainer.attr('data-uuid', post_uuid); - } - - categoryList.init(postContainer, composer.posts[post_uuid]); - scheduler.init(postContainer, composer.posts); - - formatting.addHandler(postContainer); - formatting.addComposerButtons(); - preview.handleToggler(postContainer); - postQueue.showAlert(postContainer, postData); - uploads.initialize(post_uuid); - tags.init(postContainer, composer.posts[post_uuid]); - autocomplete.init(postContainer, post_uuid); - - postContainer.on('change', 'input, textarea', function () { - composer.posts[post_uuid].modified = true; - }); - - postContainer.on('click', '.composer-submit', function (e) { - e.preventDefault(); - e.stopPropagation(); // Other click events bring composer back to active state which is undesired on submit - - $(this).attr('disabled', true); - post(post_uuid); - }); - - require(['mousetrap'], function (mousetrap) { - mousetrap(postContainer.get(0)).bind('mod+enter', function () { - postContainer.find('.composer-submit').attr('disabled', true); - post(post_uuid); - }); - }); - - postContainer.find('.composer-discard').on('click', function (e) { - e.preventDefault(); - - if (!composer.posts[post_uuid].modified) { - composer.discard(post_uuid); - return removeComposerHistory(); - } - - formatting.exitFullscreen(); - - var btn = $(this).prop('disabled', true); - translator.translate('[[modules:composer.discard]]', function (translated) { - bootbox.confirm(translated, function (confirm) { - if (confirm) { - composer.discard(post_uuid); - removeComposerHistory(); - } - btn.prop('disabled', false); - }); - }); - }); - - postContainer.find('.composer-minimize, .minimize .trigger').on('click', function (e) { - e.preventDefault(); - e.stopPropagation(); - composer.minimize(post_uuid); - }); - - const textareaEl = postContainer.find('textarea'); - textareaEl.on('input propertychange', utils.debounce(function () { - preview.render(postContainer); - }, 250)); - - textareaEl.on('scroll', function () { - preview.matchScroll(postContainer); - }); - - drafts.init(postContainer, postData); - const draft = drafts.get(postData.save_id); - - preview.render(postContainer, function () { - preview.matchScroll(postContainer); - }); - - handleHelp(postContainer); - handleSearch(postContainer); - focusElements(postContainer); - if (postData.action === 'posts.edit') { - composer.updateThumbCount(post_uuid, postContainer); - } - - // Hide "zen mode" if fullscreen API is not enabled/available (ahem, iOS...) - if (!screenfull.isEnabled) { - $('[data-format="zen"]').parent().addClass('hidden'); - } - - hooks.fire('action:composer.enhanced', { postContainer, postData, draft }); - }; - - async function getSelectedCategory(postData) { - if (ajaxify.data.template.category && parseInt(postData.cid, 10) === parseInt(ajaxify.data.cid, 10)) { - // no need to load data if we are already on the category page - return ajaxify.data; - } else if (parseInt(postData.cid, 10)) { - return await api.get(`/api/category/${postData.cid}`, {}); - } - return null; - } - - async function createNewComposer(post_uuid) { - var postData = composer.posts[post_uuid]; - - var isTopic = postData ? postData.hasOwnProperty('cid') : false; - var isMain = postData ? !!postData.isMain : false; - var isEditing = postData ? !!postData.pid : false; - var isGuestPost = postData ? parseInt(postData.uid, 10) === 0 : false; - const isScheduled = postData.timestamp > Date.now(); - - // see - // https://github.com/NodeBB/NodeBB/issues/2994 and - // https://github.com/NodeBB/NodeBB/issues/1951 - // remove when 1951 is resolved - - var title = postData.title.replace(/%/g, '%').replace(/,/g, ','); - postData.category = await getSelectedCategory(postData); - const privileges = postData.category ? postData.category.privileges : ajaxify.data.privileges; - var data = { - topicTitle: title, - titleLength: title.length, - body: translator.escape(utils.escapeHTML(postData.body)), - mobile: composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm', - resizable: true, - thumb: postData.thumb, - isTopicOrMain: isTopic || isMain, - maximumTitleLength: config.maximumTitleLength, - maximumPostLength: config.maximumPostLength, - minimumTagLength: config.minimumTagLength, - maximumTagLength: config.maximumTagLength, - 'composer:showHelpTab': config['composer:showHelpTab'], - isTopic: isTopic, - isEditing: isEditing, - canSchedule: !!(isMain && privileges && - ((privileges['topics:schedule'] && !isEditing) || (isScheduled && privileges.view_scheduled))), - showHandleInput: config.allowGuestHandles && - (app.user.uid === 0 || (isEditing && isGuestPost && app.user.isAdmin)), - handle: postData ? postData.handle || '' : undefined, - formatting: composer.formatting, - tagWhitelist: postData.category ? postData.category.tagWhitelist : ajaxify.data.tagWhitelist, - privileges: app.user.privileges, - selectedCategory: postData.category, - submitOptions: [ - // Add items using `filter:composer.create`, or just add them to the
          in DOM - // { - // action: 'foobar', - // text: 'Text Label', - // } - ], - }; - - if (data.mobile) { - mobileHistoryAppend(); - - app.toggleNavbar(false); - } - - postData.mobile = composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm'; - - ({ postData, createData: data } = await hooks.fire('filter:composer.create', { - postData: postData, - createData: data, - })); - - app.parseAndTranslate('composer', data, function (composerTemplate) { - if ($('.composer.composer[data-uuid="' + post_uuid + '"]').length) { - return; - } - composerTemplate = $(composerTemplate); - - composerTemplate.find('.title').each(function () { - $(this).text(translator.unescape($(this).text())); - }); - - composerTemplate.attr('data-uuid', post_uuid); - - $(document.body).append(composerTemplate); - - var postContainer = $(composerTemplate[0]); - - resize.reposition(postContainer); - composer.enhance(postContainer, post_uuid, postData); - /* - Everything after this line is applied to the resizable composer only - Want something done to both resizable composer and the one in /compose? - Put it in composer.enhance(). - - Eventually, stuff after this line should be moved into composer.enhance(). - */ - - activate(post_uuid); - - postContainer.on('click', function () { - if (!taskbar.isActive(post_uuid)) { - taskbar.updateActive(post_uuid); - } - }); - - resize.handleResize(postContainer); - - if (composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { - var submitBtns = postContainer.find('.composer-submit'); - var mobileSubmitBtn = postContainer.find('.mobile-navbar .composer-submit'); - var textareaEl = postContainer.find('.write'); - var idx = textareaEl.attr('tabindex'); - - submitBtns.removeAttr('tabindex'); - mobileSubmitBtn.attr('tabindex', parseInt(idx, 10) + 1); - } - - $(window).trigger('action:composer.loaded', { - postContainer: postContainer, - post_uuid: post_uuid, - composerData: composer.posts[post_uuid], - formatting: composer.formatting, - }); - - scrollStop.apply(postContainer.find('.write')); - focusElements(postContainer); - onShow(); - }); - } - - function mobileHistoryAppend() { - var path = 'compose?p=' + window.location.pathname; - var returnPath = window.location.pathname.slice(1) + window.location.search; - - // Remove relative path from returnPath - if (returnPath.startsWith(config.relative_path.slice(1))) { - returnPath = returnPath.slice(config.relative_path.length); - } - - // Add in return path to be caught by ajaxify when post is completed, or if back is pressed - window.history.replaceState({ - url: null, - returnPath: returnPath, - }, returnPath, config.relative_path + '/' + returnPath); - - // Update address bar in case f5 is pressed - window.history.pushState({ - url: path, - }, path, `${config.relative_path}/${returnPath}`); - } - - function handleHelp(postContainer) { - const helpBtn = postContainer.find('[data-action="help"]'); - helpBtn.on('click', async function () { - const html = await socket.emit('plugins.composer.renderHelp'); - if (html && html.length > 0) { - bootbox.dialog({ - size: 'large', - message: html, - onEscape: true, - backdrop: true, - onHidden: function () { - helpBtn.focus(); - }, - }); - } - }); - } - - function handleSearch(postContainer) { - var uuid = postContainer.attr('data-uuid'); - var isEditing = composer.posts[uuid] && composer.posts[uuid].action === 'posts.edit'; - var env = utils.findBootstrapEnvironment(); - var isMobile = env === 'xs' || env === 'sm'; - if (isEditing || isMobile) { - return; - } - - search.enableQuickSearch({ - searchElements: { - inputEl: postContainer.find('input.title'), - resultEl: postContainer.find('.quick-search-container'), - }, - searchOptions: { - composer: 1, - }, - hideOnNoMatches: true, - hideDuringSearch: true, - }); - } - - function activate(post_uuid) { - if (composer.active && composer.active !== post_uuid) { - composer.minimize(composer.active); - } - - composer.active = post_uuid; - const postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - postContainer.css('visibility', 'visible'); - $(window).trigger('action:composer.activate', { - post_uuid: post_uuid, - postContainer: postContainer, - }); - } - - function focusElements(postContainer) { - setTimeout(function () { - var title = postContainer.find('input.title'); - - if (title.length) { - title.focus(); - } else { - postContainer.find('textarea').focus().putCursorAtEnd(); - } - }, 20); - } - - async function post(post_uuid) { - var postData = composer.posts[post_uuid]; - var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - var handleEl = postContainer.find('.handle'); - var titleEl = postContainer.find('.title'); - var bodyEl = postContainer.find('textarea'); - var thumbEl = postContainer.find('input#topic-thumb-url'); - var onComposeRoute = postData.hasOwnProperty('template') && postData.template.compose === true; - const submitBtn = postContainer.find('.composer-submit'); - - titleEl.val(titleEl.val().trim()); - bodyEl.val(utils.rtrim(bodyEl.val())); - if (thumbEl.length) { - thumbEl.val(thumbEl.val().trim()); - } - - var action = postData.action; - - var checkTitle = (postData.hasOwnProperty('cid') || parseInt(postData.pid, 10)) && postContainer.find('input.title').length; - var isCategorySelected = !checkTitle || (checkTitle && parseInt(postData.cid, 10)); - - // Specifically for checking title/body length via plugins - var payload = { - post_uuid: post_uuid, - postData: postData, - postContainer: postContainer, - titleEl: titleEl, - titleLen: titleEl.val().length, - bodyEl: bodyEl, - bodyLen: bodyEl.val().length, - }; - - await hooks.fire('filter:composer.check', payload); - $(window).trigger('action:composer.check', payload); - - if (payload.error) { - return composerAlert(post_uuid, payload.error); - } - - if (uploads.inProgress[post_uuid] && uploads.inProgress[post_uuid].length) { - return composerAlert(post_uuid, '[[error:still-uploading]]'); - } else if (checkTitle && payload.titleLen < parseInt(config.minimumTitleLength, 10)) { - return composerAlert(post_uuid, '[[error:title-too-short, ' + config.minimumTitleLength + ']]'); - } else if (checkTitle && payload.titleLen > parseInt(config.maximumTitleLength, 10)) { - return composerAlert(post_uuid, '[[error:title-too-long, ' + config.maximumTitleLength + ']]'); - } else if (action === 'topics.post' && !isCategorySelected) { - return composerAlert(post_uuid, '[[error:category-not-selected]]'); - } else if (payload.bodyLen < parseInt(config.minimumPostLength, 10)) { - return composerAlert(post_uuid, '[[error:content-too-short, ' + config.minimumPostLength + ']]'); - } else if (payload.bodyLen > parseInt(config.maximumPostLength, 10)) { - return composerAlert(post_uuid, '[[error:content-too-long, ' + config.maximumPostLength + ']]'); - } else if (checkTitle && !tags.isEnoughTags(post_uuid)) { - return composerAlert(post_uuid, '[[error:not-enough-tags, ' + tags.minTagCount() + ']]'); - } else if (scheduler.isActive() && scheduler.getTimestamp() <= Date.now()) { - return composerAlert(post_uuid, '[[error:scheduling-to-past]]'); - } - - let composerData = { - uuid: post_uuid, - }; - let method = 'post'; - let route = ''; - - if (action === 'topics.post') { - route = '/topics'; - composerData = { - ...composerData, - handle: handleEl ? handleEl.val() : undefined, - title: titleEl.val(), - content: bodyEl.val(), - thumb: thumbEl.val() || '', - cid: categoryList.getSelectedCid(), - tags: tags.getTags(post_uuid), - timestamp: scheduler.getTimestamp(), - }; - } else if (action === 'posts.reply') { - route = `/topics/${postData.tid}`; - composerData = { - ...composerData, - tid: postData.tid, - handle: handleEl ? handleEl.val() : undefined, - content: bodyEl.val(), - toPid: postData.toPid, - }; - } else if (action === 'posts.edit') { - method = 'put'; - route = `/posts/${postData.pid}`; - composerData = { - ...composerData, - pid: postData.pid, - handle: handleEl ? handleEl.val() : undefined, - content: bodyEl.val(), - title: titleEl.val(), - thumb: thumbEl.val() || '', - tags: tags.getTags(post_uuid), - timestamp: scheduler.getTimestamp(), - }; - } - var submitHookData = { - composerEl: postContainer, - action: action, - composerData: composerData, - postData: postData, - redirect: true, - }; - - await hooks.fire('filter:composer.submit', submitHookData); - hooks.fire('action:composer.submit', Object.freeze(submitHookData)); - - // Minimize composer (and set textarea as readonly) while submitting - var taskbarIconEl = $('#taskbar .composer[data-uuid="' + post_uuid + '"] i'); - var textareaEl = postContainer.find('.write'); - taskbarIconEl.removeClass('fa-plus').addClass('fa-circle-o-notch fa-spin'); - composer.minimize(post_uuid); - textareaEl.prop('readonly', true); - - api[method](route, composerData) - .then((data) => { - submitBtn.removeAttr('disabled'); - postData.submitted = true; - - composer.discard(post_uuid); - drafts.removeDraft(postData.save_id); - - if (data.queued) { - alerts.alert({ - type: 'success', - title: '[[global:alert.success]]', - message: data.message, - timeout: 10000, - clickfn: function () { - ajaxify.go(`/post-queue/${data.id}`); - }, - }); - } else if (action === 'topics.post') { - if (submitHookData.redirect) { - ajaxify.go('topic/' + data.slug, undefined, (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm')); - } - } else if (action === 'posts.reply') { - if (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { - window.history.back(); - } else if (submitHookData.redirect && - ((ajaxify.data.template.name !== 'topic') || - (ajaxify.data.template.topic && parseInt(postData.tid, 10) !== parseInt(ajaxify.data.tid, 10))) - ) { - ajaxify.go('post/' + data.pid); - } - } else { - removeComposerHistory(); - } - - hooks.fire('action:composer.' + action, { composerData: composerData, data: data }); - }) - .catch((err) => { - // Restore composer on error - composer.load(post_uuid); - textareaEl.prop('readonly', false); - if (err.message === '[[error:email-not-confirmed]]') { - return messagesModule.showEmailConfirmWarning(err.message); - } - composerAlert(post_uuid, err.message); - }); - } - - function onShow() { - $('html').addClass('composing'); - } - - function onHide() { - $('#content').css({ paddingBottom: 0 }); - $('html').removeClass('composing'); - app.toggleNavbar(true); - formatting.exitFullscreen(); - } - - composer.discard = function (post_uuid) { - if (composer.posts[post_uuid]) { - var postData = composer.posts[post_uuid]; - var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - postContainer.remove(); - drafts.removeDraft(postData.save_id); - topicThumbs.deleteAll(post_uuid); - - taskbar.discard('composer', post_uuid); - $('[data-action="post"]').removeAttr('disabled'); - - hooks.fire('action:composer.discard', { - post_uuid: post_uuid, - postData: postData, - }); - delete composer.posts[post_uuid]; - composer.active = undefined; - } - scheduler.reset(); - onHide(); - }; - - // Alias to .discard(); - composer.close = composer.discard; - - composer.minimize = function (post_uuid) { - var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - postContainer.css('visibility', 'hidden'); - composer.active = undefined; - taskbar.minimize('composer', post_uuid); - $(window).trigger('action:composer.minimize', { - post_uuid: post_uuid, - }); - - onHide(); - }; - - composer.minimizeActive = function () { - if (composer.active) { - composer.miminize(composer.active); - } - }; - - composer.updateThumbCount = function (uuid, postContainer) { - const composerObj = composer.posts[uuid]; - if (composerObj.action === 'topics.post' || (composerObj.action === 'posts.edit' && composerObj.isMain)) { - const calls = [ - topicThumbs.get(uuid), - ]; - if (composerObj.pid) { - calls.push(topicThumbs.getByPid(composerObj.pid)); - } - Promise.all(calls).then((thumbs) => { - const thumbCount = thumbs.flat().length; - const formatEl = postContainer.find('[data-format="thumbs"]'); - formatEl.find('.badge') - .text(thumbCount) - .toggleClass('hidden', !thumbCount); - }); - } - }; - - return composer; -}); diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index 70e381b8b4..52573bb934 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -1,886 +1,290 @@ -'use strict'; - -define('composer', [ - 'taskbar', - 'translator', - 'composer/uploads', - 'composer/formatting', - 'composer/drafts', - 'composer/tags', - 'composer/categoryList', - 'composer/preview', - 'composer/resize', - 'composer/autocomplete', - 'composer/scheduler', - 'composer/post-queue', - 'scrollStop', - 'topicThumbs', - 'api', - 'bootbox', - 'alerts', - 'hooks', - 'messages', - 'search', - 'screenfull', -], function (taskbar, translator, uploads, formatting, drafts, tags, - categoryList, preview, resize, autocomplete, scheduler, postQueue, scrollStop, - topicThumbs, api, bootbox, alerts, hooks, messagesModule, search, screenfull) { - var composer = { - active: undefined, - posts: {}, - bsEnvironment: undefined, - formatting: undefined, - }; - - $(window).off('resize', onWindowResize).on('resize', onWindowResize); - onWindowResize(); - - $(window).on('action:composer.topics.post', function (ev, data) { - localStorage.removeItem('category:' + data.data.cid + ':bookmark'); - localStorage.removeItem('category:' + data.data.cid + ':bookmark:clicked'); - }); - - $(window).on('popstate', function () { - var env = utils.findBootstrapEnvironment(); - if (composer.active && (env === 'xs' || env === 'sm')) { - if (!composer.posts[composer.active].modified) { - composer.discard(composer.active); - if (composer.discardConfirm && composer.discardConfirm.length) { - composer.discardConfirm.modal('hide'); - delete composer.discardConfirm; - } - return; - } - - translator.translate('[[modules:composer.discard]]', function (translated) { - composer.discardConfirm = bootbox.confirm(translated, function (confirm) { - if (confirm) { - composer.discard(composer.active); - } else { - composer.posts[composer.active].modified = true; - } - }); - composer.posts[composer.active].modified = false; - }); - } - }); - - function removeComposerHistory() { - var env = composer.bsEnvironment; - if (ajaxify.data.template.compose === true || env === 'xs' || env === 'sm') { - history.back(); - } - } - - function onWindowResize() { - var env = utils.findBootstrapEnvironment(); - var isMobile = env === 'xs' || env === 'sm'; - - if (preview.toggle) { - if (preview.env !== env && isMobile) { - preview.env = env; - preview.toggle(false); - } - preview.env = env; - } - - if (composer.active !== undefined) { - resize.reposition($('.composer[data-uuid="' + composer.active + '"]')); - - if (!isMobile && window.location.pathname.startsWith(config.relative_path + '/compose')) { - /* - * If this conditional is met, we're no longer in mobile/tablet - * resolution but we've somehow managed to have a mobile - * composer load, so let's go back to the topic - */ - history.back(); - } else if (isMobile && !window.location.pathname.startsWith(config.relative_path + '/compose')) { - /* - * In this case, we're in mobile/tablet resolution but the composer - * that loaded was a regular composer, so let's fix the address bar - */ - mobileHistoryAppend(); - } - } - composer.bsEnvironment = env; - } - - function alreadyOpen(post) { - // If a composer for the same cid/tid/pid is already open, return the uuid, else return bool false - var type; - var id; - - if (post.hasOwnProperty('cid')) { - type = 'cid'; - } else if (post.hasOwnProperty('tid')) { - type = 'tid'; - } else if (post.hasOwnProperty('pid')) { - type = 'pid'; - } - - id = post[type]; - - // Find a match - for (var uuid in composer.posts) { - if (composer.posts[uuid].hasOwnProperty(type) && id === composer.posts[uuid][type]) { - return uuid; - } - } - - // No matches... - return false; - } - - function push(post) { - if (!post) { - return; - } - - var uuid = utils.generateUUID(); - var existingUUID = alreadyOpen(post); - - if (existingUUID) { - taskbar.updateActive(existingUUID); - return composer.load(existingUUID); - } - - var actionText = '[[topic:composer.new-topic]]'; - if (post.action === 'posts.reply') { - actionText = '[[topic:composer.replying-to]]'; - } else if (post.action === 'posts.edit') { - actionText = '[[topic:composer.editing-in]]'; - } - - translator.translate(actionText, function (translatedAction) { - taskbar.push('composer', uuid, { - title: translatedAction.replace('%1', '"' + post.title + '"'), - }); - }); - - composer.posts[uuid] = post; - composer.load(uuid); - } - - async function composerAlert(post_uuid, message) { - $('.composer[data-uuid="' + post_uuid + '"]').find('.composer-submit').removeAttr('disabled'); - - const { showAlert } = await hooks.fire('filter:composer.error', { post_uuid, message, showAlert: true }); - - if (showAlert) { - alerts.alert({ - type: 'danger', - timeout: 10000, - title: '', - message: message, - alert_id: 'post_error', - }); - } - } - - composer.findByTid = function (tid) { - // Iterates through the initialised composers and returns the uuid of the matching composer - for (var uuid in composer.posts) { - if (composer.posts.hasOwnProperty(uuid) && composer.posts[uuid].hasOwnProperty('tid') && parseInt(composer.posts[uuid].tid, 10) === parseInt(tid, 10)) { - return uuid; - } - } - - return null; - }; - - composer.addButton = function (iconClass, onClick, title) { - formatting.addButton(iconClass, onClick, title); - }; - - composer.newTopic = async (data) => { - let pushData = { - save_id: data.save_id, - action: 'topics.post', - cid: data.cid, - handle: data.handle, - title: data.title || '', - body: data.body || '', - tags: data.tags || [], - modified: !!((data.title && data.title.length) || (data.body && data.body.length)), - isMain: true, - }; - - ({ pushData } = await hooks.fire('filter:composer.topic.push', { - data: data, - pushData: pushData, - })); - - push(pushData); - }; - - composer.addQuote = function (data) { - // tid, toPid, selectedPid, title, username, text, uuid - data.uuid = data.uuid || composer.active; - - var escapedTitle = (data.title || '') - .replace(/([\\`*_{}[\]()#+\-.!])/g, '\\$1') - .replace(/\[/g, '[') - .replace(/\]/g, ']') - .replace(/%/g, '%') - .replace(/,/g, ','); - - if (data.body) { - data.body = '> ' + data.body.replace(/\n/g, '\n> ') + '\n\n'; - } - var link = '[' + escapedTitle + '](' + config.relative_path + '/post/' + encodeURIComponent(data.selectedPid || data.toPid) + ')'; - if (data.uuid === undefined) { - if (data.title && (data.selectedPid || data.toPid)) { - composer.newReply({ - tid: data.tid, - toPid: data.toPid, - title: data.title, - body: '[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n' + data.body, - }); - } else { - composer.newReply({ - tid: data.tid, - toPid: data.toPid, - title: data.title, - body: '[[modules:composer.user-said, ' + data.username + ']]\n' + data.body, - }); - } - return; - } else if (data.uuid !== composer.active) { - // If the composer is not currently active, activate it - composer.load(data.uuid); - } - - var postContainer = $('.composer[data-uuid="' + data.uuid + '"]'); - var bodyEl = postContainer.find('textarea'); - var prevText = bodyEl.val(); - if (data.title && (data.selectedPid || data.toPid)) { - translator.translate('[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n', config.defaultLang, onTranslated); - } else { - translator.translate('[[modules:composer.user-said, ' + data.username + ']]\n', config.defaultLang, onTranslated); - } - - function onTranslated(translated) { - composer.posts[data.uuid].body = (prevText.length ? prevText + '\n\n' : '') + translated + data.body; - bodyEl.val(composer.posts[data.uuid].body); - focusElements(postContainer); - preview.render(postContainer); - } - }; - - composer.newReply = function (data) { - translator.translate(data.body, config.defaultLang, function (translated) { - push({ - save_id: data.save_id, - action: 'posts.reply', - tid: data.tid, - toPid: data.toPid, - title: data.title, - body: translated, - modified: !!(translated && translated.length), - isMain: false, - }); - }); - }; - - composer.editPost = function (data) { - // pid, text - socket.emit('plugins.composer.push', data.pid, function (err, postData) { - if (err) { - return alerts.error(err); - } - postData.save_id = data.save_id; - postData.action = 'posts.edit'; - postData.pid = data.pid; - postData.modified = false; - if (data.body) { - postData.body = data.body; - postData.modified = true; - } - if (data.title) { - postData.title = data.title; - postData.modified = true; - } - push(postData); - }); - }; - - composer.load = function (post_uuid) { - var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - if (postContainer.length) { - activate(post_uuid); - resize.reposition(postContainer); - focusElements(postContainer); - onShow(); - } else if (composer.formatting) { - createNewComposer(post_uuid); - } else { - socket.emit('plugins.composer.getFormattingOptions', function (err, options) { - if (err) { - return alerts.error(err); - } - composer.formatting = options; - createNewComposer(post_uuid); - }); - } - }; - - composer.enhance = function (postContainer, post_uuid, postData) { - /* - This method enhances a composer container with client-side sugar (preview, etc) - Everything in here also applies to the /compose route - */ - - if (!post_uuid && !postData) { - post_uuid = utils.generateUUID(); - composer.posts[post_uuid] = ajaxify.data; - postData = ajaxify.data; - postContainer.attr('data-uuid', post_uuid); - } - - categoryList.init(postContainer, composer.posts[post_uuid]); - scheduler.init(postContainer, composer.posts); - - formatting.addHandler(postContainer); - formatting.addComposerButtons(); - preview.handleToggler(postContainer); - postQueue.showAlert(postContainer, postData); - uploads.initialize(post_uuid); - tags.init(postContainer, composer.posts[post_uuid]); - autocomplete.init(postContainer, post_uuid); - - postContainer.on('change', 'input, textarea', function () { - composer.posts[post_uuid].modified = true; - }); - - postContainer.on('click', '.composer-submit', function (e) { - e.preventDefault(); - e.stopPropagation(); // Other click events bring composer back to active state which is undesired on submit - - $(this).attr('disabled', true); - post(post_uuid); - }); - - require(['mousetrap'], function (mousetrap) { - mousetrap(postContainer.get(0)).bind('mod+enter', function () { - postContainer.find('.composer-submit').attr('disabled', true); - post(post_uuid); +
            + + {{{ each topics }}} +
          • > + + + + + + +
            +
            + + {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} + + {{{ if showSelect }}} +
            + +
            + {{{ end }}} +
            +
            +

            + {./title} +

            + + + + [[topic:watching]] + + + + [[topic:ignoring]] + + + + [[topic:scheduled]] + + + + {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} + + + + [[topic:locked]] + + + + [[topic:moved]] + + {{{ each ./icons }}}{@value}{{{ end }}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + + +
            + + + {humanReadableNumber(./postcount, 0)} + + + +
            + + +
            + + +
            + +
            + + {{{ if showSelect }}} +
            + +
            + {{{ end }}} +
            + {{{ if ./thumbs.length }}} + + + +{increment(./thumbs.length, "-1")} + + {{{ end }}} +
            + +
            +
            + {{{ if !reputation:disabled }}} +
            + {humanReadableNumber(./votes, 0)} + [[global:votes]] + +
            + {{{ end }}} +
            + {humanReadableNumber(./postcount, 0)} + [[global:posts]] + +
            +
            + {humanReadableNumber(./viewcount, 0)} + [[global:views]] + +
            +
            +
            +
            + {{{ if ./unreplied }}} +
            + [[category:no-replies]] +
            + {{{ else }}} + {{{ if ./teaser.pid }}} + +
            + + {./teaser.content} +
            + {{{ end }}} + {{{ end }}} +
            +
            +
            +
          • + {{{ end }}} +
          + + + + + From 25f631dcde462da9f67ab5b2dffbfa302263a186 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:53:03 -0700 Subject: [PATCH 21/50] Revert "Added the button for voice memo record - user story 2" --- node_modules_real/composer-formatting.tpl | 75 ----------------------- 1 file changed, 75 deletions(-) delete mode 100644 node_modules_real/composer-formatting.tpl diff --git a/node_modules_real/composer-formatting.tpl b/node_modules_real/composer-formatting.tpl deleted file mode 100644 index 0f1b72c967..0000000000 --- a/node_modules_real/composer-formatting.tpl +++ /dev/null @@ -1,75 +0,0 @@ -
          -
            - {{{ each formatting }}} - {{{ if ./spacer }}} -
          • - {{{ else }}} - {{{ if (./visibility.desktop && ((isTopicOrMain && ./visibility.main) || (!isTopicOrMain && ./visibility.reply))) }}} - {{{ if ./dropdownItems.length }}} - - {{{ else }}} -
          • - -
          • - {{{ end }}} - {{{ end }}} - {{{ end }}} - {{{ end }}} - - {{{ if privileges.upload:post:image }}} -
          • - -
          • - {{{ end }}} - - {{{ if privileges.upload:post:file }}} -
          • - -
          • - {{{ end }}} - -
            - -
            -
          -
          - - - {{{ if composer:showHelpTab }}} - - {{{ end }}} -
          -
          -# From 1c88d525121fe88a30b59ef17968a8af85d888c4 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:54:12 -0700 Subject: [PATCH 22/50] Revert "Implemented back-end to retrieve and showcase users' available chats" --- node_modules_real/topics_list.tpl | 140 +----------------------------- 1 file changed, 4 insertions(+), 136 deletions(-) diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index 52573bb934..f81253379d 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -1,5 +1,5 @@
            - + # {{{ each topics }}}
          • > @@ -48,7 +48,7 @@ [[topic:moved]] - {{{ each ./icons }}}{@value}{{{ end }}} + {{{each ./icons}}}{@value}{{{end}}} {{{ if !template.category }}} {function.buildCategoryLabel, ./category, "a", "border"} @@ -135,8 +135,9 @@
          • - {{{ end }}} + {{{end}}}
          + - - - From 32e7e844600007e4c8a65f2900bede4630b0ce43 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:54:50 -0700 Subject: [PATCH 23/50] Revert "Implement Front-End Modal for Future Available Chats on Share Button Click" --- node_modules_real/topics_list.tpl | 149 ++++++++++++++++++++++++++---- 1 file changed, 130 insertions(+), 19 deletions(-) diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index f81253379d..2de1038f29 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -136,23 +136,134 @@ {{{end}}} -
        +======= - - + {{{ each topics }}} +
      • > + + + + + + +
        +
        + + {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} + + {{{ if showSelect }}} +
        + +
        + {{{ end }}} +
        +
        +

        + {./title} +

        + + + + [[topic:watching]] + + + + [[topic:ignoring]] + + + + [[topic:scheduled]] + + + + {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} + + + + [[topic:locked]] + + + + [[topic:moved]] + + {{{each ./icons}}}{@value}{{{end}}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + + +
        + + + {humanReadableNumber(./postcount, 0)} + + + +
        + + +
        + {{{ if showSelect }}} +
        + +
        + {{{ end }}} +
        + {{{ if ./thumbs.length }}} + + + +{increment(./thumbs.length, "-1")} + + {{{ end }}} +
        + +
        +
        + {{{ if !reputation:disabled }}} +
        + {humanReadableNumber(./votes, 0)} + [[global:votes]] + +
        + {{{ end }}} +
        + {humanReadableNumber(./postcount, 0)} + [[global:posts]] + +
        +
        + {humanReadableNumber(./viewcount, 0)} + [[global:views]] + +
        +
        +
        +
        + {{{ if ./unreplied }}} +
        + [[category:no-replies]] +
        + {{{ else }}} + {{{ if ./teaser.pid }}} + +
        + + {./teaser.content} +
        + {{{ end }}} + {{{ end }}} +
        +
        +
        +
      • + {{{end}}} +
      From 5f8e56ca2d416afc6b44a3e7ce2a81dee730804b Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:55:19 -0700 Subject: [PATCH 24/50] Revert "node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl. - Added frontend buttons" --- node_modules_real/topics_list.tpl | 138 ------------------------------ 1 file changed, 138 deletions(-) diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index 2de1038f29..7b26d21f0e 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -1,142 +1,4 @@
        - # - {{{ each topics }}} -
      • > - - - - - - -
        -
        - - {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} - - {{{ if showSelect }}} -
        - -
        - {{{ end }}} -
        -
        -

        - {./title} -

        - - - - [[topic:watching]] - - - - [[topic:ignoring]] - - - - [[topic:scheduled]] - - - - {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} - - - - [[topic:locked]] - - - - [[topic:moved]] - - {{{each ./icons}}}{@value}{{{end}}} - - {{{ if !template.category }}} - {function.buildCategoryLabel, ./category, "a", "border"} - {{{ end }}} - - - -
        - - - {humanReadableNumber(./postcount, 0)} - - - -
        - - -
        - - -
        - -
        - - {{{ if showSelect }}} -
        - -
        - {{{ end }}} -
        - {{{ if ./thumbs.length }}} - - - +{increment(./thumbs.length, "-1")} - - {{{ end }}} -
        - -
        -
        - {{{ if !reputation:disabled }}} -
        - {humanReadableNumber(./votes, 0)} - [[global:votes]] - -
        - {{{ end }}} -
        - {humanReadableNumber(./postcount, 0)} - [[global:posts]] - -
        -
        - {humanReadableNumber(./viewcount, 0)} - [[global:views]] - -
        -
        -
        -
        - {{{ if ./unreplied }}} -
        - [[category:no-replies]] -
        - {{{ else }}} - {{{ if ./teaser.pid }}} - -
        - - {./teaser.content} -
        - {{{ end }}} - {{{ end }}} -
        -
        -
        -
      • - {{{end}}} -======= {{{ each topics }}}
      • > From 91053aac86753a7ed57b8e29324f2d13bf7cdff8 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:56:19 -0700 Subject: [PATCH 25/50] Revert "Adding node modules file we edited to git repository " --- README.md | 11 --- dump.rdb | Bin 159585 -> 0 bytes node_modules_real/topics_list.tpl | 131 ------------------------------ 3 files changed, 142 deletions(-) delete mode 100644 dump.rdb delete mode 100644 node_modules_real/topics_list.tpl diff --git a/README.md b/README.md index ef1ac825d7..6ef180f625 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,6 @@ [![Coverage Status](https://coveralls.io/repos/github/CMU-313/NodeBB/badge.svg)](https://coveralls.io/github/CMU-313/NodeBB) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=CMU-313_NodeBB&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=CMU-313_NodeBB) - -****************************************** NOTICE ************************************************ - -The collaborators on this project are Seckhen Andrade, Nikoloz Devidze, Ghani Raissov, Davit Charkviani, Yousuf Alkhiyami - -Please know that there is a folder called node_module_real, which contains the files that are modified compared to the files that are in the node_modules we get by npm install. Please take the code from the files in the node_modules_real folder (files have the same names as in the node_module generated by npm install) and paste (replace the code) them in the respective files. - - -*************************************************************************************************** - - [**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and supports either Redis, MongoDB, or a PostgreSQL database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB takes the best of the modern web: real-time streaming discussions, mobile responsiveness, and rich RESTful read/write APIs, while staying true to the original bulletin board/forum format → categorical hierarchies, local user accounts, and asynchronous messaging. NodeBB by itself contains a "common core" of basic functionality, while additional functionality and integrations are enabled through the use of third-party plugins. diff --git a/dump.rdb b/dump.rdb deleted file mode 100644 index c71ec0a686aaf28ee9f0ee9fb215df0b73a21129..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159585 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 diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl deleted file mode 100644 index 7b26d21f0e..0000000000 --- a/node_modules_real/topics_list.tpl +++ /dev/null @@ -1,131 +0,0 @@ -
          - - {{{ each topics }}} -
        • > - - - - - - -
          -
          - - {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} - - {{{ if showSelect }}} -
          - -
          - {{{ end }}} -
          -
          -

          - {./title} -

          - - - - [[topic:watching]] - - - - [[topic:ignoring]] - - - - [[topic:scheduled]] - - - - {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} - - - - [[topic:locked]] - - - - [[topic:moved]] - - {{{each ./icons}}}{@value}{{{end}}} - - {{{ if !template.category }}} - {function.buildCategoryLabel, ./category, "a", "border"} - {{{ end }}} - - - -
          - - - {humanReadableNumber(./postcount, 0)} - - - -
          - - -
          - {{{ if showSelect }}} -
          - -
          - {{{ end }}} -
          - {{{ if ./thumbs.length }}} - - - +{increment(./thumbs.length, "-1")} - - {{{ end }}} -
          - -
          -
          - {{{ if !reputation:disabled }}} -
          - {humanReadableNumber(./votes, 0)} - [[global:votes]] - -
          - {{{ end }}} -
          - {humanReadableNumber(./postcount, 0)} - [[global:posts]] - -
          -
          - {humanReadableNumber(./viewcount, 0)} - [[global:views]] - -
          -
          -
          -
          - {{{ if ./unreplied }}} -
          - [[category:no-replies]] -
          - {{{ else }}} - {{{ if ./teaser.pid }}} - -
          - - {./teaser.content} -
          - {{{ end }}} - {{{ end }}} -
          -
          -
          -
        • - {{{end}}} -
        From 9c9be3935d319024f71365a27f238dd8e15edb14 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:57:15 -0700 Subject: [PATCH 26/50] Revert "replicated project1 changes here too" --- src/upgrades/1.1.1/upload_privileges.js | 85 +++++++------------------ 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/src/upgrades/1.1.1/upload_privileges.js b/src/upgrades/1.1.1/upload_privileges.js index dc3529079a..d343f4ebfa 100644 --- a/src/upgrades/1.1.1/upload_privileges.js +++ b/src/upgrades/1.1.1/upload_privileges.js @@ -1,77 +1,38 @@ 'use strict'; +const async = require('async'); const db = require('../../database'); + module.exports = { name: 'Giving upload privileges', timestamp: Date.UTC(2016, 6, 12), - method: async function () { - console.log('graissov: Executing method function'); + method: function (callback) { const privilegesAPI = require('../../privileges'); const meta = require('../../meta'); - const cids = await getCategoryIds(); - await processCategories(cids, privilegesAPI, meta); - }, -}; - -// Helper function to get category IDs -function getCategoryIds() { - console.log('graissov: Executing getCategoryIds function'); - return new Promise((resolve, reject) => { db.getSortedSetRange('categories:cid', 0, -1, (err, cids) => { if (err) { - return reject(err); + return callback(err); } - resolve(cids); - }); - }); -} - -// Helper function to process each category -async function processCategories(cids, privilegesAPI, meta) { - console.log('graissov: Executing processCategories function'); - await Promise.all(cids.map(async (cid) => { - const data = await getCategoryData(cid, privilegesAPI); - await processGroups(data.groups, cid, privilegesAPI, meta); - })); -} -// Helper function to get category data -function getCategoryData(cid, privilegesAPI) { - console.log('graissov: Executing getCategoryData function'); - return new Promise((resolve, reject) => { - privilegesAPI.categories.list(cid, (err, data) => { - if (err) { - return reject(err); - } - resolve(data); + async.eachSeries(cids, (cid, next) => { + privilegesAPI.categories.list(cid, (err, data) => { + if (err) { + return next(err); + } + async.eachSeries(data.groups, (group, next) => { + if (group.name === 'guests' && parseInt(meta.config.allowGuestUploads, 10) !== 1) { + return next(); + } + if (group.privileges['groups:read']) { + privilegesAPI.categories.give(['upload:post:image'], cid, group.name, next); + } else { + next(); + } + }, next); + }); + }, callback); }); - }); -} - -// Helper function to process groups within a category -async function processGroups(groups, cid, privilegesAPI, meta) { - console.log('graissov: Executing processGroups function'); - await Promise.all(groups.map(async (group) => { - if (group.name === 'guests' && parseInt(meta.config.allowGuestUploads, 10) !== 1) { - return; // Skip guests if uploads are not allowed - } - if (group.privileges['groups:read']) { - await giveUploadPrivilege(cid, group.name, privilegesAPI); - } - })); -} - -// Helper function to give upload privileges -function giveUploadPrivilege(cid, groupName, privilegesAPI) { - console.log('graissov: Executing giveUploadPrivilege function'); - return new Promise((resolve, reject) => { - privilegesAPI.categories.give(['upload:post:image'], cid, groupName, (err) => { - if (err) { - return reject(err); - } - resolve(); - }); - }); -} + }, +}; From 6a3c75932370757fc35da9dffe06538d467c630d Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:58:13 -0700 Subject: [PATCH 27/50] Revert "Matched sound_settings.js to match project one's file " --- src/upgrades/1.4.4/sound_settings.js | 110 ++++++++++++--------------- 1 file changed, 49 insertions(+), 61 deletions(-) diff --git a/src/upgrades/1.4.4/sound_settings.js b/src/upgrades/1.4.4/sound_settings.js index dcd857d246..ae0a6d8fa3 100644 --- a/src/upgrades/1.4.4/sound_settings.js +++ b/src/upgrades/1.4.4/sound_settings.js @@ -2,76 +2,64 @@ const async = require('async'); const db = require('../../database'); -const meta = require('../../meta'); -const batch = require('../../batch'); -const soundMap = { - 'notification.mp3': 'Default | Deedle-dum', - 'waterdrop-high.mp3': 'Default | Water drop (high)', - 'waterdrop-low.mp3': 'Default | Water drop (low)', -}; - -function updateGlobalSoundSettings(callback) { - console.log('Seckhen Ariel Andrade Cuellar'); - const keys = ['chat-incoming', 'chat-outgoing', 'notification']; - db.getObject('settings:sounds', (err, settings) => { - if (err || !settings) { - return callback(err); - } - - const updatedSettings = keys.reduce((acc, key) => { - if (settings[key] && !settings[key].includes(' | ')) { - acc[key] = soundMap[settings[key]] || ''; - } - return acc; - }, {}); - - meta.configs.setMultiple(updatedSettings, callback); - }); -} -function updateUserSoundSettings(callback) { - console.log('Seckhen Ariel Andrade Cuellar'); +module.exports = { + name: 'Update global and user sound settings', + timestamp: Date.UTC(2017, 1, 25), + method: function (callback) { + const meta = require('../../meta'); + const batch = require('../../batch'); - const keys = ['notificationSound', 'incomingChatSound', 'outgoingChatSound']; + const map = { + 'notification.mp3': 'Default | Deedle-dum', + 'waterdrop-high.mp3': 'Default | Water drop (high)', + 'waterdrop-low.mp3': 'Default | Water drop (low)', + }; - batch.processSortedSet('users:joindate', processUserBatch, callback); + async.parallel([ + function (cb) { + const keys = ['chat-incoming', 'chat-outgoing', 'notification']; - function processUserBatch(ids, next) { - console.log('Seckhen Ariel Andrade Cuellar'); - async.each(ids, updateUserSettings, next); - } + db.getObject('settings:sounds', (err, settings) => { + if (err || !settings) { + return cb(err); + } - function updateUserSettings(uid, next) { - console.log('Seckhen Ariel Andrade Cuellar'); - db.getObject(`user:${uid}:settings`, (err, settings) => { - if (err || !settings) { - return next(err); - } + keys.forEach((key) => { + if (settings[key] && !settings[key].includes(' | ')) { + settings[key] = map[settings[key]] || ''; + } + }); - const newSettings = keys.reduce((acc, key) => { - if (settings[key] && !settings[key].includes(' | ')) { - acc[key] = soundMap[settings[key]] || ''; - } - return acc; - }, {}); + meta.configs.setMultiple(settings, cb); + }); + }, + function (cb) { + const keys = ['notificationSound', 'incomingChatSound', 'outgoingChatSound']; - if (Object.keys(newSettings).length) { - db.setObject(`user:${uid}:settings`, newSettings, next); - } else { - setImmediate(next); - } - }); - } -} + batch.processSortedSet('users:joindate', (ids, next) => { + async.each(ids, (uid, next) => { + db.getObject(`user:${uid}:settings`, (err, settings) => { + if (err || !settings) { + return next(err); + } + const newSettings = {}; + keys.forEach((key) => { + if (settings[key] && !settings[key].includes(' | ')) { + newSettings[key] = map[settings[key]] || ''; + } + }); -module.exports = { - name: 'Update global and user sound settings', - timestamp: Date.UTC(2017, 1, 25), - method: function (callback) { - async.parallel([ - updateGlobalSoundSettings, - updateUserSoundSettings, + if (Object.keys(newSettings).length) { + db.setObject(`user:${uid}:settings`, newSettings, next); + } else { + setImmediate(next); + } + }); + }, next); + }, cb); + }, ], callback); }, }; From 97bb224354cf98ffed06ed1a70699bdd7889458d Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:59:19 -0700 Subject: [PATCH 28/50] Revert "Matched The Modified File(sorted.js) from Project NodeBB" --- src/topics/sorted.js | 163 ++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 87 deletions(-) diff --git a/src/topics/sorted.js b/src/topics/sorted.js index bceda88015..98292f0ddb 100644 --- a/src/topics/sorted.js +++ b/src/topics/sorted.js @@ -11,23 +11,13 @@ const meta = require('../meta'); const plugins = require('../plugins'); module.exports = function (Topics) { - console.log('ndevidze'); Topics.getSortedTopics = async function (params) { const data = { nextStart: 0, topicCount: 0, topics: [], }; - params = initializeParams(params); - data.tids = await getTids(params); - data.tids = await sortTids(data.tids, params); - data.tids = await filterTids(data.tids.slice(0, meta.config.recentMaxTopics), params); - data.topicCount = data.tids.length; - data.topics = await getTopics(data.tids, params); - data.nextStart = params.stop + 1; - return data; - }; - function initializeParams(params) { + params.term = params.term || 'alltime'; params.sort = params.sort || 'recent'; params.query = params.query || {}; @@ -38,8 +28,15 @@ module.exports = function (Topics) { if (params.tags && !Array.isArray(params.tags)) { params.tags = [params.tags]; } - return params; - } + data.tids = await getTids(params); + data.tids = await sortTids(data.tids, params); + data.tids = await filterTids(data.tids.slice(0, meta.config.recentMaxTopics), params); + data.topicCount = data.tids.length; + data.topics = await getTopics(data.tids, params); + data.nextStart = params.stop + 1; + return data; + }; + async function getTids(params) { if (plugins.hooks.hasListeners('filter:topics.getSortedTids')) { const result = await plugins.hooks.fire('filter:topics.getSortedTids', { params: params, tids: [] }); @@ -140,80 +137,69 @@ module.exports = function (Topics) { } async function getCidTids(params) { - console.log('getting Tids'); if (params.tags.length) { - return await getTidsForTagsAndCids(params); + return _.intersection(...await Promise.all(params.tags.map(async (tag) => { + const sets = params.cids.map(cid => `cid:${cid}:tag:${tag}:topics`); + return await db.getSortedSetRevRange(sets, 0, -1); + }))); } - const sets = getCidSets(params.cids, params.sort); - let pinnedTids = await db.getSortedSetRevRange(sets.pinnedSets, 0, -1); - pinnedTids = await Topics.tools.checkPinExpiry(pinnedTids); - const tids = await db[sets.method](sets.normalSets, 0, meta.config.recentMaxTopics - 1); - return pinnedTids.concat(tids); - } - function getCidSets(cids, sort) { + const sets = []; const pinnedSets = []; - cids.forEach((cid) => { - if (sort === 'recent' || sort === 'old') { + params.cids.forEach((cid) => { + if (params.sort === 'recent' || params.sort === 'old') { sets.push(`cid:${cid}:tids`); } else { - sets.push(`cid:${cid}:tids${sort ? `:${sort}` : ''}`); + sets.push(`cid:${cid}:tids${params.sort ? `:${params.sort}` : ''}`); } pinnedSets.push(`cid:${cid}:tids:pinned`); }); - const method = (sort === 'old') ? 'getSortedSetRange' : 'getSortedSetRevRange'; - return { normalSets: sets, pinnedSets: pinnedSets, method: method }; - } - async function getTidsForTagsAndCids(params) { - return _.intersection( - ...await Promise.all(params.tags.map(async (tag) => { - const sets = params.cids.map(cid => `cid:${cid}:tag:${tag}:topics`); - return await db.getSortedSetRevRange(sets, 0, -1); - })) - ); + let pinnedTids = await db.getSortedSetRevRange(pinnedSets, 0, -1); + pinnedTids = await Topics.tools.checkPinExpiry(pinnedTids); + const method = params.sort === 'old' ? + 'getSortedSetRange' : + 'getSortedSetRevRange'; + const tids = await db[method](sets, 0, meta.config.recentMaxTopics - 1); + return pinnedTids.concat(tids); } async function sortTids(tids, params) { - console.log('sorting Tids'); - if (canSkipSorting(params)) { + if (params.term === 'alltime' && !params.cids && !params.tags.length && params.filter !== 'watched' && !params.floatPinned) { return tids; } + if (params.sort === 'posts' && params.term !== 'alltime') { return tids; } + const { sortMap, fields } = await plugins.hooks.fire('filter:topics.sortOptions', { params, - fields: getFields(), - sortMap: getSortMap(), + fields: [ + 'tid', 'timestamp', 'lastposttime', 'upvotes', 'downvotes', 'postcount', 'pinned', + ], + sortMap: { + recent: sortRecent, + old: sortOld, + create: sortCreate, + posts: sortPopular, + votes: sortVotes, + views: sortViews, + }, }); + const topicData = await Topics.getTopicsFields(tids, fields); - const sortFn = getSortFunction(params.sort, sortMap); + const sortFn = sortMap.hasOwnProperty(params.sort) && sortMap[params.sort] ? + sortMap[params.sort] : sortRecent; + if (params.floatPinned) { floatPinned(topicData, sortFn); } else { topicData.sort(sortFn); } + return topicData.map(topic => topic && topic.tid); } - function canSkipSorting(params) { - return params.term === 'alltime' && !params.cids && !params.tags.length && params.filter !== 'watched' && !params.floatPinned; - } - function getFields() { - return ['tid', 'timestamp', 'lastposttime', 'upvotes', 'downvotes', 'postcount', 'pinned']; - } - function getSortMap() { - return { - recent: sortRecent, - old: sortOld, - create: sortCreate, - posts: sortPopular, - votes: sortVotes, - views: sortViews, - }; - } - function getSortFunction(sort, sortMap) { - return sortMap.hasOwnProperty(sort) && sortMap[sort] ? sortMap[sort] : sortRecent; - } + function floatPinned(topicData, sortFn) { topicData.sort((a, b) => (a.pinned !== b.pinned ? b.pinned - a.pinned : sortFn(a, b))); } @@ -249,46 +235,49 @@ module.exports = function (Topics) { } async function filterTids(tids, params) { - console.log('filtering Tids'); - tids = await applyFilterByType(tids, params); - tids = await privileges.topics.filterTids('topics:read', tids, params.uid); - const topicData = await Topics.getTopicsFields(tids, ['uid', 'tid', 'cid', 'tags']); + const { filter } = params; + const { uid } = params; + + if (filter === 'new') { + tids = await Topics.filterNewTids(tids, uid); + } else if (filter === 'unreplied') { + tids = await Topics.filterUnrepliedTids(tids); + } else { + tids = await Topics.filterNotIgnoredTids(tids, uid); + } + + tids = await privileges.topics.filterTids('topics:read', tids, uid); + let topicData = await Topics.getTopicsFields(tids, ['uid', 'tid', 'cid', 'tags']); const topicCids = _.uniq(topicData.map(topic => topic.cid)).filter(Boolean); + + async function getIgnoredCids() { + if (params.cids || filter === 'watched' || meta.config.disableRecentCategoryFilter) { + return []; + } + return await categories.isIgnored(topicCids, uid); + } const [ignoredCids, filtered] = await Promise.all([ - getIgnoredCids(params, topicCids), - user.blocks.filter(params.uid, topicData), + getIgnoredCids(), + user.blocks.filter(uid, topicData), ]); - return filterTopicsByCidsAndTags(filtered, ignoredCids, params); - } - async function applyFilterByType(tids, params) { - switch (params.filter) { - case 'new': - return await Topics.filterNewTids(tids, params.uid); - case 'unreplied': - return await Topics.filterUnrepliedTids(tids); - default: - return await Topics.filterNotIgnoredTids(tids, params.uid); - } - } - async function getIgnoredCids(params, topicCids) { - if (params.cids || params.filter === 'watched' || meta.config.disableRecentCategoryFilter) { - return []; - } - return await categories.isIgnored(topicCids, params.uid); - } - function filterTopicsByCidsAndTags(topicData, ignoredCids, params) { - const isCidIgnored = _.zipObject(topicData.map(t => t.cid), ignoredCids); + + const isCidIgnored = _.zipObject(topicCids, ignoredCids); + topicData = filtered; + const cids = params.cids && params.cids.map(String); const { tags } = params; - const tids = topicData.filter(t => ( + tids = topicData.filter(t => ( t && t.cid && !isCidIgnored[t.cid] && (!cids || cids.includes(String(t.cid))) && (!tags.length || tags.every(tag => t.tags.find(topicTag => topicTag.value === tag))) )).map(t => t.tid); - return plugins.hooks.fire('filter:topics.filterSortedTids', { tids, params }).then(result => result.tids); + + const result = await plugins.hooks.fire('filter:topics.filterSortedTids', { tids: tids, params: params }); + return result.tids; } + async function getTopics(tids, params) { tids = tids.slice(params.start, params.stop !== -1 ? params.stop + 1 : undefined); const topicData = await Topics.getTopicsByTids(tids, params); From bf0e2f19d844dadb724b414c3566a0ad14960c77 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:00:02 -0700 Subject: [PATCH 29/50] Revert "Imported Improvements for tags.js to Match Project 1" --- src/topics/tags.js | 123 +++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/src/topics/tags.js b/src/topics/tags.js index db4ba219fb..daab4e5f77 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -19,26 +19,19 @@ const cache = require('../cache'); module.exports = function (Topics) { Topics.createTags = async function (tags, tid, timestamp) { - console.log('dcharkvi'); if (!Array.isArray(tags) || !tags.length) { return; } + const cid = await Topics.getTopicField(tid, 'cid'); - const topicSets = generateTopicSets(tags, cid); - await db.sortedSetsAdd(topicSets, timestamp, tid); - await Topics.updateCategoryTagsCount([cid], tags); - await updateTagCountsForAll(tags); - }; - function generateTopicSets(tags, cid) { - console.log('dcharkvi'); - return tags.map(tag => `tag:${tag}:topics`).concat( + const topicSets = tags.map(tag => `tag:${tag}:topics`).concat( tags.map(tag => `cid:${cid}:tag:${tag}:topics`) ); - } - async function updateTagCountsForAll(tags) { - console.log('dcharkvi'); + await db.sortedSetsAdd(topicSets, timestamp, tid); + await Topics.updateCategoryTagsCount([cid], tags); await Promise.all(tags.map(updateTagCount)); - } + }; + Topics.filterTags = async function (tags, cid) { const result = await plugins.hooks.fire('filter:tags.filter', { tags: tags, cid: cid }); tags = _.uniq(result.tags) @@ -72,41 +65,36 @@ module.exports = function (Topics) { cids.map(cid => `cid:${cid}:tags`), '-inf', 0 ); }; + Topics.validateTags = async function (tags, cid, uid, tid = null) { - console.log('dcharkvi'); if (!Array.isArray(tags)) { throw new Error('[[error:invalid-data]]'); } tags = _.uniq(tags); - const [categoryData, isPrivileged, currentTags] = await getValidationData(tags, cid, uid, tid); - validateTagCount(tags, categoryData); - await validateSystemTags(tags, currentTags, isPrivileged); - }; - async function getValidationData(tags, cid, uid, tid) { - return await Promise.all([ + const [categoryData, isPrivileged, currentTags] = await Promise.all([ categories.getCategoryFields(cid, ['minTags', 'maxTags']), user.isPrivileged(uid), tid ? Topics.getTopicTags(tid) : [], ]); - } - function validateTagCount(tags, categoryData) { if (tags.length < parseInt(categoryData.minTags, 10)) { throw new Error(`[[error:not-enough-tags, ${categoryData.minTags}]]`); } else if (tags.length > parseInt(categoryData.maxTags, 10)) { throw new Error(`[[error:too-many-tags, ${categoryData.maxTags}]]`); } - } - async function validateSystemTags(tags, currentTags, isPrivileged) { + const addedTags = tags.filter(tag => !currentTags.includes(tag)); const removedTags = currentTags.filter(tag => !tags.includes(tag)); - const systemTags = (meta.config.systemTags || '').split(',').map(tag => tag.trim()).filter(Boolean); - if (!isPrivileged && systemTags.length && addedTags.some(tag => systemTags.includes(tag))) { + const systemTags = (meta.config.systemTags || '').split(','); + + if (!isPrivileged && systemTags.length && addedTags.length && addedTags.some(tag => systemTags.includes(tag))) { throw new Error('[[error:cant-use-system-tag]]'); } - if (!isPrivileged && systemTags.length && removedTags.some(tag => systemTags.includes(tag))) { + + if (!isPrivileged && systemTags.length && removedTags.length && removedTags.some(tag => systemTags.includes(tag))) { throw new Error('[[error:cant-remove-system-tag]]'); } - } + }; + async function filterCategoryTags(tags, cid) { const tagWhitelist = await categories.getTagWhitelist([cid]); if (!Array.isArray(tagWhitelist[0]) || !tagWhitelist[0].length) { @@ -115,6 +103,7 @@ module.exports = function (Topics) { const whitelistSet = new Set(tagWhitelist[0]); return tags.filter(tag => whitelistSet.has(tag)); } + Topics.createEmptyTag = async function (tag) { if (!tag) { throw new Error('[[error:invalid-tag]]'); @@ -135,55 +124,67 @@ module.exports = function (Topics) { .map(cid => ([`cid:${cid}:tags`, 0, tag])); await db.sortedSetAddBulk(bulkAdd); }; + Topics.renameTags = async function (data) { - console.log('dcharkvi'); - await Promise.all(data.map(tagData => renameTag(tagData.value, tagData.newName))); + for (const tagData of data) { + // eslint-disable-next-line no-await-in-loop + await renameTag(tagData.value, tagData.newName); + } }; + async function renameTag(tag, newTagName) { - console.log('dcharkvi'); if (!newTagName || tag === newTagName) { return; } newTagName = utils.cleanUpTag(newTagName, meta.config.maximumTagLength); + await Topics.createEmptyTag(newTagName); - const allCids = await processTagForAllTids(tag, newTagName); - await Topics.updateCategoryTagsCount(Object.keys(allCids), [newTagName]); - await updateTagCount(newTagName); - } - async function processTagForAllTids(tag, newTagName) { const allCids = {}; + await batch.processSortedSet(`tag:${tag}:topics`, async (tids) => { const topicData = await Topics.getTopicsFields(tids, ['tid', 'cid', 'tags']); const cids = topicData.map(t => t.cid); topicData.forEach((t) => { allCids[t.cid] = true; }); const scores = await db.sortedSetScores(`tag:${tag}:topics`, tids); - await updateTagTopics(newTagName, tag, scores, tids, topicData, cids); + // update tag::topics + await db.sortedSetAdd(`tag:${newTagName}:topics`, scores, tids); + await db.sortedSetRemove(`tag:${tag}:topics`, tids); + + // update cid::tag::topics + await db.sortedSetAddBulk(topicData.map( + (t, index) => [`cid:${t.cid}:tag:${newTagName}:topics`, scores[index], t.tid] + )); + await db.sortedSetRemove(cids.map(cid => `cid:${cid}:tag:${tag}:topics`), tids); + + // update 'tags' field in topic hash + topicData.forEach((topic) => { + topic.tags = topic.tags.map(tagItem => tagItem.value); + const index = topic.tags.indexOf(tag); + if (index !== -1) { + topic.tags.splice(index, 1, newTagName); + } + }); + await db.setObjectBulk( + topicData.map(t => [`topic:${t.tid}`, { tags: t.tags.join(',') }]), + ); }, {}); - return allCids; - } - async function updateTagTopics(newTagName, tag, scores, tids, topicData, cids) { - await db.sortedSetAdd(`tag:${newTagName}:topics`, scores, tids); - await db.sortedSetRemove(`tag:${tag}:topics`, tids); - // Update cid::tag::topics - await db.sortedSetAddBulk(topicData.map( - (t, index) => [`cid:${t.cid}:tag:${newTagName}:topics`, scores[index], t.tid] - )); - await db.sortedSetRemove(cids.map(cid => `cid:${cid}:tag:${tag}:topics`), tids); - // Update 'tags' field in topic hash - await updateTopicHashTags(tids, topicData, tag, newTagName); - } - async function updateTopicHashTags(tids, topicData, oldTag, newTag) { - topicData.forEach((topic) => { - topic.tags = topic.tags.map(tagItem => tagItem.value); - const index = topic.tags.indexOf(oldTag); - if (index !== -1) { - topic.tags.splice(index, 1, newTag); - } - }); - await db.setObjectBulk( - topicData.map(t => [`topic:${t.tid}`, { tags: t.tags.join(',') }]), - ); + const followers = await db.getSortedSetRangeWithScores(`tag:${tag}:followers`, 0, -1); + if (followers.length) { + const userKeys = followers.map(item => `uid:${item.value}:followed_tags`); + const scores = await db.sortedSetsScore(userKeys, tag); + await db.sortedSetsRemove(userKeys, tag); + await db.sortedSetsAdd(userKeys, scores, newTagName); + await db.sortedSetAdd( + `tag:${newTagName}:followers`, + followers.map(item => item.score), + followers.map(item => item.value), + ); + } + await Topics.deleteTag(tag); + await updateTagCount(newTagName); + await Topics.updateCategoryTagsCount(Object.keys(allCids), [newTagName]); } + async function updateTagCount(tag) { const count = await Topics.getTagTopicCount(tag); await db.sortedSetAdd('tags:topic:count', count || 0, tag); From a1411252e2c653b0a037e82476ebbbe3022d840b Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:20:18 -0700 Subject: [PATCH 30/50] Revert "Revert "Imported Improvements for tags.js to Match Project 1"" --- src/topics/tags.js | 123 ++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 62 deletions(-) diff --git a/src/topics/tags.js b/src/topics/tags.js index daab4e5f77..db4ba219fb 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -19,19 +19,26 @@ const cache = require('../cache'); module.exports = function (Topics) { Topics.createTags = async function (tags, tid, timestamp) { + console.log('dcharkvi'); if (!Array.isArray(tags) || !tags.length) { return; } - const cid = await Topics.getTopicField(tid, 'cid'); - const topicSets = tags.map(tag => `tag:${tag}:topics`).concat( - tags.map(tag => `cid:${cid}:tag:${tag}:topics`) - ); + const topicSets = generateTopicSets(tags, cid); await db.sortedSetsAdd(topicSets, timestamp, tid); await Topics.updateCategoryTagsCount([cid], tags); - await Promise.all(tags.map(updateTagCount)); + await updateTagCountsForAll(tags); }; - + function generateTopicSets(tags, cid) { + console.log('dcharkvi'); + return tags.map(tag => `tag:${tag}:topics`).concat( + tags.map(tag => `cid:${cid}:tag:${tag}:topics`) + ); + } + async function updateTagCountsForAll(tags) { + console.log('dcharkvi'); + await Promise.all(tags.map(updateTagCount)); + } Topics.filterTags = async function (tags, cid) { const result = await plugins.hooks.fire('filter:tags.filter', { tags: tags, cid: cid }); tags = _.uniq(result.tags) @@ -65,36 +72,41 @@ module.exports = function (Topics) { cids.map(cid => `cid:${cid}:tags`), '-inf', 0 ); }; - Topics.validateTags = async function (tags, cid, uid, tid = null) { + console.log('dcharkvi'); if (!Array.isArray(tags)) { throw new Error('[[error:invalid-data]]'); } tags = _.uniq(tags); - const [categoryData, isPrivileged, currentTags] = await Promise.all([ + const [categoryData, isPrivileged, currentTags] = await getValidationData(tags, cid, uid, tid); + validateTagCount(tags, categoryData); + await validateSystemTags(tags, currentTags, isPrivileged); + }; + async function getValidationData(tags, cid, uid, tid) { + return await Promise.all([ categories.getCategoryFields(cid, ['minTags', 'maxTags']), user.isPrivileged(uid), tid ? Topics.getTopicTags(tid) : [], ]); + } + function validateTagCount(tags, categoryData) { if (tags.length < parseInt(categoryData.minTags, 10)) { throw new Error(`[[error:not-enough-tags, ${categoryData.minTags}]]`); } else if (tags.length > parseInt(categoryData.maxTags, 10)) { throw new Error(`[[error:too-many-tags, ${categoryData.maxTags}]]`); } - + } + async function validateSystemTags(tags, currentTags, isPrivileged) { const addedTags = tags.filter(tag => !currentTags.includes(tag)); const removedTags = currentTags.filter(tag => !tags.includes(tag)); - const systemTags = (meta.config.systemTags || '').split(','); - - if (!isPrivileged && systemTags.length && addedTags.length && addedTags.some(tag => systemTags.includes(tag))) { + const systemTags = (meta.config.systemTags || '').split(',').map(tag => tag.trim()).filter(Boolean); + if (!isPrivileged && systemTags.length && addedTags.some(tag => systemTags.includes(tag))) { throw new Error('[[error:cant-use-system-tag]]'); } - - if (!isPrivileged && systemTags.length && removedTags.length && removedTags.some(tag => systemTags.includes(tag))) { + if (!isPrivileged && systemTags.length && removedTags.some(tag => systemTags.includes(tag))) { throw new Error('[[error:cant-remove-system-tag]]'); } - }; - + } async function filterCategoryTags(tags, cid) { const tagWhitelist = await categories.getTagWhitelist([cid]); if (!Array.isArray(tagWhitelist[0]) || !tagWhitelist[0].length) { @@ -103,7 +115,6 @@ module.exports = function (Topics) { const whitelistSet = new Set(tagWhitelist[0]); return tags.filter(tag => whitelistSet.has(tag)); } - Topics.createEmptyTag = async function (tag) { if (!tag) { throw new Error('[[error:invalid-tag]]'); @@ -124,67 +135,55 @@ module.exports = function (Topics) { .map(cid => ([`cid:${cid}:tags`, 0, tag])); await db.sortedSetAddBulk(bulkAdd); }; - Topics.renameTags = async function (data) { - for (const tagData of data) { - // eslint-disable-next-line no-await-in-loop - await renameTag(tagData.value, tagData.newName); - } + console.log('dcharkvi'); + await Promise.all(data.map(tagData => renameTag(tagData.value, tagData.newName))); }; - async function renameTag(tag, newTagName) { + console.log('dcharkvi'); if (!newTagName || tag === newTagName) { return; } newTagName = utils.cleanUpTag(newTagName, meta.config.maximumTagLength); - await Topics.createEmptyTag(newTagName); + const allCids = await processTagForAllTids(tag, newTagName); + await Topics.updateCategoryTagsCount(Object.keys(allCids), [newTagName]); + await updateTagCount(newTagName); + } + async function processTagForAllTids(tag, newTagName) { const allCids = {}; - await batch.processSortedSet(`tag:${tag}:topics`, async (tids) => { const topicData = await Topics.getTopicsFields(tids, ['tid', 'cid', 'tags']); const cids = topicData.map(t => t.cid); topicData.forEach((t) => { allCids[t.cid] = true; }); const scores = await db.sortedSetScores(`tag:${tag}:topics`, tids); - // update tag::topics - await db.sortedSetAdd(`tag:${newTagName}:topics`, scores, tids); - await db.sortedSetRemove(`tag:${tag}:topics`, tids); - - // update cid::tag::topics - await db.sortedSetAddBulk(topicData.map( - (t, index) => [`cid:${t.cid}:tag:${newTagName}:topics`, scores[index], t.tid] - )); - await db.sortedSetRemove(cids.map(cid => `cid:${cid}:tag:${tag}:topics`), tids); - - // update 'tags' field in topic hash - topicData.forEach((topic) => { - topic.tags = topic.tags.map(tagItem => tagItem.value); - const index = topic.tags.indexOf(tag); - if (index !== -1) { - topic.tags.splice(index, 1, newTagName); - } - }); - await db.setObjectBulk( - topicData.map(t => [`topic:${t.tid}`, { tags: t.tags.join(',') }]), - ); + await updateTagTopics(newTagName, tag, scores, tids, topicData, cids); }, {}); - const followers = await db.getSortedSetRangeWithScores(`tag:${tag}:followers`, 0, -1); - if (followers.length) { - const userKeys = followers.map(item => `uid:${item.value}:followed_tags`); - const scores = await db.sortedSetsScore(userKeys, tag); - await db.sortedSetsRemove(userKeys, tag); - await db.sortedSetsAdd(userKeys, scores, newTagName); - await db.sortedSetAdd( - `tag:${newTagName}:followers`, - followers.map(item => item.score), - followers.map(item => item.value), - ); - } - await Topics.deleteTag(tag); - await updateTagCount(newTagName); - await Topics.updateCategoryTagsCount(Object.keys(allCids), [newTagName]); + return allCids; + } + async function updateTagTopics(newTagName, tag, scores, tids, topicData, cids) { + await db.sortedSetAdd(`tag:${newTagName}:topics`, scores, tids); + await db.sortedSetRemove(`tag:${tag}:topics`, tids); + // Update cid::tag::topics + await db.sortedSetAddBulk(topicData.map( + (t, index) => [`cid:${t.cid}:tag:${newTagName}:topics`, scores[index], t.tid] + )); + await db.sortedSetRemove(cids.map(cid => `cid:${cid}:tag:${tag}:topics`), tids); + // Update 'tags' field in topic hash + await updateTopicHashTags(tids, topicData, tag, newTagName); + } + async function updateTopicHashTags(tids, topicData, oldTag, newTag) { + topicData.forEach((topic) => { + topic.tags = topic.tags.map(tagItem => tagItem.value); + const index = topic.tags.indexOf(oldTag); + if (index !== -1) { + topic.tags.splice(index, 1, newTag); + } + }); + await db.setObjectBulk( + topicData.map(t => [`topic:${t.tid}`, { tags: t.tags.join(',') }]), + ); } - async function updateTagCount(tag) { const count = await Topics.getTagTopicCount(tag); await db.sortedSetAdd('tags:topic:count', count || 0, tag); From 11ccc6a1b972f5bdecd0938e6f044d8c368a9969 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:49:41 -0700 Subject: [PATCH 31/50] Revert "Revert "Matched The Modified File(sorted.js) from Project NodeBB"" --- src/topics/sorted.js | 163 +++++++++++++++++++++++-------------------- 1 file changed, 87 insertions(+), 76 deletions(-) diff --git a/src/topics/sorted.js b/src/topics/sorted.js index 98292f0ddb..bceda88015 100644 --- a/src/topics/sorted.js +++ b/src/topics/sorted.js @@ -11,13 +11,23 @@ const meta = require('../meta'); const plugins = require('../plugins'); module.exports = function (Topics) { + console.log('ndevidze'); Topics.getSortedTopics = async function (params) { const data = { nextStart: 0, topicCount: 0, topics: [], }; - + params = initializeParams(params); + data.tids = await getTids(params); + data.tids = await sortTids(data.tids, params); + data.tids = await filterTids(data.tids.slice(0, meta.config.recentMaxTopics), params); + data.topicCount = data.tids.length; + data.topics = await getTopics(data.tids, params); + data.nextStart = params.stop + 1; + return data; + }; + function initializeParams(params) { params.term = params.term || 'alltime'; params.sort = params.sort || 'recent'; params.query = params.query || {}; @@ -28,15 +38,8 @@ module.exports = function (Topics) { if (params.tags && !Array.isArray(params.tags)) { params.tags = [params.tags]; } - data.tids = await getTids(params); - data.tids = await sortTids(data.tids, params); - data.tids = await filterTids(data.tids.slice(0, meta.config.recentMaxTopics), params); - data.topicCount = data.tids.length; - data.topics = await getTopics(data.tids, params); - data.nextStart = params.stop + 1; - return data; - }; - + return params; + } async function getTids(params) { if (plugins.hooks.hasListeners('filter:topics.getSortedTids')) { const result = await plugins.hooks.fire('filter:topics.getSortedTids', { params: params, tids: [] }); @@ -137,69 +140,80 @@ module.exports = function (Topics) { } async function getCidTids(params) { + console.log('getting Tids'); if (params.tags.length) { - return _.intersection(...await Promise.all(params.tags.map(async (tag) => { - const sets = params.cids.map(cid => `cid:${cid}:tag:${tag}:topics`); - return await db.getSortedSetRevRange(sets, 0, -1); - }))); + return await getTidsForTagsAndCids(params); } - + const sets = getCidSets(params.cids, params.sort); + let pinnedTids = await db.getSortedSetRevRange(sets.pinnedSets, 0, -1); + pinnedTids = await Topics.tools.checkPinExpiry(pinnedTids); + const tids = await db[sets.method](sets.normalSets, 0, meta.config.recentMaxTopics - 1); + return pinnedTids.concat(tids); + } + function getCidSets(cids, sort) { const sets = []; const pinnedSets = []; - params.cids.forEach((cid) => { - if (params.sort === 'recent' || params.sort === 'old') { + cids.forEach((cid) => { + if (sort === 'recent' || sort === 'old') { sets.push(`cid:${cid}:tids`); } else { - sets.push(`cid:${cid}:tids${params.sort ? `:${params.sort}` : ''}`); + sets.push(`cid:${cid}:tids${sort ? `:${sort}` : ''}`); } pinnedSets.push(`cid:${cid}:tids:pinned`); }); - let pinnedTids = await db.getSortedSetRevRange(pinnedSets, 0, -1); - pinnedTids = await Topics.tools.checkPinExpiry(pinnedTids); - const method = params.sort === 'old' ? - 'getSortedSetRange' : - 'getSortedSetRevRange'; - const tids = await db[method](sets, 0, meta.config.recentMaxTopics - 1); - return pinnedTids.concat(tids); + const method = (sort === 'old') ? 'getSortedSetRange' : 'getSortedSetRevRange'; + return { normalSets: sets, pinnedSets: pinnedSets, method: method }; + } + async function getTidsForTagsAndCids(params) { + return _.intersection( + ...await Promise.all(params.tags.map(async (tag) => { + const sets = params.cids.map(cid => `cid:${cid}:tag:${tag}:topics`); + return await db.getSortedSetRevRange(sets, 0, -1); + })) + ); } async function sortTids(tids, params) { - if (params.term === 'alltime' && !params.cids && !params.tags.length && params.filter !== 'watched' && !params.floatPinned) { + console.log('sorting Tids'); + if (canSkipSorting(params)) { return tids; } - if (params.sort === 'posts' && params.term !== 'alltime') { return tids; } - const { sortMap, fields } = await plugins.hooks.fire('filter:topics.sortOptions', { params, - fields: [ - 'tid', 'timestamp', 'lastposttime', 'upvotes', 'downvotes', 'postcount', 'pinned', - ], - sortMap: { - recent: sortRecent, - old: sortOld, - create: sortCreate, - posts: sortPopular, - votes: sortVotes, - views: sortViews, - }, + fields: getFields(), + sortMap: getSortMap(), }); - const topicData = await Topics.getTopicsFields(tids, fields); - const sortFn = sortMap.hasOwnProperty(params.sort) && sortMap[params.sort] ? - sortMap[params.sort] : sortRecent; - + const sortFn = getSortFunction(params.sort, sortMap); if (params.floatPinned) { floatPinned(topicData, sortFn); } else { topicData.sort(sortFn); } - return topicData.map(topic => topic && topic.tid); } - + function canSkipSorting(params) { + return params.term === 'alltime' && !params.cids && !params.tags.length && params.filter !== 'watched' && !params.floatPinned; + } + function getFields() { + return ['tid', 'timestamp', 'lastposttime', 'upvotes', 'downvotes', 'postcount', 'pinned']; + } + function getSortMap() { + return { + recent: sortRecent, + old: sortOld, + create: sortCreate, + posts: sortPopular, + votes: sortVotes, + views: sortViews, + }; + } + function getSortFunction(sort, sortMap) { + return sortMap.hasOwnProperty(sort) && sortMap[sort] ? sortMap[sort] : sortRecent; + } function floatPinned(topicData, sortFn) { topicData.sort((a, b) => (a.pinned !== b.pinned ? b.pinned - a.pinned : sortFn(a, b))); } @@ -235,49 +249,46 @@ module.exports = function (Topics) { } async function filterTids(tids, params) { - const { filter } = params; - const { uid } = params; - - if (filter === 'new') { - tids = await Topics.filterNewTids(tids, uid); - } else if (filter === 'unreplied') { - tids = await Topics.filterUnrepliedTids(tids); - } else { - tids = await Topics.filterNotIgnoredTids(tids, uid); - } - - tids = await privileges.topics.filterTids('topics:read', tids, uid); - let topicData = await Topics.getTopicsFields(tids, ['uid', 'tid', 'cid', 'tags']); + console.log('filtering Tids'); + tids = await applyFilterByType(tids, params); + tids = await privileges.topics.filterTids('topics:read', tids, params.uid); + const topicData = await Topics.getTopicsFields(tids, ['uid', 'tid', 'cid', 'tags']); const topicCids = _.uniq(topicData.map(topic => topic.cid)).filter(Boolean); - - async function getIgnoredCids() { - if (params.cids || filter === 'watched' || meta.config.disableRecentCategoryFilter) { - return []; - } - return await categories.isIgnored(topicCids, uid); - } const [ignoredCids, filtered] = await Promise.all([ - getIgnoredCids(), - user.blocks.filter(uid, topicData), + getIgnoredCids(params, topicCids), + user.blocks.filter(params.uid, topicData), ]); - - const isCidIgnored = _.zipObject(topicCids, ignoredCids); - topicData = filtered; - + return filterTopicsByCidsAndTags(filtered, ignoredCids, params); + } + async function applyFilterByType(tids, params) { + switch (params.filter) { + case 'new': + return await Topics.filterNewTids(tids, params.uid); + case 'unreplied': + return await Topics.filterUnrepliedTids(tids); + default: + return await Topics.filterNotIgnoredTids(tids, params.uid); + } + } + async function getIgnoredCids(params, topicCids) { + if (params.cids || params.filter === 'watched' || meta.config.disableRecentCategoryFilter) { + return []; + } + return await categories.isIgnored(topicCids, params.uid); + } + function filterTopicsByCidsAndTags(topicData, ignoredCids, params) { + const isCidIgnored = _.zipObject(topicData.map(t => t.cid), ignoredCids); const cids = params.cids && params.cids.map(String); const { tags } = params; - tids = topicData.filter(t => ( + const tids = topicData.filter(t => ( t && t.cid && !isCidIgnored[t.cid] && (!cids || cids.includes(String(t.cid))) && (!tags.length || tags.every(tag => t.tags.find(topicTag => topicTag.value === tag))) )).map(t => t.tid); - - const result = await plugins.hooks.fire('filter:topics.filterSortedTids', { tids: tids, params: params }); - return result.tids; + return plugins.hooks.fire('filter:topics.filterSortedTids', { tids, params }).then(result => result.tids); } - async function getTopics(tids, params) { tids = tids.slice(params.start, params.stop !== -1 ? params.stop + 1 : undefined); const topicData = await Topics.getTopicsByTids(tids, params); From 9cf178c96c302027a02a00cc70ad9ae275939283 Mon Sep 17 00:00:00 2001 From: Seckhen <111176979+Seckhen@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:01:22 +0300 Subject: [PATCH 32/50] Revert "Revert "Matched sound_settings.js to match project one's file "" --- src/upgrades/1.4.4/sound_settings.js | 110 +++++++++++++++------------ 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/src/upgrades/1.4.4/sound_settings.js b/src/upgrades/1.4.4/sound_settings.js index ae0a6d8fa3..dcd857d246 100644 --- a/src/upgrades/1.4.4/sound_settings.js +++ b/src/upgrades/1.4.4/sound_settings.js @@ -2,64 +2,76 @@ const async = require('async'); const db = require('../../database'); +const meta = require('../../meta'); +const batch = require('../../batch'); +const soundMap = { + 'notification.mp3': 'Default | Deedle-dum', + 'waterdrop-high.mp3': 'Default | Water drop (high)', + 'waterdrop-low.mp3': 'Default | Water drop (low)', +}; -module.exports = { - name: 'Update global and user sound settings', - timestamp: Date.UTC(2017, 1, 25), - method: function (callback) { - const meta = require('../../meta'); - const batch = require('../../batch'); +function updateGlobalSoundSettings(callback) { + console.log('Seckhen Ariel Andrade Cuellar'); + const keys = ['chat-incoming', 'chat-outgoing', 'notification']; + db.getObject('settings:sounds', (err, settings) => { + if (err || !settings) { + return callback(err); + } - const map = { - 'notification.mp3': 'Default | Deedle-dum', - 'waterdrop-high.mp3': 'Default | Water drop (high)', - 'waterdrop-low.mp3': 'Default | Water drop (low)', - }; + const updatedSettings = keys.reduce((acc, key) => { + if (settings[key] && !settings[key].includes(' | ')) { + acc[key] = soundMap[settings[key]] || ''; + } + return acc; + }, {}); - async.parallel([ - function (cb) { - const keys = ['chat-incoming', 'chat-outgoing', 'notification']; + meta.configs.setMultiple(updatedSettings, callback); + }); +} + +function updateUserSoundSettings(callback) { + console.log('Seckhen Ariel Andrade Cuellar'); - db.getObject('settings:sounds', (err, settings) => { - if (err || !settings) { - return cb(err); - } + const keys = ['notificationSound', 'incomingChatSound', 'outgoingChatSound']; - keys.forEach((key) => { - if (settings[key] && !settings[key].includes(' | ')) { - settings[key] = map[settings[key]] || ''; - } - }); + batch.processSortedSet('users:joindate', processUserBatch, callback); - meta.configs.setMultiple(settings, cb); - }); - }, - function (cb) { - const keys = ['notificationSound', 'incomingChatSound', 'outgoingChatSound']; + function processUserBatch(ids, next) { + console.log('Seckhen Ariel Andrade Cuellar'); + async.each(ids, updateUserSettings, next); + } - batch.processSortedSet('users:joindate', (ids, next) => { - async.each(ids, (uid, next) => { - db.getObject(`user:${uid}:settings`, (err, settings) => { - if (err || !settings) { - return next(err); - } - const newSettings = {}; - keys.forEach((key) => { - if (settings[key] && !settings[key].includes(' | ')) { - newSettings[key] = map[settings[key]] || ''; - } - }); + function updateUserSettings(uid, next) { + console.log('Seckhen Ariel Andrade Cuellar'); + db.getObject(`user:${uid}:settings`, (err, settings) => { + if (err || !settings) { + return next(err); + } - if (Object.keys(newSettings).length) { - db.setObject(`user:${uid}:settings`, newSettings, next); - } else { - setImmediate(next); - } - }); - }, next); - }, cb); - }, + const newSettings = keys.reduce((acc, key) => { + if (settings[key] && !settings[key].includes(' | ')) { + acc[key] = soundMap[settings[key]] || ''; + } + return acc; + }, {}); + + if (Object.keys(newSettings).length) { + db.setObject(`user:${uid}:settings`, newSettings, next); + } else { + setImmediate(next); + } + }); + } +} + +module.exports = { + name: 'Update global and user sound settings', + timestamp: Date.UTC(2017, 1, 25), + method: function (callback) { + async.parallel([ + updateGlobalSoundSettings, + updateUserSoundSettings, ], callback); }, }; From 33ede5ca2bc3f08503b41e986b7c374167cb8368 Mon Sep 17 00:00:00 2001 From: Ghani Raissov <111187247+graissov@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:09:20 +0300 Subject: [PATCH 33/50] Revert "Revert "replicated project1 changes here too"" --- src/upgrades/1.1.1/upload_privileges.js | 85 ++++++++++++++++++------- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/src/upgrades/1.1.1/upload_privileges.js b/src/upgrades/1.1.1/upload_privileges.js index d343f4ebfa..dc3529079a 100644 --- a/src/upgrades/1.1.1/upload_privileges.js +++ b/src/upgrades/1.1.1/upload_privileges.js @@ -1,38 +1,77 @@ 'use strict'; -const async = require('async'); const db = require('../../database'); - module.exports = { name: 'Giving upload privileges', timestamp: Date.UTC(2016, 6, 12), - method: function (callback) { + method: async function () { + console.log('graissov: Executing method function'); const privilegesAPI = require('../../privileges'); const meta = require('../../meta'); + const cids = await getCategoryIds(); + await processCategories(cids, privilegesAPI, meta); + }, +}; + +// Helper function to get category IDs +function getCategoryIds() { + console.log('graissov: Executing getCategoryIds function'); + return new Promise((resolve, reject) => { db.getSortedSetRange('categories:cid', 0, -1, (err, cids) => { if (err) { - return callback(err); + return reject(err); } + resolve(cids); + }); + }); +} + +// Helper function to process each category +async function processCategories(cids, privilegesAPI, meta) { + console.log('graissov: Executing processCategories function'); + await Promise.all(cids.map(async (cid) => { + const data = await getCategoryData(cid, privilegesAPI); + await processGroups(data.groups, cid, privilegesAPI, meta); + })); +} - async.eachSeries(cids, (cid, next) => { - privilegesAPI.categories.list(cid, (err, data) => { - if (err) { - return next(err); - } - async.eachSeries(data.groups, (group, next) => { - if (group.name === 'guests' && parseInt(meta.config.allowGuestUploads, 10) !== 1) { - return next(); - } - if (group.privileges['groups:read']) { - privilegesAPI.categories.give(['upload:post:image'], cid, group.name, next); - } else { - next(); - } - }, next); - }); - }, callback); +// Helper function to get category data +function getCategoryData(cid, privilegesAPI) { + console.log('graissov: Executing getCategoryData function'); + return new Promise((resolve, reject) => { + privilegesAPI.categories.list(cid, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); }); - }, -}; + }); +} + +// Helper function to process groups within a category +async function processGroups(groups, cid, privilegesAPI, meta) { + console.log('graissov: Executing processGroups function'); + await Promise.all(groups.map(async (group) => { + if (group.name === 'guests' && parseInt(meta.config.allowGuestUploads, 10) !== 1) { + return; // Skip guests if uploads are not allowed + } + if (group.privileges['groups:read']) { + await giveUploadPrivilege(cid, group.name, privilegesAPI); + } + })); +} + +// Helper function to give upload privileges +function giveUploadPrivilege(cid, groupName, privilegesAPI) { + console.log('graissov: Executing giveUploadPrivilege function'); + return new Promise((resolve, reject) => { + privilegesAPI.categories.give(['upload:post:image'], cid, groupName, (err) => { + if (err) { + return reject(err); + } + resolve(); + }); + }); +} From caf170d87a786cc2a02e9b0b905a10800619a206 Mon Sep 17 00:00:00 2001 From: Seckhen <111176979+Seckhen@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:17:24 +0300 Subject: [PATCH 34/50] Revert "Revert "Adding node modules file we edited to git repository "" --- README.md | 11 +++ dump.rdb | Bin 0 -> 159585 bytes node_modules_real/topics_list.tpl | 131 ++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 dump.rdb create mode 100644 node_modules_real/topics_list.tpl diff --git a/README.md b/README.md index 6ef180f625..ef1ac825d7 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,17 @@ [![Coverage Status](https://coveralls.io/repos/github/CMU-313/NodeBB/badge.svg)](https://coveralls.io/github/CMU-313/NodeBB) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=CMU-313_NodeBB&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=CMU-313_NodeBB) + +****************************************** NOTICE ************************************************ + +The collaborators on this project are Seckhen Andrade, Nikoloz Devidze, Ghani Raissov, Davit Charkviani, Yousuf Alkhiyami + +Please know that there is a folder called node_module_real, which contains the files that are modified compared to the files that are in the node_modules we get by npm install. Please take the code from the files in the node_modules_real folder (files have the same names as in the node_module generated by npm install) and paste (replace the code) them in the respective files. + + +*************************************************************************************************** + + [**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and supports either Redis, MongoDB, or a PostgreSQL database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB takes the best of the modern web: real-time streaming discussions, mobile responsiveness, and rich RESTful read/write APIs, while staying true to the original bulletin board/forum format → categorical hierarchies, local user accounts, and asynchronous messaging. NodeBB by itself contains a "common core" of basic functionality, while additional functionality and integrations are enabled through the use of third-party plugins. diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000000000000000000000000000000000..c71ec0a686aaf28ee9f0ee9fb215df0b73a21129 GIT binary patch literal 159585 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 literal 0 HcmV?d00001 diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl new file mode 100644 index 0000000000..7b26d21f0e --- /dev/null +++ b/node_modules_real/topics_list.tpl @@ -0,0 +1,131 @@ +
          + + {{{ each topics }}} +
        • > + + + + + + +
          +
          + + {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} + + {{{ if showSelect }}} +
          + +
          + {{{ end }}} +
          +
          +

          + {./title} +

          + + + + [[topic:watching]] + + + + [[topic:ignoring]] + + + + [[topic:scheduled]] + + + + {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} + + + + [[topic:locked]] + + + + [[topic:moved]] + + {{{each ./icons}}}{@value}{{{end}}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + + +
          + + + {humanReadableNumber(./postcount, 0)} + + + +
          + + +
          + {{{ if showSelect }}} +
          + +
          + {{{ end }}} +
          + {{{ if ./thumbs.length }}} + + + +{increment(./thumbs.length, "-1")} + + {{{ end }}} +
          + +
          +
          + {{{ if !reputation:disabled }}} +
          + {humanReadableNumber(./votes, 0)} + [[global:votes]] + +
          + {{{ end }}} +
          + {humanReadableNumber(./postcount, 0)} + [[global:posts]] + +
          +
          + {humanReadableNumber(./viewcount, 0)} + [[global:views]] + +
          +
          +
          +
          + {{{ if ./unreplied }}} +
          + [[category:no-replies]] +
          + {{{ else }}} + {{{ if ./teaser.pid }}} + +
          + + {./teaser.content} +
          + {{{ end }}} + {{{ end }}} +
          +
          +
          +
        • + {{{end}}} +
        From 94caaae8ec1b8fe971ec80638634ecf31852aace Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:28:16 -0700 Subject: [PATCH 35/50] Revert "Revert "node_modules/nodebb-theme-harmony/templates/partials/topics_list.tpl. - Added frontend buttons"" --- node_modules_real/topics_list.tpl | 138 ++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index 7b26d21f0e..2de1038f29 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -1,4 +1,142 @@
          + # + {{{ each topics }}} +
        • > + + + + + + +
          +
          + + {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} + + {{{ if showSelect }}} +
          + +
          + {{{ end }}} +
          +
          +

          + {./title} +

          + + + + [[topic:watching]] + + + + [[topic:ignoring]] + + + + [[topic:scheduled]] + + + + {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} + + + + [[topic:locked]] + + + + [[topic:moved]] + + {{{each ./icons}}}{@value}{{{end}}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + + +
          + + + {humanReadableNumber(./postcount, 0)} + + + +
          + + +
          + + +
          + +
          + + {{{ if showSelect }}} +
          + +
          + {{{ end }}} +
          + {{{ if ./thumbs.length }}} + + + +{increment(./thumbs.length, "-1")} + + {{{ end }}} +
          + +
          +
          + {{{ if !reputation:disabled }}} +
          + {humanReadableNumber(./votes, 0)} + [[global:votes]] + +
          + {{{ end }}} +
          + {humanReadableNumber(./postcount, 0)} + [[global:posts]] + +
          +
          + {humanReadableNumber(./viewcount, 0)} + [[global:views]] + +
          +
          +
          +
          + {{{ if ./unreplied }}} +
          + [[category:no-replies]] +
          + {{{ else }}} + {{{ if ./teaser.pid }}} + +
          + + {./teaser.content} +
          + {{{ end }}} + {{{ end }}} +
          +
          +
          +
        • + {{{end}}} +======= {{{ each topics }}}
        • > From a70b5c73c43440b33f5dac29a9a7dfd5c59713ec Mon Sep 17 00:00:00 2001 From: NickDevi Date: Thu, 26 Sep 2024 19:35:40 +0300 Subject: [PATCH 36/50] Revert "Revert "Implement Front-End Modal for Future Available Chats on Share Button Click"" --- node_modules_real/topics_list.tpl | 149 ++++-------------------------- 1 file changed, 19 insertions(+), 130 deletions(-) diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index 2de1038f29..f81253379d 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -136,134 +136,23 @@
        • {{{end}}} -======= - - {{{ each topics }}} -
        • > - - - - - - -
          -
          - - {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} - - {{{ if showSelect }}} -
          - -
          - {{{ end }}} -
          -
          -

          - {./title} -

          - - - - [[topic:watching]] - - - - [[topic:ignoring]] - - - - [[topic:scheduled]] - - - - {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} - - - - [[topic:locked]] - - - - [[topic:moved]] - - {{{each ./icons}}}{@value}{{{end}}} - - {{{ if !template.category }}} - {function.buildCategoryLabel, ./category, "a", "border"} - {{{ end }}} - - - -
          - - - {humanReadableNumber(./postcount, 0)} - - - -
          - - -
          - {{{ if showSelect }}} -
          - -
          - {{{ end }}} -
          - {{{ if ./thumbs.length }}} - - - +{increment(./thumbs.length, "-1")} - - {{{ end }}} -
          - -
          -
          - {{{ if !reputation:disabled }}} -
          - {humanReadableNumber(./votes, 0)} - [[global:votes]] - -
          - {{{ end }}} -
          - {humanReadableNumber(./postcount, 0)} - [[global:posts]] - -
          -
          - {humanReadableNumber(./viewcount, 0)} - [[global:views]] - -
          -
          -
          -
          - {{{ if ./unreplied }}} -
          - [[category:no-replies]] -
          - {{{ else }}} - {{{ if ./teaser.pid }}} - -
          - - {./teaser.content} -
          - {{{ end }}} - {{{ end }}} -
          -
          -
          -
        • - {{{end}}}
        + + + From 297e2283ccaa902b55a0d996931798acd4b87713 Mon Sep 17 00:00:00 2001 From: Ghani Raissov <111187247+graissov@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:42:10 +0300 Subject: [PATCH 37/50] Revert "Revert "Implemented back-end to retrieve and showcase users' available chats"" --- node_modules_real/topics_list.tpl | 140 +++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 4 deletions(-) diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index f81253379d..52573bb934 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -1,5 +1,5 @@
          - # + {{{ each topics }}}
        • > @@ -48,7 +48,7 @@ [[topic:moved]] - {{{each ./icons}}}{@value}{{{end}}} + {{{ each ./icons }}}{@value}{{{ end }}} {{{ if !template.category }}} {function.buildCategoryLabel, ./category, "a", "border"} @@ -135,9 +135,8 @@
        • - {{{end}}} + {{{ end }}}
        - + + + From 81d005838a1117f443467711884d289990f1638c Mon Sep 17 00:00:00 2001 From: Seckhen <111176979+Seckhen@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:02:42 +0300 Subject: [PATCH 38/50] Revert "Revert "Added the button for voice memo record - user story 2"" --- node_modules_real/composer-formatting.tpl | 75 +++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 node_modules_real/composer-formatting.tpl diff --git a/node_modules_real/composer-formatting.tpl b/node_modules_real/composer-formatting.tpl new file mode 100644 index 0000000000..0f1b72c967 --- /dev/null +++ b/node_modules_real/composer-formatting.tpl @@ -0,0 +1,75 @@ +
        +
          + {{{ each formatting }}} + {{{ if ./spacer }}} +
        • + {{{ else }}} + {{{ if (./visibility.desktop && ((isTopicOrMain && ./visibility.main) || (!isTopicOrMain && ./visibility.reply))) }}} + {{{ if ./dropdownItems.length }}} + + {{{ else }}} +
        • + +
        • + {{{ end }}} + {{{ end }}} + {{{ end }}} + {{{ end }}} + + {{{ if privileges.upload:post:image }}} +
        • + +
        • + {{{ end }}} + + {{{ if privileges.upload:post:file }}} +
        • + +
        • + {{{ end }}} + +
          + +
          +
        +
        + + + {{{ if composer:showHelpTab }}} + + {{{ end }}} +
        +
        +# From e77ce979ff6d8296a3c19e549eb0a01855a77c47 Mon Sep 17 00:00:00 2001 From: Ghani Raissov <111187247+graissov@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:10:26 +0300 Subject: [PATCH 39/50] Revert "Revert "User story2 back end - Choosing mp3 file for saving in database"" --- node_modules_real/composer.js | 886 ++++++++++++++++++++++ node_modules_real/topics_list.tpl | 1168 ++++++++++++++++++++++------- 2 files changed, 1768 insertions(+), 286 deletions(-) create mode 100644 node_modules_real/composer.js diff --git a/node_modules_real/composer.js b/node_modules_real/composer.js new file mode 100644 index 0000000000..051b7a0338 --- /dev/null +++ b/node_modules_real/composer.js @@ -0,0 +1,886 @@ +'use strict'; + +define('composer', [ + 'taskbar', + 'translator', + 'composer/uploads', + 'composer/formatting', + 'composer/drafts', + 'composer/tags', + 'composer/categoryList', + 'composer/preview', + 'composer/resize', + 'composer/autocomplete', + 'composer/scheduler', + 'composer/post-queue', + 'scrollStop', + 'topicThumbs', + 'api', + 'bootbox', + 'alerts', + 'hooks', + 'messages', + 'search', + 'screenfull', +], function (taskbar, translator, uploads, formatting, drafts, tags, + categoryList, preview, resize, autocomplete, scheduler, postQueue, scrollStop, + topicThumbs, api, bootbox, alerts, hooks, messagesModule, search, screenfull) { + var composer = { + active: undefined, + posts: {}, + bsEnvironment: undefined, + formatting: undefined, + }; + + $(window).off('resize', onWindowResize).on('resize', onWindowResize); + onWindowResize(); + + $(window).on('action:composer.topics.post', function (ev, data) { + localStorage.removeItem('category:' + data.data.cid + ':bookmark'); + localStorage.removeItem('category:' + data.data.cid + ':bookmark:clicked'); + }); + + $(window).on('popstate', function () { + var env = utils.findBootstrapEnvironment(); + if (composer.active && (env === 'xs' || env === 'sm')) { + if (!composer.posts[composer.active].modified) { + composer.discard(composer.active); + if (composer.discardConfirm && composer.discardConfirm.length) { + composer.discardConfirm.modal('hide'); + delete composer.discardConfirm; + } + return; + } + + translator.translate('[[modules:composer.discard]]', function (translated) { + composer.discardConfirm = bootbox.confirm(translated, function (confirm) { + if (confirm) { + composer.discard(composer.active); + } else { + composer.posts[composer.active].modified = true; + } + }); + composer.posts[composer.active].modified = false; + }); + } + }); + + function removeComposerHistory() { + var env = composer.bsEnvironment; + if (ajaxify.data.template.compose === true || env === 'xs' || env === 'sm') { + history.back(); + } + } + // + function onWindowResize() { + var env = utils.findBootstrapEnvironment(); + var isMobile = env === 'xs' || env === 'sm'; + + if (preview.toggle) { + if (preview.env !== env && isMobile) { + preview.env = env; + preview.toggle(false); + } + preview.env = env; + } + + if (composer.active !== undefined) { + resize.reposition($('.composer[data-uuid="' + composer.active + '"]')); + + if (!isMobile && window.location.pathname.startsWith(config.relative_path + '/compose')) { + /* + * If this conditional is met, we're no longer in mobile/tablet + * resolution but we've somehow managed to have a mobile + * composer load, so let's go back to the topic + */ + history.back(); + } else if (isMobile && !window.location.pathname.startsWith(config.relative_path + '/compose')) { + /* + * In this case, we're in mobile/tablet resolution but the composer + * that loaded was a regular composer, so let's fix the address bar + */ + mobileHistoryAppend(); + } + } + composer.bsEnvironment = env; + } + + function alreadyOpen(post) { + // If a composer for the same cid/tid/pid is already open, return the uuid, else return bool false + var type; + var id; + + if (post.hasOwnProperty('cid')) { + type = 'cid'; + } else if (post.hasOwnProperty('tid')) { + type = 'tid'; + } else if (post.hasOwnProperty('pid')) { + type = 'pid'; + } + + id = post[type]; + + // Find a match + for (var uuid in composer.posts) { + if (composer.posts[uuid].hasOwnProperty(type) && id === composer.posts[uuid][type]) { + return uuid; + } + } + + // No matches... + return false; + } + + function push(post) { + if (!post) { + return; + } + + var uuid = utils.generateUUID(); + var existingUUID = alreadyOpen(post); + + if (existingUUID) { + taskbar.updateActive(existingUUID); + return composer.load(existingUUID); + } + + var actionText = '[[topic:composer.new-topic]]'; + if (post.action === 'posts.reply') { + actionText = '[[topic:composer.replying-to]]'; + } else if (post.action === 'posts.edit') { + actionText = '[[topic:composer.editing-in]]'; + } + + translator.translate(actionText, function (translatedAction) { + taskbar.push('composer', uuid, { + title: translatedAction.replace('%1', '"' + post.title + '"'), + }); + }); + + composer.posts[uuid] = post; + composer.load(uuid); + } + + async function composerAlert(post_uuid, message) { + $('.composer[data-uuid="' + post_uuid + '"]').find('.composer-submit').removeAttr('disabled'); + + const { showAlert } = await hooks.fire('filter:composer.error', { post_uuid, message, showAlert: true }); + + if (showAlert) { + alerts.alert({ + type: 'danger', + timeout: 10000, + title: '', + message: message, + alert_id: 'post_error', + }); + } + } + + composer.findByTid = function (tid) { + // Iterates through the initialised composers and returns the uuid of the matching composer + for (var uuid in composer.posts) { + if (composer.posts.hasOwnProperty(uuid) && composer.posts[uuid].hasOwnProperty('tid') && parseInt(composer.posts[uuid].tid, 10) === parseInt(tid, 10)) { + return uuid; + } + } + + return null; + }; + + composer.addButton = function (iconClass, onClick, title) { + formatting.addButton(iconClass, onClick, title); + }; + + composer.newTopic = async (data) => { + let pushData = { + save_id: data.save_id, + action: 'topics.post', + cid: data.cid, + handle: data.handle, + title: data.title || '', + body: data.body || '', + tags: data.tags || [], + modified: !!((data.title && data.title.length) || (data.body && data.body.length)), + isMain: true, + }; + + ({ pushData } = await hooks.fire('filter:composer.topic.push', { + data: data, + pushData: pushData, + })); + + push(pushData); + }; + + composer.addQuote = function (data) { + // tid, toPid, selectedPid, title, username, text, uuid + data.uuid = data.uuid || composer.active; + + var escapedTitle = (data.title || '') + .replace(/([\\`*_{}[\]()#+\-.!])/g, '\\$1') + .replace(/\[/g, '[') + .replace(/\]/g, ']') + .replace(/%/g, '%') + .replace(/,/g, ','); + + if (data.body) { + data.body = '> ' + data.body.replace(/\n/g, '\n> ') + '\n\n'; + } + var link = '[' + escapedTitle + '](' + config.relative_path + '/post/' + encodeURIComponent(data.selectedPid || data.toPid) + ')'; + if (data.uuid === undefined) { + if (data.title && (data.selectedPid || data.toPid)) { + composer.newReply({ + tid: data.tid, + toPid: data.toPid, + title: data.title, + body: '[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n' + data.body, + }); + } else { + composer.newReply({ + tid: data.tid, + toPid: data.toPid, + title: data.title, + body: '[[modules:composer.user-said, ' + data.username + ']]\n' + data.body, + }); + } + return; + } else if (data.uuid !== composer.active) { + // If the composer is not currently active, activate it + composer.load(data.uuid); + } + + var postContainer = $('.composer[data-uuid="' + data.uuid + '"]'); + var bodyEl = postContainer.find('textarea'); + var prevText = bodyEl.val(); + if (data.title && (data.selectedPid || data.toPid)) { + translator.translate('[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n', config.defaultLang, onTranslated); + } else { + translator.translate('[[modules:composer.user-said, ' + data.username + ']]\n', config.defaultLang, onTranslated); + } + + function onTranslated(translated) { + composer.posts[data.uuid].body = (prevText.length ? prevText + '\n\n' : '') + translated + data.body; + bodyEl.val(composer.posts[data.uuid].body); + focusElements(postContainer); + preview.render(postContainer); + } + }; + + composer.newReply = function (data) { + translator.translate(data.body, config.defaultLang, function (translated) { + push({ + save_id: data.save_id, + action: 'posts.reply', + tid: data.tid, + toPid: data.toPid, + title: data.title, + body: translated, + modified: !!(translated && translated.length), + isMain: false, + }); + }); + }; + + composer.editPost = function (data) { + // pid, text + socket.emit('plugins.composer.push', data.pid, function (err, postData) { + if (err) { + return alerts.error(err); + } + postData.save_id = data.save_id; + postData.action = 'posts.edit'; + postData.pid = data.pid; + postData.modified = false; + if (data.body) { + postData.body = data.body; + postData.modified = true; + } + if (data.title) { + postData.title = data.title; + postData.modified = true; + } + push(postData); + }); + }; + + composer.load = function (post_uuid) { + var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + if (postContainer.length) { + activate(post_uuid); + resize.reposition(postContainer); + focusElements(postContainer); + onShow(); + } else if (composer.formatting) { + createNewComposer(post_uuid); + } else { + socket.emit('plugins.composer.getFormattingOptions', function (err, options) { + if (err) { + return alerts.error(err); + } + composer.formatting = options; + createNewComposer(post_uuid); + }); + } + }; + + composer.enhance = function (postContainer, post_uuid, postData) { + /* + This method enhances a composer container with client-side sugar (preview, etc) + Everything in here also applies to the /compose route + */ + + if (!post_uuid && !postData) { + post_uuid = utils.generateUUID(); + composer.posts[post_uuid] = ajaxify.data; + postData = ajaxify.data; + postContainer.attr('data-uuid', post_uuid); + } + + categoryList.init(postContainer, composer.posts[post_uuid]); + scheduler.init(postContainer, composer.posts); + + formatting.addHandler(postContainer); + formatting.addComposerButtons(); + preview.handleToggler(postContainer); + postQueue.showAlert(postContainer, postData); + uploads.initialize(post_uuid); + tags.init(postContainer, composer.posts[post_uuid]); + autocomplete.init(postContainer, post_uuid); + + postContainer.on('change', 'input, textarea', function () { + composer.posts[post_uuid].modified = true; + }); + + postContainer.on('click', '.composer-submit', function (e) { + e.preventDefault(); + e.stopPropagation(); // Other click events bring composer back to active state which is undesired on submit + + $(this).attr('disabled', true); + post(post_uuid); + }); + + require(['mousetrap'], function (mousetrap) { + mousetrap(postContainer.get(0)).bind('mod+enter', function () { + postContainer.find('.composer-submit').attr('disabled', true); + post(post_uuid); + }); + }); + + postContainer.find('.composer-discard').on('click', function (e) { + e.preventDefault(); + + if (!composer.posts[post_uuid].modified) { + composer.discard(post_uuid); + return removeComposerHistory(); + } + + formatting.exitFullscreen(); + + var btn = $(this).prop('disabled', true); + translator.translate('[[modules:composer.discard]]', function (translated) { + bootbox.confirm(translated, function (confirm) { + if (confirm) { + composer.discard(post_uuid); + removeComposerHistory(); + } + btn.prop('disabled', false); + }); + }); + }); + + postContainer.find('.composer-minimize, .minimize .trigger').on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + composer.minimize(post_uuid); + }); + + const textareaEl = postContainer.find('textarea'); + textareaEl.on('input propertychange', utils.debounce(function () { + preview.render(postContainer); + }, 250)); + + textareaEl.on('scroll', function () { + preview.matchScroll(postContainer); + }); + + drafts.init(postContainer, postData); + const draft = drafts.get(postData.save_id); + + preview.render(postContainer, function () { + preview.matchScroll(postContainer); + }); + + handleHelp(postContainer); + handleSearch(postContainer); + focusElements(postContainer); + if (postData.action === 'posts.edit') { + composer.updateThumbCount(post_uuid, postContainer); + } + + // Hide "zen mode" if fullscreen API is not enabled/available (ahem, iOS...) + if (!screenfull.isEnabled) { + $('[data-format="zen"]').parent().addClass('hidden'); + } + + hooks.fire('action:composer.enhanced', { postContainer, postData, draft }); + }; + + async function getSelectedCategory(postData) { + if (ajaxify.data.template.category && parseInt(postData.cid, 10) === parseInt(ajaxify.data.cid, 10)) { + // no need to load data if we are already on the category page + return ajaxify.data; + } else if (parseInt(postData.cid, 10)) { + return await api.get(`/api/category/${postData.cid}`, {}); + } + return null; + } + + async function createNewComposer(post_uuid) { + var postData = composer.posts[post_uuid]; + + var isTopic = postData ? postData.hasOwnProperty('cid') : false; + var isMain = postData ? !!postData.isMain : false; + var isEditing = postData ? !!postData.pid : false; + var isGuestPost = postData ? parseInt(postData.uid, 10) === 0 : false; + const isScheduled = postData.timestamp > Date.now(); + + // see + // https://github.com/NodeBB/NodeBB/issues/2994 and + // https://github.com/NodeBB/NodeBB/issues/1951 + // remove when 1951 is resolved + + var title = postData.title.replace(/%/g, '%').replace(/,/g, ','); + postData.category = await getSelectedCategory(postData); + const privileges = postData.category ? postData.category.privileges : ajaxify.data.privileges; + var data = { + topicTitle: title, + titleLength: title.length, + body: translator.escape(utils.escapeHTML(postData.body)), + mobile: composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm', + resizable: true, + thumb: postData.thumb, + isTopicOrMain: isTopic || isMain, + maximumTitleLength: config.maximumTitleLength, + maximumPostLength: config.maximumPostLength, + minimumTagLength: config.minimumTagLength, + maximumTagLength: config.maximumTagLength, + 'composer:showHelpTab': config['composer:showHelpTab'], + isTopic: isTopic, + isEditing: isEditing, + canSchedule: !!(isMain && privileges && + ((privileges['topics:schedule'] && !isEditing) || (isScheduled && privileges.view_scheduled))), + showHandleInput: config.allowGuestHandles && + (app.user.uid === 0 || (isEditing && isGuestPost && app.user.isAdmin)), + handle: postData ? postData.handle || '' : undefined, + formatting: composer.formatting, + tagWhitelist: postData.category ? postData.category.tagWhitelist : ajaxify.data.tagWhitelist, + privileges: app.user.privileges, + selectedCategory: postData.category, + submitOptions: [ + // Add items using `filter:composer.create`, or just add them to the
          in DOM + // { + // action: 'foobar', + // text: 'Text Label', + // } + ], + }; + + if (data.mobile) { + mobileHistoryAppend(); + + app.toggleNavbar(false); + } + + postData.mobile = composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm'; + + ({ postData, createData: data } = await hooks.fire('filter:composer.create', { + postData: postData, + createData: data, + })); + + app.parseAndTranslate('composer', data, function (composerTemplate) { + if ($('.composer.composer[data-uuid="' + post_uuid + '"]').length) { + return; + } + composerTemplate = $(composerTemplate); + + composerTemplate.find('.title').each(function () { + $(this).text(translator.unescape($(this).text())); + }); + + composerTemplate.attr('data-uuid', post_uuid); + + $(document.body).append(composerTemplate); + + var postContainer = $(composerTemplate[0]); + + resize.reposition(postContainer); + composer.enhance(postContainer, post_uuid, postData); + /* + Everything after this line is applied to the resizable composer only + Want something done to both resizable composer and the one in /compose? + Put it in composer.enhance(). + + Eventually, stuff after this line should be moved into composer.enhance(). + */ + + activate(post_uuid); + + postContainer.on('click', function () { + if (!taskbar.isActive(post_uuid)) { + taskbar.updateActive(post_uuid); + } + }); + + resize.handleResize(postContainer); + + if (composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { + var submitBtns = postContainer.find('.composer-submit'); + var mobileSubmitBtn = postContainer.find('.mobile-navbar .composer-submit'); + var textareaEl = postContainer.find('.write'); + var idx = textareaEl.attr('tabindex'); + + submitBtns.removeAttr('tabindex'); + mobileSubmitBtn.attr('tabindex', parseInt(idx, 10) + 1); + } + + $(window).trigger('action:composer.loaded', { + postContainer: postContainer, + post_uuid: post_uuid, + composerData: composer.posts[post_uuid], + formatting: composer.formatting, + }); + + scrollStop.apply(postContainer.find('.write')); + focusElements(postContainer); + onShow(); + }); + } + + function mobileHistoryAppend() { + var path = 'compose?p=' + window.location.pathname; + var returnPath = window.location.pathname.slice(1) + window.location.search; + + // Remove relative path from returnPath + if (returnPath.startsWith(config.relative_path.slice(1))) { + returnPath = returnPath.slice(config.relative_path.length); + } + + // Add in return path to be caught by ajaxify when post is completed, or if back is pressed + window.history.replaceState({ + url: null, + returnPath: returnPath, + }, returnPath, config.relative_path + '/' + returnPath); + + // Update address bar in case f5 is pressed + window.history.pushState({ + url: path, + }, path, `${config.relative_path}/${returnPath}`); + } + + function handleHelp(postContainer) { + const helpBtn = postContainer.find('[data-action="help"]'); + helpBtn.on('click', async function () { + const html = await socket.emit('plugins.composer.renderHelp'); + if (html && html.length > 0) { + bootbox.dialog({ + size: 'large', + message: html, + onEscape: true, + backdrop: true, + onHidden: function () { + helpBtn.focus(); + }, + }); + } + }); + } + + function handleSearch(postContainer) { + var uuid = postContainer.attr('data-uuid'); + var isEditing = composer.posts[uuid] && composer.posts[uuid].action === 'posts.edit'; + var env = utils.findBootstrapEnvironment(); + var isMobile = env === 'xs' || env === 'sm'; + if (isEditing || isMobile) { + return; + } + + search.enableQuickSearch({ + searchElements: { + inputEl: postContainer.find('input.title'), + resultEl: postContainer.find('.quick-search-container'), + }, + searchOptions: { + composer: 1, + }, + hideOnNoMatches: true, + hideDuringSearch: true, + }); + } + + function activate(post_uuid) { + if (composer.active && composer.active !== post_uuid) { + composer.minimize(composer.active); + } + + composer.active = post_uuid; + const postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + postContainer.css('visibility', 'visible'); + $(window).trigger('action:composer.activate', { + post_uuid: post_uuid, + postContainer: postContainer, + }); + } + + function focusElements(postContainer) { + setTimeout(function () { + var title = postContainer.find('input.title'); + + if (title.length) { + title.focus(); + } else { + postContainer.find('textarea').focus().putCursorAtEnd(); + } + }, 20); + } + + async function post(post_uuid) { + var postData = composer.posts[post_uuid]; + var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + var handleEl = postContainer.find('.handle'); + var titleEl = postContainer.find('.title'); + var bodyEl = postContainer.find('textarea'); + var thumbEl = postContainer.find('input#topic-thumb-url'); + var onComposeRoute = postData.hasOwnProperty('template') && postData.template.compose === true; + const submitBtn = postContainer.find('.composer-submit'); + + titleEl.val(titleEl.val().trim()); + bodyEl.val(utils.rtrim(bodyEl.val())); + if (thumbEl.length) { + thumbEl.val(thumbEl.val().trim()); + } + + var action = postData.action; + + var checkTitle = (postData.hasOwnProperty('cid') || parseInt(postData.pid, 10)) && postContainer.find('input.title').length; + var isCategorySelected = !checkTitle || (checkTitle && parseInt(postData.cid, 10)); + + // Specifically for checking title/body length via plugins + var payload = { + post_uuid: post_uuid, + postData: postData, + postContainer: postContainer, + titleEl: titleEl, + titleLen: titleEl.val().length, + bodyEl: bodyEl, + bodyLen: bodyEl.val().length, + }; + + await hooks.fire('filter:composer.check', payload); + $(window).trigger('action:composer.check', payload); + + if (payload.error) { + return composerAlert(post_uuid, payload.error); + } + + if (uploads.inProgress[post_uuid] && uploads.inProgress[post_uuid].length) { + return composerAlert(post_uuid, '[[error:still-uploading]]'); + } else if (checkTitle && payload.titleLen < parseInt(config.minimumTitleLength, 10)) { + return composerAlert(post_uuid, '[[error:title-too-short, ' + config.minimumTitleLength + ']]'); + } else if (checkTitle && payload.titleLen > parseInt(config.maximumTitleLength, 10)) { + return composerAlert(post_uuid, '[[error:title-too-long, ' + config.maximumTitleLength + ']]'); + } else if (action === 'topics.post' && !isCategorySelected) { + return composerAlert(post_uuid, '[[error:category-not-selected]]'); + } else if (payload.bodyLen < parseInt(config.minimumPostLength, 10)) { + return composerAlert(post_uuid, '[[error:content-too-short, ' + config.minimumPostLength + ']]'); + } else if (payload.bodyLen > parseInt(config.maximumPostLength, 10)) { + return composerAlert(post_uuid, '[[error:content-too-long, ' + config.maximumPostLength + ']]'); + } else if (checkTitle && !tags.isEnoughTags(post_uuid)) { + return composerAlert(post_uuid, '[[error:not-enough-tags, ' + tags.minTagCount() + ']]'); + } else if (scheduler.isActive() && scheduler.getTimestamp() <= Date.now()) { + return composerAlert(post_uuid, '[[error:scheduling-to-past]]'); + } + + let composerData = { + uuid: post_uuid, + }; + let method = 'post'; + let route = ''; + + if (action === 'topics.post') { + route = '/topics'; + composerData = { + ...composerData, + handle: handleEl ? handleEl.val() : undefined, + title: titleEl.val(), + content: bodyEl.val(), + thumb: thumbEl.val() || '', + cid: categoryList.getSelectedCid(), + tags: tags.getTags(post_uuid), + timestamp: scheduler.getTimestamp(), + }; + } else if (action === 'posts.reply') { + route = `/topics/${postData.tid}`; + composerData = { + ...composerData, + tid: postData.tid, + handle: handleEl ? handleEl.val() : undefined, + content: bodyEl.val(), + toPid: postData.toPid, + }; + } else if (action === 'posts.edit') { + method = 'put'; + route = `/posts/${postData.pid}`; + composerData = { + ...composerData, + pid: postData.pid, + handle: handleEl ? handleEl.val() : undefined, + content: bodyEl.val(), + title: titleEl.val(), + thumb: thumbEl.val() || '', + tags: tags.getTags(post_uuid), + timestamp: scheduler.getTimestamp(), + }; + } + var submitHookData = { + composerEl: postContainer, + action: action, + composerData: composerData, + postData: postData, + redirect: true, + }; + + await hooks.fire('filter:composer.submit', submitHookData); + hooks.fire('action:composer.submit', Object.freeze(submitHookData)); + + // Minimize composer (and set textarea as readonly) while submitting + var taskbarIconEl = $('#taskbar .composer[data-uuid="' + post_uuid + '"] i'); + var textareaEl = postContainer.find('.write'); + taskbarIconEl.removeClass('fa-plus').addClass('fa-circle-o-notch fa-spin'); + composer.minimize(post_uuid); + textareaEl.prop('readonly', true); + + api[method](route, composerData) + .then((data) => { + submitBtn.removeAttr('disabled'); + postData.submitted = true; + + composer.discard(post_uuid); + drafts.removeDraft(postData.save_id); + + if (data.queued) { + alerts.alert({ + type: 'success', + title: '[[global:alert.success]]', + message: data.message, + timeout: 10000, + clickfn: function () { + ajaxify.go(`/post-queue/${data.id}`); + }, + }); + } else if (action === 'topics.post') { + if (submitHookData.redirect) { + ajaxify.go('topic/' + data.slug, undefined, (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm')); + } + } else if (action === 'posts.reply') { + if (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { + window.history.back(); + } else if (submitHookData.redirect && + ((ajaxify.data.template.name !== 'topic') || + (ajaxify.data.template.topic && parseInt(postData.tid, 10) !== parseInt(ajaxify.data.tid, 10))) + ) { + ajaxify.go('post/' + data.pid); + } + } else { + removeComposerHistory(); + } + + hooks.fire('action:composer.' + action, { composerData: composerData, data: data }); + }) + .catch((err) => { + // Restore composer on error + composer.load(post_uuid); + textareaEl.prop('readonly', false); + if (err.message === '[[error:email-not-confirmed]]') { + return messagesModule.showEmailConfirmWarning(err.message); + } + composerAlert(post_uuid, err.message); + }); + } + + function onShow() { + $('html').addClass('composing'); + } + + function onHide() { + $('#content').css({ paddingBottom: 0 }); + $('html').removeClass('composing'); + app.toggleNavbar(true); + formatting.exitFullscreen(); + } + + composer.discard = function (post_uuid) { + if (composer.posts[post_uuid]) { + var postData = composer.posts[post_uuid]; + var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + postContainer.remove(); + drafts.removeDraft(postData.save_id); + topicThumbs.deleteAll(post_uuid); + + taskbar.discard('composer', post_uuid); + $('[data-action="post"]').removeAttr('disabled'); + + hooks.fire('action:composer.discard', { + post_uuid: post_uuid, + postData: postData, + }); + delete composer.posts[post_uuid]; + composer.active = undefined; + } + scheduler.reset(); + onHide(); + }; + + // Alias to .discard(); + composer.close = composer.discard; + + composer.minimize = function (post_uuid) { + var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); + postContainer.css('visibility', 'hidden'); + composer.active = undefined; + taskbar.minimize('composer', post_uuid); + $(window).trigger('action:composer.minimize', { + post_uuid: post_uuid, + }); + + onHide(); + }; + + composer.minimizeActive = function () { + if (composer.active) { + composer.miminize(composer.active); + } + }; + + composer.updateThumbCount = function (uuid, postContainer) { + const composerObj = composer.posts[uuid]; + if (composerObj.action === 'topics.post' || (composerObj.action === 'posts.edit' && composerObj.isMain)) { + const calls = [ + topicThumbs.get(uuid), + ]; + if (composerObj.pid) { + calls.push(topicThumbs.getByPid(composerObj.pid)); + } + Promise.all(calls).then((thumbs) => { + const thumbCount = thumbs.flat().length; + const formatEl = postContainer.find('[data-format="thumbs"]'); + formatEl.find('.badge') + .text(thumbCount) + .toggleClass('hidden', !thumbCount); + }); + } + }; + + return composer; +}); diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index 52573bb934..70e381b8b4 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -1,290 +1,886 @@ -
            - - {{{ each topics }}} -
          • > - - - - - - -
            -
            - - {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")} - - {{{ if showSelect }}} -
            - -
            - {{{ end }}} -
            -
            -

            - {./title} -

            - - - - [[topic:watching]] - - - - [[topic:ignoring]] - - - - [[topic:scheduled]] - - - - {{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}} - - - - [[topic:locked]] - - - - [[topic:moved]] - - {{{ each ./icons }}}{@value}{{{ end }}} - - {{{ if !template.category }}} - {function.buildCategoryLabel, ./category, "a", "border"} - {{{ end }}} - - - -
            - - - {humanReadableNumber(./postcount, 0)} - - - -
            - - -
            - - -
            - -
            - - {{{ if showSelect }}} -
            - -
            - {{{ end }}} -
            - {{{ if ./thumbs.length }}} - - - +{increment(./thumbs.length, "-1")} - - {{{ end }}} -
            - -
            -
            - {{{ if !reputation:disabled }}} -
            - {humanReadableNumber(./votes, 0)} - [[global:votes]] - -
            - {{{ end }}} -
            - {humanReadableNumber(./postcount, 0)} - [[global:posts]] - -
            -
            - {humanReadableNumber(./viewcount, 0)} - [[global:views]] - -
            -
            -
            -
            - {{{ if ./unreplied }}} -
            - [[category:no-replies]] -
            - {{{ else }}} - {{{ if ./teaser.pid }}} - -
            - - {./teaser.content} -
            - {{{ end }}} - {{{ end }}} -
            -
            -
            -
          • - {{{ end }}} -
          - - - - - + return composer; +}); From 4f30796f04e7fe19737f40a067bd2d23788f9615 Mon Sep 17 00:00:00 2001 From: Seckhen <111176979+Seckhen@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:26:04 +0300 Subject: [PATCH 40/50] Revert "Revert "Added Front-End Emoji Reaction UI for Chat Messages"" --- src/views/partials/chats/message.tpl | 173 ++++++++++++++++----------- 1 file changed, 105 insertions(+), 68 deletions(-) diff --git a/src/views/partials/chats/message.tpl b/src/views/partials/chats/message.tpl index 527a055f17..f4b01a8733 100644 --- a/src/views/partials/chats/message.tpl +++ b/src/views/partials/chats/message.tpl @@ -1,76 +1,113 @@ -
        • + +{{{ if messages.reactionTo }}} +
        • + +
        • +{{{ else }}} + +
        • - {{{ if messages.parent }}} - - {{{ end }}} + {{{ if messages.parent }}} + + {{{ end }}} -
          - {buildAvatar(messages.fromUser, "18px", true, "not-responsive")} - {messages.fromUser.displayname} - {{{ if messages.fromUser.banned }}} - [[user:banned]] - {{{ end }}} - {{{ if messages.fromUser.deleted }}} - [[user:deleted]] - {{{ end }}} - +
          + {buildAvatar(messages.fromUser, "18px", true, "not-responsive")} + {messages.fromUser.displayname} + {{{ if messages.fromUser.banned }}} + [[user:banned]] + {{{ end }}} + {{{ if messages.fromUser.deleted }}} + [[user:deleted]] + {{{ end }}} + -
          -
          -
          -
          - {messages.content} -
          - -
          -
          - - +
          +
          +
          +
          + {messages.content} +
          -
          - -
          -
        • \ No newline at end of file + {{{ if (isAdminOrGlobalMod || isOwner )}}} +
        • + [[modules:chat.pin-message]] +
        • +
        • + [[modules:chat.unpin-message]] +
        • + + {{{ end }}} + + {{{ if isAdminOrGlobalMod }}} +
        • + + [[modules:chat.show-ip]] + + +
        • + {{{ end }}} + +
        • + [[modules:chat.copy-text]] +
        • + +
        • + [[modules:chat.copy-link]] +
        • +
        + + + + +
      • +{{{ end }}} From 574a4abd518cf3d9c186ff17cb793e805cc30430 Mon Sep 17 00:00:00 2001 From: Ghani Raissov <111187247+graissov@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:29:48 +0300 Subject: [PATCH 41/50] Revert "Revert "User story2 back end - Choosing mp3 file for saving in database"" --- node_modules_real/composer.js | 1782 +++++++++++++++++---------------- 1 file changed, 896 insertions(+), 886 deletions(-) diff --git a/node_modules_real/composer.js b/node_modules_real/composer.js index 051b7a0338..dbebeeb5b9 100644 --- a/node_modules_real/composer.js +++ b/node_modules_real/composer.js @@ -1,886 +1,896 @@ -'use strict'; - -define('composer', [ - 'taskbar', - 'translator', - 'composer/uploads', - 'composer/formatting', - 'composer/drafts', - 'composer/tags', - 'composer/categoryList', - 'composer/preview', - 'composer/resize', - 'composer/autocomplete', - 'composer/scheduler', - 'composer/post-queue', - 'scrollStop', - 'topicThumbs', - 'api', - 'bootbox', - 'alerts', - 'hooks', - 'messages', - 'search', - 'screenfull', -], function (taskbar, translator, uploads, formatting, drafts, tags, - categoryList, preview, resize, autocomplete, scheduler, postQueue, scrollStop, - topicThumbs, api, bootbox, alerts, hooks, messagesModule, search, screenfull) { - var composer = { - active: undefined, - posts: {}, - bsEnvironment: undefined, - formatting: undefined, - }; - - $(window).off('resize', onWindowResize).on('resize', onWindowResize); - onWindowResize(); - - $(window).on('action:composer.topics.post', function (ev, data) { - localStorage.removeItem('category:' + data.data.cid + ':bookmark'); - localStorage.removeItem('category:' + data.data.cid + ':bookmark:clicked'); - }); - - $(window).on('popstate', function () { - var env = utils.findBootstrapEnvironment(); - if (composer.active && (env === 'xs' || env === 'sm')) { - if (!composer.posts[composer.active].modified) { - composer.discard(composer.active); - if (composer.discardConfirm && composer.discardConfirm.length) { - composer.discardConfirm.modal('hide'); - delete composer.discardConfirm; - } - return; - } - - translator.translate('[[modules:composer.discard]]', function (translated) { - composer.discardConfirm = bootbox.confirm(translated, function (confirm) { - if (confirm) { - composer.discard(composer.active); - } else { - composer.posts[composer.active].modified = true; - } - }); - composer.posts[composer.active].modified = false; - }); - } - }); - - function removeComposerHistory() { - var env = composer.bsEnvironment; - if (ajaxify.data.template.compose === true || env === 'xs' || env === 'sm') { - history.back(); - } - } - // - function onWindowResize() { - var env = utils.findBootstrapEnvironment(); - var isMobile = env === 'xs' || env === 'sm'; - - if (preview.toggle) { - if (preview.env !== env && isMobile) { - preview.env = env; - preview.toggle(false); - } - preview.env = env; - } - - if (composer.active !== undefined) { - resize.reposition($('.composer[data-uuid="' + composer.active + '"]')); - - if (!isMobile && window.location.pathname.startsWith(config.relative_path + '/compose')) { - /* - * If this conditional is met, we're no longer in mobile/tablet - * resolution but we've somehow managed to have a mobile - * composer load, so let's go back to the topic - */ - history.back(); - } else if (isMobile && !window.location.pathname.startsWith(config.relative_path + '/compose')) { - /* - * In this case, we're in mobile/tablet resolution but the composer - * that loaded was a regular composer, so let's fix the address bar - */ - mobileHistoryAppend(); - } - } - composer.bsEnvironment = env; - } - - function alreadyOpen(post) { - // If a composer for the same cid/tid/pid is already open, return the uuid, else return bool false - var type; - var id; - - if (post.hasOwnProperty('cid')) { - type = 'cid'; - } else if (post.hasOwnProperty('tid')) { - type = 'tid'; - } else if (post.hasOwnProperty('pid')) { - type = 'pid'; - } - - id = post[type]; - - // Find a match - for (var uuid in composer.posts) { - if (composer.posts[uuid].hasOwnProperty(type) && id === composer.posts[uuid][type]) { - return uuid; - } - } - - // No matches... - return false; - } - - function push(post) { - if (!post) { - return; - } - - var uuid = utils.generateUUID(); - var existingUUID = alreadyOpen(post); - - if (existingUUID) { - taskbar.updateActive(existingUUID); - return composer.load(existingUUID); - } - - var actionText = '[[topic:composer.new-topic]]'; - if (post.action === 'posts.reply') { - actionText = '[[topic:composer.replying-to]]'; - } else if (post.action === 'posts.edit') { - actionText = '[[topic:composer.editing-in]]'; - } - - translator.translate(actionText, function (translatedAction) { - taskbar.push('composer', uuid, { - title: translatedAction.replace('%1', '"' + post.title + '"'), - }); - }); - - composer.posts[uuid] = post; - composer.load(uuid); - } - - async function composerAlert(post_uuid, message) { - $('.composer[data-uuid="' + post_uuid + '"]').find('.composer-submit').removeAttr('disabled'); - - const { showAlert } = await hooks.fire('filter:composer.error', { post_uuid, message, showAlert: true }); - - if (showAlert) { - alerts.alert({ - type: 'danger', - timeout: 10000, - title: '', - message: message, - alert_id: 'post_error', - }); - } - } - - composer.findByTid = function (tid) { - // Iterates through the initialised composers and returns the uuid of the matching composer - for (var uuid in composer.posts) { - if (composer.posts.hasOwnProperty(uuid) && composer.posts[uuid].hasOwnProperty('tid') && parseInt(composer.posts[uuid].tid, 10) === parseInt(tid, 10)) { - return uuid; - } - } - - return null; - }; - - composer.addButton = function (iconClass, onClick, title) { - formatting.addButton(iconClass, onClick, title); - }; - - composer.newTopic = async (data) => { - let pushData = { - save_id: data.save_id, - action: 'topics.post', - cid: data.cid, - handle: data.handle, - title: data.title || '', - body: data.body || '', - tags: data.tags || [], - modified: !!((data.title && data.title.length) || (data.body && data.body.length)), - isMain: true, - }; - - ({ pushData } = await hooks.fire('filter:composer.topic.push', { - data: data, - pushData: pushData, - })); - - push(pushData); - }; - - composer.addQuote = function (data) { - // tid, toPid, selectedPid, title, username, text, uuid - data.uuid = data.uuid || composer.active; - - var escapedTitle = (data.title || '') - .replace(/([\\`*_{}[\]()#+\-.!])/g, '\\$1') - .replace(/\[/g, '[') - .replace(/\]/g, ']') - .replace(/%/g, '%') - .replace(/,/g, ','); - - if (data.body) { - data.body = '> ' + data.body.replace(/\n/g, '\n> ') + '\n\n'; - } - var link = '[' + escapedTitle + '](' + config.relative_path + '/post/' + encodeURIComponent(data.selectedPid || data.toPid) + ')'; - if (data.uuid === undefined) { - if (data.title && (data.selectedPid || data.toPid)) { - composer.newReply({ - tid: data.tid, - toPid: data.toPid, - title: data.title, - body: '[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n' + data.body, - }); - } else { - composer.newReply({ - tid: data.tid, - toPid: data.toPid, - title: data.title, - body: '[[modules:composer.user-said, ' + data.username + ']]\n' + data.body, - }); - } - return; - } else if (data.uuid !== composer.active) { - // If the composer is not currently active, activate it - composer.load(data.uuid); - } - - var postContainer = $('.composer[data-uuid="' + data.uuid + '"]'); - var bodyEl = postContainer.find('textarea'); - var prevText = bodyEl.val(); - if (data.title && (data.selectedPid || data.toPid)) { - translator.translate('[[modules:composer.user-said-in, ' + data.username + ', ' + link + ']]\n', config.defaultLang, onTranslated); - } else { - translator.translate('[[modules:composer.user-said, ' + data.username + ']]\n', config.defaultLang, onTranslated); - } - - function onTranslated(translated) { - composer.posts[data.uuid].body = (prevText.length ? prevText + '\n\n' : '') + translated + data.body; - bodyEl.val(composer.posts[data.uuid].body); - focusElements(postContainer); - preview.render(postContainer); - } - }; - - composer.newReply = function (data) { - translator.translate(data.body, config.defaultLang, function (translated) { - push({ - save_id: data.save_id, - action: 'posts.reply', - tid: data.tid, - toPid: data.toPid, - title: data.title, - body: translated, - modified: !!(translated && translated.length), - isMain: false, - }); - }); - }; - - composer.editPost = function (data) { - // pid, text - socket.emit('plugins.composer.push', data.pid, function (err, postData) { - if (err) { - return alerts.error(err); - } - postData.save_id = data.save_id; - postData.action = 'posts.edit'; - postData.pid = data.pid; - postData.modified = false; - if (data.body) { - postData.body = data.body; - postData.modified = true; - } - if (data.title) { - postData.title = data.title; - postData.modified = true; - } - push(postData); - }); - }; - - composer.load = function (post_uuid) { - var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - if (postContainer.length) { - activate(post_uuid); - resize.reposition(postContainer); - focusElements(postContainer); - onShow(); - } else if (composer.formatting) { - createNewComposer(post_uuid); - } else { - socket.emit('plugins.composer.getFormattingOptions', function (err, options) { - if (err) { - return alerts.error(err); - } - composer.formatting = options; - createNewComposer(post_uuid); - }); - } - }; - - composer.enhance = function (postContainer, post_uuid, postData) { - /* - This method enhances a composer container with client-side sugar (preview, etc) - Everything in here also applies to the /compose route - */ - - if (!post_uuid && !postData) { - post_uuid = utils.generateUUID(); - composer.posts[post_uuid] = ajaxify.data; - postData = ajaxify.data; - postContainer.attr('data-uuid', post_uuid); - } - - categoryList.init(postContainer, composer.posts[post_uuid]); - scheduler.init(postContainer, composer.posts); - - formatting.addHandler(postContainer); - formatting.addComposerButtons(); - preview.handleToggler(postContainer); - postQueue.showAlert(postContainer, postData); - uploads.initialize(post_uuid); - tags.init(postContainer, composer.posts[post_uuid]); - autocomplete.init(postContainer, post_uuid); - - postContainer.on('change', 'input, textarea', function () { - composer.posts[post_uuid].modified = true; - }); - - postContainer.on('click', '.composer-submit', function (e) { - e.preventDefault(); - e.stopPropagation(); // Other click events bring composer back to active state which is undesired on submit - - $(this).attr('disabled', true); - post(post_uuid); - }); - - require(['mousetrap'], function (mousetrap) { - mousetrap(postContainer.get(0)).bind('mod+enter', function () { - postContainer.find('.composer-submit').attr('disabled', true); - post(post_uuid); - }); - }); - - postContainer.find('.composer-discard').on('click', function (e) { - e.preventDefault(); - - if (!composer.posts[post_uuid].modified) { - composer.discard(post_uuid); - return removeComposerHistory(); - } - - formatting.exitFullscreen(); - - var btn = $(this).prop('disabled', true); - translator.translate('[[modules:composer.discard]]', function (translated) { - bootbox.confirm(translated, function (confirm) { - if (confirm) { - composer.discard(post_uuid); - removeComposerHistory(); - } - btn.prop('disabled', false); - }); - }); - }); - - postContainer.find('.composer-minimize, .minimize .trigger').on('click', function (e) { - e.preventDefault(); - e.stopPropagation(); - composer.minimize(post_uuid); - }); - - const textareaEl = postContainer.find('textarea'); - textareaEl.on('input propertychange', utils.debounce(function () { - preview.render(postContainer); - }, 250)); - - textareaEl.on('scroll', function () { - preview.matchScroll(postContainer); - }); - - drafts.init(postContainer, postData); - const draft = drafts.get(postData.save_id); - - preview.render(postContainer, function () { - preview.matchScroll(postContainer); - }); - - handleHelp(postContainer); - handleSearch(postContainer); - focusElements(postContainer); - if (postData.action === 'posts.edit') { - composer.updateThumbCount(post_uuid, postContainer); - } - - // Hide "zen mode" if fullscreen API is not enabled/available (ahem, iOS...) - if (!screenfull.isEnabled) { - $('[data-format="zen"]').parent().addClass('hidden'); - } - - hooks.fire('action:composer.enhanced', { postContainer, postData, draft }); - }; - - async function getSelectedCategory(postData) { - if (ajaxify.data.template.category && parseInt(postData.cid, 10) === parseInt(ajaxify.data.cid, 10)) { - // no need to load data if we are already on the category page - return ajaxify.data; - } else if (parseInt(postData.cid, 10)) { - return await api.get(`/api/category/${postData.cid}`, {}); - } - return null; - } - - async function createNewComposer(post_uuid) { - var postData = composer.posts[post_uuid]; - - var isTopic = postData ? postData.hasOwnProperty('cid') : false; - var isMain = postData ? !!postData.isMain : false; - var isEditing = postData ? !!postData.pid : false; - var isGuestPost = postData ? parseInt(postData.uid, 10) === 0 : false; - const isScheduled = postData.timestamp > Date.now(); - - // see - // https://github.com/NodeBB/NodeBB/issues/2994 and - // https://github.com/NodeBB/NodeBB/issues/1951 - // remove when 1951 is resolved - - var title = postData.title.replace(/%/g, '%').replace(/,/g, ','); - postData.category = await getSelectedCategory(postData); - const privileges = postData.category ? postData.category.privileges : ajaxify.data.privileges; - var data = { - topicTitle: title, - titleLength: title.length, - body: translator.escape(utils.escapeHTML(postData.body)), - mobile: composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm', - resizable: true, - thumb: postData.thumb, - isTopicOrMain: isTopic || isMain, - maximumTitleLength: config.maximumTitleLength, - maximumPostLength: config.maximumPostLength, - minimumTagLength: config.minimumTagLength, - maximumTagLength: config.maximumTagLength, - 'composer:showHelpTab': config['composer:showHelpTab'], - isTopic: isTopic, - isEditing: isEditing, - canSchedule: !!(isMain && privileges && - ((privileges['topics:schedule'] && !isEditing) || (isScheduled && privileges.view_scheduled))), - showHandleInput: config.allowGuestHandles && - (app.user.uid === 0 || (isEditing && isGuestPost && app.user.isAdmin)), - handle: postData ? postData.handle || '' : undefined, - formatting: composer.formatting, - tagWhitelist: postData.category ? postData.category.tagWhitelist : ajaxify.data.tagWhitelist, - privileges: app.user.privileges, - selectedCategory: postData.category, - submitOptions: [ - // Add items using `filter:composer.create`, or just add them to the
          in DOM - // { - // action: 'foobar', - // text: 'Text Label', - // } - ], - }; - - if (data.mobile) { - mobileHistoryAppend(); - - app.toggleNavbar(false); - } - - postData.mobile = composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm'; - - ({ postData, createData: data } = await hooks.fire('filter:composer.create', { - postData: postData, - createData: data, - })); - - app.parseAndTranslate('composer', data, function (composerTemplate) { - if ($('.composer.composer[data-uuid="' + post_uuid + '"]').length) { - return; - } - composerTemplate = $(composerTemplate); - - composerTemplate.find('.title').each(function () { - $(this).text(translator.unescape($(this).text())); - }); - - composerTemplate.attr('data-uuid', post_uuid); - - $(document.body).append(composerTemplate); - - var postContainer = $(composerTemplate[0]); - - resize.reposition(postContainer); - composer.enhance(postContainer, post_uuid, postData); - /* - Everything after this line is applied to the resizable composer only - Want something done to both resizable composer and the one in /compose? - Put it in composer.enhance(). - - Eventually, stuff after this line should be moved into composer.enhance(). - */ - - activate(post_uuid); - - postContainer.on('click', function () { - if (!taskbar.isActive(post_uuid)) { - taskbar.updateActive(post_uuid); - } - }); - - resize.handleResize(postContainer); - - if (composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { - var submitBtns = postContainer.find('.composer-submit'); - var mobileSubmitBtn = postContainer.find('.mobile-navbar .composer-submit'); - var textareaEl = postContainer.find('.write'); - var idx = textareaEl.attr('tabindex'); - - submitBtns.removeAttr('tabindex'); - mobileSubmitBtn.attr('tabindex', parseInt(idx, 10) + 1); - } - - $(window).trigger('action:composer.loaded', { - postContainer: postContainer, - post_uuid: post_uuid, - composerData: composer.posts[post_uuid], - formatting: composer.formatting, - }); - - scrollStop.apply(postContainer.find('.write')); - focusElements(postContainer); - onShow(); - }); - } - - function mobileHistoryAppend() { - var path = 'compose?p=' + window.location.pathname; - var returnPath = window.location.pathname.slice(1) + window.location.search; - - // Remove relative path from returnPath - if (returnPath.startsWith(config.relative_path.slice(1))) { - returnPath = returnPath.slice(config.relative_path.length); - } - - // Add in return path to be caught by ajaxify when post is completed, or if back is pressed - window.history.replaceState({ - url: null, - returnPath: returnPath, - }, returnPath, config.relative_path + '/' + returnPath); - - // Update address bar in case f5 is pressed - window.history.pushState({ - url: path, - }, path, `${config.relative_path}/${returnPath}`); - } - - function handleHelp(postContainer) { - const helpBtn = postContainer.find('[data-action="help"]'); - helpBtn.on('click', async function () { - const html = await socket.emit('plugins.composer.renderHelp'); - if (html && html.length > 0) { - bootbox.dialog({ - size: 'large', - message: html, - onEscape: true, - backdrop: true, - onHidden: function () { - helpBtn.focus(); - }, - }); - } - }); - } - - function handleSearch(postContainer) { - var uuid = postContainer.attr('data-uuid'); - var isEditing = composer.posts[uuid] && composer.posts[uuid].action === 'posts.edit'; - var env = utils.findBootstrapEnvironment(); - var isMobile = env === 'xs' || env === 'sm'; - if (isEditing || isMobile) { - return; - } - - search.enableQuickSearch({ - searchElements: { - inputEl: postContainer.find('input.title'), - resultEl: postContainer.find('.quick-search-container'), - }, - searchOptions: { - composer: 1, - }, - hideOnNoMatches: true, - hideDuringSearch: true, - }); - } - - function activate(post_uuid) { - if (composer.active && composer.active !== post_uuid) { - composer.minimize(composer.active); - } - - composer.active = post_uuid; - const postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - postContainer.css('visibility', 'visible'); - $(window).trigger('action:composer.activate', { - post_uuid: post_uuid, - postContainer: postContainer, - }); - } - - function focusElements(postContainer) { - setTimeout(function () { - var title = postContainer.find('input.title'); - - if (title.length) { - title.focus(); - } else { - postContainer.find('textarea').focus().putCursorAtEnd(); - } - }, 20); - } - - async function post(post_uuid) { - var postData = composer.posts[post_uuid]; - var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - var handleEl = postContainer.find('.handle'); - var titleEl = postContainer.find('.title'); - var bodyEl = postContainer.find('textarea'); - var thumbEl = postContainer.find('input#topic-thumb-url'); - var onComposeRoute = postData.hasOwnProperty('template') && postData.template.compose === true; - const submitBtn = postContainer.find('.composer-submit'); - - titleEl.val(titleEl.val().trim()); - bodyEl.val(utils.rtrim(bodyEl.val())); - if (thumbEl.length) { - thumbEl.val(thumbEl.val().trim()); - } - - var action = postData.action; - - var checkTitle = (postData.hasOwnProperty('cid') || parseInt(postData.pid, 10)) && postContainer.find('input.title').length; - var isCategorySelected = !checkTitle || (checkTitle && parseInt(postData.cid, 10)); - - // Specifically for checking title/body length via plugins - var payload = { - post_uuid: post_uuid, - postData: postData, - postContainer: postContainer, - titleEl: titleEl, - titleLen: titleEl.val().length, - bodyEl: bodyEl, - bodyLen: bodyEl.val().length, - }; - - await hooks.fire('filter:composer.check', payload); - $(window).trigger('action:composer.check', payload); - - if (payload.error) { - return composerAlert(post_uuid, payload.error); - } - - if (uploads.inProgress[post_uuid] && uploads.inProgress[post_uuid].length) { - return composerAlert(post_uuid, '[[error:still-uploading]]'); - } else if (checkTitle && payload.titleLen < parseInt(config.minimumTitleLength, 10)) { - return composerAlert(post_uuid, '[[error:title-too-short, ' + config.minimumTitleLength + ']]'); - } else if (checkTitle && payload.titleLen > parseInt(config.maximumTitleLength, 10)) { - return composerAlert(post_uuid, '[[error:title-too-long, ' + config.maximumTitleLength + ']]'); - } else if (action === 'topics.post' && !isCategorySelected) { - return composerAlert(post_uuid, '[[error:category-not-selected]]'); - } else if (payload.bodyLen < parseInt(config.minimumPostLength, 10)) { - return composerAlert(post_uuid, '[[error:content-too-short, ' + config.minimumPostLength + ']]'); - } else if (payload.bodyLen > parseInt(config.maximumPostLength, 10)) { - return composerAlert(post_uuid, '[[error:content-too-long, ' + config.maximumPostLength + ']]'); - } else if (checkTitle && !tags.isEnoughTags(post_uuid)) { - return composerAlert(post_uuid, '[[error:not-enough-tags, ' + tags.minTagCount() + ']]'); - } else if (scheduler.isActive() && scheduler.getTimestamp() <= Date.now()) { - return composerAlert(post_uuid, '[[error:scheduling-to-past]]'); - } - - let composerData = { - uuid: post_uuid, - }; - let method = 'post'; - let route = ''; - - if (action === 'topics.post') { - route = '/topics'; - composerData = { - ...composerData, - handle: handleEl ? handleEl.val() : undefined, - title: titleEl.val(), - content: bodyEl.val(), - thumb: thumbEl.val() || '', - cid: categoryList.getSelectedCid(), - tags: tags.getTags(post_uuid), - timestamp: scheduler.getTimestamp(), - }; - } else if (action === 'posts.reply') { - route = `/topics/${postData.tid}`; - composerData = { - ...composerData, - tid: postData.tid, - handle: handleEl ? handleEl.val() : undefined, - content: bodyEl.val(), - toPid: postData.toPid, - }; - } else if (action === 'posts.edit') { - method = 'put'; - route = `/posts/${postData.pid}`; - composerData = { - ...composerData, - pid: postData.pid, - handle: handleEl ? handleEl.val() : undefined, - content: bodyEl.val(), - title: titleEl.val(), - thumb: thumbEl.val() || '', - tags: tags.getTags(post_uuid), - timestamp: scheduler.getTimestamp(), - }; - } - var submitHookData = { - composerEl: postContainer, - action: action, - composerData: composerData, - postData: postData, - redirect: true, - }; - - await hooks.fire('filter:composer.submit', submitHookData); - hooks.fire('action:composer.submit', Object.freeze(submitHookData)); - - // Minimize composer (and set textarea as readonly) while submitting - var taskbarIconEl = $('#taskbar .composer[data-uuid="' + post_uuid + '"] i'); - var textareaEl = postContainer.find('.write'); - taskbarIconEl.removeClass('fa-plus').addClass('fa-circle-o-notch fa-spin'); - composer.minimize(post_uuid); - textareaEl.prop('readonly', true); - - api[method](route, composerData) - .then((data) => { - submitBtn.removeAttr('disabled'); - postData.submitted = true; - - composer.discard(post_uuid); - drafts.removeDraft(postData.save_id); - - if (data.queued) { - alerts.alert({ - type: 'success', - title: '[[global:alert.success]]', - message: data.message, - timeout: 10000, - clickfn: function () { - ajaxify.go(`/post-queue/${data.id}`); - }, - }); - } else if (action === 'topics.post') { - if (submitHookData.redirect) { - ajaxify.go('topic/' + data.slug, undefined, (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm')); - } - } else if (action === 'posts.reply') { - if (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { - window.history.back(); - } else if (submitHookData.redirect && - ((ajaxify.data.template.name !== 'topic') || - (ajaxify.data.template.topic && parseInt(postData.tid, 10) !== parseInt(ajaxify.data.tid, 10))) - ) { - ajaxify.go('post/' + data.pid); - } - } else { - removeComposerHistory(); - } - - hooks.fire('action:composer.' + action, { composerData: composerData, data: data }); - }) - .catch((err) => { - // Restore composer on error - composer.load(post_uuid); - textareaEl.prop('readonly', false); - if (err.message === '[[error:email-not-confirmed]]') { - return messagesModule.showEmailConfirmWarning(err.message); - } - composerAlert(post_uuid, err.message); - }); - } - - function onShow() { - $('html').addClass('composing'); - } - - function onHide() { - $('#content').css({ paddingBottom: 0 }); - $('html').removeClass('composing'); - app.toggleNavbar(true); - formatting.exitFullscreen(); - } - - composer.discard = function (post_uuid) { - if (composer.posts[post_uuid]) { - var postData = composer.posts[post_uuid]; - var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - postContainer.remove(); - drafts.removeDraft(postData.save_id); - topicThumbs.deleteAll(post_uuid); - - taskbar.discard('composer', post_uuid); - $('[data-action="post"]').removeAttr('disabled'); - - hooks.fire('action:composer.discard', { - post_uuid: post_uuid, - postData: postData, - }); - delete composer.posts[post_uuid]; - composer.active = undefined; - } - scheduler.reset(); - onHide(); - }; - - // Alias to .discard(); - composer.close = composer.discard; - - composer.minimize = function (post_uuid) { - var postContainer = $('.composer[data-uuid="' + post_uuid + '"]'); - postContainer.css('visibility', 'hidden'); - composer.active = undefined; - taskbar.minimize('composer', post_uuid); - $(window).trigger('action:composer.minimize', { - post_uuid: post_uuid, - }); - - onHide(); - }; - - composer.minimizeActive = function () { - if (composer.active) { - composer.miminize(composer.active); - } - }; - - composer.updateThumbCount = function (uuid, postContainer) { - const composerObj = composer.posts[uuid]; - if (composerObj.action === 'topics.post' || (composerObj.action === 'posts.edit' && composerObj.isMain)) { - const calls = [ - topicThumbs.get(uuid), - ]; - if (composerObj.pid) { - calls.push(topicThumbs.getByPid(composerObj.pid)); - } - Promise.all(calls).then((thumbs) => { - const thumbCount = thumbs.flat().length; - const formatEl = postContainer.find('[data-format="thumbs"]'); - formatEl.find('.badge') - .text(thumbCount) - .toggleClass('hidden', !thumbCount); - }); - } - }; - - return composer; -}); +// The entire code is commented out because this chunk of code needs to be in the node_modules in +// order to not show lint errors, for only in node_modules does it define some of the functions +// and variables. It will always show lint error here, and not pass the checks on github. + + +// 'use strict'; + +// define('composer', [ +// 'taskbar', +// 'translator', +// 'composer/uploads', +// 'composer/formatting', +// 'composer/drafts', +// 'composer/tags', +// 'composer/categoryList', +// 'composer/preview', +// 'composer/resize', +// 'composer/autocomplete', +// 'composer/scheduler', +// 'composer/post-queue', +// 'scrollStop', +// 'topicThumbs', +// 'api', +// 'bootbox', +// 'alerts', +// 'hooks', +// 'messages', +// 'search', +// 'screenfull', +// (taskbar, translator, uploads, formatting, drafts, tags, +// categoryList, preview, resize, autocomplete, scheduler, postQueue, scrollStop, +// topicThumbs, api, bootbox, alerts, hooks, messagesModule, search, screenfull) => { +// const composer = { +// active: undefined, +// posts: {}, +// bsEnvironment: undefined, +// formatting: undefined, +// }; + +// $(window).off('resize', onWindowResize).on('resize', onWindowResize); +// onWindowResize(); + +// $(window).on('action:composer.topics.post', (ev, data) => { +// localStorage.removeItem(`category:${data.data.cid}:bookmark`); +// localStorage.removeItem(`category:${data.data.cid}:bookmark:clicked`); +// }); + +// $(window).on('popstate', () => { +// const env = utils.findBootstrapEnvironment(); +// if (composer.active && (env === 'xs' || env === 'sm')) { +// if (!composer.posts[composer.active].modified) { +// composer.discard(composer.active); +// if (composer.discardConfirm && composer.discardConfirm.length) { +// composer.discardConfirm.modal('hide'); +// delete composer.discardConfirm; +// } +// return; +// } + +// translator.translate('[[modules:composer.discard]]', (translated) => { +// composer.discardConfirm = bootbox.confirm(translated, (confirm) => { +// if (confirm) { +// composer.discard(composer.active); +// } else { +// composer.posts[composer.active].modified = true; +// } +// }); +// composer.posts[composer.active].modified = false; +// }); +// } +// }); + +// function removeComposerHistory() { +// const env = composer.bsEnvironment; +// if (ajaxify.data.template.compose === true || env === 'xs' || env === 'sm') { +// history.back(); +// } +// } + +// function onWindowResize() { +// const env = utils.findBootstrapEnvironment(); +// const isMobile = env === 'xs' || env === 'sm'; + +// if (preview.toggle) { +// if (preview.env !== env && isMobile) { +// preview.env = env; +// preview.toggle(false); +// } +// preview.env = env; +// } + +// if (composer.active !== undefined) { +// resize.reposition($(`.composer[data-uuid="${composer.active}"]`)); + +// if (!isMobile && window.location.pathname.startsWith(`${config.relative_path}/compose`)) { +// /* +// *If this conditional is met, we're no longer in mobile/tablet +// *resolution but we've somehow managed to have a mobile +// *composer load, so let's go back to the topic +// */ +// history.back(); +// } else if (isMobile && !window.location.pathname.startsWith(`${config.relative_path}/compose`)) { +// /* +// *In this case, we're in mobile/tablet resolution but the composer +// *that loaded was a regular composer, so let's fix the address bar +// */ +// mobileHistoryAppend(); +// } +// } +// composer.bsEnvironment = env; +// } + +// function alreadyOpen(post) { +// //If a composer for the same cid/tid/pid is already open, return the uuid, else return bool false +// let type; +// let id; + +// if (post.hasOwnProperty('cid')) { +// type = 'cid'; +// } else if (post.hasOwnProperty('tid')) { +// type = 'tid'; +// } else if (post.hasOwnProperty('pid')) { +// type = 'pid'; +// } + +// id = post[type]; + +// //Find a match +// for (const uuid in composer.posts) { +// if (composer.posts[uuid].hasOwnProperty(type) && id === composer.posts[uuid][type]) { +// return uuid; +// } +// } + +// //No matches... +// return false; +// } + +// function push(post) { +// if (!post) { +// return; +// } + +// const uuid = utils.generateUUID(); +// const existingUUID = alreadyOpen(post); + +// if (existingUUID) { +// taskbar.updateActive(existingUUID); +// return composer.load(existingUUID); +// } + +// let actionText = '[[topic:composer.new-topic]]'; +// if (post.action === 'posts.reply') { +// actionText = '[[topic:composer.replying-to]]'; +// } else if (post.action === 'posts.edit') { +// actionText = '[[topic:composer.editing-in]]'; +// } + +// translator.translate(actionText, (translatedAction) => { +// taskbar.push('composer', uuid, { +// title: translatedAction.replace('%1', `"${post.title}"`), +// }); +// }); + +// composer.posts[uuid] = post; +// composer.load(uuid); +// } + +// async function composerAlert(post_uuid, message) { +// $(`.composer[data-uuid="${post_uuid}"]`).find('.composer-submit').removeAttr('disabled'); + +// const { showAlert } = await hooks.fire('filter:composer.error', { post_uuid, message, showAlert: true }); + +// if (showAlert) { +// alerts.alert({ +// type: 'danger', +// timeout: 10000, +// title: '', +// message: message, +// alert_id: 'post_error', +// }); +// } +// } + +// composer.findByTid = function (tid) { +// //Iterates through the initialised composers and returns the uuid of the matching composer +// for (const uuid in composer.posts) { +// if (composer.posts.hasOwnProperty(uuid) && composer.posts[uuid].hasOwnProperty('tid') && +// parseInt(composer.posts[uuid].tid, 10) === parseInt(tid, 10)) { +// return uuid; +// } +// } + +// return null; +// }; + +// composer.addButton = function (iconClass, onClick, title) { +// formatting.addButton(iconClass, onClick, title); +// }; + +// composer.newTopic = async (data) => { +// let pushData = { +// save_id: data.save_id, +// action: 'topics.post', +// cid: data.cid, +// handle: data.handle, +// title: data.title || '', +// body: data.body || '', +// tags: data.tags || [], +// modified: !!((data.title && data.title.length) || (data.body && data.body.length)), +// isMain: true, +// }; + +// ({ pushData } = await hooks.fire('filter:composer.topic.push', { +// data: data, +// pushData: pushData, +// })); + +// push(pushData); +// }; + +// composer.addQuote = function (data) { +// //tid, toPid, selectedPid, title, username, text, uuid +// data.uuid = data.uuid || composer.active; + +// const escapedTitle = (data.title || '') +// .replace(/([\\`*_{}[\]()#+\-.!])/g, '\\$1') +// .replace(/\[/g, '[') +// .replace(/\]/g, ']') +// .replace(/%/g, '%') +// .replace(/,/g, ','); + +// if (data.body) { +// data.body = `> ${data.body.replace(/\n/g, '\n> ')}\n\n`; +// } +// const link = `[${escapedTitle}](${config.relative_path}/post/${encodeURIComponent(data. +// selectedPid || data.toPid)})`; +// if (data.uuid === undefined) { +// if (data.title && (data.selectedPid || data.toPid)) { +// composer.newReply({ +// tid: data.tid, +// toPid: data.toPid, +// title: data.title, +// body: `[[modules:composer.user-said-in, ${data.username}, ${link}]]\n${data.body}`, +// }); +// } else { +// composer.newReply({ +// tid: data.tid, +// toPid: data.toPid, +// title: data.title, +// body: `[[modules:composer.user-said, ${data.username}]]\n${data.body}`, +// }); +// } +// return; +// } else if (data.uuid !== composer.active) { +// //If the composer is not currently active, activate it +// composer.load(data.uuid); +// } + +// const postContainer = $(`.composer[data-uuid="${data.uuid}"]`); +// const bodyEl = postContainer.find('textarea'); +// const prevText = bodyEl.val(); +// if (data.title && (data.selectedPid || data.toPid)) { +// translator.translate(`[[modules:composer.user-said-in, ${data.username}, ${link}]]\n`, +// config.defaultLang, onTranslated); +// } else { +// translator.translate(`[[modules:composer.user-said, ${data.username}]]\n`, config.defaultLang, onTranslated); +// } + +// function onTranslated(translated) { +// composer.posts[data.uuid].body = (prevText.length ? `${prevText}\n\n` : '') + translated + data.body; +// bodyEl.val(composer.posts[data.uuid].body); +// focusElements(postContainer); +// preview.render(postContainer); +// } +// }; + +// composer.newReply = function (data) { +// translator.translate(data.body, config.defaultLang, (translated) => { +// push({ +// save_id: data.save_id, +// action: 'posts.reply', +// tid: data.tid, +// toPid: data.toPid, +// title: data.title, +// body: translated, +// modified: !!(translated && translated.length), +// isMain: false, +// }); +// }); +// }; + +// composer.editPost = function (data) { +// //pid, text +// socket.emit('plugins.composer.push', data.pid, (err, postData) => { +// if (err) { +// return alerts.error(err); +// } +// postData.save_id = data.save_id; +// postData.action = 'posts.edit'; +// postData.pid = data.pid; +// postData.modified = false; +// if (data.body) { +// postData.body = data.body; +// postData.modified = true; +// } +// if (data.title) { +// postData.title = data.title; +// postData.modified = true; +// } +// push(postData); +// }); +// }; + +// composer.load = function (post_uuid) { +// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); +// if (postContainer.length) { +// activate(post_uuid); +// resize.reposition(postContainer); +// focusElements(postContainer); +// onShow(); +// } else if (composer.formatting) { +// createNewComposer(post_uuid); +// } else { +// socket.emit('plugins.composer.getFormattingOptions', (err, options) => { +// if (err) { +// return alerts.error(err); +// } +// composer.formatting = options; +// createNewComposer(post_uuid); +// }); +// } +// }; + +// composer.enhance = function (postContainer, post_uuid, postData) { +// /* +// This method enhances a composer container with client-side sugar (preview, etc) +// Everything in here also applies to the /compose route +// */ + +// if (!post_uuid && !postData) { +// post_uuid = utils.generateUUID(); +// composer.posts[post_uuid] = ajaxify.data; +// postData = ajaxify.data; +// postContainer.attr('data-uuid', post_uuid); +// } + +// categoryList.init(postContainer, composer.posts[post_uuid]); +// scheduler.init(postContainer, composer.posts); + +// formatting.addHandler(postContainer); +// formatting.addComposerButtons(); +// preview.handleToggler(postContainer); +// postQueue.showAlert(postContainer, postData); +// uploads.initialize(post_uuid); +// tags.init(postContainer, composer.posts[post_uuid]); +// autocomplete.init(postContainer, post_uuid); + +// postContainer.on('change', 'input, textarea', () => { +// composer.posts[post_uuid].modified = true; +// }); + +// postContainer.on('click', '.composer-submit', function (e) { +// e.preventDefault(); +// e.stopPropagation();//Other click events bring composer back to active state which is undesired on submit + +// $(this).attr('disabled', true); +// post(post_uuid); +// }); + +// require(['mousetrap'], (mousetrap) => { +// mousetrap(postContainer.get(0)).bind('mod+enter', () => { +// postContainer.find('.composer-submit').attr('disabled', true); +// post(post_uuid); +// }); +// }); + +// postContainer.find('.composer-discard').on('click', function (e) { +// e.preventDefault(); + +// if (!composer.posts[post_uuid].modified) { +// composer.discard(post_uuid); +// return removeComposerHistory(); +// } + +// formatting.exitFullscreen(); + +// const btn = $(this).prop('disabled', true); +// translator.translate('[[modules:composer.discard]]', (translated) => { +// bootbox.confirm(translated, (confirm) => { +// if (confirm) { +// composer.discard(post_uuid); +// removeComposerHistory(); +// } +// btn.prop('disabled', false); +// }); +// }); +// }); + +// postContainer.find('.composer-minimize, .minimize .trigger').on('click', (e) => { +// e.preventDefault(); +// e.stopPropagation(); +// composer.minimize(post_uuid); +// }); + +// const textareaEl = postContainer.find('textarea'); +// textareaEl.on('input propertychange', utils.debounce(() => { +// preview.render(postContainer); +// }, 250)); + +// textareaEl.on('scroll', () => { +// preview.matchScroll(postContainer); +// }); + +// drafts.init(postContainer, postData); +// const draft = drafts.get(postData.save_id); + +// preview.render(postContainer, () => { +// preview.matchScroll(postContainer); +// }); + +// handleHelp(postContainer); +// handleSearch(postContainer); +// focusElements(postContainer); +// if (postData.action === 'posts.edit') { +// composer.updateThumbCount(post_uuid, postContainer); +// } + +// //Hide "zen mode" if fullscreen API is not enabled/available (ahem, iOS...) +// if (!screenfull.isEnabled) { +// $('[data-format="zen"]').parent().addClass('hidden'); +// } + +// hooks.fire('action:composer.enhanced', { postContainer, postData, draft }); +// }; + +// async function getSelectedCategory(postData) { +// if (ajaxify.data.template.category && parseInt(postData.cid, 10) === parseInt(ajaxify.data.cid, 10)) { +// //no need to load data if we are already on the category page +// return ajaxify.data; +// } else if (parseInt(postData.cid, 10)) { +// return await api.get(`/api/category/${postData.cid}`, {}); +// } +// return null; +// } + +// async function createNewComposer(post_uuid) { +// let postData = composer.posts[post_uuid]; + +// const isTopic = postData ? postData.hasOwnProperty('cid') : false; +// const isMain = postData ? !!postData.isMain : false; +// const isEditing = postData ? !!postData.pid : false; +// const isGuestPost = postData ? parseInt(postData.uid, 10) === 0 : false; +// const isScheduled = postData.timestamp > Date.now(); + +// //see +// //https://github.com/NodeBB/NodeBB/issues/2994 and +// //https://github.com/NodeBB/NodeBB/issues/1951 +// //remove when 1951 is resolved + +// const title = postData.title.replace(/%/g, '%').replace(/,/g, ','); +// postData.category = await getSelectedCategory(postData); +// const privileges = postData.category ? postData.category.privileges : ajaxify.data.privileges; +// let data = { +// topicTitle: title, +// titleLength: title.length, +// body: translator.escape(utils.escapeHTML(postData.body)), +// mobile: composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm', +// resizable: true, +// thumb: postData.thumb, +// isTopicOrMain: isTopic || isMain, +// maximumTitleLength: config.maximumTitleLength, +// maximumPostLength: config.maximumPostLength, +// minimumTagLength: config.minimumTagLength, +// maximumTagLength: config.maximumTagLength, +// 'composer:showHelpTab': config['composer:showHelpTab'], +// isTopic: isTopic, +// isEditing: isEditing, +// canSchedule: !!(isMain && privileges && +// ((privileges['topics:schedule'] && !isEditing) || (isScheduled && privileges.view_scheduled))), +// showHandleInput: config.allowGuestHandles && +// (app.user.uid === 0 || (isEditing && isGuestPost && app.user.isAdmin)), +// handle: postData ? postData.handle || '' : undefined, +// formatting: composer.formatting, +// tagWhitelist: postData.category ? postData.category.tagWhitelist : ajaxify.data.tagWhitelist, +// privileges: app.user.privileges, +// selectedCategory: postData.category, +// submitOptions: [ +// //Add items using `filter:composer.create`, or just add them to the
            in DOM +// //{ +// // action: 'foobar', +// // text: 'Text Label', +// //} +// ], +// }; + +// if (data.mobile) { +// mobileHistoryAppend(); + +// app.toggleNavbar(false); +// } + +// postData.mobile = composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm'; + +// ({ postData, createData: data } = await hooks.fire('filter:composer.create', { +// postData: postData, +// createData: data, +// })); + +// app.parseAndTranslate('composer', data, (composerTemplate) => { +// if ($(`.composer.composer[data-uuid="${post_uuid}"]`).length) { +// return; +// } +// composerTemplate = $(composerTemplate); + +// composerTemplate.find('.title').each(function () { +// $(this).text(translator.unescape($(this).text())); +// }); + +// composerTemplate.attr('data-uuid', post_uuid); + +// $(document.body).append(composerTemplate); + +// const postContainer = $(composerTemplate[0]); + +// resize.reposition(postContainer); +// composer.enhance(postContainer, post_uuid, postData); +// /* +// Everything after this line is applied to the resizable composer only +// Want something done to both resizable composer and the one in /compose? +// Put it in composer.enhance(). + +// Eventually, stuff after this line should be moved into composer.enhance(). +// */ + +// activate(post_uuid); + +// postContainer.on('click', () => { +// if (!taskbar.isActive(post_uuid)) { +// taskbar.updateActive(post_uuid); +// } +// }); + +// resize.handleResize(postContainer); + +// if (composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { +// const submitBtns = postContainer.find('.composer-submit'); +// const mobileSubmitBtn = postContainer.find('.mobile-navbar .composer-submit'); +// const textareaEl = postContainer.find('.write'); +// const idx = textareaEl.attr('tabindex'); + +// submitBtns.removeAttr('tabindex'); +// mobileSubmitBtn.attr('tabindex', parseInt(idx, 10) + 1); +// } + +// $(window).trigger('action:composer.loaded', { +// postContainer: postContainer, +// post_uuid: post_uuid, +// composerData: composer.posts[post_uuid], +// formatting: composer.formatting, +// }); + +// scrollStop.apply(postContainer.find('.write')); +// focusElements(postContainer); +// onShow(); +// }); +// } + +// function mobileHistoryAppend() { +// const path = `compose?p=${window.location.pathname}`; +// let returnPath = window.location.pathname.slice(1) + window.location.search; + +// //Remove relative path from returnPath +// if (returnPath.startsWith(config.relative_path.slice(1))) { +// returnPath = returnPath.slice(config.relative_path.length); +// } + +// //Add in return path to be caught by ajaxify when post is completed, or if back is pressed +// window.history.replaceState({ +// url: null, +// returnPath: returnPath, +// }, returnPath, `${config.relative_path}/${returnPath}`); + +// //Update address bar in case f5 is pressed +// window.history.pushState({ +// url: path, +// }, path, `${config.relative_path}/${returnPath}`); +// } + +// function handleHelp(postContainer) { +// const helpBtn = postContainer.find('[data-action="help"]'); +// helpBtn.on('click', async () => { +// const html = await socket.emit('plugins.composer.renderHelp'); +// if (html && html.length > 0) { +// bootbox.dialog({ +// size: 'large', +// message: html, +// onEscape: true, +// backdrop: true, +// onHidden: function () { +// helpBtn.focus(); +// }, +// }); +// } +// }); +// } + +// function handleSearch(postContainer) { +// const uuid = postContainer.attr('data-uuid'); +// const isEditing = composer.posts[uuid] && composer.posts[uuid].action === 'posts.edit'; +// const env = utils.findBootstrapEnvironment(); +// const isMobile = env === 'xs' || env === 'sm'; +// if (isEditing || isMobile) { +// return; +// } + +// search.enableQuickSearch({ +// searchElements: { +// inputEl: postContainer.find('input.title'), +// resultEl: postContainer.find('.quick-search-container'), +// }, +// searchOptions: { +// composer: 1, +// }, +// hideOnNoMatches: true, +// hideDuringSearch: true, +// }); +// } + +// function activate(post_uuid) { +// if (composer.active && composer.active !== post_uuid) { +// composer.minimize(composer.active); +// } + +// composer.active = post_uuid; +// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); +// postContainer.css('visibility', 'visible'); +// $(window).trigger('action:composer.activate', { +// post_uuid: post_uuid, +// postContainer: postContainer, +// }); +// } + +// function focusElements(postContainer) { +// setTimeout(() => { +// const title = postContainer.find('input.title'); + +// if (title.length) { +// title.focus(); +// } else { +// postContainer.find('textarea').focus().putCursorAtEnd(); +// } +// }, 20); +// } + +// async function post(post_uuid) { +// const postData = composer.posts[post_uuid]; +// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); +// const handleEl = postContainer.find('.handle'); +// const titleEl = postContainer.find('.title'); +// const bodyEl = postContainer.find('textarea'); +// const thumbEl = postContainer.find('input#topic-thumb-url'); +// const onComposeRoute = postData.hasOwnProperty('template') && postData.template.compose === true; +// const submitBtn = postContainer.find('.composer-submit'); + +// titleEl.val(titleEl.val().trim()); +// bodyEl.val(utils.rtrim(bodyEl.val())); +// if (thumbEl.length) { +// thumbEl.val(thumbEl.val().trim()); +// } + +// const { action } = postData; + +// const checkTitle = (postData.hasOwnProperty('cid') || parseInt(postData.pid, 10)) +// && postContainer.find('input.title').length; +// const isCategorySelected = !checkTitle || (checkTitle && parseInt(postData.cid, 10)); + +// //Specifically for checking title/body length via plugins +// const payload = { +// post_uuid: post_uuid, +// postData: postData, +// postContainer: postContainer, +// titleEl: titleEl, +// titleLen: titleEl.val().length, +// bodyEl: bodyEl, +// bodyLen: bodyEl.val().length, +// }; + +// await hooks.fire('filter:composer.check', payload); +// $(window).trigger('action:composer.check', payload); + +// if (payload.error) { +// return composerAlert(post_uuid, payload.error); +// } + +// if (uploads.inProgress[post_uuid] && uploads.inProgress[post_uuid].length) { +// return composerAlert(post_uuid, '[[error:still-uploading]]'); +// } else if (checkTitle && payload.titleLen < parseInt(config.minimumTitleLength, 10)) { +// return composerAlert(post_uuid, `[[error:title-too-short, ${config.minimumTitleLength}]]`); +// } else if (checkTitle && payload.titleLen > parseInt(config.maximumTitleLength, 10)) { +// return composerAlert(post_uuid, `[[error:title-too-long, ${config.maximumTitleLength}]]`); +// } else if (action === 'topics.post' && !isCategorySelected) { +// return composerAlert(post_uuid, '[[error:category-not-selected]]'); +// } else if (payload.bodyLen < parseInt(config.minimumPostLength, 10)) { +// return composerAlert(post_uuid, `[[error:content-too-short, ${config.minimumPostLength}]]`); +// } else if (payload.bodyLen > parseInt(config.maximumPostLength, 10)) { +// return composerAlert(post_uuid, `[[error:content-too-long, ${config.maximumPostLength}]]`); +// } else if (checkTitle && !tags.isEnoughTags(post_uuid)) { +// return composerAlert(post_uuid, `[[error:not-enough-tags, ${tags.minTagCount()}]]`); +// } else if (scheduler.isActive() && scheduler.getTimestamp() <= Date.now()) { +// return composerAlert(post_uuid, '[[error:scheduling-to-past]]'); +// } + +// let composerData = { +// uuid: post_uuid, +// }; +// let method = 'post'; +// let route = ''; + +// if (action === 'topics.post') { +// route = '/topics'; +// composerData = { +// ...composerData, +// handle: handleEl ? handleEl.val() : undefined, +// title: titleEl.val(), +// content: bodyEl.val(), +// thumb: thumbEl.val() || '', +// cid: categoryList.getSelectedCid(), +// tags: tags.getTags(post_uuid), +// timestamp: scheduler.getTimestamp(), +// }; +// } else if (action === 'posts.reply') { +// route = `/topics/${postData.tid}`; +// composerData = { +// ...composerData, +// tid: postData.tid, +// handle: handleEl ? handleEl.val() : undefined, +// content: bodyEl.val(), +// toPid: postData.toPid, +// }; +// } else if (action === 'posts.edit') { +// method = 'put'; +// route = `/posts/${postData.pid}`; +// composerData = { +// ...composerData, +// pid: postData.pid, +// handle: handleEl ? handleEl.val() : undefined, +// content: bodyEl.val(), +// title: titleEl.val(), +// thumb: thumbEl.val() || '', +// tags: tags.getTags(post_uuid), +// timestamp: scheduler.getTimestamp(), +// }; +// } +// const submitHookData = { +// composerEl: postContainer, +// action: action, +// composerData: composerData, +// postData: postData, +// redirect: true, +// }; + +// await hooks.fire('filter:composer.submit', submitHookData); +// hooks.fire('action:composer.submit', Object.freeze(submitHookData)); + +// //Minimize composer (and set textarea as readonly) while submitting +// const taskbarIconEl = $(`#taskbar .composer[data-uuid="${post_uuid}"] i`); +// const textareaEl = postContainer.find('.write'); +// taskbarIconEl.removeClass('fa-plus').addClass('fa-circle-o-notch fa-spin'); +// composer.minimize(post_uuid); +// textareaEl.prop('readonly', true); + +// api[method](route, composerData) +// .then((data) => { +// submitBtn.removeAttr('disabled'); +// postData.submitted = true; + +// composer.discard(post_uuid); +// drafts.removeDraft(postData.save_id); + +// if (data.queued) { +// alerts.alert({ +// type: 'success', +// title: '[[global:alert.success]]', +// message: data.message, +// timeout: 10000, +// clickfn: function () { +// ajaxify.go(`/post-queue/${data.id}`); +// }, +// }); +// } else if (action === 'topics.post') { +// if (submitHookData.redirect) { +// ajaxify.go(`topic/${data.slug}`, undefined, (onComposeRoute || composer.bsEnvironment +// === 'xs' || composer.bsEnvironment === 'sm')); +// } +// } else if (action === 'posts.reply') { +// if (onComposeRoute || composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') { +// window.history.back(); +// } else if (submitHookData.redirect && +// ((ajaxify.data.template.name !== 'topic') || +// (ajaxify.data.template.topic && parseInt(postData.tid, 10) !== parseInt(ajaxify.data.tid, 10))) +// ) { +// ajaxify.go(`post/${data.pid}`); +// } +// } else { +// removeComposerHistory(); +// } + +// hooks.fire(`action:composer.${action}`, { composerData: composerData, data: data }); +// }) +// .catch((err) => { +// //Restore composer on error +// composer.load(post_uuid); +// textareaEl.prop('readonly', false); +// if (err.message === '[[error:email-not-confirmed]]') { +// return messagesModule.showEmailConfirmWarning(err.message); +// } +// composerAlert(post_uuid, err.message); +// }); +// } + +// function onShow() { +// $('html').addClass('composing'); +// } + +// function onHide() { +// $('#content').css({ paddingBottom: 0 }); +// $('html').removeClass('composing'); +// app.toggleNavbar(true); +// formatting.exitFullscreen(); +// } + +// composer.discard = function (post_uuid) { +// if (composer.posts[post_uuid]) { +// const postData = composer.posts[post_uuid]; +// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); +// postContainer.remove(); +// drafts.removeDraft(postData.save_id); +// topicThumbs.deleteAll(post_uuid); + +// taskbar.discard('composer', post_uuid); +// $('[data-action="post"]').removeAttr('disabled'); + +// hooks.fire('action:composer.discard', { +// post_uuid: post_uuid, +// postData: postData, +// }); +// delete composer.posts[post_uuid]; +// composer.active = undefined; +// } +// scheduler.reset(); +// onHide(); +// }; + +// //Alias to .discard(); +// composer.close = composer.discard; + +// composer.minimize = function (post_uuid) { +// const postContainer = $(`.composer[data-uuid="${post_uuid}"]`); +// postContainer.css('visibility', 'hidden'); +// composer.active = undefined; +// taskbar.minimize('composer', post_uuid); +// $(window).trigger('action:composer.minimize', { +// post_uuid: post_uuid, +// }); + +// onHide(); +// }; + +// composer.minimizeActive = function () { +// if (composer.active) { +// composer.miminize(composer.active); +// } +// }; + +// composer.updateThumbCount = function (uuid, postContainer) { +// const composerObj = composer.posts[uuid]; +// if (composerObj.action === 'topics.post' || (composerObj.action === 'posts.edit' && composerObj.isMain)) { +// const calls = [ +// topicThumbs.get(uuid), +// ]; +// if (composerObj.pid) { +// calls.push(topicThumbs.getByPid(composerObj.pid)); +// } +// Promise.all(calls).then((thumbs) => { +// const thumbCount = thumbs.flat().length; +// const formatEl = postContainer.find('[data-format="thumbs"]'); +// formatEl.find('.badge') +// .text(thumbCount) +// .toggleClass('hidden', !thumbCount); +// }); +// } +// }; + +// return composer; +// }); From eb2b9f11e6ca16980842a106d2add471dc2a6b83 Mon Sep 17 00:00:00 2001 From: Ghani Raissov <111187247+graissov@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:38:05 +0300 Subject: [PATCH 42/50] Revert "Revert "modified lint errors with comments out and added the last code to make the code full"" --- node_modules_real/composer.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/node_modules_real/composer.js b/node_modules_real/composer.js index dbebeeb5b9..298077dfc0 100644 --- a/node_modules_real/composer.js +++ b/node_modules_real/composer.js @@ -198,6 +198,24 @@ // formatting.addButton(iconClass, onClick, title); // }; +// Adding event listener for MP3 upload button +// composer.addButton('fa-file-audio-o', function () { +// document.getElementById('mp3-files').click(); // Trigger the hidden MP3 file input +// }, 'Upload MP3'); + +// // Handle MP3 file selection and trigger the upload process +// document.getElementById('mp3-files').addEventListener('change', function (e) { +// const files = e.target.files; +// if (files && files.length) { +// // Call the general file upload function from the uploads module +// uploads.uploadContentFiles({ +// files: files, // The selected MP3 files +// post_uuid: composer.active, // The active post (to track the composer instance) +// route: '/api/post/upload' // The route for file upload +// }); +// } +// }); + // composer.newTopic = async (data) => { // let pushData = { // save_id: data.save_id, From 55eaddefdded5538d5aeb8a3873144ed1f31d92f Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 21:05:37 +0300 Subject: [PATCH 43/50] Enhanced message template to display emoji reactions and added emoji menu for users to react to messages --- src/views/partials/chats/message.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/partials/chats/message.tpl b/src/views/partials/chats/message.tpl index 12bf2414b1..fb9cbfdf97 100644 --- a/src/views/partials/chats/message.tpl +++ b/src/views/partials/chats/message.tpl @@ -1,5 +1,5 @@ -{{{ if messages.reactionTo }}} +{{{ if messages.reactionTo }}}
          • From d3eac229dfccf1cefb5b9fdc933bdc60ac5a4a46 Mon Sep 17 00:00:00 2001 From: Dachi Date: Thu, 26 Sep 2024 11:14:00 -0700 Subject: [PATCH 44/50] fixed lint error: --- public/src/client/chats.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/src/client/chats.js b/public/src/client/chats.js index a34f153172..70a44740c0 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -1,5 +1,7 @@ 'use strict'; + // HI + define('forum/chats', [ 'components', 'mousetrap', From fa264cb08099635092504eddc3c57c2b84d956e9 Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 21:52:23 +0300 Subject: [PATCH 45/50] added css files to modify the emojis on frontend! --- public/scss/chats.scss | 279 ++++++++++++++++++++++++++++++----------- 1 file changed, 209 insertions(+), 70 deletions(-) diff --git a/public/scss/chats.scss b/public/scss/chats.scss index e9bd116a90..4a4f81bef6 100644 --- a/public/scss/chats.scss +++ b/public/scss/chats.scss @@ -4,124 +4,263 @@ width: 32px; height: 32px; span:first-child { - top: 0; - left: 8px; + top: 0; + left: 8px; } span:last-child { - left: 0; - top: 8px; + left: 0; + top: 8px; } -} + } -body.page-user-chats { + body.page-user-chats { #content { - max-width: 100%; - margin-bottom: 0!important; + max-width: 100%; + margin-bottom: 0 !important; } overflow: hidden; [data-widget-area="footer"] { - display: none; + display: none; } height: 100%; -} + } -[component="chat/recent"] { + [component="chat/recent"] { .active .chat-room-btn { - background-color: var(--btn-ghost-hover-color); + background-color: var(--btn-ghost-hover-color); } -} + } -[component="chat/nav-wrapper"] { + [component="chat/nav-wrapper"] { width: 300px; [component="chat/public/room"].unread { - font-weight: $font-weight-bold; + font-weight: $font-weight-bold; } -} + } -[component="chat/user/list"] [data-uid] { + [component="chat/user/list"] [data-uid] { [component="chat/user/list/username"] { - color: $text-muted; + color: $text-muted; } &.online { - [component="chat/user/list/username"] { - color: initial; - font-weight: $font-weight-semibold; - } + [component="chat/user/list/username"] { + color: initial; + font-weight: $font-weight-semibold; + } } -} + } -.expanded-chat { + .expanded-chat { .chat-content { - .message-body { - @include fix-lists; - } + .message-body { + @include fix-lists; + } + + .chat-message { + .message-body-wrapper { + .controls { + opacity: 0; + transition: $transition-fade; + &:has([aria-expanded="true"]) { + opacity: 1; + } + [data-action="restore"], + [data-action="unpin"] { + display: none; + } + } + &:hover { + .controls { + opacity: 1; + } + } + + /* Added styles for reactions */ + .chat-message-reactions { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-top: 5px; + } - .chat-message { - .message-body-wrapper { - .controls { - opacity: 0; - transition: $transition-fade; - &:has([aria-expanded="true"]) { opacity: 1; } - [data-action="restore"], [data-action="unpin"] { display: none; } - } - &:hover { - .controls { opacity: 1; } - } + .chat-message-reactions .reaction { + background-color: #e9ecef; + border-radius: 12px; + padding: 2px 8px; + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + gap: 4px; + } + + .chat-message-reactions .reaction:hover { + background-color: #ced4da; + } + + .chat-message-reactions .emoji { + margin-right: 4px; + } + + .chat-message-reactions .count { + font-weight: bold; + } + } + &.deleted { + .message-body { + opacity: 0.3; + } + .message-body-wrapper .controls { + [data-action] { + display: none; } - &.deleted { - .message-body { opacity: 0.3; } - .message-body-wrapper .controls { - [data-action] { display: none; } - [data-action="restore"] { display: block; } - } + [data-action="restore"] { + display: block; } - &.pinned { - .message-body-wrapper .controls { - [data-action="pin"] { display: none; } - [data-action="unpin"] { display: block;} - } + } + } + &.pinned { + .message-body-wrapper .controls { + [data-action="pin"] { + display: none; + } + [data-action="unpin"] { + display: block; } + } } + } } -} + } + + /* Styles for the emoji menu */ + .react-wrapper { + position: relative; + display: inline-block; + } -/* Mobile handling of chat page */ -@include media-breakpoint-down(lg) { - .page-user-chats.chat-loaded { - padding-bottom: 4.75rem; + .react-wrapper .emoji-menu { + display: none; /* Hidden by default */ + position: absolute; + bottom: 100%; /* Position above the react button */ + right: 0; + z-index: 1000; + background-color: #fff; + border: 1px solid #ccc; + border-radius: 4px; + padding: 5px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + } + + .react-wrapper .emoji-menu .emoji-option { + font-size: 20px; + cursor: pointer; + margin: 2px; + } + + .react-wrapper .emoji-menu .emoji-option:hover { + background-color: #f0f0f0; + border-radius: 4px; + } + + /* Mobile handling of chat page */ + @include media-breakpoint-down(lg) { + .page-user-chats.chat-loaded { + padding-bottom: 4.75rem; } -} + } -@include media-breakpoint-down(md) { - .page-user-chats.chat-loaded { - padding-bottom: initial; + @include media-breakpoint-down(md) { + .page-user-chats.chat-loaded { + padding-bottom: initial; } [component="chat/nav-wrapper"] { - width: 100%; + width: 100%; } - .page-user-chats.chat-loaded .bottombar { - display: none!important; + display: none !important; } [component="chat/nav-wrapper"][data-loaded="1"] { - display: none!important; + display: none !important; } [component="chat/nav-wrapper"][data-loaded="0"] + [component="chat/main-wrapper"] { - display: none!important; + display: none !important; } -} + } -.chat-modal { + .chat-modal { left: auto; top: auto; bottom: 0px; right: 2rem; - width: auto!important; - height: auto!important; - [component="chat/user/list/btn"], [component="chat/pinned/messages/btn"] { - display: none!important; + width: auto !important; + height: auto !important; + [component="chat/user/list/btn"], + [component="chat/pinned/messages/btn"] { + display: none !important; } -} + } + // Add the following styles to chats.scss + +.chat-message-reactions { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-top: 5px; + } + + .chat-message-reactions .reaction { + background-color: #e9ecef; + border-radius: 12px; + padding: 2px 8px; + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + gap: 4px; + } + + .chat-message-reactions .reaction:hover { + background-color: #ced4da; + } + + .chat-message-reactions .reaction.user-reacted { + background-color: #d1e7dd; + } + + .chat-message-reactions .emoji { + margin-right: 4px; + } + + .message-body-wrapper { + position: relative; + } + + .message-reactions-count { + position: absolute; + right: 0; + bottom: 0; + margin-right: 0.5rem; + margin-bottom: 0.25rem; + } + + .reactions-bubble { + background-color: #e9ecef; + border-radius: 50%; + padding: 4px 8px; + display: flex; + align-items: center; + } + + .reaction-count { + font-weight: bold; + } + .reaction-message { + background-color: #f1f1f1; + font-style: italic; + padding: 5px; + border-radius: 5px; + } \ No newline at end of file From 06ec39a6e7c3ceca7ae342ebc6385af6d7cbb96c Mon Sep 17 00:00:00 2001 From: Seckhen Date: Thu, 26 Sep 2024 22:09:37 +0300 Subject: [PATCH 46/50] the file topics_list.tpl had wrong code for some reason after others submitted to it already, so I am adding that code plus my addition to it --- node_modules_real/topics_list.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node_modules_real/topics_list.tpl b/node_modules_real/topics_list.tpl index 52573bb934..2ccae877e6 100644 --- a/node_modules_real/topics_list.tpl +++ b/node_modules_real/topics_list.tpl @@ -284,7 +284,7 @@ function fetchTopicTitle(tid, callback) { } }); } -### + }); From 11b7c6700595a9844e0b8092279d606e825f8689 Mon Sep 17 00:00:00 2001 From: Dachi Date: Sun, 20 Oct 2024 12:00:18 -0700 Subject: [PATCH 47/50] adding the initial file for the topics-menu-list.tpl --- node_modules_real/post-menu-list.tpl | 132 +++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 node_modules_real/post-menu-list.tpl diff --git a/node_modules_real/post-menu-list.tpl b/node_modules_real/post-menu-list.tpl new file mode 100644 index 0000000000..5e53e7df7a --- /dev/null +++ b/node_modules_real/post-menu-list.tpl @@ -0,0 +1,132 @@ +{{{ if posts.display_moderator_tools }}} +
          • + + [[topic:edit]] + +
          • +
          • + + [[topic:delete]] + +
          • +
          • + + [[topic:restore]] + +
          • +{{{ if posts.display_purge_tools }}} +
          • + + [[topic:purge]] + +
          • +{{{ end }}} + +{{{ if posts.display_move_tools }}} +
          • + + [[topic:move]] + +
          • +{{{ end }}} + +{{{ if posts.display_change_owner_tools }}} +
          • + + [[topic:change-owner]] + +
          • +{{{ end }}} + +{{{ if posts.ip }}} +
          • + + [[topic:copy-ip]] {posts.ip} + +
          • +{{{ if posts.display_ip_ban }}} +
          • + + [[topic:ban-ip]] {posts.ip} + +
          • +{{{ end }}} +{{{ end }}} +{{{ end }}} + +{{{ each posts.tools }}} +
          • + + {{./html}} + +
          • +{{{ end }}} + +{{{ if !posts.deleted }}} + {{{ if posts.display_history}}} +
          • + + [[topic:view-history]] + +
          • + {{{ end }}} + + {{{ if config.loggedIn }}} +
          • + + + + + + [[topic:bookmark]] + {posts.bookmarks}  + +
          • + {{{ end }}} + +
          • + + [[topic:copy-permalink]] + +
          • + + {{{ if postSharing.length }}} + {{{ if config.loggedIn }}}{{{ end }}} + + {{{ end }}} +
          • + {{{ each postSharing }}} + + {{{ end }}} +
          • +{{{ end }}} + +{{{ if posts.display_flag_tools }}} + + +
          • + [[topic:flag-post]] +
          • +
          • + [[topic:already-flagged]] +
          • + +{{{ if (!posts.selfPost && posts.uid) }}} +
          • + [[topic:flag-user]] +
          • +{{{ end }}} +{{{ end }}} + +{{{ if posts.display_moderator_tools }}} +{{{ if posts.flags.exists }}} +
          • + [[topic:view-flag-report]] +
          • +{{{ if (posts.flags.state == "open") }}} +
          • + [[topic:resolve-flag]] +
          • +{{{ end }}} +{{{ end }}} +{{{ end }}} \ No newline at end of file From 2f3337fb0cd4522162741b4323d9f8c4ee13729e Mon Sep 17 00:00:00 2001 From: Dachi Date: Sun, 20 Oct 2024 12:40:18 -0700 Subject: [PATCH 48/50] finished the functionality of adding notes to posts fully --- node_modules_real/post-menu-list.tpl | 173 ++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 1 deletion(-) diff --git a/node_modules_real/post-menu-list.tpl b/node_modules_real/post-menu-list.tpl index 5e53e7df7a..20fde75b0e 100644 --- a/node_modules_real/post-menu-list.tpl +++ b/node_modules_real/post-menu-list.tpl @@ -127,6 +127,177 @@
          • [[topic:resolve-flag]]
          • + +{{{ end }}} {{{ end }}} {{{ end }}} -{{{ end }}} \ No newline at end of file +
          • + + [[topic:manage-notes]] + +
          • + + + + + + + + From 667ec02ad784e627fa3aa5a48625fcda7b232689 Mon Sep 17 00:00:00 2001 From: Dachi Date: Sun, 20 Oct 2024 12:54:36 -0700 Subject: [PATCH 49/50] just adding the starting version of the post-menu-list.tpl file from node_modules --- node_modules_real/post-menu-list.tpl | 132 +++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 node_modules_real/post-menu-list.tpl diff --git a/node_modules_real/post-menu-list.tpl b/node_modules_real/post-menu-list.tpl new file mode 100644 index 0000000000..5e53e7df7a --- /dev/null +++ b/node_modules_real/post-menu-list.tpl @@ -0,0 +1,132 @@ +{{{ if posts.display_moderator_tools }}} +
          • + + [[topic:edit]] + +
          • +
          • + + [[topic:delete]] + +
          • +
          • + + [[topic:restore]] + +
          • +{{{ if posts.display_purge_tools }}} +
          • + + [[topic:purge]] + +
          • +{{{ end }}} + +{{{ if posts.display_move_tools }}} +
          • + + [[topic:move]] + +
          • +{{{ end }}} + +{{{ if posts.display_change_owner_tools }}} +
          • + + [[topic:change-owner]] + +
          • +{{{ end }}} + +{{{ if posts.ip }}} +
          • + + [[topic:copy-ip]] {posts.ip} + +
          • +{{{ if posts.display_ip_ban }}} +
          • + + [[topic:ban-ip]] {posts.ip} + +
          • +{{{ end }}} +{{{ end }}} +{{{ end }}} + +{{{ each posts.tools }}} +
          • + + {{./html}} + +
          • +{{{ end }}} + +{{{ if !posts.deleted }}} + {{{ if posts.display_history}}} +
          • + + [[topic:view-history]] + +
          • + {{{ end }}} + + {{{ if config.loggedIn }}} +
          • + + + + + + [[topic:bookmark]] + {posts.bookmarks}  + +
          • + {{{ end }}} + +
          • + + [[topic:copy-permalink]] + +
          • + + {{{ if postSharing.length }}} + {{{ if config.loggedIn }}}{{{ end }}} + + {{{ end }}} +
          • + {{{ each postSharing }}} + + {{{ end }}} +
          • +{{{ end }}} + +{{{ if posts.display_flag_tools }}} + + +
          • + [[topic:flag-post]] +
          • +
          • + [[topic:already-flagged]] +
          • + +{{{ if (!posts.selfPost && posts.uid) }}} +
          • + [[topic:flag-user]] +
          • +{{{ end }}} +{{{ end }}} + +{{{ if posts.display_moderator_tools }}} +{{{ if posts.flags.exists }}} +
          • + [[topic:view-flag-report]] +
          • +{{{ if (posts.flags.state == "open") }}} +
          • + [[topic:resolve-flag]] +
          • +{{{ end }}} +{{{ end }}} +{{{ end }}} \ No newline at end of file From 6adf80d6152cff66e53dbd49ebe23aeb35b907a1 Mon Sep 17 00:00:00 2001 From: DachiCharkviani <130381604+DachiCharkviani@users.noreply.github.com> Date: Sun, 20 Oct 2024 14:39:53 -0700 Subject: [PATCH 50/50] Update README.md Added explanations about how our automated tests work. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ef1ac825d7..9ccec45462 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ The collaborators on this project are Seckhen Andrade, Nikoloz Devidze, Ghani Ra Please know that there is a folder called node_module_real, which contains the files that are modified compared to the files that are in the node_modules we get by npm install. Please take the code from the files in the node_modules_real folder (files have the same names as in the node_module generated by npm install) and paste (replace the code) them in the respective files. +Please download the jest dependencies through npm install jest and npm install jest-environment-jsdom. After this also add "test:jest": "jest --config ./jest.config.js", in package.json's "scripts" section. After this, run npm run test:jest, and the tests about our added material will get executed! + ***************************************************************************************************