mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-04 21:15:55 +01:00
Merge commit '33d7b9b3ab58382d979b93b3155a87485170fa58' into v4.x
This commit is contained in:
50
CHANGELOG.md
50
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Отваряне на страничните ленти",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "פתח סרגלי צד",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Բացել կողքի տողերը",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "사이드바 열기",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "เปิดแถบข้าง",
|
||||
|
||||
@@ -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ç",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "打开侧栏",
|
||||
|
||||
@@ -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": "打開側欄",
|
||||
|
||||
@@ -18,6 +18,8 @@ get:
|
||||
latestVersion:
|
||||
type: string
|
||||
nullable: true
|
||||
hideAllTime:
|
||||
type: boolean
|
||||
upgradeAvailable:
|
||||
type: boolean
|
||||
nullable: true
|
||||
|
||||
@@ -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`);
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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),
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}`]);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user