mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-07-21 10:31:42 +02:00
Merge remote-tracking branch 'origin/develop' into activitypub
This commit is contained in:
103
CHANGELOG.md
103
CHANGELOG.md
@@ -1,3 +1,106 @@
|
||||
#### v3.6.3 (2024-01-12)
|
||||
|
||||
##### Chores
|
||||
|
||||
* incrementing version number - v3.6.2 (0f577a57)
|
||||
* update changelog for v3.6.2 (82a936c3)
|
||||
* incrementing version number - v3.6.1 (f1a69468)
|
||||
* incrementing version number - v3.6.0 (4cdf85f8)
|
||||
* incrementing version number - v3.5.3 (ed0e8783)
|
||||
* incrementing version number - v3.5.2 (52fbb2da)
|
||||
* incrementing version number - v3.5.1 (4c543488)
|
||||
* incrementing version number - v3.5.0 (d06fb4f0)
|
||||
* incrementing version number - v3.4.3 (5c984250)
|
||||
* incrementing version number - v3.4.2 (3f0dac38)
|
||||
* incrementing version number - v3.4.1 (01e69574)
|
||||
* incrementing version number - v3.4.0 (fd9247c5)
|
||||
* incrementing version number - v3.3.9 (5805e770)
|
||||
* incrementing version number - v3.3.8 (a5603565)
|
||||
* incrementing version number - v3.3.7 (b26f1744)
|
||||
* incrementing version number - v3.3.6 (7fb38792)
|
||||
* incrementing version number - v3.3.4 (a67f84ea)
|
||||
* incrementing version number - v3.3.3 (f94d239b)
|
||||
* incrementing version number - v3.3.2 (ec9dac97)
|
||||
* incrementing version number - v3.3.1 (151cc68f)
|
||||
* incrementing version number - v3.3.0 (fc1ad70f)
|
||||
* incrementing version number - v3.2.3 (b06d3e63)
|
||||
* incrementing version number - v3.2.2 (758ecfcd)
|
||||
* incrementing version number - v3.2.1 (20145074)
|
||||
* incrementing version number - v3.2.0 (9ecac38e)
|
||||
* incrementing version number - v3.1.7 (0b4e81ab)
|
||||
* incrementing version number - v3.1.6 (b3a3b130)
|
||||
* incrementing version number - v3.1.5 (ec19343a)
|
||||
* incrementing version number - v3.1.4 (2452783c)
|
||||
* incrementing version number - v3.1.3 (3b4e9d3f)
|
||||
* incrementing version number - v3.1.2 (40fa3489)
|
||||
* incrementing version number - v3.1.1 (40250733)
|
||||
* incrementing version number - v3.1.0 (0cb386bd)
|
||||
* incrementing version number - v3.0.1 (26f6ea49)
|
||||
* incrementing version number - v3.0.0 (224e08cd)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* #12275, pin sharp to 0.32.6 (f3927ce7)
|
||||
* topic event translations closes #12273 (5f91cc83)
|
||||
|
||||
#### v3.6.2 (2024-01-10)
|
||||
|
||||
##### Chores
|
||||
|
||||
* up composer (ef8f8db7)
|
||||
* up harmony (2bed405c)
|
||||
* up harmony (b6dbe1a6)
|
||||
* up lavender (16f0affa)
|
||||
* up themes (980bfee8)
|
||||
* incrementing version number - v3.6.1 (f1a69468)
|
||||
* update changelog for v3.6.1 (1e4abdbf)
|
||||
* incrementing version number - v3.6.0 (4cdf85f8)
|
||||
* incrementing version number - v3.5.3 (ed0e8783)
|
||||
* incrementing version number - v3.5.2 (52fbb2da)
|
||||
* incrementing version number - v3.5.1 (4c543488)
|
||||
* incrementing version number - v3.5.0 (d06fb4f0)
|
||||
* incrementing version number - v3.4.3 (5c984250)
|
||||
* incrementing version number - v3.4.2 (3f0dac38)
|
||||
* incrementing version number - v3.4.1 (01e69574)
|
||||
* incrementing version number - v3.4.0 (fd9247c5)
|
||||
* incrementing version number - v3.3.9 (5805e770)
|
||||
* incrementing version number - v3.3.8 (a5603565)
|
||||
* incrementing version number - v3.3.7 (b26f1744)
|
||||
* incrementing version number - v3.3.6 (7fb38792)
|
||||
* incrementing version number - v3.3.4 (a67f84ea)
|
||||
* incrementing version number - v3.3.3 (f94d239b)
|
||||
* incrementing version number - v3.3.2 (ec9dac97)
|
||||
* incrementing version number - v3.3.1 (151cc68f)
|
||||
* incrementing version number - v3.3.0 (fc1ad70f)
|
||||
* incrementing version number - v3.2.3 (b06d3e63)
|
||||
* incrementing version number - v3.2.2 (758ecfcd)
|
||||
* incrementing version number - v3.2.1 (20145074)
|
||||
* incrementing version number - v3.2.0 (9ecac38e)
|
||||
* incrementing version number - v3.1.7 (0b4e81ab)
|
||||
* incrementing version number - v3.1.6 (b3a3b130)
|
||||
* incrementing version number - v3.1.5 (ec19343a)
|
||||
* incrementing version number - v3.1.4 (2452783c)
|
||||
* incrementing version number - v3.1.3 (3b4e9d3f)
|
||||
* incrementing version number - v3.1.2 (40fa3489)
|
||||
* incrementing version number - v3.1.1 (40250733)
|
||||
* incrementing version number - v3.1.0 (0cb386bd)
|
||||
* incrementing version number - v3.0.1 (26f6ea49)
|
||||
* incrementing version number - v3.0.0 (224e08cd)
|
||||
|
||||
##### New Features
|
||||
|
||||
* #12247 (0af19afd)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* recent loading (d3d6d77d)
|
||||
* closes #12246, direction is passed in as string as query param with new api call (8867f243)
|
||||
* stricter selector for sort (a74b5141)
|
||||
|
||||
##### Performance Improvements
|
||||
|
||||
* dont load all followed tids on unread/recent?filter=watched (563e03b6)
|
||||
|
||||
#### v3.6.1 (2023-12-22)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "3.6.1",
|
||||
"version": "3.6.3",
|
||||
"homepage": "https://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -94,7 +94,7 @@
|
||||
"multiparty": "4.2.3",
|
||||
"nconf": "0.12.1",
|
||||
"nodebb-plugin-2factor": "7.4.0",
|
||||
"nodebb-plugin-composer-default": "10.2.30",
|
||||
"nodebb-plugin-composer-default": "10.2.31",
|
||||
"nodebb-plugin-dbsearch": "6.2.3",
|
||||
"nodebb-plugin-emoji": "5.1.13",
|
||||
"nodebb-plugin-emoji-android": "4.0.0",
|
||||
@@ -103,10 +103,10 @@
|
||||
"nodebb-plugin-ntfy": "1.7.3",
|
||||
"nodebb-plugin-spam-be-gone": "2.2.0",
|
||||
"nodebb-rewards-essentials": "1.0.0",
|
||||
"nodebb-theme-harmony": "1.1.108",
|
||||
"nodebb-theme-harmony": "1.2.3",
|
||||
"nodebb-theme-lavender": "7.1.7",
|
||||
"nodebb-theme-peace": "2.1.26",
|
||||
"nodebb-theme-persona": "13.2.50",
|
||||
"nodebb-theme-peace": "2.2.0",
|
||||
"nodebb-theme-persona": "13.3.1",
|
||||
"nodebb-widget-essentials": "7.0.14",
|
||||
"nodemailer": "6.9.8",
|
||||
"nprogress": "0.2.0",
|
||||
@@ -127,10 +127,10 @@
|
||||
"sass": "1.69.7",
|
||||
"semver": "7.5.4",
|
||||
"serve-favicon": "2.5.0",
|
||||
"sharp": "0.33.1",
|
||||
"sharp": "0.32.6",
|
||||
"sitemap": "7.1.1",
|
||||
"socket.io": "4.7.3",
|
||||
"socket.io-client": "4.7.3",
|
||||
"socket.io": "4.7.4",
|
||||
"socket.io-client": "4.7.4",
|
||||
"@socket.io/redis-adapter": "8.2.1",
|
||||
"sortablejs": "1.15.1",
|
||||
"spdx-license-list": "6.8.0",
|
||||
@@ -163,7 +163,7 @@
|
||||
"grunt": "1.6.1",
|
||||
"grunt-contrib-watch": "1.1.0",
|
||||
"husky": "8.0.3",
|
||||
"jsdom": "23.0.1",
|
||||
"jsdom": "23.2.0",
|
||||
"lint-staged": "15.2.0",
|
||||
"mocha": "10.2.0",
|
||||
"mocha-lcov-reporter": "1.3.0",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "В заглавията",
|
||||
"in-titles-posts": "В заглавията и публикациите",
|
||||
"in-posts": "В публикациите",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "В категориите",
|
||||
"in-users": "В потребителите",
|
||||
"in-tags": "В етикетите",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In Titeln",
|
||||
"in-titles-posts": "In Titeln und Beiträgen",
|
||||
"in-posts": "In Beiträgen",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In Kategorien",
|
||||
"in-users": "In Benutzern",
|
||||
"in-tags": "In Tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "En títulos",
|
||||
"in-titles-posts": "En títulos y posts",
|
||||
"in-posts": "En posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "En categorías",
|
||||
"in-users": "En usuarios",
|
||||
"in-tags": "En etiquetas",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "در دستهبندیها",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "Dans les titres",
|
||||
"in-titles-posts": "Dans les titres et messages",
|
||||
"in-posts": "Dans les messages",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "Dans les catégories",
|
||||
"in-users": "Dans les utilisateurs",
|
||||
"in-tags": "Dans les mots-clés",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "התיקיה קיימת",
|
||||
"invalid-pagination-value": "ערך דף לא חוקי, חייב להיות לפחות %1 ולא מעל %2",
|
||||
"username-taken": "שם משתמש תפוס",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "כתובת האימייל כבר תפוסה.",
|
||||
"email-nochange": "כתובת דוא\"ל שהוזן זהה לדוא\"ל שנמצא כבר",
|
||||
"email-invited": "נשלחה כבר הזמנה לדוא\"ל זה",
|
||||
"email-not-confirmed": "פרסום בקטגוריות או בנושאים מסוימים מופעל רק לאחר אישור הדוא\"ל שלכם, אנא לחצו כאן כדי לשלוח אימות לדוא\"ל שלכם.",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "בכותרות הנושאים",
|
||||
"in-titles-posts": "בכותרות ובפוסטים",
|
||||
"in-posts": "בפוסטים",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "בקטגוריות",
|
||||
"in-users": "במשתמשים",
|
||||
"in-tags": "בתגיות",
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"upload-success": "הקובץ הועלה בהצלחה!",
|
||||
"maximum-file-size": "מקסימום %1 קילובייט",
|
||||
"no-uploads-found": "לא נמצאו העלאות!",
|
||||
"public-uploads-info": "העלאות הינם ציבוריות. כל הגולשים יוכלו לראותם.",
|
||||
"public-uploads-info": "העלאות הינם ציבוריות. כל מי שיש ברשותו לינק לקובץ יוכל לראות אותו.",
|
||||
"private-uploads-info": "העלאות הינם פרטיות. רק משתמשים מחוברים יוכלו לראותם."
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "Címekben",
|
||||
"in-titles-posts": "Címekben és bejegyzésekben",
|
||||
"in-posts": "Bejegyzésekben",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "Kategóriákban",
|
||||
"in-users": "Felhasználóknál",
|
||||
"in-tags": "Címkékben",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "Վերնագրերում",
|
||||
"in-titles-posts": "Վերնագրերում և գրառումներում",
|
||||
"in-posts": "Գրառումներում",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "Կատեգորիաներում",
|
||||
"in-users": "Օգտատերերում",
|
||||
"in-tags": "Պիտակներում",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "Nei titoli",
|
||||
"in-titles-posts": "Nei titoli e nei post",
|
||||
"in-posts": "Nei post",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "Nelle categorie",
|
||||
"in-users": "Negli utenti",
|
||||
"in-tags": "Nei tag",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categorieën",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "W tytułach",
|
||||
"in-titles-posts": "W tytułach i postach",
|
||||
"in-posts": "W postach",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "W kategoriach",
|
||||
"in-users": "W użytkownikach",
|
||||
"in-tags": "W tagach",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "В заголовках",
|
||||
"in-titles-posts": "В заголовках и постах",
|
||||
"in-posts": "В постах",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "В категориях",
|
||||
"in-users": "В пользователях",
|
||||
"in-tags": "В тегах",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "У насловима",
|
||||
"in-titles-posts": "У насловима и објавама",
|
||||
"in-posts": "У објавама",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "У категоријама",
|
||||
"in-users": "У корисницима",
|
||||
"in-tags": "У ознакама",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "Başlıklarda",
|
||||
"in-titles-posts": "Başlıklarda ve iletilerde",
|
||||
"in-posts": "İletilerde",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "Kategorilerde",
|
||||
"in-users": "Kullanıcılarda",
|
||||
"in-tags": "Etiketlerde",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "Trong tiêu đề",
|
||||
"in-titles-posts": "Trong tiêu đề và bài đăng",
|
||||
"in-posts": "Trong bài đăng",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "Trong danh mục",
|
||||
"in-users": "Trong người dùng",
|
||||
"in-tags": "Trong thẻ",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "在标题中",
|
||||
"in-titles-posts": "在标题和帖子中",
|
||||
"in-posts": "在帖子中",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "在板块中",
|
||||
"in-users": "在用户中",
|
||||
"in-tags": "在标签中",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"in-titles": "In titles",
|
||||
"in-titles-posts": "In titles and posts",
|
||||
"in-posts": "In posts",
|
||||
"in-bookmarks": "In bookmarks",
|
||||
"in-categories": "In categories",
|
||||
"in-users": "In users",
|
||||
"in-tags": "In tags",
|
||||
|
||||
@@ -51,18 +51,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
@include btn-ghost-base();
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
.btn-outline-sm {
|
||||
@include btn-ghost-base();
|
||||
border-color: $border-color;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
@include color-mode(dark) {
|
||||
.btn-light {
|
||||
@extend .btn-dark;
|
||||
@@ -70,7 +58,7 @@
|
||||
.text-bg-light {
|
||||
@extend .text-bg-dark;
|
||||
}
|
||||
.btn-ghost, .btn-ghost-sm, .btn-outline, .btn-outline-sm {
|
||||
.btn-ghost, .btn-ghost-sm {
|
||||
color: $btn-ghost-color-dark;
|
||||
&:hover, &.active {
|
||||
background-color: $btn-ghost-hover-color-dark;
|
||||
|
||||
51
public/scss/btn-ghost.scss
Normal file
51
public/scss/btn-ghost.scss
Normal file
@@ -0,0 +1,51 @@
|
||||
$btn-ghost-hover-color: mix($light, $dark, 90%)!default;
|
||||
$btn-ghost-active-color: lighten($btn-ghost-hover-color, 5%)!default;
|
||||
$btn-ghost-hover-color-dark: mix($dark, $light, 90%)!default;
|
||||
$btn-ghost-active-color-dark: lighten($btn-ghost-hover-color-dark, 5%)!default;
|
||||
|
||||
:root {
|
||||
--btn-ghost-hover-color: #{$btn-ghost-hover-color};
|
||||
--btn-ghost-active-color: #{$btn-ghost-active-color};
|
||||
}
|
||||
[data-bs-theme="dark"] {
|
||||
--btn-ghost-hover-color: #{$btn-ghost-hover-color-dark};
|
||||
--btn-ghost-active-color: #{$btn-ghost-active-color-dark};
|
||||
}
|
||||
|
||||
@mixin btn-ghost-base {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: ($spacer * 0.5);
|
||||
border-radius: $border-radius-sm;
|
||||
border-width: 1px;
|
||||
border-color: transparent;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
padding: ($spacer * 0.25) ($spacer * 0.5);
|
||||
text-align: left;
|
||||
--bs-text-opacity: 1;
|
||||
color: inherit !important;
|
||||
cursor: pointer;
|
||||
&:hover, &.active {
|
||||
background-color: var(--btn-ghost-hover-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
@include btn-ghost-base();
|
||||
line-height: 1.5rem;
|
||||
> i {
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-ghost-sm {
|
||||
@include btn-ghost-base();
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
> i {
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
}
|
||||
127
public/scss/chats.scss
Normal file
127
public/scss/chats.scss
Normal file
@@ -0,0 +1,127 @@
|
||||
// chats need a bit of css
|
||||
|
||||
.stacked-avatars {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
span:first-child {
|
||||
top: 0;
|
||||
left: 8px;
|
||||
}
|
||||
span:last-child {
|
||||
left: 0;
|
||||
top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
body.page-user-chats {
|
||||
#content {
|
||||
max-width: 100%;
|
||||
margin-bottom: 0!important;
|
||||
}
|
||||
overflow: hidden;
|
||||
[data-widget-area="footer"] {
|
||||
display: none;
|
||||
}
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[component="chat/recent"] {
|
||||
.active .chat-room-btn {
|
||||
background-color: var(--btn-ghost-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
[component="chat/nav-wrapper"] {
|
||||
width: 300px;
|
||||
[component="chat/public/room"].unread {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
|
||||
[component="chat/user/list"] [data-uid] {
|
||||
[component="chat/user/list/username"] {
|
||||
color: $text-muted;
|
||||
}
|
||||
&.online {
|
||||
[component="chat/user/list/username"] {
|
||||
color: initial;
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.expanded-chat {
|
||||
.chat-content {
|
||||
.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; }
|
||||
}
|
||||
}
|
||||
&.deleted {
|
||||
.message-body { opacity: 0.3; }
|
||||
.message-body-wrapper .controls {
|
||||
[data-action] { display: none; }
|
||||
[data-action="restore"] { display: block; }
|
||||
}
|
||||
}
|
||||
&.pinned {
|
||||
.message-body-wrapper .controls {
|
||||
[data-action="pin"] { display: none; }
|
||||
[data-action="unpin"] { display: block;}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
[component="chat/nav-wrapper"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.page-user-chats.chat-loaded .bottombar {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
[component="chat/nav-wrapper"][data-loaded="1"] {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
[component="chat/nav-wrapper"][data-loaded="0"] + [component="chat/main-wrapper"] {
|
||||
display: none!important;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
// core scss files shared by all themes
|
||||
@import "flags";
|
||||
@import "chats";
|
||||
@import "global";
|
||||
@import "modals";
|
||||
@import "btn-ghost";
|
||||
@import "modules/picture-switcher";
|
||||
@import "modules/bottom-sheet";
|
||||
@import "modules/icon-picker";
|
||||
|
||||
@@ -35,7 +35,7 @@ define('forum/chats/recent', ['alerts', 'api', 'chat'], function (alerts, api, c
|
||||
return;
|
||||
}
|
||||
recentChats.attr('loading', 1);
|
||||
app.get(`/chats`, {
|
||||
api.get(`/chats`, {
|
||||
uid: ajaxify.data.uid,
|
||||
after: recentChats.attr('data-nextstart'),
|
||||
}).then(({ rooms, nextStart }) => {
|
||||
|
||||
@@ -119,7 +119,7 @@ define('forum/search', [
|
||||
in: $('#search-in').val(),
|
||||
};
|
||||
searchData.term = $('#search-input').val();
|
||||
if (searchData.in === 'posts' || searchData.in === 'titlesposts' || searchData.in === 'titles') {
|
||||
if (['posts', 'titlesposts', 'titles', 'bookmarks'].includes(searchData.in)) {
|
||||
searchData.matchWords = form.find('#match-words-filter').val();
|
||||
searchData.by = selectedUsers.length ? selectedUsers.map(u => u.username) : undefined;
|
||||
searchData.categories = selectedCids.length ? selectedCids : undefined;
|
||||
@@ -143,7 +143,7 @@ define('forum/search', [
|
||||
}
|
||||
|
||||
function updateFormItemVisiblity(searchIn) {
|
||||
const hideTitlePostFilters = !searchIn.includes('posts') && !searchIn.includes('titles');
|
||||
const hideTitlePostFilters = !['posts', 'titles', 'bookmarks'].some(token => searchIn.includes(token));
|
||||
$('.post-search-item').toggleClass('hidden', hideTitlePostFilters);
|
||||
}
|
||||
|
||||
@@ -252,6 +252,7 @@ define('forum/search', [
|
||||
|
||||
function categoryFilterDropdown(_selectedCids) {
|
||||
ajaxify.data.allCategoriesUrl = '';
|
||||
selectedCids = _selectedCids || [];
|
||||
const dropdownEl = $('[component="category/filter"]');
|
||||
categoryFilter.init(dropdownEl, {
|
||||
selectedCids: _selectedCids,
|
||||
@@ -290,6 +291,7 @@ define('forum/search', [
|
||||
}
|
||||
|
||||
function userFilterDropdown(el, _selectedUsers) {
|
||||
selectedUsers = _selectedUsers || [];
|
||||
userFilter.init(el, {
|
||||
selectedUsers: _selectedUsers,
|
||||
template: 'partials/search-filters',
|
||||
|
||||
@@ -36,7 +36,7 @@ searchController.search = async function (req, res, next) {
|
||||
let allowed = (req.query.in === 'users' && userPrivileges['search:users']) ||
|
||||
(req.query.in === 'tags' && userPrivileges['search:tags']) ||
|
||||
(req.query.in === 'categories') ||
|
||||
(['titles', 'titlesposts', 'posts'].includes(req.query.in) && userPrivileges['search:content']);
|
||||
(['titles', 'titlesposts', 'posts', 'bookmarks'].includes(req.query.in) && userPrivileges['search:content']);
|
||||
({ allowed } = await plugins.hooks.fire('filter:search.isAllowed', {
|
||||
uid: req.uid,
|
||||
query: req.query,
|
||||
|
||||
@@ -261,4 +261,21 @@ module.exports = function (Posts) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Posts.filterPidsByUid = async function (pids, uids) {
|
||||
if (!uids) {
|
||||
return pids;
|
||||
}
|
||||
|
||||
if (!Array.isArray(uids) || uids.length === 1) {
|
||||
return await filterPidsBySingleUid(pids, uids);
|
||||
}
|
||||
const pidsArr = await Promise.all(uids.map(uid => Posts.filterPidsByUid(pids, uid)));
|
||||
return _.union(...pidsArr);
|
||||
};
|
||||
|
||||
async function filterPidsBySingleUid(pids, uid) {
|
||||
const isMembers = await db.isSortedSetMembers(`uid:${parseInt(uid, 10)}:posts`, pids);
|
||||
return pids.filter((pid, index) => pid && isMembers[index]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
const db = require('./database');
|
||||
const batch = require('./batch');
|
||||
const posts = require('./posts');
|
||||
const topics = require('./topics');
|
||||
const categories = require('./categories');
|
||||
@@ -18,7 +19,7 @@ search.search = async function (data) {
|
||||
data.sortBy = data.sortBy || 'relevance';
|
||||
|
||||
let result;
|
||||
if (data.searchIn === 'posts' || data.searchIn === 'titles' || data.searchIn === 'titlesposts') {
|
||||
if (['posts', 'titles', 'titlesposts', 'bookmarks'].includes(data.searchIn)) {
|
||||
result = await searchInContent(data);
|
||||
} else if (data.searchIn === 'users') {
|
||||
result = await user.search(data);
|
||||
@@ -68,6 +69,8 @@ async function searchInContent(data) {
|
||||
const tid = inTopic[1];
|
||||
const cleanedTerm = data.query.replace(inTopic[0], '');
|
||||
pids = await topics.search(tid, cleanedTerm);
|
||||
} else if (data.searchIn === 'bookmarks') {
|
||||
pids = await searchInBookmarks(data, searchCids, searchUids);
|
||||
} else {
|
||||
[pids, tids] = await Promise.all([
|
||||
doSearch('post', ['posts', 'titlesposts']),
|
||||
@@ -115,8 +118,46 @@ async function searchInContent(data) {
|
||||
return Object.assign(returnData, metadata);
|
||||
}
|
||||
|
||||
async function searchInBookmarks(data, searchCids, searchUids) {
|
||||
const { uid, query, matchWords } = data;
|
||||
const allPids = [];
|
||||
await batch.processSortedSet(`uid:${uid}:bookmarks`, async (pids) => {
|
||||
if (Array.isArray(searchCids) && searchCids.length) {
|
||||
pids = await posts.filterPidsByCid(pids, searchCids);
|
||||
}
|
||||
if (Array.isArray(searchUids) && searchUids.length) {
|
||||
pids = await posts.filterPidsByUid(pids, searchUids);
|
||||
}
|
||||
if (query) {
|
||||
const tokens = String(query).split(' ');
|
||||
const postData = await db.getObjectsFields(pids.map(pid => `post:${pid}`), ['content', 'tid']);
|
||||
const tids = _.uniq(postData.map(p => p.tid));
|
||||
const topicData = await db.getObjectsFields(tids.map(tid => `topic:${tid}`), ['title']);
|
||||
const tidToTopic = _.zipObject(tids, topicData);
|
||||
pids = pids.filter((pid, i) => {
|
||||
const content = String(postData[i].content);
|
||||
const title = String(tidToTopic[postData[i].tid].title);
|
||||
const method = (matchWords === 'any' ? 'some' : 'every');
|
||||
return tokens[method](
|
||||
token => content.includes(token) || title.includes(token)
|
||||
);
|
||||
});
|
||||
}
|
||||
allPids.push(...pids);
|
||||
}, {
|
||||
batch: 500,
|
||||
});
|
||||
|
||||
return allPids;
|
||||
}
|
||||
|
||||
async function filterAndSort(pids, data) {
|
||||
if (data.sortBy === 'relevance' && !data.replies && !data.timeRange && !data.hasTags && !plugins.hooks.hasListeners('filter:search.filterAndSort')) {
|
||||
if (data.sortBy === 'relevance' &&
|
||||
!data.replies &&
|
||||
!data.timeRange &&
|
||||
!data.hasTags &&
|
||||
data.searchIn !== 'bookmarks' &&
|
||||
!plugins.hooks.hasListeners('filter:search.filterAndSort')) {
|
||||
return pids;
|
||||
}
|
||||
let postsData = await getMatchedPosts(pids, data);
|
||||
|
||||
@@ -30,43 +30,43 @@ const Events = module.exports;
|
||||
Events._types = {
|
||||
pin: {
|
||||
icon: 'fa-thumb-tack',
|
||||
translation: async event => translateSimple(event, 'topic:user-pinned-topic'),
|
||||
translation: async (event, language) => translateSimple(event, language, 'topic:user-pinned-topic'),
|
||||
},
|
||||
unpin: {
|
||||
icon: 'fa-thumb-tack fa-rotate-90',
|
||||
translation: async event => translateSimple(event, 'topic:user-unpinned-topic'),
|
||||
translation: async (event, language) => translateSimple(event, language, 'topic:user-unpinned-topic'),
|
||||
},
|
||||
lock: {
|
||||
icon: 'fa-lock',
|
||||
translation: async event => translateSimple(event, 'topic:user-locked-topic'),
|
||||
translation: async (event, language) => translateSimple(event, language, 'topic:user-locked-topic'),
|
||||
},
|
||||
unlock: {
|
||||
icon: 'fa-unlock',
|
||||
translation: async event => translateSimple(event, 'topic:user-unlocked-topic'),
|
||||
translation: async (event, language) => translateSimple(event, language, 'topic:user-unlocked-topic'),
|
||||
},
|
||||
delete: {
|
||||
icon: 'fa-trash',
|
||||
translation: async event => translateSimple(event, 'topic:user-deleted-topic'),
|
||||
translation: async (event, language) => translateSimple(event, language, 'topic:user-deleted-topic'),
|
||||
},
|
||||
restore: {
|
||||
icon: 'fa-trash-o',
|
||||
translation: async event => translateSimple(event, 'topic:user-restored-topic'),
|
||||
translation: async (event, language) => translateSimple(event, language, 'topic:user-restored-topic'),
|
||||
},
|
||||
move: {
|
||||
icon: 'fa-arrow-circle-right',
|
||||
translation: async event => translateEventArgs(event, 'topic:user-moved-topic-from', renderUser(event), `${event.fromCategory.name}`, renderTimeago(event)),
|
||||
translation: async (event, language) => translateEventArgs(event, language, 'topic:user-moved-topic-from', renderUser(event), `${event.fromCategory.name}`, renderTimeago(event)),
|
||||
},
|
||||
'post-queue': {
|
||||
icon: 'fa-history',
|
||||
translation: async event => translateEventArgs(event, 'topic:user-queued-post', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
|
||||
translation: async (event, language) => translateEventArgs(event, language, 'topic:user-queued-post', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
|
||||
},
|
||||
backlink: {
|
||||
icon: 'fa-link',
|
||||
translation: async event => translateEventArgs(event, 'topic:user-referenced-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
|
||||
translation: async (event, language) => translateEventArgs(event, language, 'topic:user-referenced-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
|
||||
},
|
||||
fork: {
|
||||
icon: 'fa-code-fork',
|
||||
translation: async event => translateEventArgs(event, 'topic:user-forked-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
|
||||
translation: async (event, language) => translateEventArgs(event, language, 'topic:user-forked-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -76,14 +76,14 @@ Events.init = async () => {
|
||||
Events._types = types;
|
||||
};
|
||||
|
||||
async function translateEventArgs(event, prefix, ...args) {
|
||||
async function translateEventArgs(event, language, prefix, ...args) {
|
||||
const key = getTranslationKey(event, prefix);
|
||||
const compiled = translator.compile.apply(null, [key, ...args]);
|
||||
return utils.decodeHTMLEntities(await translator.translate(compiled));
|
||||
return utils.decodeHTMLEntities(await translator.translate(compiled, language));
|
||||
}
|
||||
|
||||
async function translateSimple(event, prefix) {
|
||||
return await translateEventArgs(event, prefix, renderUser(event), renderTimeago(event));
|
||||
async function translateSimple(event, language, prefix) {
|
||||
return await translateEventArgs(event, language, prefix, renderUser(event), renderTimeago(event));
|
||||
}
|
||||
|
||||
Events.translateSimple = translateSimple; // so plugins can perform translate
|
||||
@@ -162,9 +162,10 @@ async function modifyEvent({ tid, uid, eventIds, timestamps, events }) {
|
||||
});
|
||||
}
|
||||
|
||||
const [users, fromCategories] = await Promise.all([
|
||||
const [users, fromCategories, userSettings] = await Promise.all([
|
||||
getUserInfo(events.map(event => event.uid).filter(Boolean)),
|
||||
getCategoryInfo(events.map(event => event.fromCid).filter(Boolean)),
|
||||
user.getSettings(uid),
|
||||
]);
|
||||
|
||||
// Remove backlink events if backlinks are disabled
|
||||
@@ -200,7 +201,7 @@ async function modifyEvent({ tid, uid, eventIds, timestamps, events }) {
|
||||
|
||||
await Promise.all(events.map(async (event) => {
|
||||
if (Events._types[event.type].translation) {
|
||||
event.text = await Events._types[event.type].translation(event);
|
||||
event.text = await Events._types[event.type].translation(event, userSettings.userLang);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
37
src/views/chat.tpl
Normal file
37
src/views/chat.tpl
Normal file
@@ -0,0 +1,37 @@
|
||||
<div id="chat-modal" class="chat-modal d-flex flex-nowrap modal hide overflow-visible" tabindex="-1" role="dialog" aria-labelledby="Chat" aria-hidden="true" data-center="false">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content" component="chat/message/window">
|
||||
<div class="modal-header d-flex gap-4 justify-content-between">
|
||||
<div class="fs-6 flex-grow-1 fw-semibold tracking-tight text-truncate text-nowrap" component="chat/room/name" data-icon="{icon}">{{{ if ./roomName }}}<i class="fa {icon} text-muted"></i> {roomName}{{{ else }}}{./chatWithMessage}{{{ end}}}</div>
|
||||
<div class="d-flex gap-1 align-items-center">
|
||||
<button type="button" class="btn-ghost-sm d-none d-md-flex" data-action="maximize" title="[[modules:chat.maximize]]" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="fa fa-fw fa-expand text-muted"></i>
|
||||
</button>
|
||||
|
||||
<!-- IMPORT partials/chats/options.tpl -->
|
||||
|
||||
<button id="chat-close-btn" type="button" class="btn-close btn-ghost-sm" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- IMPORT partials/chats/scroll-up-alert.tpl -->
|
||||
<div class="modal-body d-flex flex-column" style="height: 500px;">
|
||||
<div class="d-flex flex-grow-1 gap-1 overflow-auto" style="min-width: 0px;">
|
||||
<div component="chat/messages" class="expanded-chat d-flex flex-column flex-grow-1" data-roomid="{roomId}" style="min-width: 0px;">
|
||||
|
||||
<ul component="chat/message/content" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1">
|
||||
<!-- IMPORT partials/chats/messages.tpl -->
|
||||
</ul>
|
||||
<ul component="chat/message/search/results" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1 hidden">
|
||||
<div component="chat/message/search/no-results" class="text-center p-4 d-flex flex-column">
|
||||
<div class="p-4"><i class="fa-solid fa-wind fs-2 text-muted"></i></div>
|
||||
<div class="text-xs fw-semibold text-muted">[[search:no-matches]]</div>
|
||||
</div>
|
||||
</ul>
|
||||
<!-- IMPORT partials/chats/composer.tpl -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="imagedrop"><div>[[topic:composer.drag-and-drop-images]]</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
54
src/views/chats.tpl
Normal file
54
src/views/chats.tpl
Normal file
@@ -0,0 +1,54 @@
|
||||
<div class="chats-full d-flex gap-1 h-100 mt-3 mt-md-0 py-md-3">
|
||||
<div component="chat/nav-wrapper" class="flex-shrink-0 d-flex flex-column h-100 gap-1" data-loaded="{{{ if roomId }}}1{{{ else }}}0{{{ end }}}">
|
||||
|
||||
<div>
|
||||
<button component="chat/create" class="btn btn-primary btn-sm w-100">[[modules:chat.create-room]]</button>
|
||||
</div>
|
||||
|
||||
{{{ if publicRooms.length }}}
|
||||
<hr class="my-1">
|
||||
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<div class="d-flex gap-1 align-items-center justify-content-between justify-content-lg-start">
|
||||
<button class="btn-ghost-sm p-1 order-1 order-lg-0" data-bs-toggle="collapse" data-bs-target="#public-rooms"
|
||||
onclick="$(this).find('i').toggleClass('fa-rotate-180');"><i class="fa fa-fw fa-chevron-up" style="transition: 0.25s ease;"></i></button>
|
||||
<label class="text-sm text-muted lh-1">[[modules:chat.public-rooms, {publicRooms.length}]]</label>
|
||||
</div>
|
||||
<div id="public-rooms" component="chat/public" class="collapse show">
|
||||
<div class="d-flex gap-1 flex-column">
|
||||
{{{ each publicRooms }}}
|
||||
<div component="chat/public/room" class="btn-ghost-sm ff-sans justify-content-between hover-parent {{{ if ./unread}}}unread{{{ end }}}" data-roomid="{./roomId}">
|
||||
<div class="d-flex gap-1 align-items-center"><i class="fa {./icon} text-muted"></i> {./roomName} <div component="chat/public/room/unread/count" data-count="{./unreadCount}" class="badge border bg-light text-primary {{{ if !./unreadCount }}}hidden{{{ end }}}">{./unreadCountText}</div></div>
|
||||
<div>
|
||||
<div component="chat/public/room/sort/handle" class="text-muted {{{ if isAdmin }}}hover-d-block{{{ else }}}d-none{{{ end }}}" style="cursor:grab;"><i class="fa fa-bars"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{{ end }}}
|
||||
|
||||
<hr class="my-1">
|
||||
|
||||
<div class="d-flex flex-column gap-1 overflow-auto">
|
||||
{{{ if publicRooms.length }}}
|
||||
<div class="d-flex gap-1 align-items-center justify-content-between justify-content-lg-start">
|
||||
<button class="btn-ghost-sm p-1 order-1 order-lg-0" data-bs-toggle="collapse" data-bs-target="#private-rooms"
|
||||
onclick="$(this).find('i').toggleClass('fa-rotate-180')"><i class="fa fa-fw fa-chevron-up" style="transition: 0.25s ease;"></i></button>
|
||||
<label class="text-sm text-muted lh-1">[[modules:chat.private-rooms, {privateRoomCount}]]</label>
|
||||
</div>
|
||||
{{{ end }}}
|
||||
|
||||
<div id="private-rooms" component="chat/recent" class="chats-list overflow-auto mb-0 pe-1 pb-5 pb-lg-0 collapse show" data-nextstart="{nextStart}">
|
||||
{{{each rooms}}}
|
||||
<!-- IMPORT partials/chats/recent_room.tpl -->
|
||||
{{{end}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div component="chat/main-wrapper" class="flex-grow-1 ms-md-2 ps-md-2 border-1 border-start-md h-100" style="min-width: 0;" data-roomid="{roomId}">
|
||||
<!-- IMPORT partials/chats/message-window.tpl -->
|
||||
</div>
|
||||
<div class="imagedrop"><div>[[topic:composer.drag-and-drop-images]]</div></div>
|
||||
</div>
|
||||
1
src/views/partials/chats/add-reaction.tpl
Normal file
1
src/views/partials/chats/add-reaction.tpl
Normal file
@@ -0,0 +1 @@
|
||||
<!-- This partial intentionally left blank; overwritten by nodebb-plugin-reactions -->
|
||||
27
src/views/partials/chats/composer.tpl
Normal file
27
src/views/partials/chats/composer.tpl
Normal file
@@ -0,0 +1,27 @@
|
||||
<div component="chat/composer" class="d-flex flex-column border-top pt-2 align-items-start">
|
||||
<div component="chat/composer/replying-to" data-tomid="" class="text-sm px-2 mb-1 d-flex gap-2 align-items-center hidden">
|
||||
<div component="chat/composer/replying-to-text"></div> <button component="chat/composer/replying-to-cancel" class="btn-ghost-sm px-2 py-1"><i class="fa fa-times"></i></button>
|
||||
</div>
|
||||
<div class="w-100 flex-grow-1 flex-nowrap position-relative d-flex rounded-2 border border-secondary p-1 align-items-end">
|
||||
<button component="chat/upload/button" class="btn-ghost-sm px-2" type="button" title="[[global:upload]]" data-bs-toggle="tooltip"><i class="fa fa-fw fa-upload"></i></button>
|
||||
<div class="flex-grow-1 align-self-center">
|
||||
<textarea component="chat/input" placeholder="[[modules:chat.placeholder.mobile]]" class="bg-transparent text-body form-control chat-input mousetrap rounded-0 border-0 shadow-none px-1 py-0" style="min-height: 1.5rem;height:0;max-height:30vh;resize:none;"></textarea>
|
||||
</div>
|
||||
<div class="d-flex gap-1">
|
||||
{{{ each composerActions }}}
|
||||
<button data-action="{./action}" class="btn-ghost-sm px-2 {./class}" type="button" title="{./title}" data-bs-toggle="tooltip"><i class="fa {./icon}"></i></button>
|
||||
{{{ end }}}
|
||||
<button class="btn-ghost-sm px-2" type="button" data-action="send" title="[[modules:chat.send]]" data-bs-toggle="tooltip"><i class="fa fa-fw fa-paper-plane text-primary"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center text-xs w-100 px-2 mt-1">
|
||||
<div component="chat/composer/typing" class="">
|
||||
<div component="chat/composer/typing/users" class="hidden"></div>
|
||||
<div component="chat/composer/typing/text" class="hidden"></div>
|
||||
</div>
|
||||
<div component="chat/message/remaining" class="text-xs text-muted">{maximumChatMessageLength}</div>
|
||||
</div>
|
||||
<form class="hidden" component="chat/upload" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="files[]" multiple class="hidden"/>
|
||||
</form>
|
||||
</div>
|
||||
10
src/views/partials/chats/dropdown.tpl
Normal file
10
src/views/partials/chats/dropdown.tpl
Normal file
@@ -0,0 +1,10 @@
|
||||
{{{ if !rooms.length }}}
|
||||
<li class="text-center p-4 d-flex flex-column">
|
||||
<div class="p-4"><i class="fa-solid fa-wind fs-2 text-muted"></i></div>
|
||||
<div class="text-xs fw-semibold text-muted">[[modules:chat.no-active]]</div>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
|
||||
{{{ each rooms }}}
|
||||
<!-- IMPORT partials/chats/recent_room.tpl -->
|
||||
{{{ end }}}
|
||||
43
src/views/partials/chats/message-window.tpl
Normal file
43
src/views/partials/chats/message-window.tpl
Normal file
@@ -0,0 +1,43 @@
|
||||
<div component="chat/message/window" class="d-flex flex-column h-100">
|
||||
{{{ if widgets.header.length }}}
|
||||
<div data-widget-area="header">
|
||||
{{{each widgets.header}}}
|
||||
{{widgets.header.html}}
|
||||
{{{end}}}
|
||||
</div>
|
||||
{{{ end }}}
|
||||
{{{ if !roomId }}}
|
||||
<div class="d-flex flex-column align-items-center gap-3 my-auto">
|
||||
<i class="fa-solid fa-wind fs-2 text-muted"></i>
|
||||
<span class="text-muted text-sm">[[modules:chat.no-active]]</span>
|
||||
</div>
|
||||
{{{ else }}}
|
||||
<div component="chat/header" class="d-flex align-items-center px-md-3 gap-3">
|
||||
<a href="#" data-action="close" role="button" class="flex-shrink-0 d-flex d-md-none btn btn-ghost border align-text-top"><i class="fa fa-chevron-left"></i></a>
|
||||
<h5 component="chat/header/title" class="members flex-grow-1 fw-semibold tracking-tight mb-0 text-truncate text-nowrap">
|
||||
{{{ if ./roomName }}}<i class="fa {icon} text-muted"></i> {roomName}{{{ else }}}{./chatWithMessage}{{{ end}}}
|
||||
</h5>
|
||||
|
||||
<!-- IMPORT partials/chats/options.tpl -->
|
||||
</div>
|
||||
<!-- IMPORT partials/chats/scroll-up-alert.tpl -->
|
||||
<hr class="my-1"/>
|
||||
<div class="d-flex flex-grow-1 gap-1 overflow-auto" style="min-width: 0px;">
|
||||
<div component="chat/messages" class="expanded-chat d-flex flex-column flex-grow-1" data-roomid="{roomId}" style="min-width: 0px;">
|
||||
<ul component="chat/message/content" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1">
|
||||
<!-- IMPORT partials/chats/messages.tpl -->
|
||||
</ul>
|
||||
<ul component="chat/message/search/results" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1 hidden">
|
||||
<div component="chat/message/search/no-results" class="text-center p-4 d-flex flex-column">
|
||||
<div class="p-4"><i class="fa-solid fa-wind fs-2 text-muted"></i></div>
|
||||
<div class="text-xs fw-semibold text-muted">[[search:no-matches]]</div>
|
||||
</div>
|
||||
</ul>
|
||||
<!-- IMPORT partials/chats/composer.tpl -->
|
||||
</div>
|
||||
|
||||
<!-- IMPORT partials/chats/user-list.tpl -->
|
||||
<!-- IMPORT partials/chats/pinned-messages.tpl -->
|
||||
</div>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
73
src/views/partials/chats/message.tpl
Normal file
73
src/views/partials/chats/message.tpl
Normal file
@@ -0,0 +1,73 @@
|
||||
<li component="chat/message" class="chat-message mx-2 pe-2 {{{ if messages.deleted }}} deleted{{{ end }}} {{{ if messages.pinned}}} pinned{{{ end }}} {{{ if messages.newSet }}}border-top pt-3{{{ end }}}" data-mid="{messages.messageId}" data-uid="{messages.fromuid}" data-index="{messages.index}" data-self="{messages.self}" data-break="{messages.newSet}" data-timestamp="{messages.timestamp}" data-username="{messages.fromUser.username}">
|
||||
|
||||
{{{ if messages.parent }}}
|
||||
<!-- IMPORT partials/chats/parent.tpl -->
|
||||
{{{ end }}}
|
||||
|
||||
<div class="message-header lh-1 d-flex align-items-center gap-2 text-sm {{{ if !messages.newSet }}}hidden{{{ end }}} pb-2">
|
||||
<a href="{config.relative_path}/user/{messages.fromUser.userslug}" class="text-decoration-none">{buildAvatar(messages.fromUser, "18px", true, "not-responsive")}</a>
|
||||
<span class="chat-user fw-semibold"><a href="{config.relative_path}/user/{messages.fromUser.userslug}">{messages.fromUser.displayname}</a></span>
|
||||
{{{ if messages.fromUser.banned }}}
|
||||
<span class="badge bg-danger">[[user:banned]]</span>
|
||||
{{{ end }}}
|
||||
{{{ if messages.fromUser.deleted }}}
|
||||
<span class="badge bg-danger">[[user:deleted]]</span>
|
||||
{{{ end }}}
|
||||
<span class="chat-timestamp text-muted timeago" title="{messages.timestampISO}"></span>
|
||||
|
||||
<div component="chat/message/edited" class="text-muted ms-auto {{{ if !messages.edited }}}hidden{{{ end }}}" title="[[global:edited-timestamp, {isoTimeToLocaleString(messages.editedISO, config.userLang)}]]"><i class="fa fa-edit"></i></span></div>
|
||||
</div>
|
||||
<div class="message-body-wrapper">
|
||||
<div component="chat/message/body" class="message-body ps-0 py-0 overflow-auto text-break">
|
||||
{messages.content}
|
||||
</div>
|
||||
<!-- IMPORT partials/chats/reactions.tpl -->
|
||||
<div component="chat/message/controls" class="position-relative">
|
||||
<div class="btn-group border shadow-sm controls position-absolute bg-body end-0" style="bottom:1rem;">
|
||||
<!-- IMPORT partials/chats/add-reaction.tpl -->
|
||||
<button class="btn btn-sm btn-link" data-action="reply" title="[[topic:reply]]"><i class="fa fa-reply"></i></button>
|
||||
|
||||
<div class="btn-group d-inline-block">
|
||||
<button class="btn btn-sm btn-link dropdown-toggle" data-bs-toggle="dropdown"><i class="fa fa-ellipsis" type="button"></i></button>
|
||||
<ul class="dropdown-menu dropdown-menu-end p-1 text-sm list-unstyled">
|
||||
{{{ if (isAdminOrGlobalMod || (!config.disableChatMessageEditing && messages.self)) }}}
|
||||
<li>
|
||||
<a href="#" class="dropdown-item rounded-1" data-action="edit"><span class="d-inline-flex align-items-center gap-2"><i class="fa fa-fw fa-pencil text-muted"></i> [[topic:edit]]</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="dropdown-item rounded-1" data-action="delete"><span class="d-inline-flex align-items-center gap-2"><i class="fa fa-fw fa-trash text-muted"></i> [[topic:delete]]</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="dropdown-item rounded-1" data-action="restore"><span class="d-inline-flex align-items-center gap-2"><i class="fa fa-fw fa-repeat text-muted"></i> [[topic:restore]]</span></a>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
|
||||
{{{ if (isAdminOrGlobalMod || isOwner )}}}
|
||||
<li>
|
||||
<a href="#" class="dropdown-item rounded-1" data-action="pin"><span class="d-inline-flex align-items-center gap-2"><i class="fa fa-fw fa-thumbtack text-muted"></i> [[modules:chat.pin-message]]</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="dropdown-item rounded-1" data-action="unpin"><span class="d-inline-flex align-items-center gap-2"><i class="fa fa-fw fa-thumbtack fa-rotate-90 text-muted"></i> [[modules:chat.unpin-message]]</span></a>
|
||||
</li>
|
||||
<li class="dropdown-divider"></li>
|
||||
{{{ end }}}
|
||||
|
||||
{{{ if isAdminOrGlobalMod }}}
|
||||
<li>
|
||||
<a href="#" class="dropdown-item rounded-1 chat-ip-button"><span class="d-inline-flex align-items-center gap-2"><i class="fa fa-fw fa-info-circle text-muted"></i> [[modules:chat.show-ip]]</span></a>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
|
||||
<li>
|
||||
<a href="#" class="dropdown-item rounded-1" data-action="copy-text" data-mid="{messages.mid}"><span class="d-inline-flex align-items-center gap-2"><i class="fa fa-fw fa-copy text-muted"></i> [[modules:chat.copy-text]]</span></a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="#" class="dropdown-item rounded-1" data-action="copy-link" data-mid="{messages.mid}"><span class="d-inline-flex align-items-center gap-2"><i class="fa fa-fw fa-link text-muted"></i> [[modules:chat.copy-link]]</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
7
src/views/partials/chats/messages.tpl
Normal file
7
src/views/partials/chats/messages.tpl
Normal file
@@ -0,0 +1,7 @@
|
||||
{{{each messages}}}
|
||||
{{{ if !./system }}}
|
||||
<!-- IMPORT partials/chats/message.tpl -->
|
||||
{{{ else }}}
|
||||
<!-- IMPORT partials/chats/system-message.tpl -->
|
||||
{{{ end }}}
|
||||
{{{end}}}
|
||||
100
src/views/partials/chats/options.tpl
Normal file
100
src/views/partials/chats/options.tpl
Normal file
@@ -0,0 +1,100 @@
|
||||
<div class="d-flex gap-1 align-items-stretch">
|
||||
<!-- search -->
|
||||
<button class="btn-ghost-sm" component="chat/room/search/toggle" data-manual-tooltip="1" title="[[global:header.search]]">
|
||||
<i class="fa fa-search text-muted"></i>
|
||||
</button>
|
||||
<div component="chat/room/search/container" class="position-relative hidden align-self-center">
|
||||
<input component="chat/room/search" class="form-control form-control-sm" placeholder="[[search:type-to-search]]" style="width: 150px;">
|
||||
<a component="chat/room/search/clear" href="#" class="hidden px-2 py-1 position-absolute top-50 end-0 translate-middle-y">
|
||||
<i class="fa fa-times text-muted opacity-75"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- notification dropdown -->
|
||||
<div class="dropdown d-flex" data-manual-tooltip="1" title="[[modules:chat.notification-settings]]" component="chat/notification/setting">
|
||||
<button class="btn-ghost-sm position-relative" data-bs-toggle="dropdown">
|
||||
<i class="fa fa-bell text-muted"></i>
|
||||
<span class="position-absolute top-0 end-0 text-xs text-muted opacity-50" style="font-size: 10px!important; padding: 1px; line-height: 10px;">
|
||||
<i component="chat/notification/setting/icon" class="fa {notificationOptionsIcon}"></i>
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end p-1 text-sm">
|
||||
{{{ each notificationOptions }}}
|
||||
<li>
|
||||
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="#" data-value="{./value}" data-icon="{./icon}">
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="flex-grow-1">{./label}</div>
|
||||
<i class="flex-shrink-0 fa fa-fw fa-check {{{ if !./selected }}}hidden{{{ end }}}"></i>
|
||||
</div>
|
||||
{{{ if @first}}}
|
||||
<div component="chat/notification/setting/sub-label" class="text-sm text-muted">{./subLabel}</div>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{{{ if @first }}}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{{{ end }}}
|
||||
{{{ end }}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- pinned messages -->
|
||||
<button component="chat/pinned/messages/btn" class="btn-ghost-sm d-none d-lg-flex flex-nowrap" title="[[modules:chat.pinned-messages]]" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="fa fa-thumb-tack text-muted"></i>
|
||||
</button>
|
||||
|
||||
<!-- manage/options dropdown -->
|
||||
<div class="dropdown d-flex" data-manual-tooltip="1" title="[[modules:chat.options]]">
|
||||
<button class="btn-ghost-sm" data-bs-toggle="dropdown" component="chat/controlsToggle">
|
||||
<i class="fa fa-gear text-muted"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end p-1 text-sm" component="chat/controls">
|
||||
<li>
|
||||
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="#" data-action="manage">
|
||||
<i class="fa fa-fw text-muted fa-cog"></i> [[modules:chat.manage-room]]
|
||||
</a>
|
||||
</li>
|
||||
{{{ if isOwner }}}
|
||||
<li>
|
||||
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="#" data-action="rename">
|
||||
<i class="fa fa-fw text-muted fa-edit"></i> [[modules:chat.rename-room]]
|
||||
</a>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
<li>
|
||||
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="#" data-action="leave">
|
||||
<i class="fa fa-fw text-muted fa-sign-out"></i> [[modules:chat.leave-room]]
|
||||
</a>
|
||||
</li>
|
||||
{{{ if (public && isAdmin) }}}
|
||||
<li>
|
||||
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="#" data-action="delete">
|
||||
<i class="fa fa-fw text-danger fa-trash"></i> [[modules:chat.delete-room]]
|
||||
</a>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- users toggle -->
|
||||
{{{ if users.length }}}
|
||||
<div component="chat/user/list/btn" class="btn-ghost-sm d-none d-lg-flex flex-nowrap gap-3" title="[[modules:chat.view-users-list]]" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<div class="d-flex text-nowrap">
|
||||
{{{ if ./users.0 }}}
|
||||
<span style="width: 18px; z-index: 3;" class="text-decoration-none" href="{config.relative_path}/user/{./users.0.userslug}">{buildAvatar(./users.0, "24px", true)}</span>
|
||||
{{{ end }}}
|
||||
{{{ if ./users.1 }}}
|
||||
<span style="width: 18px; z-index: 2;" class="text-decoration-none" href="{config.relative_path}/user/{./users.1.userslug}">{buildAvatar(./users.1, "24px", true)}</span>
|
||||
{{{ end }}}
|
||||
{{{ if ./users.2 }}}
|
||||
<span style="width: 18px; z-index: 1;" class="text-decoration-none" href="{config.relative_path}/user/{./users.2.userslug}">{buildAvatar(./users.2, "24px", true)}</span>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
{./userCount}
|
||||
</div>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
13
src/views/partials/chats/parent.tpl
Normal file
13
src/views/partials/chats/parent.tpl
Normal file
@@ -0,0 +1,13 @@
|
||||
<div class="d-flex ms-4 mb-2 align-items-center">
|
||||
<div component="chat/message/parent" data-parent-mid="{messages.parent.mid}" data-uid="{messages.parent.fromuid}" class="btn-ghost-sm ff-secondary align-items-start flex-row w-100">
|
||||
<div class="d-flex gap-2 text-sm text-nowrap">
|
||||
<div><i class="fa fa-sm fa-reply opacity-50"></i></div>
|
||||
<div class="d-flex flex-nowrap gap-1 align-items-center">
|
||||
<a href="{config.relative_path}/user/{messages.parent.user.userslug}" class="text-decoration-none lh-1">{buildAvatar(messages.parent.user, "14px", true, "not-responsive align-middle")}</a>
|
||||
<a class="chat-user fw-semibold" href="{config.relative_path}/user/{messages.parent.user.userslug}">{messages.parent.user.displayname}</a>
|
||||
</div>
|
||||
<span class="chat-timestamp text-muted timeago text-nowrap hidden" title="{messages.parent.timestampISO}"></span>
|
||||
</div>
|
||||
<div component="chat/message/parent/content" class="text-muted line-clamp-1 w-100">{messages.parent.content}</div>
|
||||
</div>
|
||||
</div>
|
||||
32
src/views/partials/chats/pinned-messages-list.tpl
Normal file
32
src/views/partials/chats/pinned-messages-list.tpl
Normal file
@@ -0,0 +1,32 @@
|
||||
{{{ each messages }}}
|
||||
<li component="chat/message" class="chat-message mx-2 pe-2 {{{ if messages.deleted }}} deleted{{{ end }}} {{{ if messages.pinned}}} pinned{{{ end }}} border-top pt-3" data-mid="{messages.messageId}" data-uid="{messages.fromuid}" data-self="{messages.self}" data-timestamp="{messages.timestamp}" data-username="{messages.fromUser.username}" data-index="{./index}">
|
||||
|
||||
{{{ if messages.parent }}}
|
||||
<!-- IMPORT partials/chats/parent.tpl -->
|
||||
{{{ end }}}
|
||||
|
||||
<div class="message-header lh-1 d-flex align-items-center gap-2 text-sm pb-2">
|
||||
<a href="{config.relative_path}/user/{messages.fromUser.userslug}" class="text-decoration-none">{buildAvatar(messages.fromUser, "18px", true, "not-responsive")}</a>
|
||||
<span class="chat-user fw-semibold"><a href="{config.relative_path}/user/{messages.fromUser.userslug}">{messages.fromUser.displayname}</a></span>
|
||||
|
||||
<span class="chat-timestamp text-muted timeago" title="{messages.timestampISO}"></span>
|
||||
<div component="chat/message/edited" class="text-muted ms-auto {{{ if !messages.edited }}}hidden{{{ end }}}" title="[[global:edited-timestamp, {isoTimeToLocaleString(messages.editedISO, config.userLang)}]]"><i class="fa fa-edit"></i></span></div>
|
||||
</div>
|
||||
<div class="message-body-wrapper">
|
||||
<div component="chat/message/body" class="message-body ps-0 py-0 overflow-auto text-break">
|
||||
{messages.content}
|
||||
</div>
|
||||
<div component="chat/message/controls" class="position-relative">
|
||||
<div class="btn-group border shadow-sm controls position-absolute bg-body end-0" style="bottom:1rem;">
|
||||
{{{ if (isAdminOrGlobalMod || (!config.disableChatMessageEditing && messages.self)) }}}
|
||||
<button class="btn btn-sm btn-link" data-action="edit" title="[[topic:edit]]"><i class="fa fa-pencil"></i></button>
|
||||
{{{ end }}}
|
||||
{{{ if (isAdminOrGlobalMod || isOwner )}}}
|
||||
<button class="btn btn-sm btn-link" data-action="pin" title="[[modules:chat.pin-message]]"><i class="fa fa-thumbtack"></i></button>
|
||||
<button class="btn btn-sm btn-link" data-action="unpin" title="[[modules:chat.unpin-message]]"><i class="fa fa-thumbtack fa-rotate-90"></i></button>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
12
src/views/partials/chats/pinned-messages.tpl
Normal file
12
src/views/partials/chats/pinned-messages.tpl
Normal file
@@ -0,0 +1,12 @@
|
||||
<div component="chat/messages/pinned/container" class="d-flex flex-column expanded-chat border-start hidden" style="min-width:340px; width: 340px;">
|
||||
<h3 class="fs-6 p-1 mb-0 text-center text-secondary">[[modules:chat.pinned-messages]]</h3>
|
||||
|
||||
<div component="chat/messages/pinned/empty" class="text-center p-4 d-flex flex-column">
|
||||
<div class="p-4"><i class="fa-solid fa-wind fs-2 text-muted"></i></div>
|
||||
<div class="text-xs fw-semibold text-muted">[[modules:chat.no-pinned-messages]]</div>
|
||||
</div>
|
||||
|
||||
<ul component="chat/messages/pinned" class="chat-content list-unstyled d-flex flex-column gap-1 p-1 overflow-auto">
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
1
src/views/partials/chats/reactions.tpl
Normal file
1
src/views/partials/chats/reactions.tpl
Normal file
@@ -0,0 +1 @@
|
||||
<!-- This partial intentionally left blank; overwritten by nodebb-plugin-reactions -->
|
||||
55
src/views/partials/chats/recent_room.tpl
Normal file
55
src/views/partials/chats/recent_room.tpl
Normal file
@@ -0,0 +1,55 @@
|
||||
{{{ if (loadingMore && @first)}}}
|
||||
<hr class="my-1" />
|
||||
{{{ end }}}
|
||||
<div component="chat/recent/room" data-roomid="{./roomId}" data-full="1" class="rounded-1 {{{ if ./unread }}}unread{{{ end }}}">
|
||||
<div class="d-flex gap-1 justify-content-between">
|
||||
<div class="chat-room-btn position-relative d-flex flex-grow-1 gap-2 justify-content-start align-items-start btn-ghost-sm ff-sans">
|
||||
<div class="main-avatar">
|
||||
{{{ if ./users.length }}}
|
||||
{{{ if ./groupChat}}}
|
||||
<div class="position-relative stacked-avatars">
|
||||
<span class="text-decoration-none position-absolute" href="{config.relative_path}/user/{./users.1.userslug}">{buildAvatar(./users.1, "24px", true)}</span>
|
||||
<span class="text-decoration-none position-absolute" href="{config.relative_path}/user/{./users.0.userslug}" >{buildAvatar(./users.0, "24px", true)}</span>
|
||||
</div>
|
||||
{{{ else }}}
|
||||
<span href="{config.relative_path}/user/{./users.0.userslug}" class="text-decoration-none">{buildAvatar(./users.0, "32px", true)}</span>
|
||||
{{{ end }}}
|
||||
{{{ else }}}
|
||||
<span class="avatar avatar-rounded text-bg-warning" component="avatar/icon" style="--avatar-size: 32px;">?</span>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-grow-1 flex-column w-100">
|
||||
<div component="chat/room/title" class="room-name fw-semibold text-xs text-break">
|
||||
{{{ if ./roomName}}}
|
||||
{./roomName}
|
||||
{{{ else }}}
|
||||
{{{ if !./lastUser.uid }}}
|
||||
[[modules:chat.no-users-in-room]]
|
||||
{{{ else }}}
|
||||
{./usernames}
|
||||
{{{ end }}}
|
||||
{{{ end }}}
|
||||
</div>
|
||||
|
||||
{{{ if ./teaser }}}
|
||||
<div class="teaser-content text-sm line-clamp-3 text-break">
|
||||
<span href="#" class="text-decoration-none">{buildAvatar(./teaser.user, "14px", true)}</span>
|
||||
<strong class="text-xs fw-semibold teaser-username">{./teaser.user.username}:</strong>
|
||||
{./teaser.content}
|
||||
</div>
|
||||
<div class="teaser-timestamp text-muted text-xs">{{{ if ./teaser.timeagoLong }}}{./teaser.timeagoLong}{{{ else }}}<span class="timeago" title="{./teaser.timestampISO}"></span>{{{ end }}}</div>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="mark-read btn-ghost-sm" style="width: 1.5rem; height: 1.5rem;">
|
||||
<i class="unread fa fa-2xs fa-circle text-primary {{{ if !./unread }}}hidden{{{ end }}}"></i>
|
||||
<i class="read fa fa-2xs fa-circle-o text-secondary {{{ if ./unread }}}hidden{{{ end }}}"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{{ if !@last }}}
|
||||
<hr class="my-1" />
|
||||
{{{ end }}}
|
||||
3
src/views/partials/chats/scroll-up-alert.tpl
Normal file
3
src/views/partials/chats/scroll-up-alert.tpl
Normal file
@@ -0,0 +1,3 @@
|
||||
<div class="position-relative">
|
||||
<div component="chat/messages/scroll-up-alert" class="py-1 mt-1 position-absolute start-50 top-50 translate-middle text-sm scroll-up-alert alert alert-info d-none d-md-block text-nowrap hidden" role="button" style="z-index: 500;"><i class="fa fa-fw fa-arrow-down"></i> [[modules:chat.scroll-up-alert]]</div>
|
||||
</div>
|
||||
7
src/views/partials/chats/system-message.tpl
Normal file
7
src/views/partials/chats/system-message.tpl
Normal file
@@ -0,0 +1,7 @@
|
||||
<li component="chat/system-message" class="system-message text-muted small py-2 gap-3 d-flex align-items-center justify-content-center" data-mid="{messages.messageId}" data-uid="{messages.fromuid}" data-index="{messages.index}" data-self="{messages.self}" data-break="0" data-timestamp="{messages.timestamp}">
|
||||
<hr class="d-inline-block my-1" style="width: 10%;"/>
|
||||
<div>
|
||||
[[modules:chat.system.{messages.content}, {messages.fromUser.username}, {messages.timestampISO}]]
|
||||
</div>
|
||||
<hr class="d-inline-block my-1" style="width: 10%;"/>
|
||||
</li>
|
||||
11
src/views/partials/chats/user-list.tpl
Normal file
11
src/views/partials/chats/user-list.tpl
Normal file
@@ -0,0 +1,11 @@
|
||||
<div component="chat/user/list" class="border-start hidden d-flex flex-column gap-1 p-1 overflow-auto" style="min-width:240px; width: 240px;">
|
||||
{{{ each users }}}
|
||||
<a data-index="{./index}" data-uid="{./uid}" class="btn-ghost-sm ff-secondary d-flex justify-content-start align-items-center gap-2 {{{ if ./online }}}online{{{ end}}}" href="{config.relative_path}/uid/{./uid}">
|
||||
<div>{buildAvatar(users, "24px", true)}</div>
|
||||
<div class="d-flex gap-1 flex-grow-1 text-nowrap text-truncate">
|
||||
<span component="chat/user/list/username" class="text-truncate">{./username}</span>
|
||||
{{{ if ./isOwner }}}<span><i class="fa fa-star text-warning" data-bs-toggle="tooltip" title="[[modules:chat.owner]]"></i></span>{{{ end }}}
|
||||
</div>
|
||||
</a>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
1
src/views/partials/chats/user.tpl
Normal file
1
src/views/partials/chats/user.tpl
Normal file
@@ -0,0 +1 @@
|
||||
<a href="{config.relative_path}/user/{rooms.users.userslug}" class="text-decoration-none">{buildAvatar(rooms.users, "32px", true)}</a>
|
||||
Reference in New Issue
Block a user