Compare commits

..

19 Commits

Author SHA1 Message Date
Barış Soner Uşaklı
129474f268 Merge branch 'develop' into axios 2023-12-17 19:08:58 -05:00
Barış Soner Uşaklı
40170f8133 cleaunup 2023-12-17 14:42:09 -05:00
Barış Soner Uşaklı
d4452ba25c socket.io 2023-12-17 14:37:53 -05:00
Barış Soner Uşaklı
5c6a631fc6 uploads.js 2023-12-17 14:20:45 -05:00
Barış Soner Uşaklı
dc1cd3feaa user/emails 2023-12-17 14:02:41 -05:00
Barış Soner Uşaklı
841b856e27 topics/thumbs 2023-12-17 13:32:59 -05:00
Barış Soner Uşaklı
abf81ec57d search 2023-12-17 13:09:53 -05:00
Barış Soner Uşaklı
b89502ce0b posts 2023-12-17 13:07:21 -05:00
Barış Soner Uşaklı
381e64e657 plugins 2023-12-17 12:54:22 -05:00
Barış Soner Uşaklı
110b867ed5 meta 2023-12-17 12:52:23 -05:00
Barış Soner Uşaklı
4b006b37cc remove log 2023-12-17 12:44:30 -05:00
Barış Soner Uşaklı
e51c8de9cb messaging/middleware 2023-12-17 12:42:07 -05:00
Barış Soner Uşaklı
918008fffd locale-detect 2023-12-17 12:08:48 -05:00
Barış Soner Uşaklı
2e2b1e9d6c flags 2023-12-17 12:03:49 -05:00
Barış Soner Uşaklı
ca42094f3f remove unused async 2023-12-17 11:40:31 -05:00
Barış Soner Uşaklı
3d07f14859 feeds 2023-12-17 11:40:13 -05:00
Barış Soner Uşaklı
32175666d5 add missing deps 2023-12-16 23:30:06 -05:00
Barış Soner Uşaklı
1501a9303c controller tests 2023-12-16 23:28:14 -05:00
Barış Soner Uşaklı
db75571923 axios migration 2023-12-16 19:40:36 -05:00
119 changed files with 1813 additions and 1766 deletions

View File

@@ -1,528 +1,3 @@
#### v3.6.6 (2024-02-14)
##### Chores
* incrementing version number - v3.6.5 (6c653625)
* update changelog for v3.6.5 (04039f76)
* incrementing version number - v3.6.4 (83d131b4)
* incrementing version number - v3.6.3 (fc7d2bfd)
* incrementing version number - v3.6.2 (0f577a57)
* 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
* closes #12329, fix default value of categoryWatchState (88e9fa37)
##### Tests
* fix spec (5fec8b23)
#### v3.6.5 (2024-01-31)
##### Chores
* incrementing version number - v3.6.4 (83d131b4)
* update changelog for v3.6.4 (6e6c3974)
* incrementing version number - v3.6.3 (fc7d2bfd)
* incrementing version number - v3.6.2 (0f577a57)
* 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
* #12320, .text() gets \n\t characters (67c8bd99)
#### v3.6.4 (2024-01-24)
##### Chores
* incrementing version number - v3.6.3 (fc7d2bfd)
* update changelog for v3.6.3 (92ffc57c)
* incrementing version number - v3.6.2 (0f577a57)
* 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)
##### New Features
* add success hook to quick reply (cb21f28b)
##### Bug Fixes
* if there is no bookmarkThreshold dont init unread indicator (cf40d681)
* remove leftover code from 2.x, closes #12301 (d5f445f1)
* copy single line code blocks, closes #12297 (06269cdf)
* validate plugin id in toggleActive (76f3efff)
##### Tests
* add plugin id tests (e8befbcc)
#### 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
* incrementing version number - v3.6.0 (4cdf85f8)
* update changelog for v3.6.0 (eb92cee6)
* 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
* #12243, don' set process.env.config if it doesnt exist (788404c1)
* lang key for move notification closes #12241 (48a2b5f7)
* dont send topic notification to poster (e72b26f5)
#### v3.6.0 (2023-12-20)
##### Chores
* **i18n:**
* fallback strings for new resources: nodebb.error (d3cfa1b7)
* fallback strings for new resources: nodebb.post-queue (64fe1278)
* fallback strings for new resources: nodebb.post-queue (f98205c1)
* fallback strings for new resources: nodebb.social (081352b6)
* fallback strings for new resources: nodebb.modules (4fe84ae8)
* fallback strings for new resources: nodebb.admin-settings-chat, nodebb.admin-settings-post, nodebb.admin-settings-user (94777927)
* fallback strings for new resources: nodebb.admin-settings-user, nodebb.category, nodebb.notifications (29b3a403)
* fallback strings for new resources: nodebb.error (7d8f700f)
* fallback strings for new resources: nodebb.modules (83931138)
* make error:email-taken slightly more descriptive (dd0743d3)
* up harmony (250cc771)
* up harmony (9324a75c)
* incrementing version number - v3.5.3 (ed0e8783)
* update changelog for v3.5.3 (e49ddaf8)
* up harmony (dbbf3a2c)
* up harmony (e4656bd4)
* incrementing version number - v3.5.2 (52fbb2da)
* up themes (0ec9d4c3)
* added missing deprecation warning for .getTopics (9079ad0b)
* update note at top of file (2de534fa)
* up dbsearch (b6981693)
* up themes (9a1f8e9b)
* incrementing version number - v3.5.1 (4c543488)
* up themes (ed10dda2)
* up composer (22d7e92f)
* up themes (fcdd3737)
* v4 note for deprecations (f4c36b84)
* minor re-order of routes/controllers in admin api v3 router/controller (181a9399)
* add in note at top of files ready for deletion in v4 (54a08087)
* up harmony (1b8dcbc2)
* 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)
* **deps:**
* update dependency eslint-plugin-import to v2.29.1 (#12229) (055b7597)
* update dependency eslint to v8.56.0 (#12231) (a7dd0f92)
* update dependency eslint to v8.55.0 (#12203) (32a403b2)
* update dependency lint-staged to v15.2.0 (#12210) (9763e97f)
* update dependency jsdom to v23.0.1 (#12196) (a50b141f)
* update dependency jsdom to v23 (#12186) (8c0472a0)
* update commitlint monorepo to v18.4.3 (#12177) (fd5d7b65)
* update dependency eslint to v8.54.0 (#12172) (b6275453)
* update commitlint monorepo to v18.4.2 (#12169) (d6a92d4c)
* update postgres docker tag to v16.1 (#12167) (616ff573)
* update mongo docker tag to v7 (#12166) (5f8a4f3a)
* update redis docker tag to v7.2.3 (#12165) (c41f9a75)
* update dependency lint-staged to v15.1.0 (#12161) (1796ed2b)
* update dependency @commitlint/cli to v18.4.1 (#12162) (be3d3393)
* update commitlint monorepo to v18.4.0 (#12159) (128c24f2)
* update dependency eslint to v8.53.0 (#12151) (d1a7ba35)
* update redis docker tag to v7.2.3 (#12152) (2eff6912)
* update commitlint monorepo to v18 (#12105) (97016f47)
* update dependency sass-embedded to v1.69.5 (#12127) (9e8a2116)
* **socket.io:** deprecate categories.(isModerator|ignore|watch|getSelectCategories|getMoveCategories|getCategoriesByPrivilege) (f1dbfaa2)
##### Documentation Changes
* fix improper verbiage in category watch schema (f8cc8548)
* openapi schema for api.search.categories (4ffe0417)
* **socket.io:**
* openapi schema for remaining added routes (5399e86a)
* added schema for new routes (f279bca0)
##### New Features
* add 500 page for missing tpls, closes #12230 (06221a0d)
* remove gif exif stripping exception (f8219aa6)
* update groups.leave to allow global mods to kick users out of groups (2c6024e0)
* better layout for manage chat room modal (f4faa0b7)
* update Messaging.toggleOwner to optionally take a third `state` argument (932bd292)
* new language strings for post-queue page (5d03321e)
* add some new social share buttons (ace171a6)
* docker improvements (#12031) (7f3a9968)
* closes #12158, add sortable rewards (5ea7dec9)
* add copy text (4b2491be)
* closes #12154, add exempt groups (fdff165e)
* update moved keys (119c3362)
* move new user restrrictions (3d505c5c)
* add tracking categories and make watching send notifications (#12147) (84fed97b)
* add new lang string for minutes (d8d26c9f)
* add direct message link (#12138) (4c4f3ac9)
* closes #5584, setup winston to output to file (87a859aa)
* batch.processSortedSet min/max (#12129) (6c7e6144)
##### Bug Fixes
* fix display post history (dc975838)
* update isJSON test in request lib (506d7be5)
* 503 rendering on ajaxify (f7e0fd0b)
* #12227, fix crash in redirect (2dc1def5)
* deprecated emailer hook (565ca3cc)
* extract all pages when stripping metadata (0b3eb6c0)
* incorrect call to load additional group members (78835ebb)
* closes #12185, fix cli user password reset (b9050139)
* don't require login for listing categories (50a90f8e)
* #12183, remove ensureLoggedIn middleware (0a4f3c8a)
* remove unused requires (b5940a5d)
* remove lodash require (f9c471a0)
* #12171, bump harmony (89a1134c)
* update design of post-queue (41bdc9e8)
* update post-queue template for #12171 (63ba4986)
* language key (4c7c46f3)
* another missing state (f4bbc5bd)
* add missing tracking (3b91e8e2)
* bump harmony (a5e3754b)
* #12133 dropdown menus on mobile stay open during ajaxify (4601a6f7)
* update ajaxifyTimer logic to only drop the request if the URL is the same as the one it's already processing (b4297cd8)
* #12141, use apiv3 for category search module (cefd4061)
* made parentCid optional in api.search.categories (581516c8)
* don't count internal links towards link count when restricting new users from posting links (022fa0e7)
* closes #12126, fix language keys (075cd598)
* param (e5a60dc8)
* handle public chat rooms too (6a696c43)
* suppress chat message notifications for users who are known to be in the chat room (uid is present in the corresponding socket.io room) (18c27d1a)
* **deps:**
* update dependency workerpool to v9 (#12234) (2cccbcf6)
* update dependency sharp to v0.33.1 (#12233) (45143000)
* update dependency csrf-sync to v4.0.3 (#12232) (9e2a6f86)
* update dependency ace-builds to v1.32.2 (#12228) (b6ca117a)
* update dependency ace-builds to v1.32.1 (#12226) (6036d144)
* update dependency esbuild to v0.19.9 (#12224) (d96d4d09)
* update dependency @fontsource/inter to v5.0.16 (#12219) (e32eb8b3)
* update dependency chart.js to v4.4.1 (#12217) (b6b569c0)
* update dependency nodebb-theme-persona to v13.2.49 (#12218) (6dab99fd)
* update dependency postcss to v8.4.32 (#12204) (da879704)
* update dependency @fortawesome/fontawesome-free to v6.5.1 (#12198) (b41c7f2a)
* update dependency nodebb-theme-harmony to v1.1.101 (#12199) (72d6a4b1)
* update dependency sortablejs to v1.15.1 (#12200) (cd625705)
* update dependency ace-builds to v1.32.0 (#12197) (75f063ba)
* update dependency @fortawesome/fontawesome-free to v6.5.0 (#12193) (a94f4a48)
* update dependency sharp to v0.33.0 (#12194) (1f287c74)
* update dependency fs-extra to v11.2.0 (#12191) (4eaf2320)
* update dependency passport to v0.7.0 (#12190) (bbf7c5e1)
* update dependency esbuild to v0.19.8 (#12187) (bc59856e)
* update dependency lru-cache to v10.1.0 (#12181) (22932bdb)
* update dependency esbuild to v0.19.7 (#12176) (00cb5839)
* update dependency lru-cache to v10.0.3 (#12175) (c404ef73)
* update dependency ace-builds to v1.31.2 (#12168) (b4a41af9)
* update dependency mongodb to v6.3.0 (#12170) (85936a59)
* update dependency lru-cache to v10.0.2 (#12160) (9d18d3c7)
* update dependency helmet to v7.1.0 (#12155) (50aa1a7c)
* update dependency async to v3.2.5 (#12150) (0e9eafa1)
* update dependency nodebb-theme-persona to v13.2.44 (#12149) (b489af06)
* update dependency nodebb-theme-harmony to v1.1.95 (#12148) (77b0baea)
* update dependency connect-pg-simple to v9.0.1 (#12144) (4e23d0d1)
* update dependency nodebb-theme-persona to v13.2.43 (#12140) (91e45fa2)
* update dependency nodebb-theme-harmony to v1.1.92 (#12131) (a3452c8f)
* update dependency nodebb-theme-peace to v2.1.24 (#12139) (29a59b90)
* update dependency spdx-license-list to v6.8.0 (2337d641)
* update dependency nodebb-plugin-composer-default to v10.2.25 (#12136) (cdb40719)
* update dependency cron to v3.1.6 (#12132) (2fb3af3c)
* update dependency ace-builds to v1.31.1 (#12135) (6b28f1dc)
* update dependency workerpool to v8 (#12121) (9bed7646)
* update dependency sass to v1.69.5 (#12128) (b351c00a)
* **socket.io:** update getPosts controller to return object containing posts instead of straight array (54000aab)
##### Other Changes
* **socket.io:** deprecate socketGroups.getChatGroups in favour of api.admin.listGroups (dc4cc74f)
##### Refactors
* line breaks good (4e560ade)
* replace deprecated call with api call (f91b823e)
* move async call to parallel (dabc282d)
* **socket.io:**
* deprecate SocketModules.chats.typing in favour of api.chats.toggleTyping (c1e6be77)
* deprecate SocketModules.chats.loadPinnedMessages in favour of api.chats.getPinnedMessages" (401e8636)
* deprecate SocketModules.chats.searchMessages in favour of api.search.roomMessages (f9dc3502)
* deprecate SocketModules.chats.setNotificationSetting in favour of api.chats.watch (75c8cda1)
* deprecated SocketModules.chats.toggleOwner in favour of api.chats.toggleOwner (b13c6ee4)
* deprecated SocketModules.chats.searchMembers in favour of api.search.roomUsers (6e952263)
* deprecate SocketModules.sortPublicRooms in favour of api.chats.sortPublicRooms (62b7dfd4)
* deprecate SocketModules.chat.getUnreadCount in favour of api.chats.getUnread (5eaffb42)
* deprecate SocketModules.chats.getIP in favour of api.chats.getIpAddress (214989a8)
* deprecate SocketModules.chats.hasPrivateChat in favour of api.users.getPrivateRoomId (0d3c94e6)
* deprecate SocketModules.chats.canMessage and .markAllRead with no alternative. deprecate .getRecentChats in favour of api.chats.list (a4133500)
* deprecate SocketModules.chats.isDnD in favour of api.users.getStatus (eebea4df)
* deprecate SocketModules.chats.getRaw in favour of api.chats.getRawMessage (c4b4e79b)
* deprecate socketGroups.loadMoreMembers in favour of api.groups.listMembers (807d778c)
* deprecate socketGroups.searchMembers in favour of api.groups.listMembers (d2f3333a)
* deprecate socketGroups.loadMore in favour of api.groups.list (b61e8147)
* deprecate categories.categorySearch in favour of api.search.categories (00de9d5b)
* deprecate categories.loadMore in favour of api.categories.getTopics (1ce4ca54)
* deprecate categories.loadMoreSubCategories in favour of api.categories.getChildren (010727f5)
* deprecate categories.setWatchState in favour of api.categories.setWatchState (d7c6b3d6)
* deprecate categories.getTopicCount in favour of api.categories.getTopicCount (c442b6e6)
* deprecate categories.get in favour of api.categories.list (96046373)
* deprecate categories.getRecentReplies in favour of api.categories.getPosts (52b78e83)
##### Tests
* migrate socket modules tests to v3 api (445b70de)
* migrate socket.io groups tests to use api v3 (2c1c4dfe)
#### v3.5.3 (2023-12-13)
##### Chores

View File

@@ -176,7 +176,7 @@
"onlineCutoff": 30,
"timeagoCutoff": 30,
"necroThreshold": 7,
"categoryWatchState": "tracking",
"categoryWatchState": "watching",
"submitPluginUsage": 1,
"showAverageApprovalTime": 1,
"autoApproveTime": 0,

View File

@@ -2,7 +2,7 @@
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "3.6.7",
"version": "3.5.3",
"homepage": "https://www.nodebb.org",
"repository": {
"type": "git",
@@ -38,6 +38,8 @@
"archiver": "6.0.1",
"async": "3.2.5",
"autoprefixer": "10.4.16",
"axios": "1.6.2",
"axios-cookiejar-support": "4.0.7",
"bcryptjs": "2.4.3",
"benchpressjs": "2.5.1",
"body-parser": "1.20.2",
@@ -67,8 +69,8 @@
"express": "4.18.2",
"express-session": "1.17.3",
"express-useragent": "1.0.15",
"fetch-cookie": "2.1.0",
"file-loader": "6.2.0",
"form-data": "4.0.0",
"fs-extra": "11.2.0",
"graceful-fs": "4.2.11",
"helmet": "7.1.0",
@@ -94,7 +96,7 @@
"multiparty": "4.2.3",
"nconf": "0.12.1",
"nodebb-plugin-2factor": "7.4.0",
"nodebb-plugin-composer-default": "10.2.31",
"nodebb-plugin-composer-default": "10.2.29",
"nodebb-plugin-dbsearch": "6.2.3",
"nodebb-plugin-emoji": "5.1.13",
"nodebb-plugin-emoji-android": "4.0.0",
@@ -103,10 +105,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-lavender": "7.1.7",
"nodebb-theme-peace": "2.1.26",
"nodebb-theme-persona": "13.2.50",
"nodebb-theme-harmony": "1.1.103",
"nodebb-theme-lavender": "7.1.5",
"nodebb-theme-peace": "2.1.25",
"nodebb-theme-persona": "13.2.49",
"nodebb-widget-essentials": "7.0.14",
"nodemailer": "6.9.7",
"nprogress": "0.2.0",
@@ -127,7 +129,7 @@
"sass": "1.69.5",
"semver": "7.5.4",
"serve-favicon": "2.5.0",
"sharp": "0.32.6",
"sharp": "0.33.0",
"sitemap": "7.1.1",
"socket.io": "4.7.2",
"socket.io-client": "4.7.2",
@@ -146,7 +148,7 @@
"webpack": "5.89.0",
"webpack-merge": "5.10.0",
"winston": "3.11.0",
"workerpool": "9.0.1",
"workerpool": "8.0.0",
"xml": "1.0.1",
"xregexp": "5.1.1",
"yargs": "17.7.2",
@@ -157,9 +159,9 @@
"@commitlint/cli": "18.4.3",
"@commitlint/config-angular": "18.4.3",
"coveralls": "3.1.1",
"eslint": "8.56.0",
"eslint": "8.55.0",
"eslint-config-nodebb": "0.2.1",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-import": "2.29.0",
"grunt": "1.6.1",
"grunt-contrib-watch": "1.1.0",
"husky": "8.0.3",
@@ -181,7 +183,7 @@
"url": "https://github.com/NodeBB/NodeBB/issues"
},
"engines": {
"node": ">=18"
"node": ">=16"
},
"maintainers": [
{
@@ -195,4 +197,4 @@
"url": "https://github.com/barisusakli"
}
]
}
}

View File

@@ -1,41 +0,0 @@
<html>
<head>
<title>Internal Server Error</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/assets/5xx.css" />
<script type="text/javascript">
window.onload = function() {
let count = 0;
const bounce = document.getElementById('click-me');
bounce.onclick = function() {
count++;
bounce.className = '';
setTimeout(function() {
bounce.className = 'animated bounce';
}, 50);
if (count > 5) {
document.getElementById('hide').className = '';
}
};
}
</script>
</head>
<body>
<div class="wrapper">
<div class="center">
<h1 id="click-me" class="animated bounce">500</h1>
<p>
<strong>Internal server error. </strong>
</p>
<p>
{message}
</p>
<p>
&nbsp;<small id="hide" class="hide">Alright. You can stop clicking... it's not going to make the site come back sooner!</small>
</p>
</div>
</div>
</body>
</html>

View File

@@ -2,12 +2,147 @@
<head>
<title>Excessive Load Warning</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/assets/5xx.css" />
<style type="text/css">
body {
background: #00A9EA;
color: white;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
text-align: center;
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
}
h1 {
font-size: 250px;
color: #fff;
opacity: 0.5;
margin: 10px;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
}
p {
font-size: 20px;
}
p strong {
font-size: 28px;
}
@media (max-width: 640px) {
h1 {
font-size: 125px;
}
p {
font-size: 16px;
}
p strong {
font-size: 20px;
}
}
.center {
position: relative;
top: 50%;
-webkit-transform: translateY(50%);
-ms-transform: translateY(50%);
transform: translateY(50%);
}
@-webkit-keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
@keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
.bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-transform-origin: center bottom;
-ms-transform-origin: center bottom;
transform-origin: center bottom;
}
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated.infinite {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.hinge {
-webkit-animation-duration: 2s;
animation-duration: 2s;
}
.hide {
display: none;
}
</style>
<script type="text/javascript">
window.onload = function() {
let count = 0;
const bounce = document.getElementById('click-me');
var count = 0,
bounce = document.getElementById('click-me');
bounce.onclick = function() {
count++;
bounce.className = '';

View File

@@ -1,135 +0,0 @@
body {
background: #00A9EA;
color: white;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
text-align: center;
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
}
h1 {
font-size: 250px;
color: #fff;
opacity: 0.5;
margin: 10px;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
}
p {
font-size: 20px;
}
p strong {
font-size: 28px;
}
@media (max-width: 640px) {
h1 {
font-size: 125px;
}
p {
font-size: 16px;
}
p strong {
font-size: 20px;
}
}
.center {
position: relative;
top: 50%;
-webkit-transform: translateY(50%);
-ms-transform: translateY(50%);
transform: translateY(50%);
}
@-webkit-keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
@keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
.bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-transform-origin: center bottom;
-ms-transform-origin: center bottom;
transform-origin: center bottom;
}
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated.infinite {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.hinge {
-webkit-animation-duration: 2s;
animation-duration: 2s;
}
.hide {
display: none;
}

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "رقم الصفحة غير صحيح ، يجب أن يكون بين %1 و %2 .",
"username-taken": "اسم المستخدم مأخوذ",
"email-taken": "Email address is already taken.",
"email-taken": "البريد الالكتروني مأخوذ",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -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": "Публикуването в някои категории и теми ще бъде възможно едва след като е-пощата Ви бъде потвърдена. Щръкнете тук, за да Ви изпратим е-писмо за потвърждение.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
"username-taken": "ইউজারনেম আগেই ব্যবহৃত",
"email-taken": "Email address is already taken.",
"email-taken": "ইমেইল আগেই ব্যবহৃত",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Neplatná hodnota stránkování, musí být alespoň %1 a nejvýše %2",
"username-taken": "Uživatelské jméno je již použito",
"email-taken": "Email address is already taken.",
"email-taken": "Tento e-mail je již použit",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Ugyldig side værdi, skal mindst være %1 og maks. %2",
"username-taken": "Brugernavn optaget",
"email-taken": "Email address is already taken.",
"email-taken": "Emailadresse allerede i brug",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Ordner existiert",
"invalid-pagination-value": "Ungültige Seitennummerierung, muss mindestens %1 und maximal %2 sein",
"username-taken": "Der Benutzername ist bereits vergeben",
"email-taken": "Email address is already taken.",
"email-taken": "E-Mail-Adresse vergeben",
"email-nochange": "Die eingegebene E-Mail ist die gleiche wie die bereits hinterlegte E-Mail.",
"email-invited": "E-Mail wurde bereits eingeladen",
"email-not-confirmed": "Das Schreiben von Beiträgen in einigen Kategorien oder Themen ist erst möglich, wenn Ihre E-Mail bestätigt wurde. Bitte klicken Sie hier, um eine Bestätigungs-E-Mail zu senden.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
"username-taken": "Το όνομα χρήστη είναι πιασμένο",
"email-taken": "Email address is already taken.",
"email-taken": "Το email είναι πιασμένο",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -37,7 +37,7 @@
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
"username-taken": "Username taken",
"email-taken": "Email address is already taken.",
"email-taken": "Email taken",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
@@ -255,7 +255,6 @@
"no-connection": "There seems to be a problem with your internet connection",
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin &ndash; only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
"username-taken": "Username taken",
"email-taken": "Email address is already taken.",
"email-taken": "Email taken",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
"username-taken": "Username taken",
"email-taken": "Email address is already taken.",
"email-taken": "Email taken",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Número de página inválido, debe estar entre %1 y %2",
"username-taken": "Nombre de usuario ocupado",
"email-taken": "Email address is already taken.",
"email-taken": "Correo electrónico ocupado",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Väär lehekülje numeratsioon, peab olema vähemalt %1 ja kõige rohkem %2",
"username-taken": "Kasutajanimi on juba võetud",
"email-taken": "Email address is already taken.",
"email-taken": "Email on võetud",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "ارزش گذاری صفحه نامعتبر است، کمترین مقدار <strong>%1</strong> و بیشترین مقدار <strong>%2</strong> باید باشد",
"username-taken": "این نام کاربری گرفته شده است.",
"email-taken": "Email address is already taken.",
"email-taken": "این ایمیل گرفته شده است.",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "ایمیل قبلا دعوت شده‌است",
"email-not-confirmed": "پس از تایید ایمیل شما، ارسال در برخی دسته ها یا موضوعات فعال می شود، لطفاً برای ارسال ایمیل تایید اینجا را کلیک کنید.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
"username-taken": "Käyttäjänimi varattu",
"email-taken": "Email address is already taken.",
"email-taken": "Sähköpostiosoite varattu",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Le dossier existe",
"invalid-pagination-value": "Valeur de pagination invalide. Celle-ci doit être comprise entre %1 et %2.",
"username-taken": "Ce nom d'utilisateur est déjà pris",
"email-taken": "Email address is already taken.",
"email-taken": "E-mail déjà utilisé",
"email-nochange": "Le mail saisi est déjà enregistré.",
"email-invited": "Cet utilisateur a déjà été invité.",
"email-not-confirmed": "La publication dans certaines catégories ou sujets sera activée après confirmation de l'e-mail, veuillez cliquer ici pour envoyer un e-mail de confirmation.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Valor de paxinación incorreto, ten que estar entre %1 e %2",
"username-taken": "Nome de usuario en uso",
"email-taken": "Email address is already taken.",
"email-taken": "Enderezo electrónico en uso",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -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": "פרסום בקטגוריות או בנושאים מסוימים מופעל רק לאחר אישור הדוא\"ל שלכם, אנא לחצו כאן כדי לשלוח אימות לדוא\"ל שלכם.",

View File

@@ -48,7 +48,7 @@
"user-flagged-user-multiple": "<strong>%1</strong>, <strong>%2</strong> ו-%3 אחרים דיווחו על פרופיל משתמש (%4)",
"user-posted-to": "<strong>%1</strong> פרסם תגובה ל: <strong>%2</strong>",
"user-posted-to-dual": "<strong>%1</strong> ו<strong>%2</strong> הגיבו ל: <strong>%3</strong>",
"user-posted-to-triple": "<strong>%1</strong>, <strong>%2</strong> ו<strong>%3</strong> הגיבו ל: <strong>%4</strong>",
"user-posted-to-triple": "<strong>%1</strong>, <strong>%2</strong> ו<strong>3%</strong> הגיבו ל: <strong>%4</strong>",
"user-posted-to-multiple": "<strong>%1</strong>, <strong>%2</strong> ו-%3 אחרים הגיבו ל: <strong>%4</strong>",
"user-posted-topic": "<strong>%1</strong> העלה נושא חדש: <strong>%2</strong>",
"user-edited-post": "<strong>%1</strong> ערך פוסט ב: <strong>%2</strong>",
@@ -59,7 +59,7 @@
"user-posted-topic-in-category": "<strong>%1</strong> פרסם נושא חדש ב<strong>%2</strong>",
"user-started-following-you": "<strong>%1</strong> התחיל לעקוב אחריך.",
"user-started-following-you-dual": "<strong>%1</strong> ו-<strong>%2</strong> התחילו לעקוב אחריך.",
"user-started-following-you-triple": "<strong>%1</strong>, <strong>%2</strong> ו<strong>%3</strong> התחילו לעקוב אחריך.",
"user-started-following-you-triple": "<strong>%1</strong>, <strong>%2</strong> ו<strong>3%</strong> התחילו לעקוב אחריך.",
"user-started-following-you-multiple": "<strong>%1</strong>, <strong>%2</strong> ו-%3 אחרים התחילו לעקוב אחריך.",
"new-register": "<strong>%1</strong> שלח בקשת הרשמה.",
"new-register-multiple": "ישנן <strong>%1</strong> בקשות הרשמה שמחכות לבדיקה.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Netočno numeriranje stranica, mora biti %1 ili %2",
"username-taken": "Korisničko ime je zauzeto",
"email-taken": "Email address is already taken.",
"email-taken": "Email je zauzet",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Mappa létezik",
"invalid-pagination-value": "Érvénytelen lapozási érték, legalább %1 kell lennie és legfeljebb %2 -nak/nek",
"username-taken": "Foglalt felhasználónév",
"email-taken": "Email address is already taken.",
"email-taken": "Foglalt e-mail",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Ez az email cím már meg lett hívva",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -8,7 +8,7 @@
"edit-privileges": "Խմբագրել արտոնությունները",
"select-clear-all": "Ընտրել/Մաքրել բոլորը",
"chat": "Զրույց",
"chat-with-privileged": "Խոսել առավելություն ունեցողի հետ",
"chat-with-privileged": "Chat with Privileged",
"upload-images": "Վերբեռնեք պատկերներ",
"upload-files": "Վերբեռնել Ֆայլեր",
"signature": "Ստորագրություն",

View File

@@ -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": "Որոշ կատեգորիաներում կամ թեմաներում հրապարակելը միացված կլինի, երբ ձեր էլփոստը հաստատվի, խնդրում ենք սեղմել այստեղ՝ հաստատող էլփոստը ուղարկելու համար:",

View File

@@ -178,7 +178,7 @@
"sessions.description": "Այս էջը թույլ է տալիս դիտել ցանկացած ակտիվ սեանս այս ֆորումում և անհրաժեշտության դեպքում չեղարկել դրանք: Դուք կարող եք չեղարկել ձեր սեփական սեանսը՝ դուրս գալով ձեր հաշվից:",
"revoke-session": "Չեղյալ համարել նիստը",
"browser-version-on-platform": "%1 %2 %3-ում",
"consent.title": "Ձեր Իրավունքները և Համաձայնությունը",
"consent.title": "Your Rights &amp; Consent",
"consent.lead": "Այս համայնքի ֆորումը հավաքում և մշակում է ձեր անձնական տվյալները:",
"consent.intro": "Մենք օգտագործում ենք այս տեղեկատվությունը խստորեն այս համայնքում ձեր փորձառությունն անհատականացնելու, ինչպես նաև ձեր կատարած գրառումները ձեր օգտատիրոջ հաշվին կապելու համար: Գրանցման քայլի ընթացքում ձեզանից պահանջվել է տրամադրել օգտատիրոջ անուն և էլ.փոստի հասցե, դուք կարող եք նաև լրացուցիչ տեղեկություններ տրամադրել այս կայքում ձեր օգտատիրոջ պրոֆիլը լրացնելու համար: Մենք պահպանում ենք այս տեղեկատվությունը ձեր օգտատիրոջ հաշվի ողջ կյանքի ընթացքում, և դուք կարող եք հետ վերցնել համաձայնությունը: ցանկացած պահի ջնջելով ձեր հաշիվը: Ցանկացած ժամանակ դուք կարող եք պահանջել ձեր ներդրման պատճենը այս կայքում՝ ձեր իրավունքների և amp; Համաձայնության էջ: Եթե ունեք հարցեր կամ մտահոգություններ, խորհուրդ ենք տալիս դիմել այս ֆորումի ադմինիստրատիվ թիմին:",
"consent.email-intro": "Երբեմն, մենք կարող ենք նամակներ ուղարկել ձեր գրանցված էլ․ հասցեին՝ թարմացումներ տրամադրելու և/կամ ձեզ ծանուցելու նոր գործունեության մասին, որը վերաբերում է ձեզ: Դուք կարող եք հարմարեցնել համայնքի ամփոփման հաճախականությունը (ներառյալ այն ուղղակիորեն անջատելը), ինչպես նաև ընտրել, թե ինչ տեսակի ծանուցումներ պետք է ստանալ էլփոստի միջոցով՝ ձեր օգտվողի կարգավորումների էջի միջոցով:",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Nomor pagination tidak valid, minimal %1 dan maksimal %2",
"username-taken": "Username sudah terdaftar",
"email-taken": "Email address is already taken.",
"email-taken": "Email sudah terdaftar",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "La cartella esiste",
"invalid-pagination-value": "Valore di impaginazione non valido, deve essere almeno %1 ed al massimo %2",
"username-taken": "Nome utente già esistente",
"email-taken": "Email address is already taken.",
"email-taken": "Email già esistente",
"email-nochange": "L'email inserita è la stessa dell'email già presente in archivio.",
"email-invited": "L'email è già stata invitata",
"email-not-confirmed": "Sarai abilitato a postare in alcune categorie o discussioni una volta che la tua email sarà confermata, per favore clicca qui per inviare una email di conferma.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "無効なページネーション値です。%1 から%2の値でなければありません。",
"username-taken": "ユーザー名は既に使われています",
"email-taken": "Email address is already taken.",
"email-taken": "メールアドレスは既に使われています",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -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": "이메일 인증이 완료된 후 카테고리나 화제에 새로운 포스트를 작성할 수 있습니다. 여기를 눌러 인증 메일을 다시 발송할 수 있습니다.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Bloga puslapių išdėstymo reikšmė. Ji turėtų būti ne mažesnė nei %1 ir ne didesnė nei %2",
"username-taken": "Vartotojo vardas jau užimtas",
"email-taken": "Email address is already taken.",
"email-taken": "El. pašto adresas jau užimtas",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Nederīgs vienību skaits, ir jābūt vismaz %1 un ne vairāk kā %2",
"username-taken": "Lietotājvārds jau izmantots",
"email-taken": "Email address is already taken.",
"email-taken": "E-pasta adrese jau izmantota",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Nombor halaman tidak sah, mesti tidak kurang dari %1 dan tidak lebih dari %2",
"username-taken": "Nama pengguna telah digunakan",
"email-taken": "Email address is already taken.",
"email-taken": "Emel telah digunakan",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Mappen eksisterer",
"invalid-pagination-value": "Ugyldig sidetall, må være minst %1 og maks %2",
"username-taken": "Brukernavn opptatt",
"email-taken": "Email address is already taken.",
"email-taken": "E-post opptatt",
"email-nochange": "E-posten som er angitt er den samme e-posten som allerede er lagret.",
"email-invited": "E-post har allerede fått invitasjon",
"email-not-confirmed": "Posting i enkelte kategorier eller emner blir aktivert når e-posten din er bekreftet. Klikk her for å sende en bekreftelses-e-post.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Ongeldig paginering waarde. De waarde moet op z'n minst %1 zijn en niet hoger dan %2 zijn.",
"username-taken": "Gebruikersnaam is al in gebruik",
"email-taken": "Email address is already taken.",
"email-taken": "E-mailadres is al in gebruik",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "E-mail was reeds uitgenodigd",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder istnieje",
"invalid-pagination-value": "Błędna wartość paginacji, zakres od %1 do %2",
"username-taken": "Login zajęty",
"email-taken": "Email address is already taken.",
"email-taken": "Email zajęty",
"email-nochange": "Podany email jest taki sam jak ten już zapisany.",
"email-invited": "Ten adres email otrzymał już zaproszenie",
"email-not-confirmed": "Pisanie w niektórych kategoriach albo tematach jest dozwolone wtedy gdy Twój adres email został zweryfikowany, proszę kliknij tutaj aby wysłać potwierdzający email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Valor de paginação inválido, precisa ser no mínimo %1 e no máximo %2",
"username-taken": "Nome de usuário já existe",
"email-taken": "Email address is already taken.",
"email-taken": "Email já cadastrado",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "O email já foi convidado",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Valor de paginação errado, deve ser no mínimo %1 e no máximo %2",
"username-taken": "Nome de utilizar já utilizado",
"email-taken": "Email address is already taken.",
"email-taken": "E-mail já utilizado",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
"username-taken": "Numele de utilizator este deja folosit",
"email-taken": "Email address is already taken.",
"email-taken": "Adresa de email este deja folostă",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Папка существует",
"invalid-pagination-value": "Неправильно указан номер страницы. Значение должно быть в диапазоне от %1 до %2",
"username-taken": "Это имя пользователя уже занято",
"email-taken": "Email address is already taken.",
"email-taken": "Пользователь с таким адресом электронной почты уже зарегистрирован",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Электронная почта уже была приглашена",
"email-not-confirmed": "Вы не сможете отправлять сообщения, пока ваш адрес электронной почты не подтверждён. Пожалуйста, нажмите здесь, чтобы подтвердить его.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
"username-taken": "Izina ryarafashwe mbere",
"email-taken": "Email address is already taken.",
"email-taken": "Email yarafashwe mbere",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
"username-taken": "Username taken",
"email-taken": "Email address is already taken.",
"email-taken": "Email taken",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Neplatná stránkovania hodnota, musí byť najmenej %1 a najviac %2",
"username-taken": "Užívateľské meno je už obsadené",
"email-taken": "Email address is already taken.",
"email-taken": "Tento e-mail je už obsadený",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Napačna vrednost za številčenje strani. Vrednost mora biti najmanj %1 in največ %2.",
"username-taken": "Uporabniško ime je že zasedeno.",
"email-taken": "Email address is already taken.",
"email-taken": "E-poštni naslov je že zaseden.",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Ky dokument ekziston",
"invalid-pagination-value": "Vlera e pasaktë e faqes, duhet të jetë së paku %1 dhe maksimumi %2",
"username-taken": "Username është i zënë",
"email-taken": "Email address is already taken.",
"email-taken": "Email-i është i zënë",
"email-nochange": "Email-i i futur është i njëjtë me emailin ekzistues në sistem.",
"email-invited": "Email-i është ftuar më herët",
"email-not-confirmed": "Postimi në disa kategori ose tema aktivizohet pasi emaili juaj të konfirmohet, ju lutemi klikoni këtu për të dërguar një email konfirmimi.",

View File

@@ -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": "Објављивање у неким категоријама или темама је омогућено када потврдите вашу е-пошту, кликните овде да бисте послали е-поруку за потврду.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Ogiltigt värde för siduppdelning. Värdet måste vara mellan %1 och %2",
"username-taken": "Användarnamn upptaget",
"email-taken": "Email address is already taken.",
"email-taken": "Epostadress upptagen",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "หมายเลขหน้าไม่ถูกต้อง จำเป็นต้องเป็นตัวเลขอย่างน้อย %1 และอย่างมาก %2",
"username-taken": "ชื่อผู้ใช้นี้มีการใช้แล้ว",
"email-taken": "Email address is already taken.",
"email-taken": "อีเมลนี้มีการใช้แล้ว",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Dosya mevcut",
"invalid-pagination-value": "Geçersiz sayfa numarası girdiniz, en az %1 ve en fazla %2 olabilir",
"username-taken": "Kullanıcı İsmi Alınmış",
"email-taken": "Email address is already taken.",
"email-taken": "E-posta Alınmış",
"email-nochange": "Girdiğiniz e-posta var olan e-posta ile aynı",
"email-invited": "E-posta halihazırda davet edilmiş",
"email-not-confirmed": "Ancak e-postanız onaylandıktan sonra bazı kategorilere veya konulara ileti gönderebilirsiniz; lütfen bir onay e-postası almak için buraya tıklayın.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "Невірне значення сторінки, має бути щонайменше %1 та щонайбільше %2",
"username-taken": "Це ім'я зайняте",
"email-taken": "Email address is already taken.",
"email-taken": "Ця електронна пошта зайнята",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Thư mục tồn tại",
"invalid-pagination-value": "Giá trị phân trang không hợp lệ, tối thiểu phải là %1 và tối đa là %2",
"username-taken": "Tên đăng nhập đã tồn tại",
"email-taken": "Email address is already taken.",
"email-taken": "Email đã được đăng kí",
"email-nochange": "Email đã nhập giống với email đã có trong tệp.",
"email-invited": "Email đã được mời",
"email-not-confirmed": "Đăng trong một số danh mục hoặc chủ đề được bật sau khi email của bạn được xác nhận, vui lòng nhấp vào đây để gửi email xác nhận.",

View File

@@ -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": "您需要验证您的邮箱后才能在版块或主题中发布帖子,请点击此处以发送验证邮件。",

View File

@@ -32,7 +32,7 @@
"folder-exists": "Folder exists",
"invalid-pagination-value": "無效的分頁數,必須介於 %1 和 %2 之間",
"username-taken": "此使用者名已被使用",
"email-taken": "Email address is already taken.",
"email-taken": "此電子信箱已被使用",
"email-nochange": "The email entered is the same as the email already on file.",
"email-invited": "Email was already invited",
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",

View File

@@ -117,7 +117,7 @@ get:
categoryWatchState:
type: object
properties:
tracking:
watching:
type: boolean
disableCustomUserSkins:
type: number

View File

@@ -195,7 +195,7 @@ define('admin/extend/plugins', [
let html = '';
activePlugins.forEach(function (plugin) {
html += `
<li class="d-flex justify-content-between gap-1 pointer border-bottom pb-2" data-plugin="${plugin}">
<li class="d-flex justify-content-between gap-1 pointer border-bottom pb-2">
${plugin}
<div class="d-flex gap-1">
<div class="btn-ghost-sm move-up">
@@ -233,7 +233,7 @@ define('admin/extend/plugins', [
const plugins = $('#order-active-plugins-modal .plugin-list').children();
const data = [];
plugins.each(function (index, el) {
data.push({ name: $(el).attr('data-plugin'), order: index });
data.push({ name: $(el).text(), order: index });
});
socket.emit('admin.plugins.orderActivePlugins', data, function (err) {

View File

@@ -5,7 +5,7 @@ define('admin/manage/digest', ['bootbox', 'alerts'], function (bootbox, alerts)
const Digest = {};
Digest.init = function () {
$('.digest').on('click', '[data-action]', function () {
$('table').on('click', '[data-action]', function () {
const action = this.getAttribute('data-action');
const uid = this.getAttribute('data-uid');

View File

@@ -100,6 +100,7 @@ define('admin/settings/navigation', [
translator.translate(li, function (li) {
li = $(translator.unescape(li));
$('#enabled').append(li);
componentHandler.upgradeDom();
resolve();
});
});

View File

@@ -150,7 +150,7 @@ ajaxify.widgets = { render: render };
if (data) {
let status = parseInt(data.status, 10);
if ([400, 403, 404, 500, 502, 503].includes(status)) {
if ([400, 403, 404, 500, 502, 504].includes(status)) {
if (status === 502 && retry) {
retry = false;
ajaxifyTimer = undefined;

View File

@@ -127,6 +127,7 @@ define('forum/category', [
hooks.fire('action:topics.loading');
const params = utils.params();
infinitescroll.loadMore(`/categories/${ajaxify.data.cid}/topics`, {
cid: ajaxify.data.cid,
after: after,
direction: direction,
query: params,

View File

@@ -35,7 +35,7 @@ define('forum/chats/recent', ['alerts', 'api', 'chat'], function (alerts, api, c
return;
}
recentChats.attr('loading', 1);
api.get(`/chats`, {
app.get(`/chats`, {
uid: ajaxify.data.uid,
after: recentChats.attr('data-nextstart'),
}).then(({ rooms, nextStart }) => {

View File

@@ -92,7 +92,7 @@ define('forum/groups/memberlist', ['api', 'bootbox', 'alerts'], function (api, b
const searchEl = $('[component="groups/members/search"]');
searchEl.on('keyup', utils.debounce(function () {
const query = searchEl.val();
api.get(`/groups/${ajaxify.data.group.slug}/members`, { query }, function (err, results) {
api.get(`/groups/${groupName}/members`, { query }, function (err, results) {
if (err) {
return alerts.error(err);
}

View File

@@ -224,7 +224,7 @@ define('forum/topic', [
btn.find('i').removeClass('fa-copy').addClass('fa-check');
setTimeout(() => btn.find('i').removeClass('fa-check').addClass('fa-copy'), 2000);
const codeEl = btn.parent().find('code');
if (codeEl.attr('data-lines') && codeEl.find('.hljs-ln-code[data-line-number]').length) {
if (codeEl.attr('data-lines')) {
return codeEl.find('.hljs-ln-code[data-line-number]')
.map((i, e) => e.textContent).get().join('\n');
}

View File

@@ -10,8 +10,7 @@ define('forum/topic/events', [
'components',
'translator',
'hooks',
'helpers',
], function (postTools, threadTools, posts, images, components, translator, hooks, helpers) {
], function (postTools, threadTools, posts, images, components, translator, hooks) {
const Events = {};
const events = {
@@ -153,7 +152,7 @@ define('forum/topic/events', [
editorEl.replaceWith(html);
postContainer.find('[component="post/edit-indicator"]')
.removeClass('hidden')
.translateAttr('title', `[[global:edited-timestamp, ${helpers.isoTimeToLocaleString(editData.editedISO, config.userLang)}]]`);
.translateAttr('title', `[[global:edited-timestamp, ${editData.editedISO}]]`);
postContainer.find('[component="post/editor"] .timeago').timeago();
hooks.fire('action:posts.edited', data);
});

View File

@@ -149,7 +149,7 @@ define('forum/topic/move-post', [
$(this).remove();
});
});
if (data.pids.length && ajaxify.data.template.topic &&
if (data.pids.length === 1 && ajaxify.data.template.topic &&
parseInt(data.tid, 10) === parseInt(ajaxify.data.tid, 10)) {
ajaxify.go(`/post/${data.pids[0]}`);
}

View File

@@ -353,8 +353,7 @@ define('navigator', [
}
async function updateUnreadIndicator(index) {
const { bookmarkThreshold } = ajaxify.data;
if (!paginationBlockUnreadEl.length || ajaxify.data.postcount <= bookmarkThreshold || !bookmarkThreshold) {
if (!paginationBlockUnreadEl.length || ajaxify.data.postcount <= ajaxify.data.bookmarkThreshold) {
return;
}
const currentBookmark = ajaxify.data.bookmark || storage.getItem('topic:' + ajaxify.data.tid + ':bookmark');
@@ -459,9 +458,11 @@ define('navigator', [
};
function toggle(flag) {
if (flag && (!ajaxify.data.template.topic && !ajaxify.data.template.category)) {
const path = ajaxify.removeRelativePath(window.location.pathname.slice(1));
if (flag && (!path.startsWith('topic') && !path.startsWith('category'))) {
return;
}
paginationBlockEl.toggleClass('ready', flag);
paginationBlockEl.toggleClass('noreplies', count <= 1);
}

View File

@@ -2,10 +2,10 @@
define('quickreply', [
'components', 'composer', 'composer/autocomplete', 'api',
'alerts', 'uploadHelpers', 'mousetrap', 'storage', 'hooks',
'alerts', 'uploadHelpers', 'mousetrap', 'storage',
], function (
components, composer, autocomplete, api,
alerts, uploadHelpers, mousetrap, storage, hooks
alerts, uploadHelpers, mousetrap, storage
) {
const QuickReply = {};
@@ -91,7 +91,6 @@ define('quickreply', [
components.get('topic/quickreply/text').val('');
storage.removeItem(qrDraftId);
autocomplete._active.core_qr.hide();
hooks.fire('action:quickreply.success', { data });
});
});

View File

@@ -12,8 +12,8 @@ define('sort', ['components'], function (components) {
currentSetting.find('i').addClass('fa-check');
$('body')
.off('click', '[component="thread/sort"] a[data-sort]')
.on('click', '[component="thread/sort"] a[data-sort]', function () {
.off('click', '[component="thread/sort"] a')
.on('click', '[component="thread/sort"] a', function () {
const newSetting = $(this).attr('data-sort');
const urlParams = utils.params();
urlParams.sort = newSetting;

View File

@@ -10,6 +10,7 @@ const isPrerelease = /^v?\d+\.\d+\.\d+-.+$/;
const latestReleaseUrl = 'https://api.github.com/repos/NodeBB/NodeBB/releases/latest';
async function getLatestVersion() {
return '';
const headers = {
Accept: 'application/vnd.github.v3+json',
'User-Agent': encodeURIComponent(`NodeBB Admin Control Panel/${meta.config.title}`),
@@ -18,24 +19,25 @@ async function getLatestVersion() {
if (versionCacheLastModified) {
headers['If-Modified-Since'] = versionCacheLastModified;
}
try {
const { body: latestRelease, response } = await request.get(latestReleaseUrl, {
headers: headers,
timeout: 2000,
});
const { body: latestRelease, response } = await request.get(latestReleaseUrl, {
headers: headers,
timeout: 2000,
});
if (response.statusCode === 304) {
if (!latestRelease || !latestRelease.tag_name) {
throw new Error('[[error:cant-get-latest-release]]');
}
const tagName = latestRelease.tag_name.replace(/^v/, '');
versionCache = tagName;
versionCacheLastModified = response.headers['last-modified'];
return versionCache;
} catch (err) {
if (err.response && err.response.status === 304) {
return versionCache;
}
throw err;
}
if (response.statusCode !== 200) {
throw new Error(response.statusText);
}
if (!latestRelease || !latestRelease.tag_name) {
throw new Error('[[error:cant-get-latest-release]]');
}
const tagName = latestRelease.tag_name.replace(/^v/, '');
versionCache = tagName;
versionCacheLastModified = response.headers['last-modified'];
return versionCache;
}
exports.getLatestVersion = getLatestVersion;

View File

@@ -128,7 +128,7 @@ categoriesAPI.getTopics = async (caller, data) => {
let start = Math.max(0, parseInt(data.after || 0, 10));
if (parseInt(data.direction, 10) === -1) {
if (data.direction === -1) {
start -= infScrollTopicsPerPage;
}

View File

@@ -290,7 +290,7 @@ postsAPI.move = async function (caller, data) {
]);
if (!postDeleted && !topicDeleted) {
socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, 'move', 'notifications:moved-your-post');
socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, 'move', 'notifications:moved_your_post');
}
};

View File

@@ -219,10 +219,7 @@ module.exports = function (Categories) {
min: Categories.watchStates.watching,
max: Categories.watchStates.watching,
});
const index = followers.indexOf(String(exceptUid));
if (index !== -1) {
followers.splice(index, 1);
}
if (!followers.length) {
return;
}
@@ -236,6 +233,7 @@ module.exports = function (Categories) {
const notification = await notifications.create({
type: 'new-topic-in-category',
nid: `new_topic:tid:${postData.topic.tid}:uid:${exceptUid}`,
subject: bodyShort,
bodyShort: bodyShort,
bodyLong: postData.content,
pid: postData.pid,

View File

@@ -114,9 +114,7 @@ if (!configExists && process.argv[2] !== 'setup') {
return;
}
if (configExists) {
process.env.CONFIG = configFile;
}
process.env.CONFIG = configFile;
// running commands
program

View File

@@ -7,7 +7,7 @@ const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const request = require('../request');
const { paths, pluginNamePattern } = require('../constants');
const pkgInstall = require('./package-install');
@@ -74,11 +74,7 @@ async function getCurrentVersion() {
}
async function getSuggestedModules(nbbVersion, toCheck) {
const request = require('../request');
let { response, body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?version=${nbbVersion}&package[]=${toCheck.join('&package[]=')}`);
if (!response.ok) {
throw new Error(`Unable to get suggested module for NodeBB(${nbbVersion}) ${toCheck.join(',')}`);
}
let { body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?version=${nbbVersion}&package[]=${toCheck.join('&package[]=')}`);
if (!Array.isArray(body) && toCheck.length === 1) {
body = [body];
}

View File

@@ -42,6 +42,7 @@ groupsController.get = async function (req, res, next) {
if (!group || groupName === groups.BANNED_USERS) {
return next();
}
group.isOwner = true;
const groupNameData = groupNames.map(name => ({
encodedName: encodeURIComponent(name),

View File

@@ -1,10 +1,8 @@
'use strict';
const fs = require('fs');
const nconf = require('nconf');
const winston = require('winston');
const validator = require('validator');
const path = require('path');
const translator = require('../translator');
const plugins = require('../plugins');
const middleware = require('../middleware');
@@ -56,12 +54,6 @@ exports.handleErrors = async function handleErrors(err, req, res, next) { // esl
controllers['404'].handle404(req, res);
};
const notBuiltHandler = async () => {
let file = await fs.promises.readFile(path.join(__dirname, '../../public/500.html'), { encoding: 'utf-8' });
file = file.replace('{message}', 'Failed to lookup view! Did you run `./nodebb build`?');
return res.type('text/html').send(file);
};
const defaultHandler = async function () {
if (res.headersSent) {
return;
@@ -103,8 +95,6 @@ exports.handleErrors = async function handleErrors(err, req, res, next) { // esl
data.cases[err.code](err, req, res, defaultHandler);
} else if (err.message.startsWith('[[error:no-') && err.message !== '[[error:no-privileges]]') {
notFoundHandler();
} else if (err.message.startsWith('Failed to lookup view')) {
notBuiltHandler();
} else {
await defaultHandler();
}

View File

@@ -72,6 +72,7 @@ groupsController.details = async function (req, res, next) {
if (!groupData) {
return next();
}
groupData.isOwner = groupData.isOwner || isAdmin || (isGlobalMod && !groupData.system);
res.render('groups/details', {
title: `[[pages:group, ${groupData.displayName}]]`,

View File

@@ -13,25 +13,17 @@ module.exports = function (module) {
}
value = value.map(v => helpers.valueToString(v));
try {
await module.client.collection('objects').updateOne({
_key: key,
}, {
$addToSet: {
members: {
$each: value,
},
await module.client.collection('objects').updateOne({
_key: key,
}, {
$addToSet: {
members: {
$each: value,
},
}, {
upsert: true,
});
} catch (err) {
if (err && err.message.includes('E11000 duplicate key error')) {
console.log(new Error('e11000').stack, key, value);
return await module.setAdd(key, value);
}
throw err;
}
},
}, {
upsert: true,
});
};
module.setsAdd = async function (keys, value) {

View File

@@ -3,7 +3,6 @@
const user = require('../user');
const db = require('../database');
const plugins = require('../plugins');
const privileges = require('../privileges');
const slugify = require('../slugify');
const Groups = module.exports;
@@ -131,37 +130,30 @@ Groups.get = async function (groupName, options) {
stop = (parseInt(options.userListCount, 10) || 4) - 1;
}
const [groupData, members, isMember, isPending, isInvited, isOwner, isAdmin, isGlobalMod] = await Promise.all([
const [groupData, members, pending, invited, isMember, isPending, isInvited, isOwner] = await Promise.all([
Groups.getGroupData(groupName),
Groups.getOwnersAndMembers(groupName, options.uid, 0, stop),
Groups.getPending(groupName),
Groups.getInvites(groupName),
Groups.isMember(options.uid, groupName),
Groups.isPending(options.uid, groupName),
Groups.isInvited(options.uid, groupName),
Groups.ownership.isOwner(options.uid, groupName),
privileges.admin.can('admin:groups', options.uid),
user.isGlobalModerator(options.uid),
]);
if (!groupData) {
return null;
}
groupData.isOwner = isOwner || isAdmin || (isGlobalMod && !groupData.system);
if (groupData.isOwner) {
([groupData.pending, groupData.invited] = await Promise.all([
Groups.getPending(groupName),
Groups.getInvites(groupName),
]));
}
const descriptionParsed = await plugins.hooks.fire('filter:parse.raw', String(groupData.description || ''));
groupData.descriptionParsed = descriptionParsed;
groupData.members = members;
groupData.membersNextStart = stop + 1;
groupData.pending = pending.filter(Boolean);
groupData.invited = invited.filter(Boolean);
groupData.isMember = isMember;
groupData.isPending = isPending;
groupData.isInvited = isInvited;
groupData.isOwner = isOwner;
const results = await plugins.hooks.fire('filter:group.get', { group: groupData });
return results.group;
};

View File

@@ -4,10 +4,13 @@ const db = require('../database');
const user = require('../user');
module.exports = function (Groups) {
Groups.getUsersFromSet = async function (set, fields = []) {
Groups.getUsersFromSet = async function (set, fields) {
const uids = await db.getSetMembers(set);
const userData = await user.getUsersFields(uids, fields);
return userData.filter(u => u && u.uid);
if (fields) {
return await user.getUsersFields(uids, fields);
}
return await user.getUsersData(uids);
};
Groups.getUserGroups = async function (uids) {

View File

@@ -39,7 +39,7 @@ module.exports = function (middleware) {
const data = {
site_title: meta.config.title || 'NodeBB',
message: meta.config.maintenanceModeMessage,
message: meta.config.maintenanceModeMessage || '',
};
if (res.locals.isAPI) {

View File

@@ -207,38 +207,6 @@ Hooks.hasListeners = function (hook) {
return !!(plugins.loadedHooks[hook] && plugins.loadedHooks[hook].length > 0);
};
function hookHandlerPromise(hook, hookObj, params) {
return new Promise((resolve, reject) => {
let resolved = false;
function _resolve(result) {
if (resolved) {
winston.warn(`[plugins] ${hook} already resolved in plugin ${hookObj.id}`);
return;
}
resolved = true;
resolve(result);
}
const returned = hookObj.method(params, (err, result) => {
if (err) reject(err); else _resolve(result);
});
if (utils.isPromise(returned)) {
returned.then(
payload => _resolve(payload),
err => reject(err)
);
return;
}
if (hook.startsWith('filter:') && returned !== undefined) {
_resolve(returned);
} else if (hook.startsWith('static:') && hookObj.method.length <= 1) {
// make sure it is resolved if static hook doesn't return anything and doesn't use callback
_resolve();
}
});
}
async function fireFilterHook(hook, hookList, params) {
if (!Array.isArray(hookList) || !hookList.length) {
return params;
@@ -255,7 +223,31 @@ async function fireFilterHook(hook, hookList, params) {
if (hookObj.method.constructor && hookObj.method.constructor.name === 'AsyncFunction') {
return await hookObj.method(params);
}
return hookHandlerPromise(hook, hookObj, params);
return new Promise((resolve, reject) => {
let resolved = false;
function _resolve(result) {
if (resolved) {
winston.warn(`[plugins] ${hook} already resolved in plugin ${hookObj.id}`);
return;
}
resolved = true;
resolve(result);
}
const returned = hookObj.method(params, (err, result) => {
if (err) reject(err); else _resolve(result);
});
if (utils.isPromise(returned)) {
returned.then(
payload => _resolve(payload),
err => reject(err)
);
return;
}
if (returned) {
_resolve(returned);
}
});
}
for (const hookObj of hookList) {
@@ -311,7 +303,28 @@ async function fireStaticHook(hook, hookList, params) {
return timeout(hookObj.method(params), 10000, 'timeout');
}
return hookHandlerPromise(hook, hookObj, params);
return new Promise((resolve, reject) => {
let resolved = false;
function _resolve(result) {
if (resolved) {
return;
}
resolved = true;
resolve(result);
}
const returned = hookObj.method(params, (err, result) => {
if (err) reject(err); else _resolve(result);
});
if (utils.isPromise(returned)) {
returned.then(
payload => _resolve(payload),
err => reject(err)
);
return;
}
_resolve();
});
}
for (const hookObj of hookList) {

View File

@@ -153,10 +153,8 @@ Plugins.reloadRoutes = async function (params) {
Plugins.get = async function (id) {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugins/${id}`;
const { response, body } = await request.get(url);
if (!response.ok) {
throw new Error(`[[error:unable-to-load-plugin, ${id}]]`);
}
const { body } = await request.get(url);
let normalised = await Plugins.normalise([body ? body.payload : {}]);
normalised = normalised.filter(plugin => plugin.id === id);
return normalised.length ? normalised[0] : undefined;
@@ -169,10 +167,7 @@ Plugins.list = async function (matching) {
const { version } = require(paths.currentPackage);
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugins${matching !== false ? `?version=${version}` : ''}`;
try {
const { response, body } = await request.get(url);
if (!response.ok) {
throw new Error(`[[error:unable-to-load-plugins-from-nbbpm]]`);
}
const { body } = await request.get(url);
return await Plugins.normalise(body);
} catch (err) {
winston.error(`Error loading ${url}`, err);
@@ -182,10 +177,7 @@ Plugins.list = async function (matching) {
Plugins.listTrending = async () => {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/analytics/top/week`;
const { response, body } = await request.get(url);
if (!response.ok) {
throw new Error(`[[error:unable-to-load-trending-plugins]]`);
}
const { body } = await request.get(url);
return body;
};

View File

@@ -12,7 +12,7 @@ const request = require('../request');
const db = require('../database');
const meta = require('../meta');
const pubsub = require('../pubsub');
const { paths, pluginNamePattern } = require('../constants');
const { paths } = require('../constants');
const pkgInstall = require('../cli/package-install');
const packageManager = pkgInstall.getPackageManager();
@@ -60,9 +60,6 @@ module.exports = function (Plugins) {
winston.error('Cannot activate plugins while plugin state is set in the configuration (config.json, environmental variables or terminal arguments), please modify the configuration instead');
throw new Error('[[error:plugins-set-in-configuration]]');
}
if (!pluginNamePattern.test(id)) {
throw new Error('[[error:invalid-plugin-id]]');
}
const isActive = await Plugins.isActive(id);
if (isActive) {
await db.sortedSetRemove('plugins:active', id);
@@ -77,10 +74,8 @@ module.exports = function (Plugins) {
};
Plugins.checkWhitelist = async function (id, version) {
const { response, body } = await request.get(`https://packages.nodebb.org/api/v1/plugins/${encodeURIComponent(id)}`);
if (!response.ok) {
throw new Error(`[[error:cant-connect-to-nbbpm]]`);
}
const { body } = await request.get(`https://packages.nodebb.org/api/v1/plugins/${encodeURIComponent(id)}`);
if (body && body.code === 'ok' && (version === 'latest' || body.payload.valid.includes(version))) {
return;
}
@@ -89,10 +84,7 @@ module.exports = function (Plugins) {
};
Plugins.suggest = async function (pluginId, nbbVersion) {
const { response, body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?package=${encodeURIComponent(pluginId)}&version=${encodeURIComponent(nbbVersion)}`);
if (!response.ok) {
throw new Error(`[[error:cant-connect-to-nbbpm]]`);
}
const { body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?package=${encodeURIComponent(pluginId)}&version=${encodeURIComponent(nbbVersion)}`);
return body;
};

View File

@@ -27,7 +27,7 @@ module.exports = function (Plugins) {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugin/usage`;
try {
const { response, body } = await request.post(url, {
body: {
data: {
id: hash.digest('hex'),
version: pkg.version,
plugins: Plugins.loadedPlugins,
@@ -35,7 +35,7 @@ module.exports = function (Plugins) {
timeout: 5000,
});
if (!response.ok) {
if (response.status !== 200) {
winston.error(`[plugins.submitUsageData] received ${response.status} ${body}`);
}
} catch (err) {

View File

@@ -1,80 +1,50 @@
'use strict';
const axios = require('axios').default;
const { CookieJar } = require('tough-cookie');
const fetchCookie = require('fetch-cookie');
const { wrapper } = require('axios-cookiejar-support');
wrapper(axios);
exports.jar = function () {
return new CookieJar();
};
async function call(url, method, { body, timeout, jar, ...config } = {}) {
let fetchImpl = fetch;
if (jar) {
fetchImpl = fetchCookie(fetch, jar);
}
const opts = {
async function call(url, method, config = {}) {
const result = await axios({
...config,
method,
headers: {
'content-type': 'application/json',
...config.headers,
},
};
if (timeout > 0) {
opts.signal = AbortSignal.timeout(timeout);
}
if (body && ['POST', 'PUT', 'PATCH', 'DEL', 'DELETE'].includes(method)) {
if (opts.headers['content-type'] && opts.headers['content-type'].startsWith('application/json')) {
opts.body = JSON.stringify(body);
} else {
opts.body = body;
}
}
const response = await fetchImpl(url, opts);
const { headers } = response;
const contentType = headers.get('content-type');
const jsonTest = /application\/([a-z]+\+)?json/;
const isJSON = contentType && jsonTest.test(contentType);
let respBody = await response.text();
if (isJSON && respBody) {
try {
respBody = JSON.parse(respBody);
} catch (err) {
throw new Error('invalid json in response body', url);
}
}
url: url,
});
return {
body: respBody,
body: result.data,
response: {
ok: response.ok,
status: response.status,
statusCode: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
status: result.status,
statusCode: result.status,
statusText: result.statusText,
headers: result.headers,
},
};
}
/*
const { body, response } = await request.get('someurl?foo=1&baz=2')
or
const { body, response } = await request.get('someurl', { params: { foo:1, baz: 2 } })
*/
exports.get = async (url, config) => call(url, 'GET', config);
exports.get = async (url, config) => call(url, 'get', config);
exports.head = async (url, config) => call(url, 'HEAD', config);
exports.del = async (url, config) => call(url, 'DELETE', config);
exports.head = async (url, config) => call(url, 'head', config);
exports.del = async (url, config) => call(url, 'delete', config);
exports.delete = exports.del;
exports.options = async (url, config) => call(url, 'OPTIONS', config);
exports.options = async (url, config) => call(url, 'delete', config);
/*
const { body, response } = await request.post('someurl', { body: { foo: 1, baz: 2}})
const { body, response } = await request.post('someurl', { data: { foo: 1, baz: 2}})
*/
exports.post = async (url, config) => call(url, 'POST', config);
exports.put = async (url, config) => call(url, 'PUT', config);
exports.patch = async (url, config) => call(url, 'PATCH', config);
exports.post = async (url, config) => call(url, 'post', config);
exports.put = async (url, config) => call(url, 'put', config);
exports.patch = async (url, config) => call(url, 'patch', config);

View File

@@ -13,9 +13,6 @@ module.exports = function (app, middleware, controllers) {
app.get('/css/previews/:theme', controllers.admin.themes.get);
app.get('/osd.xml', controllers.osd.handle);
app.get('/service-worker.js', (req, res) => {
res.status(200)
.type('application/javascript')
.set('Service-Worker-Allowed', `${nconf.get('relative_path')}/`)
.sendFile(path.join(__dirname, '../../build/public/src/service-worker.js'));
res.status(200).type('application/javascript').set('Service-Worker-Allowed', `${nconf.get('relative_path')}/`).sendFile(path.join(__dirname, '../../public/src/service-worker.js'));
});
};

View File

@@ -5,7 +5,6 @@ const nconf = require('nconf');
const plugins = require('../../plugins');
const events = require('../../events');
const db = require('../../database');
const { pluginNamePattern } = require('../../constants');
const Plugins = module.exports;
@@ -42,14 +41,7 @@ Plugins.orderActivePlugins = async function (socket, data) {
throw new Error('[[error:plugins-set-in-configuration]]');
}
data = data.filter(plugin => plugin && plugin.name);
data.forEach((plugin) => {
if (!pluginNamePattern.test(plugin.name)) {
throw new Error('[[error:invalid-plugin-id]]');
}
});
await db.sortedSetAdd('plugins:active', data.map(p => p.order || 0), data.map(p => p.name));
await Promise.all(data.map(plugin => db.sortedSetAdd('plugins:active', plugin.order || 0, plugin.name)));
};
Plugins.upgrade = async function (socket, data) {

View File

@@ -27,7 +27,6 @@ module.exports = function (SocketPosts) {
canDelete: privileges.posts.canDelete(data.pid, socket.uid),
canPurge: privileges.posts.canPurge(data.pid, socket.uid),
canFlag: privileges.posts.canFlag(data.pid, socket.uid),
canViewHistory: privileges.posts.can('posts:history', data.pid, socket.uid),
flagged: flags.exists('post', data.pid, socket.uid), // specifically, whether THIS calling user flagged
bookmarked: posts.hasBookmarked(data.pid, socket.uid),
postSharing: social.getActivePostSharing(),
@@ -47,7 +46,7 @@ module.exports = function (SocketPosts) {
postData.display_move_tools = results.isAdmin || results.isModerator;
postData.display_change_owner_tools = results.isAdmin || results.isModerator;
postData.display_ip_ban = (results.isAdmin || results.isGlobalMod) && !postData.selfPost;
postData.display_history = results.history && results.canViewHistory;
postData.display_history = results.history;
postData.flags = {
flagId: parseInt(results.posts.flagId, 10) || null,
can: results.canFlag.flag,

View File

@@ -30,7 +30,7 @@ module.exports = function (SocketTopics) {
parseInt(data.count, 10) || meta.config.postsPerPage || 20
));
if (parseInt(data.direction, 10) === -1) {
if (data.direction === -1) {
start -= infScrollPostsPerPage;
}

View File

@@ -36,7 +36,7 @@ module.exports = function (SocketTopics) {
const notifyUids = await privileges.categories.filterUids('topics:read', topicData.cid, uids);
socketHelpers.emitToUids('event:topic_moved', topicData, notifyUids);
if (!topicData.deleted) {
socketHelpers.sendNotificationToTopicOwner(tid, socket.uid, 'move', 'notifications:moved-your-topic');
socketHelpers.sendNotificationToTopicOwner(tid, socket.uid, 'move', 'notifications:moved_your_topic');
}
await events.log({

View File

@@ -30,43 +30,43 @@ const Events = module.exports;
Events._types = {
pin: {
icon: 'fa-thumb-tack',
translation: async (event, language) => translateSimple(event, language, 'topic:user-pinned-topic'),
translation: async event => translateSimple(event, 'topic:user-pinned-topic'),
},
unpin: {
icon: 'fa-thumb-tack fa-rotate-90',
translation: async (event, language) => translateSimple(event, language, 'topic:user-unpinned-topic'),
translation: async event => translateSimple(event, 'topic:user-unpinned-topic'),
},
lock: {
icon: 'fa-lock',
translation: async (event, language) => translateSimple(event, language, 'topic:user-locked-topic'),
translation: async event => translateSimple(event, 'topic:user-locked-topic'),
},
unlock: {
icon: 'fa-unlock',
translation: async (event, language) => translateSimple(event, language, 'topic:user-unlocked-topic'),
translation: async event => translateSimple(event, 'topic:user-unlocked-topic'),
},
delete: {
icon: 'fa-trash',
translation: async (event, language) => translateSimple(event, language, 'topic:user-deleted-topic'),
translation: async event => translateSimple(event, 'topic:user-deleted-topic'),
},
restore: {
icon: 'fa-trash-o',
translation: async (event, language) => translateSimple(event, language, 'topic:user-restored-topic'),
translation: async event => translateSimple(event, 'topic:user-restored-topic'),
},
move: {
icon: 'fa-arrow-circle-right',
translation: async (event, language) => translateEventArgs(event, language, 'topic:user-moved-topic-from', renderUser(event), `${event.fromCategory.name}`, renderTimeago(event)),
translation: async event => translateEventArgs(event, 'topic:user-moved-topic-from', renderUser(event), `${event.fromCategory.name}`, renderTimeago(event)),
},
'post-queue': {
icon: 'fa-history',
translation: async (event, language) => translateEventArgs(event, language, 'topic:user-queued-post', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
translation: async event => translateEventArgs(event, 'topic:user-queued-post', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
},
backlink: {
icon: 'fa-link',
translation: async (event, language) => translateEventArgs(event, language, 'topic:user-referenced-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
translation: async event => translateEventArgs(event, 'topic:user-referenced-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
},
fork: {
icon: 'fa-code-fork',
translation: async (event, language) => translateEventArgs(event, language, 'topic:user-forked-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)),
translation: async event => translateEventArgs(event, '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, language, prefix, ...args) {
async function translateEventArgs(event, prefix, ...args) {
const key = getTranslationKey(event, prefix);
const compiled = translator.compile.apply(null, [key, ...args]);
return utils.decodeHTMLEntities(await translator.translate(compiled, language));
return utils.decodeHTMLEntities(await translator.translate(compiled));
}
async function translateSimple(event, language, prefix) {
return await translateEventArgs(event, language, prefix, renderUser(event), renderTimeago(event));
async function translateSimple(event, prefix) {
return await translateEventArgs(event, prefix, renderUser(event), renderTimeago(event));
}
Events.translateSimple = translateSimple; // so plugins can perform translate
@@ -162,10 +162,9 @@ async function modifyEvent({ tid, uid, eventIds, timestamps, events }) {
});
}
const [users, fromCategories, userSettings] = await Promise.all([
const [users, fromCategories] = 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
@@ -201,7 +200,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, userSettings.userLang);
event.text = await Events._types[event.type].translation(event);
}
}));

View File

@@ -49,7 +49,7 @@ module.exports = function (Topics) {
tids = await Topics.filterWatchedTids(tids, params.uid);
}
} else if (params.filter === 'watched') {
tids = await getWatchedTopics(params);
tids = await db.getSortedSetRevRange(`uid:${params.uid}:followed_tids`, 0, -1);
} else if (params.cids) {
tids = await getCidTids(params);
} else if (params.tags.length) {
@@ -63,17 +63,6 @@ module.exports = function (Topics) {
return tids;
}
async function getWatchedTopics(params) {
const sortSet = ['recent', 'old'].includes(params.sort) ? 'topics:recent' : `topics:${params.sort}`;
const method = params.sort === 'old' ? 'getSortedSetIntersect' : 'getSortedSetRevIntersect';
return await db[method]({
sets: [sortSet, `uid:${params.uid}:followed_tids`],
weights: [1, 0],
start: 0,
stop: meta.config.recentMaxTopics - 1,
});
}
async function getTagTids(params) {
const sets = [
params.sort === 'old' ?

View File

@@ -620,6 +620,7 @@ module.exports = function (Topics) {
const notification = await notifications.create({
type: 'new-topic-with-tag',
nid: `new_topic:tid:${postData.topic.tid}:uid:${exceptUid}`,
subject: bodyShort,
bodyShort: bodyShort,
bodyLong: postData.content,
pid: postData.pid,

View File

@@ -210,13 +210,15 @@ module.exports = function (Topics) {
}
async function getFollowedTids(params) {
const keys = params.cid ?
params.cid.map(cid => `cid:${cid}:tids:lastposttime`) :
'topics:recent';
const recentTopicData = await db.getSortedSetRevRangeByScoreWithScores(keys, 0, -1, '+inf', params.cutoff);
const isFollowed = await db.isSortedSetMembers(`uid:${params.uid}:followed_tids`, recentTopicData.map(t => t.tid));
return recentTopicData.filter((t, i) => isFollowed[i]);
let tids = await db.getSortedSetMembers(`uid:${params.uid}:followed_tids`);
const filterCids = params.cid && params.cid.map(cid => parseInt(cid, 10));
if (filterCids) {
const topicData = await Topics.getTopicsFields(tids, ['tid', 'cid']);
tids = topicData.filter(t => filterCids.includes(t.cid)).map(t => t.tid);
}
const scores = await db.sortedSetScores('topics:recent', tids);
const data = tids.map((tid, index) => ({ value: String(tid), score: scores[index] }));
return data.filter(item => item.score > params.cutoff);
}
async function filterTidsThatHaveBlockedPosts(params) {

View File

@@ -1,4 +1,4 @@
<div class="px-lg-4 digest">
<div class="px-lg-4">
<p class="lead">[[admin/manage/digest:lead]]</p>
<p>[[admin/manage/digest:disclaimer]]</p>
<p>[[admin/manage/digest:disclaimer-continued]]</p>
@@ -8,7 +8,7 @@
<div class="mb-3">
<div class="mb-2"><em>[[admin/manage/digest:default-help, {default}]]</em></div>
<div class="d-flex gap-1 align-items-center">
<div class="d-flex gap-1">
<div>[[admin/manage/digest:manual-run]]</div>
<button class="btn btn-sm btn-outline-secondary" data-action="resend-day">[[admin/settings/user:digest-freq.daily]]</button>
<button class="btn btn-sm btn-outline-secondary" data-action="resend-week">[[admin/settings/user:digest-freq.weekly]]</button>

View File

@@ -1 +1 @@
data-index="{posts.index}" data-pid="{posts.pid}" data-uid="{posts.uid}" data-timestamp="{posts.timestamp}" data-username="{posts.user.username}" data-userslug="{posts.user.userslug}"{{{ if posts.allowDupe }}} data-allow-dupe="1"{{{ end }}}{{{ if posts.navigatorIgnore }}} data-navigator-ignore="1"{{{ end }}} itemprop="comment" itemtype="http://schema.org/Comment" itemscope
data-index="{posts.index}" data-pid="{posts.pid}" data-uid="{posts.uid}" data-timestamp="{posts.timestamp}" data-username="{posts.user.username}" data-userslug="{posts.user.userslug}"{{{ if posts.allowDupe }}} data-allow-dupe="1"{{{ end }}}{{{ if posts.navigatorIgnore }}} data-navigator-ignore="1"{{{ end }}} itemscope itemtype="http://schema.org/Comment"

View File

@@ -493,13 +493,13 @@ describe('API', async () => {
try {
if (type === 'json') {
const searchParams = new URLSearchParams(qs);
result = await request[method](`${url}?${searchParams}`, {
result = await request[method](url, {
jar: !unauthenticatedRoutes.includes(path) ? jar : undefined,
maxRedirect: 0,
redirect: 'manual',
maxRedirects: 0,
validateStatus: null, // don't throw on non-200 (e.g. 302)
headers: headers,
body: body,
params: qs,
data: body,
});
} else if (type === 'form') {
result = await helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken);

Some files were not shown because too many files have changed in this diff Show More