Merge commit '33d7b9b3ab58382d979b93b3155a87485170fa58' into v4.x

This commit is contained in:
Misty Release Bot
2025-02-17 04:45:35 +00:00
72 changed files with 297 additions and 123 deletions

View File

@@ -1,3 +1,53 @@
#### v4.0.3 (2025-02-09)
##### Chores
* up harmony (2ee0cda2)
* update persona (b6b76639)
* bump persona to fix theme description issue (cd88cce0)
* up harmony (a01bf73e)
* forgot to remove bad code (865c09a5)
* up harmony (c3f8222f)
* up harmony (f07f3801)
* up harmony (67a789ad)
* up themes (c1c5cc6e)
* up themes (b2b0ed35)
* up peace (55eedcbe)
* up themes (38a21e29)
* up harmony (58e551fe)
* incrementing version number - v4.0.2 (73fe5fcf)
* update changelog for v4.0.2 (75588ffe)
* incrementing version number - v4.0.1 (a461b758)
* incrementing version number - v4.0.0 (c1eaee45)
##### Bug Fixes
* delete from payload instead of setting null (1b4e0c87)
* regression :tmi: (f5328aa8)
* #13139, payload.version can be null (bfe6d9d8)
* tidChanged (1f8e2f9a)
* #13135, tids are not numeric for ap topics (d687f081)
* handle cases where url passed to mime does not pass because url contained a query string (5baa46d0)
* isDraft logic, closes #13119 (21156673)
* path on windows, #13119 (36063d1f)
* #13115, prevent messages from getting duplicated (1ff8e1e4)
* #13115, limit bodyLength length (8e9fdb5f)
##### Other Changes
* remove log (a8e7bf35)
##### Refactors
* events are returned inside post objects (3ab22c2c)
* move dropdown search inputs into dropdown (b993be6f)
* server.destroy (72091ec4)
* remove deprecated methods (265e44f0)
##### Tests
* search endpoint with start & end (c1b630d4)
#### v4.0.2 (2025-02-02)
##### Chores

View File

@@ -98,17 +98,17 @@
"mousetrap": "1.6.5",
"multiparty": "4.2.3",
"nconf": "0.12.1",
"nodebb-plugin-2factor": "7.5.8",
"nodebb-plugin-composer-default": "10.2.44",
"nodebb-plugin-dbsearch": "6.2.8",
"nodebb-plugin-2factor": "7.5.9",
"nodebb-plugin-composer-default": "10.2.45",
"nodebb-plugin-dbsearch": "6.2.9",
"nodebb-plugin-emoji": "6.0.2",
"nodebb-plugin-emoji-android": "4.1.1",
"nodebb-plugin-markdown": "13.0.0",
"nodebb-plugin-markdown": "13.1.0",
"nodebb-plugin-mentions": "4.6.10",
"nodebb-plugin-spam-be-gone": "2.3.0",
"nodebb-plugin-spam-be-gone": "2.3.1",
"nodebb-plugin-web-push": "0.7.2",
"nodebb-rewards-essentials": "1.0.0",
"nodebb-theme-harmony": "2.0.18",
"nodebb-rewards-essentials": "1.0.1",
"nodebb-theme-harmony": "2.0.25",
"nodebb-theme-lavender": "7.1.17",
"nodebb-theme-peace": "2.2.38",
"nodebb-theme-persona": "14.0.14",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Показване на резюмета на темите на мобилни устройства",
"settings.stickyToolbar": "Статична лента с инструменти",
"settings.stickyToolbar.help": "Лентата с инструменти в страниците с теми и категории ще стои винаги в горния край на страницата",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Автоматично скриване на долната лента",
"settings.autohideBottombar.help": "В изгледа за мобилни устройства долната лента ще се скрива, когато страницата се превърта надолу",
"settings.openSidebars": "Отваряне на страничните ленти",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Themen-Vorschau auf Mobilgeräten anzeigen",
"settings.stickyToolbar": "Klebrige Toolbar",
"settings.stickyToolbar.help": "Die Toolbar auf Themen- und Kategorieseiten bleibt oben an der Seite kleben",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Fußzeile automatisch verbergen",
"settings.autohideBottombar.help": "Die Fußzeile wird auf Mobilgeräten versteckt, sobald nach unten gescrollt wird",
"settings.openSidebars": "Seitennavigation öffnen",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -41,9 +41,6 @@
"sockets.default-placeholder": "Default: %1",
"sockets.delay": "Reconnection Delay",
"analytics.settings": "Analytics Settings",
"analytics.max-cache": "Analytics Cache Max Value",
"analytics.max-cache-help": "On high-traffic installs, the cache could be exhausted continuously if there are more concurrent active users than the Max Cache value. (Restart required)",
"compression.settings": "Compression Settings",
"compression.enable": "Enable Compression",
"compression.help": "This setting enables gzip compression. For a high-traffic website in production, the best way to put compression in place is to implement it at a reverse proxy level. You can enable it here for testing purposes."

View File

@@ -15,8 +15,9 @@
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.autohideBottombar": "Auto hide mobile navigation bar",
"settings.autohideBottombar.help": "The mobile bar will be hidden when the page is scrolled down",
"settings.topMobilebar": "Move the mobile navigation bar to the top",
"settings.openSidebars": "Open sidebars",
"settings.chatModals": "Enable chat modals"
}

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "نمایش تیزرهای تاپیک در گوشی",
"settings.stickyToolbar": "نوار ابزار چسبیده ",
"settings.stickyToolbar.help": "نوار ابزار در تاپیک و صفحه دسته بدی ها در بالای صفحه ثابت می‌ماند",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "مخفی کردن اتوماتیک منوی پایینی ",
"settings.autohideBottombar.help": "وقتی صفحه به پایین اسکرول می شود، منوی پایین در نمایش گوشی مخفی خواهد شد ",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Näytä aihe-ehdotukset mobiilinäkymässä",
"settings.stickyToolbar": "Kiinteä työkalupalkki",
"settings.stickyToolbar.help": "Aihe- ja kategoriasivujen työkalupalkki näytetään yläreunassa aina.",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Piilota alapalkki automaattisesti",
"settings.autohideBottombar.help": "Mobiilinäykymän alapalkki piilotetaan kun sivua vieritetään alaspäin.",
"settings.openSidebars": "Avaa sivupalkit",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Afficher les teasers de sujet sur mobile",
"settings.stickyToolbar": "Barre d'outils",
"settings.stickyToolbar.help": "La barre d'outils sur les pages de sujets et de catégories restera en haut de la page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Masquer automatiquement la barre inférieure",
"settings.autohideBottombar.help": "La barre inférieure sur mobile sera masquée lorsque la page défilera vers le bas",
"settings.openSidebars": "Barres latérales ouvertes",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "הצגת טיזרים של נושאים בנייד",
"settings.stickyToolbar": "הצמד את סרגל הכלים בעת גלילה",
"settings.stickyToolbar.help": "סרגל הכלים בדפי נושאים וקטגוריות ייצמד לראש העמוד בעת גלילה",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "הסתרה אוטומטית של סרגל תחתון",
"settings.autohideBottombar.help": "הסרגל התחתון בתצוגת הנייד יוסתר כאשר הדף ייגלל מטה",
"settings.openSidebars": "פתח סרגלי צד",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Oldalsáv nyitása",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Ցույց տալ թեմայի թիզերները բջջայինով",
"settings.stickyToolbar": "Կպչուն գործիքագոտի",
"settings.stickyToolbar.help": "Թեմայի և կատեգորիայի էջերի գործիքագոտին կմնա էջի վերևում",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Ավտոմատ թաքցնել ներքևի բարը",
"settings.autohideBottombar.help": "Բջջային դիտման ներքևի տողը կթաքցվի, երբ էջը ներքև իջացնեք",
"settings.openSidebars": "Բացել կողքի տողերը",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Mostra le anteprime delle discussioni su mobile",
"settings.stickyToolbar": "Barra degli strumenti adesiva",
"settings.stickyToolbar.help": "La barra degli strumenti nelle pagine delle discussioni e delle categorie si attacca alla parte superiore della pagina.",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Nascondi automaticamente la barra inferiore",
"settings.autohideBottombar.help": "La barra inferiore nella visualizzazione mobile sarà nascosta quando la pagina viene fatta scorrere verso il basso.",
"settings.openSidebars": "Apri le barre laterali",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "모바일에서 토픽 미리보기 표시",
"settings.stickyToolbar": "툴바 고정",
"settings.stickyToolbar.help": "토픽 및 카테고리 페이지의 툴바가 페이지 상단에 고정됩니다.",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "하단 바 자동 숨기기",
"settings.autohideBottombar.help": "모바일 뷰에서 페이지가 아래로 스크롤될 때 하단 막대가 숨겨집니다.",
"settings.openSidebars": "사이드바 열기",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Vis emneforhåndsvisninger på mobil",
"settings.stickyToolbar": "Festet verktøylinje",
"settings.stickyToolbar.help": "Verktøylinjen på emne- og kategorisider vil feste seg til toppen av siden",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Skjul bunnlinjen automatisk",
"settings.autohideBottombar.help": "Bunnlinjen i mobilvisning skjules automatisk når siden rulles ned",
"settings.openSidebars": "Åpne sidepaneler",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Vis emneteasere på mobil",
"settings.stickyToolbar": "Fast verktøylinje",
"settings.stickyToolbar.help": "Gjer verktøylinja fast øvst på sida når du rullar.",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Gøym botnlinja automatisk",
"settings.autohideBottombar.help": "Botnlinja vert automatisk gøymd når du rullar nedover.",
"settings.openSidebars": "Opne sidefelt",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Pokazuj zwiastuny tematów na telefonie",
"settings.stickyToolbar": "Przyklejaj pasek narzędziowy",
"settings.stickyToolbar.help": "Pasek z narzędziami na stronach tematów i kategorii będzie przyklejony do góry strony",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Automatycznie chowaj dolny panel",
"settings.autohideBottombar.help": "Dolny panel schowa się w widoku mobilnym, jeśli strona zostanie przesunięta w dół",
"settings.openSidebars": "Otwórz panele boczne",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Прикажи најавне теме на мобилном телефону",
"settings.stickyToolbar": "Лепљива трака алата",
"settings.stickyToolbar.help": "Трака алата на страницама са темама и категоријама ће бити на врху странице",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Аутоматско сакривање доње траке",
"settings.autohideBottombar.help": "Доња трака у приказу за мобилне уређаје биће скривена када се страница помера надоле",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "แสดงตัวอย่างเนื้อหากระทู้บนมือถือ",
"settings.stickyToolbar": "แถบเครื่องมือแบบ sticky",
"settings.stickyToolbar.help": "แถบเครื่องมือในหน้ากระทู้และหมวดหมู่จะอยู่ด้านบนสุดของหน้าเสมอ",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "ซ่อนแถบล่าง",
"settings.autohideBottombar.help": "แถบล่างบนมือถือจะถูกซ่อนเมื่อเลื่อนหน้าลง",
"settings.openSidebars": "เปิดแถบข้าง",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Başlık Önizlemelerini mobilde göster",
"settings.stickyToolbar": "Yapışkan Araç Çubuğu",
"settings.stickyToolbar.help": "Kategori ve konu sayfalarındaki araç çubuğu sayfanın üstünde sabitlenmiş şekilde kalacak",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Alttaki şeridi otomatik olarak gizle",
"settings.autohideBottombar.help": "Sayfanın altındaki şerit sayfayı aşağıya doğru kaydırırken mobilde gizlenecek",
"settings.openSidebars": "Yan menüleri aç",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "Open sidebars",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Hiển thị đoạn giới thiệu chủ đề trên di động",
"settings.stickyToolbar": "Thanh công cụ cố định",
"settings.stickyToolbar.help": "Thanh công cụ trên các trang chủ đề và danh mục sẽ nằm ở đầu trang",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Tự động ẩn thanh dưới cùng",
"settings.autohideBottombar.help": "Thanh dưới cùng trên chế độ xem di động sẽ ẩn khi trang được cuộn xuống",
"settings.openSidebars": "Mở thanh bên",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "在移动设备显示话题预告",
"settings.stickyToolbar": "附着工具条",
"settings.stickyToolbar.help": "主题和类别页面上的工具条将附着在页面顶部",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "自动隐藏底栏",
"settings.autohideBottombar.help": "当页面向下滚动时,移动设备视图的底栏将被隐藏",
"settings.openSidebars": "打开侧栏",

View File

@@ -13,6 +13,8 @@
"settings.mobileTopicTeasers": "Show topic teasers on mobile",
"settings.stickyToolbar": "Sticky toolbar",
"settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
"settings.topicSidebarTools": "Topic sidebar tools",
"settings.topicSidebarTools.help": "This option will move the topic tools to the sidebar on desktop",
"settings.autohideBottombar": "Auto hide bottom bar",
"settings.autohideBottombar.help": "The bottom bar on mobile view will be hidden when the page is scrolled down",
"settings.openSidebars": "打開側欄",

View File

@@ -18,6 +18,8 @@ get:
latestVersion:
type: string
nullable: true
hideAllTime:
type: boolean
upgradeAvailable:
type: boolean
nullable: true

View File

@@ -2,6 +2,7 @@
const nconf = require('nconf');
const winston = require('winston');
const _ = require('lodash');
const db = require('../database');
const meta = require('../meta');
@@ -230,19 +231,34 @@ Actors.getLocalFollowers = async (id) => {
return response;
};
Actors.getLocalFollowCounts = async (actor) => {
let followers = 0; // x local followers
let following = 0; // following x local users
if (!activitypub.helpers.isUri(actor)) {
return { followers, following };
Actors.getLocalFollowCounts = async (actors) => {
const isArray = Array.isArray(actors);
if (!isArray) {
actors = [actors];
}
[followers, following] = await Promise.all([
db.sortedSetCard(`followersRemote:${actor}`),
db.sortedSetCard(`followingRemote:${actor}`),
]);
const validActors = actors.filter(actor => activitypub.helpers.isUri(actor));
const followerKeys = validActors.map(actor => `followersRemote:${actor}`);
const followingKeys = validActors.map(actor => `followingRemote:${actor}`);
return { followers, following };
const [followersCounts, followingCounts] = await Promise.all([
db.sortedSetsCard(followerKeys),
db.sortedSetsCard(followingKeys),
]);
const actorToCounts = _.zipObject(validActors, validActors.map(
(a, idx) => ({ followers: followersCounts[idx], following: followingCounts[idx] })
));
const results = actors.map((actor) => {
if (!actorToCounts.hasOwnProperty(actor)) {
return { followers: 0, following: 0 };
}
return {
followers: actorToCounts[actor].followers,
following: actorToCounts[actor].following,
};
});
return isArray ? results : results[0];
};
Actors.remove = async (id) => {
@@ -288,7 +304,7 @@ Actors.prune = async () => {
const days = parseInt(meta.config.activitypubUserPruneDays, 10);
const timestamp = Date.now() - (1000 * 60 * 60 * 24 * days);
const uids = await db.getSortedSetRangeByScore('usersRemote:lastCrawled', 0, -1, '-inf', timestamp);
const uids = await db.getSortedSetRangeByScore('usersRemote:lastCrawled', 0, 500, '-inf', timestamp);
if (!uids.length) {
winston.info('[actors/prune] No remote users to prune, all done.');
return;
@@ -296,21 +312,23 @@ Actors.prune = async () => {
winston.info(`[actors/prune] Found ${uids.length} remote users last crawled more than ${days} days ago`);
let deletionCount = 0;
let deletionCountNonExisting = 0;
let notDeletedDueToLocalContent = 0;
const notDeletedUids = [];
await batch.processArray(uids, async (uids) => {
const exists = await db.exists(uids.map(uid => `userRemote:${uid}`));
const [postCounts, roomCounts] = await Promise.all([
db.sortedSetsCard(uids.map(uid => `uid:${uid}:posts`)),
db.sortedSetsCard(uids.map(uid => `uid:${uid}:chat:rooms`)),
]);
await Promise.all(uids.map(async (uid, idx) => {
if (!exists[idx]) {
// id in zset but not asserted, handle and return early
await db.sortedSetRemove('usersRemote:lastCrawled', uid);
return;
}
const { followers, following } = await Actors.getLocalFollowCounts(uid);
const uidsThatExist = uids.filter((uid, idx) => exists[idx]);
const uidsThatDontExist = uids.filter((uid, idx) => !exists[idx]);
const [postCounts, roomCounts, followCounts] = await Promise.all([
db.sortedSetsCard(uidsThatExist.map(uid => `uid:${uid}:posts`)),
db.sortedSetsCard(uidsThatExist.map(uid => `uid:${uid}:chat:rooms`)),
Actors.getLocalFollowCounts(uidsThatExist),
]);
await Promise.all(uidsThatExist.map(async (uid, idx) => {
const { followers, following } = followCounts[idx];
const postCount = postCounts[idx];
const roomCount = roomCounts[idx];
if ([postCount, roomCount, followers, following].every(metric => metric < 1)) {
@@ -320,12 +338,22 @@ Actors.prune = async () => {
} catch (err) {
winston.error(err.stack);
}
} else {
notDeletedDueToLocalContent += 1;
notDeletedUids.push(uid);
}
}));
deletionCountNonExisting += uidsThatDontExist.length;
await db.sortedSetRemove('usersRemote:lastCrawled', uidsThatDontExist);
// update timestamp in usersRemote:lastCrawled so we don't try to delete users
// with content over and over
const now = Date.now();
await db.sortedSetAdd('usersRemote:lastCrawled', notDeletedUids.map(() => now), notDeletedUids);
}, {
batch: 50,
interval: 1000,
});
winston.info(`[actors/prune] ${deletionCount} remote users pruned.`);
winston.info(`[actors/prune] ${deletionCount} remote users pruned. ${deletionCountNonExisting} does not exist. ${notDeletedDueToLocalContent} not deleted due to local content`);
};

View File

@@ -66,7 +66,7 @@ ActivityPub.startJobs = () => {
}
}, null, true, null, null, false); // change last argument to true for debugging
new CronJob('0 1 * * *', async () => {
new CronJob('*/30 * * * *', async () => {
try {
await ActivityPub.actors.prune();
} catch (err) {

View File

@@ -155,7 +155,10 @@ Mocks.post = async (objects) => {
await activitypub.actors.assert(Array.from(actorIds));
const posts = await Promise.all(objects.map(async (object) => {
if (!activitypub._constants.acceptedPostTypes.includes(object.type)) {
if (
!activitypub._constants.acceptedPostTypes.includes(object.type) ||
!activitypub.helpers.isUri(object.id) // sanity-check the id
) {
return null;
}
@@ -187,7 +190,7 @@ Mocks.post = async (objects) => {
}
switch (true) {
case image && image.hasOwnProperty('url') && image.url: {
case image && image.hasOwnProperty('url') && !!image.url: {
image = image.url;
break;
}

View File

@@ -3,7 +3,6 @@
const cronJob = require('cron').CronJob;
const winston = require('winston');
const nconf = require('nconf');
const crypto = require('crypto');
const util = require('util');
const _ = require('lodash');
@@ -12,36 +11,24 @@ const sleep = util.promisify(setTimeout);
const db = require('./database');
const utils = require('./utils');
const plugins = require('./plugins');
const meta = require('./meta');
const pubsub = require('./pubsub');
const cacheCreate = require('./cache/lru');
const Analytics = module.exports;
const secret = nconf.get('secret');
let local = {
counters: {},
pageViews: 0,
pageViewsRegistered: 0,
pageViewsGuest: 0,
pageViewsBot: 0,
uniqueIPCount: 0,
uniquevisitors: 0,
};
const empty = _.cloneDeep(local);
const total = _.cloneDeep(local);
let ipCache;
const runJobs = nconf.get('runJobs');
Analytics.init = async function () {
ipCache = cacheCreate({
max: parseInt(meta.config['analytics:maxCache'], 10) || 500,
ttl: 0,
});
new cronJob('*/10 * * * * *', (async () => {
publishLocalAnalytics();
if (runJobs) {
@@ -50,6 +37,12 @@ Analytics.init = async function () {
}
}), null, true);
if (runJobs) {
new cronJob('*/30 * * * *', (async () => {
await db.sortedSetsRemoveRangeByScore(['ip:recent'], '-inf', Date.now() - 172800000);
}), null, true);
}
if (runJobs) {
pubsub.on('analytics:publish', (data) => {
incrementProperties(total, data.local);
@@ -106,22 +99,17 @@ Analytics.pageView = async function (payload) {
}
if (payload.ip) {
// Retrieve hash or calculate if not present
let hash = ipCache.get(payload.ip + secret);
if (!hash) {
hash = crypto.createHash('sha1').update(payload.ip + secret).digest('hex');
ipCache.set(payload.ip + secret, hash);
}
const score = await db.sortedSetScore('ip:recent', hash);
if (!score) {
local.uniqueIPCount += 1;
}
const score = await db.sortedSetScore('ip:recent', payload.ip);
let record = !score;
if (score) {
const today = new Date();
today.setHours(today.getHours(), 0, 0, 0);
if (!score || score < today.getTime()) {
record = score < today.getTime();
}
if (record) {
local.uniquevisitors += 1;
await db.sortedSetAdd('ip:recent', Date.now(), hash);
await db.sortedSetAdd('ip:recent', Date.now(), payload.ip);
}
}
};
@@ -176,11 +164,6 @@ Analytics.writeData = async function () {
total.uniquevisitors = 0;
}
if (total.uniqueIPCount > 0) {
dbQueue.push(db.incrObjectFieldBy('global', 'uniqueIPCount', total.uniqueIPCount));
total.uniqueIPCount = 0;
}
for (const [key, value] of Object.entries(total.counters)) {
incrByBulk.push([`analytics:${key}`, value, today.getTime()]);
metrics.push(key);

View File

@@ -168,6 +168,7 @@ activitypubApi.create.privateNote = enabledCheck(async (caller, { messageObj })
const payload = {
id: `${object.id}#activity/create/${Date.now()}`,
type: 'Create',
actor: object.attributedTo,
to: object.to,
object,
};
@@ -186,6 +187,7 @@ activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => {
await activitypub.send('uid', caller.uid, targets, {
id: `${object.id}#activity/update/${Date.now()}`,
type: 'Update',
actor: object.id,
to: [activitypub._constants.publicAddress],
cc: [],
object,
@@ -201,6 +203,7 @@ activitypubApi.update.category = enabledCheck(async (caller, { cid }) => {
await activitypub.send('cid', cid, targets, {
id: `${object.id}#activity/update/${Date.now()}`,
type: 'Update',
actor: object.id,
to: [activitypub._constants.publicAddress],
cc: [],
object,
@@ -227,6 +230,7 @@ activitypubApi.update.note = enabledCheck(async (caller, { post }) => {
const payload = {
id: `${object.id}#activity/update/${post.edited || Date.now()}`,
type: 'Update',
actor: object.attributedTo,
to,
cc,
object,
@@ -251,6 +255,7 @@ activitypubApi.update.privateNote = enabledCheck(async (caller, { messageObj })
const payload = {
id: `${object.id}#activity/create/${Date.now()}`,
type: 'Update',
actor: object.attributedTo,
to,
object,
};
@@ -280,6 +285,7 @@ activitypubApi.delete.note = enabledCheck(async (caller, { pid }) => {
const payload = {
id: `${id}#activity/delete/${Date.now()}`,
type: 'Delete',
actor: object.attributedTo,
to,
cc,
object: id,
@@ -334,6 +340,7 @@ activitypubApi.announce.note = enabledCheck(async (caller, { tid }) => {
await activitypub.send('uid', caller.uid, Array.from(targets), {
id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/${Date.now()}`,
type: 'Announce',
actor: `${nconf.get('url')}/uid/${caller.uid}`,
to,
cc,
object: pid,
@@ -380,6 +387,7 @@ activitypubApi.flag = enabledCheck(async (caller, flag) => {
await activitypub.send('uid', caller.uid, reportedIds, {
id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${caller.uid}`,
type: 'Flag',
actor: `${nconf.get('url')}/uid/${caller.uid}`,
object: reportedIds,
content: reason,
});
@@ -426,6 +434,7 @@ activitypubApi.undo.flag = enabledCheck(async (caller, flag) => {
await activitypub.send('uid', caller.uid, reportedIds, {
id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/undo:flag/${caller.uid}/${Date.now()}`,
type: 'Undo',
actor: `${nconf.get('url')}/uid/${caller.uid}`,
object: {
id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${caller.uid}`,
actor: `${nconf.get('url')}/uid/${caller.uid}`,

View File

@@ -73,7 +73,7 @@ module.exports = function (Categories) {
Categories.getCategoryField = async function (cid, field) {
const category = await Categories.getCategoryFields(cid, [field]);
return category ? category[field] : null;
return category && category.hasOwnProperty(field) ? category[field] : null;
};
Categories.getCategoryFields = async function (cid, fields) {

View File

@@ -41,6 +41,7 @@ dashboardController.get = async function (req, res) {
lastrestart: lastrestart,
showSystemControls: isAdmin,
popularSearches: popularSearches,
hideAllTime: true,
});
};
@@ -128,7 +129,7 @@ async function getStats() {
}
let results = await Promise.all([
getStatsFromAnalytics('uniquevisitors', 'uniqueIPCount'),
getStatsFromAnalytics('uniquevisitors', ''),
getStatsFromAnalytics('logins', 'loginCount'),
getStatsForSet('users:joindate', 'userCount'),
getStatsForSet('posts:pid', 'postCount'),
@@ -227,6 +228,7 @@ function calculateDeltas(results) {
}
async function getGlobalField(field) {
if (!field) return 0;
const count = await db.getObjectField('global', field);
return parseInt(count, 10) || 0;
}

View File

@@ -51,7 +51,7 @@ module.exports = function (Groups) {
Groups.getGroupField = async function (groupName, field) {
const groupData = await Groups.getGroupFields(groupName, [field]);
return groupData ? groupData[field] : null;
return groupData && groupData.hasOwnProperty(field) ? groupData[field] : null;
};
Groups.getGroupFields = async function (groupName, fields) {

View File

@@ -29,7 +29,7 @@ module.exports = function (Messaging) {
Messaging.getMessageField = async (mid, field) => {
const fields = await Messaging.getMessageFields(mid, [field]);
return fields ? fields[field] : null;
return fields && fields.hasOwnProperty(field) ? fields[field] : null;
};
Messaging.getMessageFields = async (mid, fields) => {

View File

@@ -63,6 +63,7 @@ Attachments.update = async (pid, attachments) => {
await Promise.all([
db.setObjectBulk(bulkOps.hash),
db.setObjectField(`post:${pid}`, 'attachments', hashes.join(',')),
posts.clearCachedPost(pid),
]);
};

View File

@@ -37,7 +37,7 @@ module.exports = function (Posts) {
Posts.getPostField = async function (pid, field) {
const post = await Posts.getPostFields(pid, [field]);
return post ? post[field] : null;
return post && post.hasOwnProperty(field) ? post[field] : null;
};
Posts.getPostFields = async function (pid, fields) {

View File

@@ -28,8 +28,6 @@ SocketPosts.getRawPost = async function (socket, pid) {
};
SocketPosts.getPostSummaryByIndex = async function (socket, data) {
sockets.warnDeprecated(socket, 'GET /api/v3/posts/byIndex/:index/summary?tid=:tid');
if (data.index < 0) {
data.index = 0;
}

View File

@@ -2,6 +2,7 @@
'use strict';
const _ = require('lodash');
const winston = require('winston');
const db = require('../database');
const utils = require('../utils');
@@ -154,12 +155,19 @@ module.exports = function (Topics) {
plugins.hooks.fire('action:topic.post', { topic: topicData, post: postData, data: data });
if (!topicData.scheduled) {
setImmediate(async () => {
try {
if (utils.isNumber(uid)) {
// New topic notifications only sent for local-to-local follows only
user.notifications.sendTopicNotificationToFollowers(uid, topicData, postData);
await user.notifications.sendTopicNotificationToFollowers(uid, topicData, postData);
}
Topics.notifyTagFollowers(postData, uid);
categories.notifyCategoryFollowers(postData, uid);
await Topics.notifyTagFollowers(postData, uid);
await categories.notifyCategoryFollowers(postData, uid);
} catch (err) {
winston.error(err.stack);
}
});
}
return {
@@ -211,14 +219,18 @@ module.exports = function (Topics) {
}
if (parseInt(uid, 10) || activitypub.helpers.isUri(uid) || meta.config.allowGuestReplyNotifications) {
const { displayname } = postData.user;
Topics.notifyFollowers(postData, uid, {
setImmediate(async () => {
try {
await Topics.notifyFollowers(postData, uid, {
type: 'new-reply',
bodyShort: translator.compile('notifications:user-posted-to', displayname, postData.topic.title),
bodyShort: translator.compile('notifications:user-posted-to', postData.user.displayname, postData.topic.title),
nid: `new_post:tid:${postData.topic.tid}:pid:${postData.pid}:uid:${uid}`,
mergeId: `notifications:user-posted-to|${postData.topic.tid}`,
});
} catch (err) {
winston.error(err.stack);
}
});
}
analytics.increment(['posts', `posts:byCid:${data.cid}`]);

View File

@@ -40,7 +40,7 @@ module.exports = function (Topics) {
Topics.getTopicField = async function (tid, field) {
const topic = await Topics.getTopicFields(tid, [field]);
return topic ? topic[field] : null;
return topic && topic.hasOwnProperty(field) ? topic[field] : null;
};
Topics.getTopicFields = async function (tid, fields) {

View File

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

View File

@@ -161,7 +161,7 @@ module.exports = function (User) {
User.getUserField = async function (uid, field) {
const user = await User.getUserFields(uid, [field]);
return user ? user[field] : null;
return user && user.hasOwnProperty(field) ? user[field] : null;
};
User.getUserFields = async function (uid, fields) {

View File

@@ -2,9 +2,8 @@
{{{ each hooks }}}
<div class="mb-3 border rounded p-2">
<div class="" role="tab">
<h6 class="mb-0 ps-2 d-flex justify-content-between align-items-center">
<span>{hooks.hookName}</span>
<button class="btn btn-ghost btn-sm" data-bs-toggle="collapse" data-bs-parent="#accordion" data-bs-target="#{hooks.index}" aria-expanded="true" aria-controls="{hooks.index}">View hooks ({hooks.count})</button>
<h6 class="mb-0 d-flex align-items-center">
<button class="btn btn-ghost btn-sm" data-bs-toggle="collapse" data-bs-parent="#accordion" data-bs-target="#{hooks.index}" aria-expanded="true" aria-controls="{hooks.index}">{hooks.hookName} ({hooks.count})</button>
</h6>
</div>
<div id="{hooks.index}" class="accordion-collapse collapse" role="tabpanel">

View File

@@ -1,31 +1,33 @@
<div class="table-responsive mb-3">
<table class="table text-sm">
<thead>
<table class="table">
<thead class="text-xs">
<tr>
<th></th>
<th class="text-end">[[admin/dashboard:stats.yesterday]]</th>
<th class="text-end">[[admin/dashboard:stats.today]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.yesterday]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.today]]</th>
<th></th>
<th class="text-end">[[admin/dashboard:stats.last-week]]</th>
<th class="text-end">[[admin/dashboard:stats.this-week]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.last-week]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.this-week]]</th>
<th></th>
<th class="text-end">[[admin/dashboard:stats.last-month]]</th>
<th class="text-end">[[admin/dashboard:stats.this-month]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.last-month]]</th>
<th class="text-end text-nowrap">[[admin/dashboard:stats.this-month]]</th>
<th></th>
{{{ if !hideAllTime}}}
<th class="text-end">[[admin/dashboard:stats.all]]</th>
{{{ end }}}
</tr>
</thead>
<tbody>
<tbody class="text-sm">
{{{ each stats }}}
<tr>
<td>
<strong>
<td class="fw-bold text-nowrap">
{{{ if ./href }}}
<a href="{./href}">{./name}</a>
{{{ else }}}
{./name}
{{{ end }}}
</strong>
</td>
<td class="text-end">{formattedNumber(./yesterday)}</td>
<td class="text-end">{formattedNumber(./today)}</td>
@@ -38,8 +40,9 @@
<td class="text-end">{formattedNumber(./lastmonth)}</td>
<td class="text-end">{formattedNumber(./thismonth)}</td>
<td class="{./monthTextClass}"><small>{./monthIncrease}%</small></td>
{{{ if !hideAllTime}}}
<td class="text-end">{formattedNumber(./alltime)}</td>
{{{ end }}}
</tr>
{{{ end }}}
</tbody>

View File

@@ -146,20 +146,6 @@
<hr/>
<div id="analytics-settings" class="mb-4">
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/advanced:analytics.settings]]</h5>
<div class="mb-3">
<label class="form-label" for="analytics:maxCache">[[admin/settings/advanced:analytics.max-cache]]</label>
<input class="form-control" id="analytics:maxCache" type="text" value="500" placeholder="500" data-field="analytics:maxCache" />
<p class="form-text">
[[admin/settings/advanced:analytics.max-cache-help]]
</p>
</div>
</div>
<hr/>
<div id="compression-settings" class="mb-4">
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/advanced:compression.settings]]</h5>

View File

@@ -680,6 +680,12 @@ describe('User', () => {
done();
});
});
it('should return null if field or user doesn not exist', async () => {
assert.strictEqual(await User.getUserField('1', 'doesnotexist'), null);
assert.strictEqual(await User.getUserField('doesnotexistkey', 'doesnotexist'), null);
assert.strictEqual(await User.getUserField('0', 'doesnotexist'), null);
});
});
describe('profile methods', () => {