diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 7ccb52ae62..43fdf33611 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -63,7 +63,7 @@ jobs:
- 5432:5432
redis:
- image: 'redis:7.4.3'
+ image: 'redis:8.0.1'
# Set health checks to wait until redis has started
options: >-
--health-cmd "redis-cli ping"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc3228268f..540621ebaf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,42 @@
+#### v4.3.2 (2025-05-12)
+
+##### Chores
+
+* up mentions (fcf9e8b7)
+* incrementing version number - v4.3.1 (308e6b9f)
+* update changelog for v4.3.1 (2310a7b8)
+* incrementing version number - v4.3.0 (bff291db)
+* incrementing version number - v4.2.2 (17fecc24)
+* incrementing version number - v4.2.1 (852a270c)
+* incrementing version number - v4.2.0 (87581958)
+* incrementing version number - v4.1.1 (b2afbb16)
+* incrementing version number - v4.1.0 (36c80850)
+* incrementing version number - v4.0.6 (4a52fb2e)
+* incrementing version number - v4.0.5 (1792a62b)
+* incrementing version number - v4.0.4 (b1125cce)
+* incrementing version number - v4.0.3 (2b65c735)
+* incrementing version number - v4.0.2 (73fe5fcf)
+* incrementing version number - v4.0.1 (a461b758)
+* incrementing version number - v4.0.0 (c1eaee45)
+
+##### Bug Fixes
+
+* sql injection in sortedSetScan (16504bad)
+* escape flag filters (285d438c)
+* #13407, don't restart user jobs (31be083e)
+* closes #13405, catch errors in ap.verify (8174578c)
+* send proper accept header for outgoing webfinger requests (20ab9069)
+* wrap generateCollection calls in try..catch to send 404 if thrown (64fdf91b)
+* #13397, null values in category sync list (26e6a222)
+* #13392, regression from c6f2c87, unable to unfollow from pending follows (401ff797)
+* #13397, update getCidByHandle to work with remote categories, fix sync with handles causing issues with null entries (a9a5ab5e)
+* correct stage name in dev dockerfile (#13393) (10077d0f)
+
+##### Refactors
+
+* wrap ap routes in try/catch (00668bdc)
+* call verify if request is POST (dfa21329)
+
#### v4.3.1 (2025-05-07)
##### Chores
diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml
index 17339889c3..9011d0f92a 100644
--- a/docker-compose-pgsql.yml
+++ b/docker-compose-pgsql.yml
@@ -14,7 +14,7 @@ services:
- ./install/docker/setup.json:/usr/src/app/setup.json
postgres:
- image: postgres:17.4-alpine
+ image: postgres:17.5-alpine
restart: unless-stopped
environment:
POSTGRES_USER: nodebb
@@ -24,7 +24,7 @@ services:
- postgres-data:/var/lib/postgresql/data
redis:
- image: redis:7.4.3-alpine
+ image: redis:8.0.1-alpine
restart: unless-stopped
command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning']
# command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF
diff --git a/docker-compose-redis.yml b/docker-compose-redis.yml
index 9e1b05b250..da31cd6886 100644
--- a/docker-compose-redis.yml
+++ b/docker-compose-redis.yml
@@ -14,7 +14,7 @@ services:
- ./install/docker/setup.json:/usr/src/app/setup.json
redis:
- image: redis:7.4.3-alpine
+ image: redis:8.0.1-alpine
restart: unless-stopped
command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning']
# command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF
diff --git a/docker-compose.yml b/docker-compose.yml
index 3aa899c8be..637cecb0cd 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -24,7 +24,7 @@ services:
- mongo-data:/data/db
- ./install/docker/mongodb-user-init.js:/docker-entrypoint-initdb.d/user-init.js
redis:
- image: redis:7.4.3-alpine
+ image: redis:8.0.1-alpine
restart: unless-stopped
command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning']
# command: ['redis-server', '--save', '60', '1', '--loglevel', 'warning'] # uncomment if you want to use snapshotting instead of AOF
@@ -34,7 +34,7 @@ services:
- redis
postgres:
- image: postgres:17.4-alpine
+ image: postgres:17.5-alpine
restart: unless-stopped
environment:
POSTGRES_USER: nodebb
diff --git a/install/package.json b/install/package.json
index 10896920eb..dde27f60ed 100644
--- a/install/package.json
+++ b/install/package.json
@@ -30,7 +30,7 @@
"dependencies": {
"@adactive/bootstrap-tagsinput": "0.8.2",
"@fontsource/inter": "5.2.5",
- "@fontsource/poppins": "5.2.5",
+ "@fontsource/poppins": "5.2.6",
"@fortawesome/fontawesome-free": "6.7.2",
"@isaacs/ttlcache": "1.4.1",
"@nodebb/spider-detector": "2.0.3",
@@ -39,7 +39,7 @@
"@textcomplete/contenteditable": "0.1.13",
"@textcomplete/core": "0.1.13",
"@textcomplete/textarea": "0.1.13",
- "ace-builds": "1.40.1",
+ "ace-builds": "1.41.0",
"archiver": "7.0.1",
"async": "3.2.6",
"autoprefixer": "10.4.21",
@@ -47,8 +47,8 @@
"benchpressjs": "2.5.5",
"body-parser": "2.2.0",
"bootbox": "6.0.3",
- "bootstrap": "5.3.5",
- "bootswatch": "5.3.5",
+ "bootstrap": "5.3.6",
+ "bootswatch": "5.3.6",
"chalk": "4.1.2",
"chart.js": "4.4.9",
"cli-graph": "3.2.2",
@@ -64,10 +64,10 @@
"cookie-parser": "1.4.7",
"cron": "4.3.0",
"cropperjs": "1.6.2",
- "csrf-sync": "4.1.0",
+ "csrf-sync": "4.2.1",
"daemon": "1.1.0",
- "diff": "7.0.0",
- "esbuild": "0.25.3",
+ "diff": "8.0.1",
+ "esbuild": "0.25.4",
"express": "4.21.2",
"express-session": "1.18.1",
"express-useragent": "1.0.15",
@@ -89,7 +89,7 @@
"jsonwebtoken": "9.0.2",
"lodash": "4.17.21",
"logrotate-stream": "0.2.9",
- "lru-cache": "10.4.3",
+ "lru-cache": "11.1.0",
"mime": "3.0.0",
"mkdirp": "3.0.1",
"mongodb": "6.16.0",
@@ -102,8 +102,8 @@
"nodebb-plugin-dbsearch": "6.2.16",
"nodebb-plugin-emoji": "6.0.2",
"nodebb-plugin-emoji-android": "4.1.1",
- "nodebb-plugin-markdown": "13.1.2",
- "nodebb-plugin-mentions": "4.7.5",
+ "nodebb-plugin-markdown": "13.2.1",
+ "nodebb-plugin-mentions": "4.7.6",
"nodebb-plugin-spam-be-gone": "2.3.2",
"nodebb-plugin-web-push": "0.7.4",
"nodebb-rewards-essentials": "1.0.2",
@@ -112,25 +112,25 @@
"nodebb-theme-peace": "2.2.42",
"nodebb-theme-persona": "14.1.11",
"nodebb-widget-essentials": "7.0.38",
- "nodemailer": "6.10.1",
+ "nodemailer": "7.0.3",
"nprogress": "0.2.0",
"passport": "0.7.0",
"passport-http-bearer": "1.0.1",
"passport-local": "1.0.0",
- "pg": "8.15.6",
- "pg-cursor": "2.14.6",
+ "pg": "8.16.0",
+ "pg-cursor": "2.15.0",
"postcss": "8.5.3",
"postcss-clean": "1.2.0",
"progress-webpack-plugin": "1.0.16",
"prompt": "1.3.0",
"ioredis": "5.6.1",
- "rimraf": "5.0.10",
+ "rimraf": "6.0.1",
"rss": "1.2.2",
"rtlcss": "4.3.0",
- "sanitize-html": "2.16.0",
- "sass": "1.87.0",
- "satori": "0.12.2",
- "semver": "7.7.1",
+ "sanitize-html": "2.17.0",
+ "sass": "1.88.0",
+ "satori": "0.13.1",
+ "semver": "7.7.2",
"serve-favicon": "2.5.0",
"sharp": "0.32.6",
"sitemap": "8.0.0",
@@ -147,7 +147,7 @@
"toobusy-js": "0.5.1",
"tough-cookie": "5.1.2",
"validator": "13.15.0",
- "webpack": "5.99.7",
+ "webpack": "5.99.8",
"webpack-merge": "6.0.1",
"winston": "3.17.0",
"workerpool": "9.2.0",
@@ -158,10 +158,10 @@
},
"devDependencies": {
"@apidevtools/swagger-parser": "10.1.0",
- "@commitlint/cli": "19.8.0",
- "@commitlint/config-angular": "19.8.0",
+ "@commitlint/cli": "19.8.1",
+ "@commitlint/config-angular": "19.8.1",
"coveralls": "3.1.1",
- "@eslint/js": "9.25.1",
+ "@eslint/js": "9.26.0",
"@stylistic/eslint-plugin-js": "4.2.0",
"eslint-config-nodebb": "1.1.4",
"eslint-plugin-import": "2.31.0",
@@ -169,15 +169,15 @@
"grunt-contrib-watch": "1.1.0",
"husky": "8.0.3",
"jsdom": "26.1.0",
- "lint-staged": "15.5.1",
- "mocha": "11.1.0",
+ "lint-staged": "16.0.0",
+ "mocha": "11.2.2",
"mocha-lcov-reporter": "1.3.0",
"mockdate": "3.0.5",
"nyc": "17.1.0",
"smtp-server": "3.13.6"
},
"optionalDependencies": {
- "sass-embedded": "1.87.0"
+ "sass-embedded": "1.88.0"
},
"resolutions": {
"*/jquery": "3.7.1"
@@ -200,4 +200,4 @@
"url": "https://github.com/barisusakli"
}
]
-}
\ No newline at end of file
+}
diff --git a/public/language/he/admin/advanced/database.json b/public/language/he/admin/advanced/database.json
index 68c0208cb3..cf3736e51a 100644
--- a/public/language/he/admin/advanced/database.json
+++ b/public/language/he/admin/advanced/database.json
@@ -22,7 +22,7 @@
"mongo.bytes-out": "ביטים יוצאים",
"mongo.num-requests": "מספר בקשות",
"mongo.raw-info": "מידע לא מעובד מ-MongoDB",
- "mongo.unauthorized": "NodeBB לא הצליחה לקבל את המידע הדרוש מ-MongoDB. אנא בדוק שלמשתמש יש הרשאת clusterMonitor ל-admin database.",
+ "mongo.unauthorized": "NodeBB לא הצליחה לקבל את המידע הדרוש מ-MongoDB. אנא בדקו שלמשתמש יש הרשאת clusterMonitor ל-admin database.",
"redis": "Redis",
"redis.version": "גרסת Redis",
diff --git a/public/language/he/admin/advanced/errors.json b/public/language/he/admin/advanced/errors.json
index ac86dbfdbf..43ec79168d 100644
--- a/public/language/he/admin/advanced/errors.json
+++ b/public/language/he/admin/advanced/errors.json
@@ -10,6 +10,6 @@
"route": "נתיב",
"count": "ספירה",
"no-routes-not-found": "הידד! אין שגיאות 404!",
- "clear404-confirm": "האם אתה בטוח שאתה רוצה לנקות את לוג שגיאות 404?",
+ "clear404-confirm": "האם לנקות את לוג שגיאות 404?",
"clear404-success": "שגיאות \"404 לא נמצא\" נוקו"
}
\ No newline at end of file
diff --git a/public/language/he/admin/advanced/events.json b/public/language/he/admin/advanced/events.json
index 10cfb34164..65d39624ad 100644
--- a/public/language/he/admin/advanced/events.json
+++ b/public/language/he/admin/advanced/events.json
@@ -3,15 +3,15 @@
"no-events": "אין אירועים",
"control-panel": "בקרת אירועים",
"delete-events": "מחיקת אירועים",
- "confirm-delete-all-events": "האם אתה בטוח שאתה רוצה למחוק את כל האירועים שנרשמו?",
+ "confirm-delete-all-events": "האם למחוק את כל האירועים שנרשמו?",
"filters": "מסננים",
- "filters-apply": "החל מסננים",
+ "filters-apply": "החלת מסננים",
"filter-type": "סוג אירוע",
"filter-start": "מתאריך",
"filter-end": "עד תאריך",
"filter-user": "סינון לפי משתמש",
- "filter-user.placeholder": "הקלד שם משתמש לסינון...",
+ "filter-user.placeholder": "הקלידו שם משתמש לסינון...",
"filter-group": "סינון לפי קבוצה",
- "filter-group.placeholder": "הקלד שם קבוצה לסינון...",
+ "filter-group.placeholder": "הקלידו שם קבוצה לסינון...",
"filter-per-page": "פריטים בכל דף"
}
\ No newline at end of file
diff --git a/public/language/he/admin/advanced/logs.json b/public/language/he/admin/advanced/logs.json
index 5c1dd85a15..3c6038df10 100644
--- a/public/language/he/admin/advanced/logs.json
+++ b/public/language/he/admin/advanced/logs.json
@@ -1,7 +1,7 @@
{
"logs": "לוגים",
"control-panel": "בקרת לוגים",
- "reload": "טען לוג מחדש",
- "clear": "נקה לוגים",
+ "reload": "טעינת לוגים מחדש",
+ "clear": "ניקוי לוגים",
"clear-success": "הלוגים נוקו!"
}
\ No newline at end of file
diff --git a/public/language/he/admin/appearance/customise.json b/public/language/he/admin/appearance/customise.json
index fc2c805f5f..fc9ca44159 100644
--- a/public/language/he/admin/appearance/customise.json
+++ b/public/language/he/admin/appearance/customise.json
@@ -1,8 +1,8 @@
{
"customise": "התאמה אישית",
"custom-css": "CSS/SASS מותאם אישית",
- "custom-css.description": "הזן כאן הצהרות CSS/SASS משלך, שיוחלו לאחר כל הסגנונות האחרים.",
- "custom-css.enable": "הפעל CSS/SASS מותאם אישית",
+ "custom-css.description": "הזינו כאן הצהרות CSS/SASS משלך, שיוחלו לאחר כל הסגנונות האחרים.",
+ "custom-css.enable": "הפעלת CSS/SASS מותאם אישית",
"custom-js": "Javascript מותאם אישית",
"custom-js.description": "הכניסו כאן JavaScript משלכם, שיבוצע לאחר טעינת הדף לחלוטין.",
@@ -15,6 +15,6 @@
"custom-css.livereload": "הפעלת טעינה מחדש אוטומטית.",
"custom-css.livereload.description": "הפעלה זו נועדה כדי לרענן את כל החיבורים מכל מכשיר, כאשר תשמרו את הדף המותאם אישית.",
"bsvariables": "_variables.scss",
- "bsvariables.description": "ניתן לעקוף משתני Bootstrap כאן. אתה יכול גם להשתמש בכלי כמו bootstrap.build ולהדביק את הפלט כאן.
שינויים דורשים בנייה מחדש והפעלה מחדש.",
+ "bsvariables.description": "ניתן לעקוף משתני Bootstrap כאן. תוכלו גם להשתמש בכלי כמו bootstrap.build ולהדביק את הפלט כאן.
שינויים דורשים בנייה מחדש והפעלה מחדש.",
"bsvariables.enable": "Enable _variables.scss"
}
\ No newline at end of file
diff --git a/public/language/he/admin/appearance/skins.json b/public/language/he/admin/appearance/skins.json
index ceb0a6a9b8..39893ab1f6 100644
--- a/public/language/he/admin/appearance/skins.json
+++ b/public/language/he/admin/appearance/skins.json
@@ -2,15 +2,15 @@
"skins": "עיצובים",
"bootswatch-skins": "עיצובי Bootswatch",
"custom-skins": "עיצובים מותאמים אישית",
- "add-skin": "הוסף עיצוב",
- "save-custom-skins": "שמור עיצוב מותאם אישית",
+ "add-skin": "הוספת עיצוב",
+ "save-custom-skins": "שמירת עיצוב מותאם אישית",
"save-custom-skins-success": "עיצובים מותאמים אישית נשמרו בהצלחה",
"custom-skin-name": "שם עיצוב מותאם אישית",
"custom-skin-variables": "משתני עיצוב מותאם אישית",
"loading": "טוען עיצובים",
"homepage": "דף הפרוייקט",
- "select-skin": "בחר עיצוב זה",
- "revert-skin": "חזור לעיצוב מקורי",
+ "select-skin": "בחירת עיצוב זה",
+ "revert-skin": "חזרה לעיצוב מקורי",
"current-skin": "עיצוב נוכחי",
"skin-updated": "עיצוב עודכן",
"applied-success": "עיצוב %1 הוחל בהצלחה",
diff --git a/public/language/he/admin/appearance/themes.json b/public/language/he/admin/appearance/themes.json
index 507ccd8534..514681e5c8 100644
--- a/public/language/he/admin/appearance/themes.json
+++ b/public/language/he/admin/appearance/themes.json
@@ -2,12 +2,12 @@
"themes": "ערכות נושא",
"checking-for-installed": "בודק ערכות נושא מותקנות...",
"homepage": "דף הבית",
- "select-theme": "בחר ערכת נושא",
- "revert-theme": "חזור לערכת נושא מקורית",
+ "select-theme": "בחירת ערכת נושא",
+ "revert-theme": "חזרה לערכת נושא מקורית",
"current-theme": "ערכת נושא נוכחית",
"no-themes": "לא נמצאו ערכות נושא מותקנות",
- "revert-confirm": "האם אתה בטוח שאתה רוצה לשחזר את ערכת הנושא הרגילה של NodeBB?",
+ "revert-confirm": "האם לשחזר את ערכת הנושא הרגילה של NodeBB?",
"theme-changed": "ערכת הנושא שונתה",
- "revert-success": "החזרת בהצלחה את הפורום שלך לערכת הנושא ברירת המחדל.",
- "restart-to-activate": "אנא בצע בנייה והפעלה מחדש כדי להחיל את ערכת הנושא הזו."
+ "revert-success": "החזרתם בהצלחה את הפורום שלכם לערכת הנושא ברירת המחדל.",
+ "restart-to-activate": "אנא בצעו בנייה והפעלה מחדש כדי להחיל את ערכת הנושא הזו."
}
\ No newline at end of file
diff --git a/public/language/he/admin/dashboard.json b/public/language/he/admin/dashboard.json
index a1aeceeff0..bdaa869915 100644
--- a/public/language/he/admin/dashboard.json
+++ b/public/language/he/admin/dashboard.json
@@ -12,8 +12,8 @@
"page-views-custom": "טווח תאריכים מותאם אישית",
"page-views-custom-start": "תחילת טווח",
"page-views-custom-end": "סוף טווח",
- "page-views-custom-help": "הכנס טווח תאריכים של התקופה בה תרצה לצפות בתעבורת הפורום. הפורמט הנדרש הוא YYYY-MM-DD",
- "page-views-custom-error": "הזן טווח תאריכים תקין כדלהלן YYYY-MM-DD",
+ "page-views-custom-help": "הכניסו טווח תאריכים של התקופה בה תרצו לצפות בתעבורת הפורום. הפורמט הנדרש הוא YYYY-MM-DD",
+ "page-views-custom-error": "הזינו טווח תאריכים תקין כדלהלן YYYY-MM-DD",
"stats.yesterday": "אתמול",
"stats.today": "היום",
@@ -25,21 +25,21 @@
"updates": "עדכונים",
"running-version": "הפורום מעודכן לגרסה %1",
- "keep-updated": "לעדכוני אבטחה ותיקוני באגים, וודא שהפורום שלך עדכני לגרסה האחרונה.",
- "up-to-date": "אתה מעודכן ",
+ "keep-updated": "לעדכוני אבטחה ותיקוני באגים, וודאו שהפורום שלכם עדכני לגרסה האחרונה.",
+ "up-to-date": "הינך מעודכן ",
"upgrade-available": "גרסה חדשה (v%1) שוחררה. שקול לעדכן את הפורום שלך.",
"prerelease-upgrade-available": "זוהי גרסת טרום-הפצה מיושנת של NodeBB. גרסה חדשה (v%1) שוחררה. שקול לשדרג את ה-NodeBB שלך.",
"prerelease-warning": " זוהי גרסת טרום-הפצה של NodeBB. עלולים להתרחש באגים לא מכוונים.",
"fallback-emailer-not-found": "Fallback emailer לא נמצא!",
- "running-in-development": "הפורום פועל במצב פיתוח. הפורום עשוי להיות חשוף לפגיעויות פוטנציאליות; אנא פנה למנהל המערכת שלך",
- "latest-lookup-failed": "לא הצליח לחפש את הגרסה האחרונה הזמינה של NodeBB",
+ "running-in-development": "הפורום פועל במצב פיתוח. הפורום עשוי להיות חשוף לפגיעויות פוטנציאליות; אנא פנו למנהל המערכת שלכם",
+ "latest-lookup-failed": "לא הצליח למצוא את הגרסה האחרונה הזמינה של NodeBB",
"notices": "התראות",
"restart-not-required": "לא נדרשת הפעלה מחדש",
"restart-required": "נדרשת הפעלה מחדש",
"search-plugin-installed": "תוסף חיפוש הותקן",
"search-plugin-not-installed": "תוסף חיפוש לא הותקן",
- "search-plugin-tooltip": "התקן את תוסף החיפוש מעמוד התוספים על מנת להפעיל את אפשרות החיפוש",
+ "search-plugin-tooltip": "התקינו את תוסף החיפוש מעמוד התוספים על מנת להפעיל את אפשרות החיפוש",
"control-panel": "שליטת מערכת",
"rebuild-and-restart": "בנייה והפעלה מחדש",
@@ -47,9 +47,9 @@
"restart-warning": "הפעלה או בניה מחדש של הפורום תנתק את כל החיבורים הקיימים למספר שניות",
"restart-disabled": "הפעלה או בניה מחדש של הפורום בוטלה, נראה שאינך מפעיל את הפורום דרך שרת מתאים.",
"maintenance-mode": "מצב תחזוקה",
- "maintenance-mode-title": "לחץ כאן על מנת להכניס את הפורום למצב תחזוקה",
+ "maintenance-mode-title": "לחצו כאן על מנת להכניס את הפורום למצב תחזוקה",
"dark-mode": "מצב כהה",
- "realtime-chart-updates": "עדכן תרשים בזמן אמת",
+ "realtime-chart-updates": "עדכון תרשים בזמן אמת",
"active-users": "משתמשים פעילים",
"active-users.users": "משתמשים",
@@ -91,11 +91,11 @@
"start": "התחלה",
"end": "סיום",
"filter": "סינון",
- "view-as-json": "הצג כ-JSON",
- "expand-analytics": "הרחב ניתוח",
- "clear-search-history": "מחק היסטוריית חיפושים",
- "clear-search-history-confirm": "האם אתה בטוח שברצונך למחוק את כל היסטוריית החיפושים?",
+ "view-as-json": "הצגה כ-JSON",
+ "expand-analytics": "הרחבת ניתוח",
+ "clear-search-history": "מחיקת היסטוריית חיפושים",
+ "clear-search-history-confirm": "האם למחוק את כל היסטוריית החיפושים?",
"search-term": "מונח",
"search-count": "כמות",
- "view-all": "הצג הכל"
+ "view-all": "הצגת הכל"
}
diff --git a/public/language/he/admin/development/info.json b/public/language/he/admin/development/info.json
index d0b09aa382..d20aa255f5 100644
--- a/public/language/he/admin/development/info.json
+++ b/public/language/he/admin/development/info.json
@@ -1,5 +1,5 @@
{
- "you-are-on": "אתה נמצא ב %1:%2",
+ "you-are-on": "הינכם נמצאים ב %1:%2",
"ip": "IP %1",
"nodes-responded": "%1 nodes הגיבו בתוך %2 מילי שניות!",
"host": "host",
@@ -17,7 +17,7 @@
"cpu-usage": "שימוש ב-CPU",
"uptime": "משך זמן פעולת המערכת ללא השבתה",
- "registered": "רשום",
+ "registered": "רשומים",
"sockets": "Sockets",
"connection-count": "כמות חיבורים",
"guests": "אורחים",
diff --git a/public/language/he/admin/development/logger.json b/public/language/he/admin/development/logger.json
index 1bdf92c432..2177b19175 100644
--- a/public/language/he/admin/development/logger.json
+++ b/public/language/he/admin/development/logger.json
@@ -6,7 +6,7 @@
"enable-http": "הפעלת רישום HTTP",
"enable-socket": "הפעלת רישום אירועים ב-socket.io",
"file-path": "נתיב קובץ לוג",
- "file-path-placeholder": "/path/to/log/file.log ::: השאירו ריק כדי שהלוג ישמר בטרמינל שלך",
+ "file-path-placeholder": "/path/to/log/file.log ::: השאירו ריק כדי שהלוג ישמר בטרמינל שלכם",
"control-panel": "ניהול לוגים",
"update-settings": "עידכון הגדרות לוגים"
diff --git a/public/language/he/admin/extend/rewards.json b/public/language/he/admin/extend/rewards.json
index ccad7caf8b..5b054e4eed 100644
--- a/public/language/he/admin/extend/rewards.json
+++ b/public/language/he/admin/extend/rewards.json
@@ -5,11 +5,11 @@
"condition-is": "הינו:",
"condition-then": "תגמל ב:",
"max-claims": "מספר פעמים בה ניתן לדרוש תגמול",
- "zero-infinite": "הזן 0 ללא הגבלה",
- "select-reward": "בחר תגמול",
- "delete": "מחק",
- "enable": "הפעל",
- "disable": "השבת",
+ "zero-infinite": "הזינו 0 ללא הגבלה",
+ "select-reward": "בחירת תגמול",
+ "delete": "מחיקה",
+ "enable": "הפעלה",
+ "disable": "השבתה",
"alert.delete-success": "תגמול נמחק בהצלחה",
"alert.no-inputs-found": "תגמול לא חוקי - לא נמצא מידע!",
diff --git a/public/language/he/admin/extend/widgets.json b/public/language/he/admin/extend/widgets.json
index 50bd6ce969..c603426857 100644
--- a/public/language/he/admin/extend/widgets.json
+++ b/public/language/he/admin/extend/widgets.json
@@ -16,22 +16,22 @@
"container.body": "גוף",
"container.alert": "התראה",
- "alert.confirm-delete": "האם אתה בטוח שאתה רוצה למחוק את הווידג'ט?",
+ "alert.confirm-delete": "האם למחוק את הווידג'ט?",
"alert.updated": "העלאת ווידג'טים",
"alert.update-success": "הווידג'טים הועלו בהצלחה",
"alert.clone-success": "הווידג'טים שוכפלו בהצלחה",
- "error.select-clone": "בחר דף לשכפל ממנו",
+ "error.select-clone": "בחרו דף לשכפל ממנו",
"title": "כותרת",
- "title.placeholder": "כותרת (מוצגת רק בגורמים מכילים מסוימים)",
- "container": "גורם מכיל",
- "container.placeholder": "גרור ושחרר גורם מכיל (container) או הזן HTML כאן.",
+ "title.placeholder": "כותרת (מוצגת רק ב-containers מסוימים)",
+ "container": "גורם מכיל - Container",
+ "container.placeholder": "גררו ושחררו גורם מכיל (container) או הזינו HTML כאן.",
"show-to-groups": "יוצג בקבוצות",
"hide-from-groups": "יוסתר מקבוצות",
"start-date": "תאריך התחלה",
"end-date": "תאריך סיום",
- "hide-on-mobile": "הסתר במובייל",
- "hide-drafts": "הסתר טיוטות",
- "show-drafts": "הצג טיוטות"
+ "hide-on-mobile": "הסתרה במובייל",
+ "hide-drafts": "הסתרת טיוטות",
+ "show-drafts": "הצגת טיוטות"
}
\ No newline at end of file
diff --git a/public/language/he/admin/manage/categories.json b/public/language/he/admin/manage/categories.json
index ed4a9247f9..c6837046d1 100644
--- a/public/language/he/admin/manage/categories.json
+++ b/public/language/he/admin/manage/categories.json
@@ -1,9 +1,9 @@
{
"manage-categories": "ניהול קטגוריות",
- "add-category": "הוסף קטגוריה",
- "jump-to": "קפוץ אל...",
+ "add-category": "הוספת קטגוריה",
+ "jump-to": "קפיצה אל...",
"settings": "הגדרות קטגוריות",
- "edit-category": "ערוך קטגוריה",
+ "edit-category": "עריכת קטגוריה",
"privileges": "הרשאות",
"back-to-categories": "חזרה לרשימת הקטגוריות",
"name": "שם קטגוריה",
@@ -23,7 +23,7 @@
"is-section": "הגדר קטגוריה זו כמקטע ללא אפשרות כניסה, רק לתתי קטגוריות.",
"post-queue": "תור פוסטים",
"tag-whitelist": "רשימה לבנה של תגיות",
- "upload-image": "העלה תמונה",
+ "upload-image": "העלו תמונה",
"upload": "העלאה",
"delete-image": "הסרה",
"category-image": "תמונת קטגוריה",
@@ -32,8 +32,8 @@
"optional-parent-category": "קטגוריית הורים (אופציונלי)",
"top-level": "רמה עליונה",
"parent-category-none": "(ללא)",
- "copy-parent": "העתק אב",
- "copy-settings": "העתק הגדרות מ:",
+ "copy-parent": "העתקת אב",
+ "copy-settings": "העתקת הגדרות מ:",
"optional-clone-settings": "שכפול הגדרות מקטגוריה (אופציונלי)",
"clone-children": "שכפול קטגוריות והגדרות של צאצאים",
"purge": "מחיקת קטגוריה",
@@ -59,7 +59,7 @@
"privileges.section-moderation": "הרשאות מנחה",
"privileges.section-other": "אחר",
"privileges.section-user": "משתמש",
- "privileges.search-user": "הוסף משתמש",
+ "privileges.search-user": "הוספת משתמש",
"privileges.no-users": "אין הרשאות ספציפיות למשתמש בקטגוריה זו.",
"privileges.section-group": "קבוצה",
"privileges.group-private": "קבוצה זו פרטית",
@@ -89,7 +89,7 @@
"federation.syncing-intro": "קטגוריה יכולה לעקוב אחר \"שחקן קבוצתי\" באמצעות פרוטוקול ActivityPub. אם יתקבל תוכן מאחד השחקנים המפורטים למטה, הוא יתווסף אוטומטית לקטגוריה זו.",
"federation.syncing-caveat": "הגדרת סנכרון N.B. כאן יוצרת סנכרון חד כיווני. NodeBB מנסה להירשם/לעקוב אחרי השחקן, אך לא ניתן להניח את ההיפך.",
"federation.syncing-none": "קטגוריה זו אינה עוקבת כעת אחר אף אחד.",
- "federation.syncing-add": "סנכרן עם...",
+ "federation.syncing-add": "סינכרון עם...",
"federation.syncing-actorUri": "שחקן",
"federation.syncing-follow": "עקוב",
"federation.syncing-unfollow": "הפסקת מעקב",
@@ -101,7 +101,7 @@
"alert.created": "נוצר",
"alert.create-success": "קטגוריה נוצרה בהצלחה!",
- "alert.none-active": "אין לך קטגוריות פעילות.",
+ "alert.none-active": "אין לכם קטגוריות פעילות.",
"alert.create": "יצירת קטגוריה",
"alert.confirm-purge": "
האם אתם בטוחים שאתם רוצים למחוק את קטגוריית \"%1\"?
אזהרה! כל הנושאים והפוסטים בקטגוריה זו ימחקו!
מחיקת קטגוריה תסיר את כל הנושאים והפוסטים ותמחק את הקטגוריה ממסד הנתונים. אם ברצונכם להסיר את הקטגוריה באופן זמני, בחרו ב\"השבתת\" הקטגוריה.
",
"alert.purge-success": "הקטגוריה נמחקה!",
diff --git a/public/language/he/admin/menu.json b/public/language/he/admin/menu.json
index b06f729f77..d32642d723 100644
--- a/public/language/he/admin/menu.json
+++ b/public/language/he/admin/menu.json
@@ -83,11 +83,11 @@
"search.placeholder": "חיפוש הגדרות",
"search.no-results": "אין תוצאות...",
"search.search-forum": "חפש בפורום ",
- "search.keep-typing": "המשך להקליד על מנת למצוא תוצאות...",
- "search.start-typing": "התחל להקליד על מנת לראות תוצאות...",
+ "search.keep-typing": "המשיכו להקליד על מנת למצוא תוצאות...",
+ "search.start-typing": "התחילו להקליד על מנת לראות תוצאות...",
"connection-lost": "החיבור ל-%1 אבד, מנסה להתחבר מחדש...",
"alerts.version": "מעודכן ל-NodeBB v%1",
- "alerts.upgrade": "שדרג ל v%1"
+ "alerts.upgrade": "שדרגו ל v%1"
}
\ No newline at end of file
diff --git a/public/language/he/admin/settings/sounds.json b/public/language/he/admin/settings/sounds.json
index 57cca017c5..cd6cfadc8b 100644
--- a/public/language/he/admin/settings/sounds.json
+++ b/public/language/he/admin/settings/sounds.json
@@ -1,9 +1,9 @@
{
"notifications": "התראות",
"chat-messages": "הודעות צ'אט",
- "play-sound": "נגן",
+ "play-sound": "נגינה",
"incoming-message": "הודעה נכנסת",
"outgoing-message": "הודעה יוצאת",
- "upload-new-sound": "העלה צליל חדש",
+ "upload-new-sound": "העלאת צליל חדש",
"saved": "הגדרות נשמרו"
}
\ No newline at end of file
diff --git a/public/language/he/admin/settings/user.json b/public/language/he/admin/settings/user.json
index b55ddeddfe..0c0d227b39 100644
--- a/public/language/he/admin/settings/user.json
+++ b/public/language/he/admin/settings/user.json
@@ -64,7 +64,7 @@
"show-email": "הצג כתובת אימייל",
"show-fullname": "הצג שם מלא",
"restrict-chat": "אשר הודעות צ'אט רק ממשתמשים שאני עוקב אחריהם",
- "disable-incoming-chats": "Disable incoming chat messages",
+ "disable-incoming-chats": "השבתת הודעות צ'אט נכנסות",
"outgoing-new-tab": "פתח קישורים חיצוניים בכרטיסייה חדשה",
"topic-search": "הפעל חיפוש בתוך נושא",
"update-url-with-post-index": "עדכן את כתובת הURL עם מספר הפוסט הנוכחי בזמן גלישה בנושאים",
diff --git a/public/language/he/category.json b/public/language/he/category.json
index 8c233ac543..1c66c9ddcc 100644
--- a/public/language/he/category.json
+++ b/public/language/he/category.json
@@ -7,7 +7,7 @@
"new-topic-button": "נושא חדש",
"guest-login-post": "התחברו כדי לפרסם",
"no-topics": "קטגוריה זו ריקה מנושאים.
למה שלא תנסו להוסיף נושא חדש?",
- "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.",
+ "no-followers": "אף אחד באתר זה לא עוקב אחר או צופה בקטגוריה זו. עקבו אחר או צפו בקטגוריה זו כדי להתחיל לקבל עדכונים.",
"browsing": "צופים בנושא זה כעת",
"no-replies": "אין תגובות",
"no-new-posts": "אין פוסטים חדשים.",
diff --git a/public/language/he/email.json b/public/language/he/email.json
index 48b1b41c7b..f627a4aba0 100644
--- a/public/language/he/email.json
+++ b/public/language/he/email.json
@@ -5,29 +5,29 @@
"invite": "הזמנה מ%1",
"greeting-no-name": "שלום",
"greeting-with-name": "שלום %1",
- "email.verify-your-email.subject": "בבקשה אמת את המייל שלך.",
+ "email.verify-your-email.subject": "נא לאמת את המייל שלך.",
"email.verify.text1": "ביקשת לשנות או לאמת את כתובת הדוא\"ל שלך",
"email.verify.text2": "לצורכי אבטחה, אנו משנים או מאמתים את כתובת הדוא\"ל רק לאחר שבעלותך מאומת ע\"י דוא\"ל. אם לא אתה ביקשת זאת, תוכל להתעלם ממייל זו.",
"email.verify.text3": "לאחר שתאשר את כתובת הדוא\"ל, נשנה את כתובת דוא\"ל הנוכחית בכתובת הזו (%1).",
"welcome.text1": "תודה שנרשמת ל%1!",
- "welcome.text2": "על מנת להפעיל את החשבון שלך, אנו צריכים לוודא שאתה בעל חשבון המייל שנרשמת איתו.",
- "welcome.text3": "מנהל אישר את ההרשמה שלך.\nאתה יכול להתחבר עם השם משתמש והסיסמא שלך מעכשיו.",
- "welcome.cta": "לחץ כאן על מנת לאשר את כתובת המייל שלך.",
+ "welcome.text2": "להפעלת החשבון, נדרש אימות שהאימייל אתו נרשמתם, הוא בבעלותכם.",
+ "welcome.text3": "מנהל אישר את ההרשמה שלך.\nניתן להתחבר עם השם משתמש והסיסמא שלך מעכשיו.",
+ "welcome.cta": "לחצו כאן על מנת לאשר את כתובת המייל שלכם.",
"invitation.text1": "%1 הזמין אותך להצטרף ל%2",
"invitation.text2": "ההזמנה של תפוג ב %1 ימים",
- "invitation.cta": "לחץ כאן ליצירת החשבון שלך.",
- "reset.text1": "קיבלנו בקשה לאפס את הסיסמה לחשבון שלך, כנראה מפני ששכחת אותה. אם לא ביקשת לאפס את הסיסמה, אנא התעלם ממייל זה.",
- "reset.text2": "על מנת להמשיך עם תהליך איפוס הסיסמה, אנא לחץ על הלינק הבא:",
- "reset.cta": "לחץ כאן לאפס את הסיסמה שלך.",
+ "invitation.cta": "לחצו כאן ליצירת החשבון שלכם.",
+ "reset.text1": "קיבלנו בקשה לאפס את הסיסמה לחשבון שלכם, כנראה מפני ששכחתם אותה. אם לא ביקשתם לאפס את הסיסמה, אנא התעלמו מאימייל זה.",
+ "reset.text2": "על מנת להמשיך עם תהליך איפוס הסיסמה, אנא לחצו על הלינק הבא:",
+ "reset.cta": "לחצו כאן לאפס את הסיסמה שלכם.",
"reset.notify.subject": "הסיסמה שונתה בהצלחה.",
- "reset.notify.text1": "אנו מודיעים לך שב%1, סיסמתך שונתה בהצלחה.",
- "reset.notify.text2": "אם לא אישרת בקשה זו, אנא הודע למנהל מיד.",
+ "reset.notify.text1": "רצינו להסב את תשומת ליבך שב%1, סיסמתך שונתה בהצלחה.",
+ "reset.notify.text2": "אם לא אישרתם בקשה זו, אנא הודיעו למנהל בהקדם.",
"digest.unread-rooms": "חדרים שלא נקראו",
"digest.room-name-unreadcount": "%1 (%2 לא נקראו)",
"digest.latest-topics": "נושאים אחרונים מ%1",
"digest.top-topics": "נושאים עם הכי הרבה הצבעות מ-%1",
"digest.popular-topics": "הנושאים הכי פופולריים מ-%1",
- "digest.cta": "לחץ כאן כדי לבקר ב %1",
+ "digest.cta": "לחצו כאן כדי לבקר ב %1",
"digest.unsub.info": "תקציר זה נשלח אליך על-פי הגדרות החשבון שלך.",
"digest.day": "יום",
"digest.week": "שבוע",
@@ -36,23 +36,23 @@
"digest.title.day": "התקציר היומי שלך",
"digest.title.week": "התקציר השבועי שלך",
"digest.title.month": "התקציר החודשי שלך",
- "notif.chat.new-message-from-user": "New message from \"%1\"",
- "notif.chat.new-message-from-user-in-room": "New message from %1 in room %2",
- "notif.chat.cta": "לחץ כאן כדי להמשיך את השיחה",
- "notif.chat.unsub.info": "התראה הצ'אט הזו נשלחה אליך על-פי הגדרות החשבון שלך.",
- "notif.post.unsub.info": "התראת הפוסט הזו נשלחה אליך על-פי הגדרות החשבון שלך.",
- "notif.post.unsub.one-click": "ניתן גם להפסיק את קבלת המיילים כמו זה, בלחיצה על",
+ "notif.chat.new-message-from-user": "הודעה חדשה מ-\"%1\"",
+ "notif.chat.new-message-from-user-in-room": "הודעה חדשה מ-%1 בחדר %2",
+ "notif.chat.cta": "לחצו כאן כדי להמשיך את השיחה",
+ "notif.chat.unsub.info": "הודעת צ'אט זו נשלחה אליך על-פי הגדרות החשבון שלך.",
+ "notif.post.unsub.info": "התראת פוסט זו נשלחה אליך על-פי הגדרות החשבון שלך.",
+ "notif.post.unsub.one-click": "ניתן גם להפסיק את קבלת האימיילים כמו זה, בלחיצה על",
"notif.cta": "כניסה לפורום",
- "notif.cta-new-reply": "הצג פוסט",
- "notif.cta-new-chat": "הצג צ'אט",
- "notif.test.short": "בדיקת התראות.",
- "notif.test.long": "זוהי בדיקה של ההתראות במייל.",
+ "notif.cta-new-reply": "הצגת פוסט",
+ "notif.cta-new-chat": "הצגת צ'אט",
+ "notif.test.short": "בדיקת התראות",
+ "notif.test.long": "זוהי בדיקה של ההתראות באימייל.",
"test.text1": "זהו אימייל ניסיון על מנת לוודא שהגדרות המייל בוצעו כהלכה בהגדרות NodeBB.",
- "unsub.cta": "לחץ כאן לשנות הגדרות אלו",
- "unsubscribe": "בטל רישום",
- "unsub.success": "אתה לא תקבל יותר מיילים מרשימת התפוצה של %1",
+ "unsub.cta": "לחצו כאן לשנות הגדרות אלו",
+ "unsubscribe": "ביטול רישום",
+ "unsub.success": "לא תקבלו יותר אימיילים מרשימת התפוצה של %1",
"unsub.failure.title": "לא ניתן לבטל את המנוי",
- "unsub.failure.message": "למרבה הצער, לא הצלחנו לבטל את הרישום שלך מרשימת התפוצה, מכיוון שהייתה בעיה בקישור. עם זאת, אתה יכול לשנות את העדפות הדוא\"ל שלך על ידי מעבר להגדרות המשתמש שלך.
(שגיאה: %1)",
+ "unsub.failure.message": "למרבה הצער, לא הצלחנו לבטל את הרישום שלך מרשימת התפוצה, מכיוון שהייתה בעיה בקישור. עם זאת, תוכלו לשנות את העדפות הדוא\"ל שלכם על ידי מעבר להגדרות המשתמש שלכם.
(שגיאה: %1)",
"banned.subject": "הורחקת מ %1",
"banned.text1": "המשתמש %1 הורחק מ %2.",
"banned.text2": "הרחקה זו תמשך עד %1",
diff --git a/public/language/he/error.json b/public/language/he/error.json
index b7b709b4f3..82af4336b3 100644
--- a/public/language/he/error.json
+++ b/public/language/he/error.json
@@ -67,8 +67,8 @@
"no-chat-room": "חדר צ'אט לא קיים",
"no-privileges": "ההרשאות שלכם אינן מספיקות לביצוע פעולה זו.",
"category-disabled": "קטגוריה לא פעילה",
- "post-deleted": "Post deleted",
- "topic-locked": "Topic locked",
+ "post-deleted": "הפוסט נמחק",
+ "topic-locked": "הנושא נעול",
"post-edit-duration-expired": "ניתן לערוך פוסטים עד %1 שניות מרגע כתיבת הפוסט",
"post-edit-duration-expired-minutes": "ניתן לערוך פוסטים עד %1 דקות מרגע כתיבת הפוסט",
"post-edit-duration-expired-minutes-seconds": "ניתן לערוך פוסטים עד %1 דקות %2 שניות מרגע כתיבת הפוסט",
@@ -142,20 +142,20 @@
"group-leave-disabled": "אינכם רשאים לעזוב את הקבוצה כרגע",
"group-user-not-pending": "למשתמש אין בקשה ממתינה להצטרף לקבוצה זו.",
"gorup-user-not-invited": "המשתמש לא הוזמן להצטרף לקבוצה זו.",
- "post-already-deleted": "פוסט זה נמחק כבר",
+ "post-already-deleted": "פוסט זה כבר נמחק",
"post-already-restored": "פוסט זה כבר שוחזר",
"topic-already-deleted": "נושא זה כבר נמחק",
"topic-already-restored": "נושא זה כבר שוחזר",
- "cant-purge-main-post": "אינכם יכולים למחוק את הפוסט הראשי, אנא מחקו את הנושא במקום",
+ "cant-purge-main-post": "לא ניתן למחוק את הפוסט הראשי, ניתן למחוק את הנושא במקום זה",
"topic-thumbnails-are-disabled": "תמונות ממוזערות לנושא אינן מאופשרות.",
"invalid-file": "קובץ לא תקין",
"uploads-are-disabled": "העלאת קבצים אינה מאופשרת",
- "signature-too-long": "מצטערים, החתימה שלכם אינה יכולה להכיל יותר מ-%1 תווים.",
- "about-me-too-long": "מצטערים, דף האודות שלכם אינו יכול להיות ארוך מ-%1 תווים.",
+ "signature-too-long": "מצטערים, החתימה אינה יכולה להכיל יותר מ-%1 תווים.",
+ "about-me-too-long": "מצטערים, דף האודות אינו יכול להיות ארוך מ-%1 תווים.",
"cant-chat-with-yourself": "לא ניתן לעשות צ'אט עם עצמכם!",
"chat-restricted": "משתמש זה חסם את הודעות הצ'אט שלו ממשתמשים זרים. המשתמש חייב לעקוב אחריכם לפני שתוכלו לשוחח איתו בצ'אט",
- "chat-allow-list-user-already-added": "This user is already in your allow list",
- "chat-deny-list-user-already-added": "This user is already in your deny list",
+ "chat-allow-list-user-already-added": "משתמש זה כבר נמצא ברשימה הלבנה שלכם",
+ "chat-deny-list-user-already-added": "משתמש זה כבר נמצא ברשימת הדחייה שלכם",
"chat-user-blocked": "נחסמתם על ידי משתמש זה.",
"chat-disabled": "מערכת הצ'אט לא פעילה",
"too-many-messages": "שלחתם יותר מדי הודעות, אנא המתינו זמן מה.",
@@ -171,21 +171,21 @@
"cant-add-users-to-chat-room": "לא ניתן להוסיף משתמשים לחדר הצ'אט.",
"cant-remove-users-from-chat-room": "לא ניתן להסיר משתמשים מחדר הצ'אט.",
"chat-room-name-too-long": "שם החדר ארוך מדי. השם לא יכול להיות ארוך מ-%1 תווים.",
- "remote-chat-received-too-long": "קיבלת הודעת צ'אט מ %1, אבל זה היה ארוך מדי ונדחה.",
+ "remote-chat-received-too-long": "התקבלה הודעת צ'אט מ %1, אבל זה היה ארוך מדי ונדחה.",
"already-voting-for-this-post": "הצבעתם כבר בנושא זה.",
"reputation-system-disabled": "מערכת המוניטין לא פעילה.",
"downvoting-disabled": "היכולת להצביע נגד מושבתת",
"not-enough-reputation-to-chat": "נדרש %1 מוניטין כדי לכתוב בצ'אט",
"not-enough-reputation-to-upvote": "נדרש %1 מוניטין כדי להצביע בעד",
"not-enough-reputation-to-downvote": "נדרש %1 מוניטין כדי להצביע למטה",
- "not-enough-reputation-to-post-links": "אתה צריך %1 מוניטין כדי לפרסם קישורים",
+ "not-enough-reputation-to-post-links": "נדרש %1 מוניטין כדי לפרסם קישורים",
"not-enough-reputation-to-flag": "נדרש %1 מוניטין כדי לדווח על פוסט",
"not-enough-reputation-min-rep-website": "נרדש %1 מוניטין כדי להוסיף אתר אינטרנט",
"not-enough-reputation-min-rep-aboutme": "נדרש %1 מוניטין כדי להוסיף תיאור",
"not-enough-reputation-min-rep-signature": "נדרש %1 מוניטין כדי להוסיף חתימה",
"not-enough-reputation-min-rep-profile-picture": "נדרש %1 מוניטין כדי להוסיף תמונת פרופיל",
"not-enough-reputation-min-rep-cover-picture": "נדרש %1 מוניטין כדי להוסיף תמונת רקע לפרופיל",
- "not-enough-reputation-custom-field": "אתה צריך %1 מוניטין עבור %2",
+ "not-enough-reputation-custom-field": "נדרש %1 מוניטין עבור %2",
"custom-user-field-value-too-long": "ערך שדה מותאם אישית ארוך מדי, %1",
"custom-user-field-select-value-invalid": "האפשרות שנבחרה בשדה מותאם אישית אינה חוקית, %1",
"custom-user-field-invalid-text": "טקסט שדה מותאם אישית אינו חוקי, %1",
@@ -201,12 +201,12 @@
"too-many-user-flags-per-day": "ניתן לדווח רק על %1 משתמשים כל יום",
"cant-flag-privileged": "לא ניתן לדווח על מנהלים או על תוכן שנכתב על ידי מנהלים.",
"cant-locate-flag-report": "לא ניתן לאתר דוח דיווח",
- "self-vote": "אי אפשר להצביע על פוסט שיצרתם",
- "too-many-upvotes-today": "ביכולתכם להצביע בעד רק %1 פעמים ביום",
- "too-many-upvotes-today-user": "ביכולתכם להצביע בעד משתמש מסוים רק %1 פעמים ביום",
- "too-many-downvotes-today": "ביכולתכם להצביע נגד %1 פעמים ביום",
- "too-many-downvotes-today-user": "ביכולתכם להצביע נגד משתמש %1 פעמים ביום",
- "reload-failed": "אירעה תקלה ב NodeBB בזמן הטעינה של: \"%1\". המערכת תמשיך להגיש דפים קיימים, אבל כדאי שתשחזרו את הפעולות שלכם מהפעם האחרונה בה המערכת עבדה כראוי.",
+ "self-vote": "לא ניתן להצביע לפוסט שלך",
+ "too-many-upvotes-today": "ניתן להצביע בעד רק %1 פעמים ביום",
+ "too-many-upvotes-today-user": "ניתן להצביע בעד משתמש מסוים רק %1 פעמים ביום",
+ "too-many-downvotes-today": "ניתן להצביע נגד %1 פעמים ביום",
+ "too-many-downvotes-today-user": "ניתן להצביע נגד משתמש %1 פעמים ביום",
+ "reload-failed": "אירעה תקלה ב-NodeBB בזמן הטעינה של: \"%1\". המערכת תמשיך להציג דפים קיימים, אבל כדאי שתשחזרו את הפעולות שלכם מהפעם האחרונה בה המערכת עבדה כראוי.",
"registration-error": "שגיאה בהרשמה",
"parse-error": "אירעה שגיאה בעת ניתוח תגובת השרת",
"wrong-login-type-email": "אנא השתמשו בכתובת המייל שלכם להתחברות",
@@ -225,10 +225,10 @@
"session-mismatch": "סשן לא תואם",
"session-mismatch-text": "נראה שסשן ההתחברות שלכם אינו תואם לשרת. אנא רעננו את הדף.",
"no-topics-selected": "לא נבחרו נושאים!",
- "cant-move-to-same-topic": "אינכם יכולים להעביר פוסט לאותו נושא!",
- "cant-move-topic-to-same-category": "אינכם יכולים להעביר נושא לאותה קטגוריה!",
- "cannot-block-self": "אינכם יכולים לחסום את עצמכם!",
- "cannot-block-privileged": "אינך יכול לחסום מנהלים או מנחים גלובליים",
+ "cant-move-to-same-topic": "לא ניתן להעביר פוסט לאותו נושא!",
+ "cant-move-topic-to-same-category": "לא ניתן להעביר נושא לאותה קטגוריה!",
+ "cannot-block-self": "לא ניתן לחסום את עצמך!",
+ "cannot-block-privileged": "לא ניתן לחסום מנהלים או מנחים גלובליים",
"cannot-block-guest": "אורחים אינם יכולים לחסום משתמשים אחרים",
"already-blocked": "המשתמש חסום כבר",
"already-unblocked": "המשתמש שוחרר כבר מהחסימה",
@@ -237,7 +237,7 @@
"invalid-plugin-id": "מזהה תוסף לא תקין",
"plugin-not-whitelisted": "לא ניתן להתקין את התוסף – ניתן להתקין דרך הניהול רק תוספים שנמצאים ברשימה הלבנה של מנהל החבילות של NodeBB.",
"plugin-installation-via-acp-disabled": "התקנת תוסף באמצעות ACP מושבתת",
- "plugins-set-in-configuration": "אינך רשאי לשנות את מצב הפלאגין כפי שהם מוגדרים בזמן ריצה (config.json, משתני סביבה או ארגומנטים של מסוף), אנא שנה את התצורה במקום זאת.",
+ "plugins-set-in-configuration": "לא ניתן לשנות את מצב התוסף כפי שהם מוגדרים בזמן ריצה (config.json, משתני סביבה או ארגומנטים של מסוף), שנו את התצורה במקום זאת.",
"theme-not-set-in-configuration": "כאשר מגדירים תוספים פעילים בתצורה, שינוי ערכות נושא מחייב הוספת ערכת הנושא החדשה לרשימת התוספים הפעילים לפני עדכון שלו ב-ACP",
"topic-event-unrecognized": "אירוע הנושא '%1' לא מזוהה",
"category.handle-taken": "קישור הקטגוריה כבר נלקחה, אנא בחרו אחרת.",
diff --git a/public/language/he/global.json b/public/language/he/global.json
index 0c2e29a309..428cd81ac4 100644
--- a/public/language/he/global.json
+++ b/public/language/he/global.json
@@ -82,7 +82,7 @@
"downvoted": "הוצבע נגד",
"views": "צפיות",
"posters": "כותבים",
- "watching": "Watching",
+ "watching": "עוקבים",
"reputation": "מוניטין",
"lastpost": "פוסט אחרון",
"firstpost": "פוסט ראשון",
diff --git a/public/language/he/groups.json b/public/language/he/groups.json
index b97ad4d994..0b30fc0a9c 100644
--- a/public/language/he/groups.json
+++ b/public/language/he/groups.json
@@ -39,28 +39,28 @@
"details.description": "תיאור",
"details.member-post-cids": "מזהי קטגוריות להצגת פוסטים מהם",
"details.badge-preview": "תצוגה מקדימה של התג",
- "details.change-icon": "שנה אייקון",
- "details.change-label-colour": "שנה צבע תווית",
- "details.change-text-colour": "שנה צבע טקסט",
+ "details.change-icon": "שינוי אייקון",
+ "details.change-label-colour": "שינוי צבע תווית",
+ "details.change-text-colour": "שינוי צבע טקסט",
"details.badge-text": "טקסט תגית",
- "details.userTitleEnabled": "הצג תגית",
+ "details.userTitleEnabled": "הצגת תגית",
"details.private-help": "אם אפשרות זו מופעלת, הצטרפות לקבוצות ידרוש אישור מבעל הקבוצה.",
"details.hidden": "מוסתר",
"details.hidden-help": "אם אפשרות זו מופעלת, קבוצה זו לא תימצא ברשימת הקבוצות, יהיה ניתן להזמין משתמשים רק באופן ידני",
- "details.delete-group": "מחק קבוצה",
+ "details.delete-group": "מחיקת קבוצה",
"details.private-system-help": "קבוצות פרטיות מושבתות ברמת המערכת, אפשרות זו אינה עושה דבר",
"event.updated": "פרטי הקבוצה עודכנו",
"event.deleted": "קבוצת \"%1\" נמחקה",
- "membership.accept-invitation": "קבל הזמנה",
- "membership.accept.notification-title": "אתה עכשיו חבר ב%1",
+ "membership.accept-invitation": "קבלת הזמנה",
+ "membership.accept.notification-title": "מעכשיו הנך חבר ב%1",
"membership.invitation-pending": "הזמנה ממתינה",
- "membership.join-group": "הצטרפו לקבוצה",
- "membership.leave-group": "עזבו קבוצה",
+ "membership.join-group": "הצטרפות לקבוצה",
+ "membership.leave-group": "עזיבת קבוצה",
"membership.leave.notification-title": "%1 עזב את קבוצת %2",
"membership.reject": "דחייה",
"new-group.group-name": "שם קבוצה",
"upload-group-cover": "העלאת תמונת נושא לקבוצה",
"bulk-invite-instructions": "הזינו רשימה מופרדת בפסיק של משתמשים אותם תרצו להזמין לקבוצה זו.",
"bulk-invite": "הזמנת מספר משתמשים",
- "remove-group-cover-confirm": "האם להסיר תמונת נושא?"
+ "remove-group-cover-confirm": "להסיר תמונת נושא?"
}
\ No newline at end of file
diff --git a/public/language/he/search.json b/public/language/he/search.json
index 564a160918..bc304838d4 100644
--- a/public/language/he/search.json
+++ b/public/language/he/search.json
@@ -63,7 +63,7 @@
"time-older-than-15552000": "זמן: ישן מחצי שנה",
"time-newer-than-31104000": "זמן: חדש משנה",
"time-older-than-31104000": "זמן: ישן משנה",
- "sort-by": "סדר על-פי",
+ "sort-by": "מיון לפי",
"sort": "מיון",
"last-reply-time": "תאריך תגובה אחרון",
"topic-title": "כותרת הנושא",
diff --git a/public/language/he/themes/harmony.json b/public/language/he/themes/harmony.json
index f71d306f16..cace705973 100644
--- a/public/language/he/themes/harmony.json
+++ b/public/language/he/themes/harmony.json
@@ -14,7 +14,7 @@
"settings.stickyToolbar": "הצמד את סרגל הכלים בעת גלילה",
"settings.stickyToolbar.help": "סרגל הכלים בדפי נושאים וקטגוריות ייצמד לראש העמוד בעת גלילה",
"settings.topicSidebarTools": "כלי סרגל הצד",
- "settings.topicSidebarTools.help": "אפשרות זו תעביר את כלי הנושא לסרגל הצד במחשבים שולחניים",
+ "settings.topicSidebarTools.help": "אפשרות זו תעביר את כלי הנושא לסרגל הצד במחשבים",
"settings.autohideBottombar": "הסתרה אוטומטי של סרגל ניווט בנייד",
"settings.autohideBottombar.help": "הסרגל בתצוגת הנייד יוסתר כאשר הדף ייגלל מטה",
"settings.topMobilebar": "העברת סרגל הניווט בנייד לראש הדף",
diff --git a/public/language/he/uploads.json b/public/language/he/uploads.json
index 75476e2a37..00415767b5 100644
--- a/public/language/he/uploads.json
+++ b/public/language/he/uploads.json
@@ -1,9 +1,9 @@
{
"uploading-file": "מעלה את הקובץ...",
- "select-file-to-upload": "בחר קובץ להעלאה!",
+ "select-file-to-upload": "בחרו קובץ להעלאה!",
"upload-success": "הקובץ הועלה בהצלחה!",
"maximum-file-size": "מקסימום %1 קילובייט",
"no-uploads-found": "לא נמצאו העלאות!",
- "public-uploads-info": "העלאות הינם ציבוריות. כל מי שיש ברשותו לינק לקובץ יוכל לראות אותו.",
+ "public-uploads-info": "העלאות הינם ציבוריות. כל מי שיש ברשותו קישור לקובץ יוכל לראות אותו.",
"private-uploads-info": "העלאות הינם פרטיות. רק משתמשים מחוברים יוכלו לראותם."
}
\ No newline at end of file
diff --git a/public/language/he/user.json b/public/language/he/user.json
index db0dc80c4d..695f2f99c2 100644
--- a/public/language/he/user.json
+++ b/public/language/he/user.json
@@ -14,7 +14,7 @@
"account-info": "פרטי חשבון",
"admin-actions-label": "פעולות ניהול",
"ban-account": "הרחקת חשבון",
- "ban-account-confirm": "האם אתהם בטוחים שאתם רוצים להרחיק משתמש זה?",
+ "ban-account-confirm": "האם להרחיק משתמש זה?",
"unban-account": "ביטול הרחקת חשבון",
"mute-account": "השתקת חשבון",
"unmute-account": "ביטול השתקת חשבון",
@@ -105,10 +105,10 @@
"show-email": "הצגת כתובת האימייל שלי",
"show-fullname": "הצגת שמי המלא",
"restrict-chats": "אפשר רק הודעות צ'אט ממשתמשים שאני עוקב אחריהם",
- "disable-incoming-chats": "Disable incoming chat messages ",
- "chat-allow-list": "Allow chat messages from the following users",
- "chat-deny-list": "Deny chat messages from the following users",
- "chat-list-add-user": "Add user",
+ "disable-incoming-chats": "השבתת הודעות צ'אט נכנסות ",
+ "chat-allow-list": "אפשור הודעות צ'אט מהמשתמשים הבאים",
+ "chat-deny-list": "דחיית הודעות צ'אט מהמשתמשים הבאים",
+ "chat-list-add-user": "הוספת משתמש",
"digest-label": "הרשמה לקבלת תקציר",
"digest-description": "הרשמה לקבלת עדכונים בדואר אלקטרוני מפורום זה (הודעות ונושאים חדשים) בהתאם ללוח זמנים מוגדר מראש",
"digest-off": "כבוי",
diff --git a/public/language/he/world.json b/public/language/he/world.json
index a636d38898..c4d625016c 100644
--- a/public/language/he/world.json
+++ b/public/language/he/world.json
@@ -16,6 +16,6 @@
"onboard.why": "יש הרבה דברים שקורים מחוץ לפורום הזה, ולא כל זה רלוונטי לתחומי העניין שלכם. לכן מעקב אחר אנשים הוא הדרך הטובה ביותר לאותת שאתם רוצים לראות יותר ממישהו אחר.",
"onboard.how": "בינתיים, תוכלו ללחוץ על כפתורי הקיצור בחלק העליון כדי לראות על מה עוד הפורום הזה יודע, ולהתחיל לגלות תוכן חדש!",
- "show-categories": "Show categories",
- "hide-categories": "Hide categories"
+ "show-categories": "הצגת קטגוריות",
+ "hide-categories": "הסתרת קטגוריות"
}
\ No newline at end of file
diff --git a/public/language/it/category.json b/public/language/it/category.json
index ef47085300..2a22e1a760 100644
--- a/public/language/it/category.json
+++ b/public/language/it/category.json
@@ -7,7 +7,7 @@
"new-topic-button": "Nuova Discussione",
"guest-login-post": "Accedi per postare",
"no-topics": "Non ci sono discussioni in questa categoria.
Perché non ne posti una?",
- "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.",
+ "no-followers": "Nessuno su questo sito web sta monitorando o guardando questa categoria. Tieni traccia o guarda questa categoria per iniziare a ricevere aggiornamenti.",
"browsing": "navigazione",
"no-replies": "Nessuno ha risposto",
"no-new-posts": "Nessun nuovo post.",
diff --git a/public/language/it/world.json b/public/language/it/world.json
index 3b3a3dc6b3..90caea05e1 100644
--- a/public/language/it/world.json
+++ b/public/language/it/world.json
@@ -16,6 +16,6 @@
"onboard.why": "Ci sono molte cose che accadono al di fuori di questo forum e non tutte sono rilevanti per i tuoi interessi. Ecco perché seguire le persone è il modo migliore per segnalare che vuoi vedere di più da qualcuno.",
"onboard.how": "Nel frattempo, puoi cliccare sui pulsanti di scelta rapida in alto per vedere cos'altro conosce questo forum e iniziare a scoprire nuovi contenuti!",
- "show-categories": "Show categories",
- "hide-categories": "Hide categories"
+ "show-categories": "Mostra categorie",
+ "hide-categories": "Nascondi categorie"
}
\ No newline at end of file
diff --git a/public/language/nb/admin/advanced/logs.json b/public/language/nb/admin/advanced/logs.json
index ba29274563..7f08b66fc6 100644
--- a/public/language/nb/admin/advanced/logs.json
+++ b/public/language/nb/admin/advanced/logs.json
@@ -3,5 +3,5 @@
"control-panel": "Kontrollpanel for logg",
"reload": "Last inn logg på nytt",
"clear": "Tøm logg",
- "clear-success": "Logg er tømt!"
+ "clear-success": "Logg er tømt"
}
\ No newline at end of file
diff --git a/public/language/nb/login.json b/public/language/nb/login.json
index 7b71016b86..a95e58c37b 100644
--- a/public/language/nb/login.json
+++ b/public/language/nb/login.json
@@ -5,7 +5,7 @@
"forgot-password": "Glemt passord?",
"alternative-logins": "Alternativ innlogging",
"failed-login-attempt": "Innlogging mislyktes",
- "login-successful": "Du har blitt logget inn!",
+ "login-successful": "Du er innlogget!",
"dont-have-account": "Har du ikke en konto?",
"logged-out-due-to-inactivity": "Du har blitt logget ut av administratorsidene fordi du har vært inaktiv for lenge",
"caps-lock-enabled": "Caps Lock er skrudd på"
diff --git a/public/language/nb/search.json b/public/language/nb/search.json
index 498ab1f9a5..e0798274ba 100644
--- a/public/language/nb/search.json
+++ b/public/language/nb/search.json
@@ -1,7 +1,7 @@
{
"type-to-search": "Skriv for å søke",
"results-matching": "%1 resultat(er) samsvarer med \"%2\", (%3 sekunder)",
- "no-matches": "Ingen treff funnet",
+ "no-matches": "Ingen treff",
"advanced-search": "Avansert søk",
"in": "I",
"in-titles": "I titler",
diff --git a/public/language/nb/success.json b/public/language/nb/success.json
index f1d8830fa0..ec1ca0f243 100644
--- a/public/language/nb/success.json
+++ b/public/language/nb/success.json
@@ -1,7 +1,7 @@
{
"success": "Suksess",
"topic-post": "Du har nå publisert.",
- "post-queued": "Innlegget ditt er satt i kø for godkjenning. Du vil få en melding når den har blitt godkjent eller avvist.",
+ "post-queued": "Innlegget ditt er satt i kø for godkjenning. Du vil få en melding når det har blitt godkjent eller avvist.",
"authentication-successful": "Innlogging vellykket!",
"settings-saved": "Innstillinger lagret!"
}
\ No newline at end of file
diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json
index 5321f0ec4b..48dc204ac2 100644
--- a/public/language/nb/topic.json
+++ b/public/language/nb/topic.json
@@ -1,10 +1,10 @@
{
- "topic": "Emne",
+ "topic": "Innlegg",
"title": "Tittel",
- "no-topics-found": "Ingen tråder funnet!",
- "no-posts-found": "Ingen innlegg funnet!",
- "post-is-deleted": "Dette innlegget er slettet!",
- "topic-is-deleted": "Denne tråden er slettet!",
+ "no-topics-found": "Ingen innlegg funnet!",
+ "no-posts-found": "Ingen poster funnet!",
+ "post-is-deleted": "Dette svaret er slettet!",
+ "topic-is-deleted": "Dette innlegget er slettet!",
"profile": "Profil",
"posted-by": "Opprettet av %1",
"posted-by-guest": "Opprettet av Gjest",
@@ -16,7 +16,7 @@
"one-reply-to-this-post": "1 svar",
"last-reply-time": "Siste svar",
"reply-options": "Alternativer for svar",
- "reply-as-topic": "Svar som tråd",
+ "reply-as-topic": "Svar som innlegg",
"guest-login-reply": "Logg inn for å besvare",
"login-to-view": "🔒 Logg inn for å se",
"edit": "Endre",
@@ -58,7 +58,7 @@
"user-deleted-topic-ago": "%1 slett dette emnet %2",
"user-deleted-topic-on": "%1 slett dette emnet på %2",
"user-restored-topic-ago": "%1 gjenopprettet dette emnet %2",
- "user-restored-topic-on": "%1 gjenopprettet dette menet på %2",
+ "user-restored-topic-on": "%1 gjenopprettet dette emnet på %2",
"user-moved-topic-from-ago": "%1 flyttet dette emnet fra %2 %3",
"user-moved-topic-from-on": "%1 flyttet dette emnet fra %2 på %3",
"user-shared-topic-ago": "%1 delte dette emnet %2",
@@ -66,21 +66,21 @@
"user-queued-post-ago": "%1 i kø post til godkjenning %3",
"user-queued-post-on": "%1 i køpost til godkjenning %3",
"user-referenced-topic-ago": "%1 refererte dette emnet %3",
- "user-referenced-topic-on": "%1 refererte dette emnet dette emnet på %3",
+ "user-referenced-topic-on": "%1 refererte dette innlegget dette innlegget på %3",
"user-forked-topic-ago": "%1 gaflet dette emnet %3",
"user-forked-topic-on": "%1 gaflet dette emnet på %3",
- "bookmark-instructions": "Klikk her for å gå tilbake til det siste innlegget i denne tråden.",
- "flag-post": "Flagg denne posten",
- "flag-user": "Flagg denne brukeren",
- "already-flagged": "Allerede flagget",
- "view-flag-report": "Vis flaggrapport",
- "resolve-flag": "Løs flagg",
+ "bookmark-instructions": "Klikk her for å gå tilbake til det siste svaret i denne tråden.",
+ "flag-post": "Rapporter denne posten",
+ "flag-user": "Rapporter denne brukeren",
+ "already-flagged": "Allerede rapportert",
+ "view-flag-report": "Vis rapporteringsoversikt",
+ "resolve-flag": "Behandle rapport",
"merged-message": "Dette emnet er slått sammen med %2",
"forked-message": "This topic was forked from %2",
- "deleted-message": "Denne tråden har blitt slettet. Bare brukere med trådhåndterings-privilegier kan se den.",
- "following-topic.message": "Du vil nå motta varsler når noen skriver i denne tråden.",
- "not-following-topic.message": "Du vil se denne tråden i trådlisten, men du vil ikke motta varslinger når noen skriver i den.",
- "ignoring-topic.message": "Du vil ikke lenger se denne tråden blant de uleste trådene. Du vil få et varsel når du blir nevnt eller din tråd blir tilrådd.",
+ "deleted-message": "Denne innlegget har blitt slettet. ",
+ "following-topic.message": "Du vil nå motta varsler når noen svarer på dette innlegget.",
+ "not-following-topic.message": "Du vil se dette innlegget i oversikten over uleste innlegg, men du vil ikke motta varslinger når noen skriver et svar.",
+ "ignoring-topic.message": "Du vil ikke lenger se dette innlegget blant uleste innlegg. Du vil få et varsel når du blir nevnt eller innlegget blir anbefalt.",
"login-to-subscribe": "Vennligst registrer deg eller logg inn for å abonnere på denne tråden.",
"markAsUnreadForAll.success": "Tråd markert som ulest for alle.",
"mark-unread": "Merk som ulest",
diff --git a/public/language/nn-NO/admin/settings/general.json b/public/language/nn-NO/admin/settings/general.json
index f14f5c6598..2157dd08fb 100644
--- a/public/language/nn-NO/admin/settings/general.json
+++ b/public/language/nn-NO/admin/settings/general.json
@@ -32,7 +32,7 @@
"pwa": "Progressiv webapp",
"touch-icon": "Touch-ikon",
"touch-icon.upload": "Last opp touch-ikon",
- "touch-icon.help": "Bruk touch-ikonet for å visast på mobile einingar.",
+ "touch-icon.help": "Bruk touch-ikonet for å vise på mobile einingar.",
"maskable-icon": "Maskerbart ikon",
"maskable-icon.help": "Maskerbare ikon vert brukt for å tilpasse webappen til ulike skjermstorleikar.",
"outgoing-links": "Utgåande lenkjer",
diff --git a/public/language/nn-NO/admin/settings/user.json b/public/language/nn-NO/admin/settings/user.json
index e7a0a4e540..5dc36d2d55 100644
--- a/public/language/nn-NO/admin/settings/user.json
+++ b/public/language/nn-NO/admin/settings/user.json
@@ -90,7 +90,7 @@
"restrictions.seconds-edit-after-new": "Sekund til redigering for nye brukarar",
"restrictions.milliseconds-between-messages": "Millisekund mellom meldingar",
"restrictions.groups-exempt-from-new-user-restrictions": "Grupper unnateke frå nye brukarrestriksjonar",
- "guest-settings": "Gjestinnstillingar",
+ "guest-settings": "Gjesteinnstillingar",
"handles.enabled": "Aktiver handtering av gjestar",
"handles.enabled-help": "Aktiver for å la gjestar kunne sjå og interagere med forumet innanfor visse grenser.",
"topic-views.enabled": "Aktiver visning av emne for gjestar",
diff --git a/public/language/nn-NO/error.json b/public/language/nn-NO/error.json
index 49728c4b7a..3325de8889 100644
--- a/public/language/nn-NO/error.json
+++ b/public/language/nn-NO/error.json
@@ -188,7 +188,7 @@
"not-enough-reputation-custom-field": "Du treng %1 i omdømme for å %2",
"custom-user-field-value-too-long": "Tilpassa felt er er for langt, %1",
"custom-user-field-select-value-invalid": "Alternativet i tilpassa felt er ugyldig, %1 ",
- "custom-user-field-invalid-text": "Tilpassa tekst felt er ugyldig, %1",
+ "custom-user-field-invalid-text": "Tilpassa tekstfelt er ugyldig, %1",
"custom-user-field-invalid-link": "Tilpassa lenke felt er ugyldig, %1",
"custom-user-field-invalid-number": "Tilpassa felt for tal er ugyldig, %1",
"custom-user-field-invalid-date": "Tilpassa felt for dato er ugyldig, %1",
diff --git a/public/language/pl/category.json b/public/language/pl/category.json
index 38891e4ec6..5e52f25c1f 100644
--- a/public/language/pl/category.json
+++ b/public/language/pl/category.json
@@ -7,7 +7,7 @@
"new-topic-button": "Nowy temat",
"guest-login-post": "Zaloguj się, aby napisać post",
"no-topics": "W tej kategorii nie ma jeszcze żadnych tematów.
Może pora na napisanie pierwszego?",
- "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.",
+ "no-followers": "Nikt na tej witrynie nie śledzi ani nie obserwuje tej kategorii. Zacznij śledzić lub obserwować tę kategorię aby zacząć odbierać aktualizacje.",
"browsing": "przegląda",
"no-replies": "Nikt jeszcze nie odpowiedział",
"no-new-posts": "Brak nowych postów.",
diff --git a/public/language/pl/world.json b/public/language/pl/world.json
index fc9f128a41..d8b73d4cb4 100644
--- a/public/language/pl/world.json
+++ b/public/language/pl/world.json
@@ -16,6 +16,6 @@
"onboard.why": "Mnóstwo rzeczy dzieje się poza tym forum ale niekoniecznie zgodnych z Twoimi zainteresowaniami. Dlatego śledzenie konkretnych użytkowników jest dobrą metodą aby uzyskać więcej zawartości przez nich dodawanych.",
"onboard.how": "W międzyczasie możesz użyć przycisków skrótów na górze aby przekonać się jak forum jest powiązane a okaże się, że zapewnia wiele dodatkowych treści!",
- "show-categories": "Show categories",
- "hide-categories": "Hide categories"
+ "show-categories": "Pokaż kategorie",
+ "hide-categories": "Ukryj kategorie"
}
\ No newline at end of file
diff --git a/public/language/vi/category.json b/public/language/vi/category.json
index 464aca7b47..4abc703c44 100644
--- a/public/language/vi/category.json
+++ b/public/language/vi/category.json
@@ -7,7 +7,7 @@
"new-topic-button": "Chủ Đề Mới",
"guest-login-post": "Đăng nhập để đăng bài",
"no-topics": "Không có chủ đề nào trong danh mục này.
Sao bạn không thử đăng?",
- "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.",
+ "no-followers": "Không ai trên trang web này đang theo dõi hoặc xem danh mục này. Theo dõi hoặc xem danh mục này để bắt đầu nhận cập nhật.",
"browsing": "lướt xem",
"no-replies": "Không có ai trả lời",
"no-new-posts": "Không bài đăng mới.",
diff --git a/public/language/vi/flags.json b/public/language/vi/flags.json
index bc746b52f7..1ed4630497 100644
--- a/public/language/vi/flags.json
+++ b/public/language/vi/flags.json
@@ -29,7 +29,7 @@
"filter-assignee": "Ủy nhiệm",
"filter-cid": "Chuyên mục",
"filter-quick-mine": "Được giao cho tôi",
- "filter-cid-all": "Tất cả chuyên mục",
+ "filter-cid-all": "Tất cả các danh mục",
"apply-filters": "Áp Dụng Bộ Lọc",
"more-filters": "Thêm Bộ Lọc",
"fewer-filters": "Bớt Bộ Lọc",
diff --git a/public/language/vi/world.json b/public/language/vi/world.json
index 2a1521ed2b..4a34f79c9a 100644
--- a/public/language/vi/world.json
+++ b/public/language/vi/world.json
@@ -16,6 +16,6 @@
"onboard.why": "Có rất nhiều điều diễn ra bên ngoài diễn đàn này và không phải tất cả đều phù hợp với sở thích của bạn. Đó là lý do tại sao theo dõi mọi người là cách tốt nhất để báo hiệu rằng bạn muốn biết thêm thông tin từ ai đó.",
"onboard.how": "Trong thời gian chờ đợi, bạn có thể nhấp vào các nút tắt ở trên cùng để xem diễn đàn này biết thêm những gì và bắt đầu khám phá một số nội dung mới!",
- "show-categories": "Show categories",
- "hide-categories": "Hide categories"
+ "show-categories": "Hiển thị các danh mục",
+ "hide-categories": "Ẩn các danh mục"
}
\ No newline at end of file
diff --git a/public/language/zh-CN/category.json b/public/language/zh-CN/category.json
index d3c4c8c5d7..27a3f239b0 100644
--- a/public/language/zh-CN/category.json
+++ b/public/language/zh-CN/category.json
@@ -7,7 +7,7 @@
"new-topic-button": "发表主题",
"guest-login-post": "登录以发布",
"no-topics": "此版块还没有任何内容。
赶紧来发帖吧!",
- "no-followers": "Nobody on this website is tracking or watching this category. Track or watch this category in order to begin receiving updates.",
+ "no-followers": "本网站无人跟踪或关注此版块。跟踪或关注此版块,以便开始接收更新。",
"browsing": "正在浏览",
"no-replies": "尚无回复",
"no-new-posts": "没有新主题",
diff --git a/public/language/zh-CN/world.json b/public/language/zh-CN/world.json
index f9d9d1d3e9..13ea86163f 100644
--- a/public/language/zh-CN/world.json
+++ b/public/language/zh-CN/world.json
@@ -16,6 +16,6 @@
"onboard.why": "论坛之外的事情很多,而且并非所有事情都与您的兴趣相关。因此,关注他人是表明您想从某人那里了解更多信息的最佳方式。",
"onboard.how": "在此期间,您可以点击顶部的快捷按钮,了解本论坛的其他内容,并开始发现一些新内容!",
- "show-categories": "Show categories",
- "hide-categories": "Hide categories"
+ "show-categories": "显示版块",
+ "hide-categories": "隐藏版块"
}
\ No newline at end of file
diff --git a/public/openapi/components/schemas/PostObject.yaml b/public/openapi/components/schemas/PostObject.yaml
index 502ea8044d..bcb2f79e53 100644
--- a/public/openapi/components/schemas/PostObject.yaml
+++ b/public/openapi/components/schemas/PostObject.yaml
@@ -180,6 +180,8 @@ PostDataObject:
type: number
downvotes:
type: number
+ announces:
+ type: number
bookmarks:
type: number
deleterUid:
diff --git a/public/openapi/read/admin/manage/privileges/cid.yaml b/public/openapi/read/admin/manage/privileges/cid.yaml
index d4b0ed43da..c395f39172 100644
--- a/public/openapi/read/admin/manage/privileges/cid.yaml
+++ b/public/openapi/read/admin/manage/privileges/cid.yaml
@@ -21,19 +21,6 @@ get:
privileges:
type: object
properties:
- labels:
- type: object
- properties:
- users:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
- groups:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
labelData:
type: array
items:
diff --git a/public/openapi/write/categories/cid/moderator/uid.yaml b/public/openapi/write/categories/cid/moderator/uid.yaml
index 78ca52ca96..217c3c09b0 100644
--- a/public/openapi/write/categories/cid/moderator/uid.yaml
+++ b/public/openapi/write/categories/cid/moderator/uid.yaml
@@ -31,19 +31,6 @@ put:
response:
type: object
properties:
- labels:
- type: object
- properties:
- users:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
- groups:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
labelData:
type: array
items:
diff --git a/public/openapi/write/categories/cid/privileges.yaml b/public/openapi/write/categories/cid/privileges.yaml
index 6ed7c6fbf8..b450aee682 100644
--- a/public/openapi/write/categories/cid/privileges.yaml
+++ b/public/openapi/write/categories/cid/privileges.yaml
@@ -24,19 +24,6 @@ get:
response:
type: object
properties:
- labels:
- type: object
- properties:
- users:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
- groups:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
labelData:
type: array
items:
diff --git a/public/openapi/write/categories/cid/privileges/privilege.yaml b/public/openapi/write/categories/cid/privileges/privilege.yaml
index d6985f27f3..06303ac092 100644
--- a/public/openapi/write/categories/cid/privileges/privilege.yaml
+++ b/public/openapi/write/categories/cid/privileges/privilege.yaml
@@ -41,19 +41,6 @@ put:
response:
type: object
properties:
- labels:
- type: object
- properties:
- users:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
- groups:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
labelData:
type: array
items:
@@ -191,19 +178,6 @@ delete:
response:
type: object
properties:
- labels:
- type: object
- properties:
- users:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
- groups:
- type: array
- items:
- type: string
- description: Language key of the privilege name's user-friendly name
labelData:
type: array
items:
diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js
index 42f10093a0..640d936f16 100644
--- a/public/src/client/topic/postTools.js
+++ b/public/src/client/topic/postTools.js
@@ -397,7 +397,7 @@ define('forum/topic/postTools', [
return;
}
const post = button.parents('[data-pid]');
- if (post.length) {
+ if (post.length && !post.hasClass('self-post')) {
require(['slugify'], function (slugify) {
slug = slugify(post.attr('data-username'), true);
if (!slug) {
diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js
index 1e2d96441e..7b7816516d 100644
--- a/src/activitypub/feps.js
+++ b/src/activitypub/feps.js
@@ -3,6 +3,7 @@
const nconf = require('nconf');
const posts = require('../posts');
+const utils = require('../utils');
const activitypub = module.parent.exports;
const Feps = module.exports;
@@ -13,7 +14,7 @@ Feps.announce = async function announce(id, activity) {
({ id: localId } = await activitypub.helpers.resolveLocalId(id));
}
const cid = await posts.getCidByPid(localId || id);
- if (cid === -1) {
+ if (cid === -1 || !utils.isNumber(cid)) { // local cids only
return;
}
diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js
index 071c70e395..f7708b9bd9 100644
--- a/src/activitypub/helpers.js
+++ b/src/activitypub/helpers.js
@@ -452,11 +452,13 @@ Helpers.makeSet = (object, properties) => new Set(properties.reduce((memo, prope
[object[property]] :
[]), []));
-Helpers.generateCollection = async ({ set, method, page, perPage, url }) => {
+Helpers.generateCollection = async ({ set, method, count, page, perPage, url }) => {
if (!method) {
- method = db.getSortedSetRange;
+ method = db.getSortedSetRange.bind(null, set);
+ } else if (set) {
+ method = method.bind(null, set);
}
- const count = await db.sortedSetCard(set);
+ count = count || await db.sortedSetCard(set);
const pageCount = Math.max(1, Math.ceil(count / perPage));
let items = [];
let paginate = true;
@@ -474,7 +476,7 @@ Helpers.generateCollection = async ({ set, method, page, perPage, url }) => {
const start = Math.max(0, ((page - 1) * perPage) - 1);
const stop = Math.max(0, start + perPage - 1);
- items = await method(set, start, stop);
+ items = await method.call(null, start, stop);
}
const object = {
@@ -490,8 +492,6 @@ Helpers.generateCollection = async ({ set, method, page, perPage, url }) => {
object.next = page < pageCount ? `${url}?page=${page + 1}` : null;
object.prev = page > 1 ? `${url}?page=${page - 1}` : null;
}
- } else {
- object.orderedItems = [];
}
if (paginate) {
diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js
index 2fb2810a6c..ab02cef6db 100644
--- a/src/activitypub/inbox.js
+++ b/src/activitypub/inbox.js
@@ -41,6 +41,7 @@ inbox.create = async (req) => {
return await activitypub.notes.assertPrivate(object);
}
+ // Category sync, remove when cross-posting available
const { cids } = await activitypub.actors.getLocalFollowers(actor);
let cid = null;
if (cids.size > 0) {
@@ -49,7 +50,7 @@ inbox.create = async (req) => {
const asserted = await activitypub.notes.assert(0, object, { cid });
if (asserted) {
- activitypub.feps.announce(object.id, req.body);
+ await activitypub.feps.announce(object.id, req.body);
// api.activitypub.add(req, { pid: object.id });
}
};
@@ -84,8 +85,8 @@ inbox.update = async (req) => {
throw new Error('[[error:activitypub.origin-mismatch]]');
}
- switch (object.type) {
- case 'Note': {
+ switch (true) {
+ case activitypub._constants.acceptedPostTypes.includes(object.type): {
const [isNote, isMessage] = await Promise.all([
posts.exists(object.id),
messaging.messageExists(object.id),
@@ -95,6 +96,7 @@ inbox.update = async (req) => {
switch (true) {
case isNote: {
const postData = await activitypub.mocks.post(object);
+ postData.tags = await activitypub.notes._normalizeTags(postData._activitypub.tag, postData.cid);
await posts.edit(postData);
const isDeleted = await posts.getPostField(object.id, 'deleted');
if (isDeleted) {
@@ -136,16 +138,12 @@ inbox.update = async (req) => {
break;
}
- case 'Application': // falls through
- case 'Group': // falls through
- case 'Organization': // falls through
- case 'Service': // falls through
- case 'Person': {
+ case activitypub._constants.acceptableActorTypes.has(object.type): {
await activitypub.actors.assert(object.id, { update: true });
break;
}
- case 'Tombstone': {
+ case object.type === 'Tombstone': {
const [isNote, isMessage/* , isActor */] = await Promise.all([
posts.exists(object.id),
messaging.messageExists(object.id),
@@ -247,12 +245,12 @@ inbox.like = async (req) => {
activitypub.helpers.log(`[activitypub/inbox/like] id ${id} via ${actor}`);
const result = await posts.upvote(id, actor);
- activitypub.feps.announce(object.id, req.body);
+ await activitypub.feps.announce(object.id, req.body);
socketHelpers.upvote(result, 'notifications:upvoted-your-post-in');
};
inbox.announce = async (req) => {
- const { actor, object, published, to, cc } = req.body;
+ let { actor, object, published, to, cc } = req.body;
activitypub.helpers.log(`[activitypub/inbox/announce] Parsing Announce(${object.type}) from ${actor}`);
let timestamp = new Date(published);
timestamp = timestamp.toString() !== 'Invalid Date' ? timestamp.getTime() : Date.now();
@@ -265,12 +263,19 @@ inbox.announce = async (req) => {
let tid;
let pid;
+ // Category sync, remove when cross-posting available
const { cids } = await activitypub.actors.getLocalFollowers(actor);
let cid = null;
if (cids.size > 0) {
cid = Array.from(cids)[0];
}
+ // 1b12 announce
+ const categoryActor = await categories.exists(actor);
+ if (categoryActor) {
+ cid = actor;
+ }
+
switch(true) {
case object.type === 'Like': {
const id = object.object.id || object.object;
@@ -292,6 +297,12 @@ inbox.announce = async (req) => {
break;
}
+ case object.type === 'Create': {
+ object = object.object;
+ // falls through
+ }
+
+ // Announce(Object)
case activitypub._constants.acceptedPostTypes.includes(object.type): {
if (String(object.id).startsWith(nconf.get('url'))) { // Local object
const { type, id } = await activitypub.helpers.resolveLocalId(object.id);
@@ -315,13 +326,7 @@ inbox.announce = async (req) => {
}
}
- // Handle case where Announce(Create(Note-ish)) is received
- if (object.type === 'Create' && activitypub._constants.acceptedPostTypes.includes(object.object.type)) {
- pid = object.object.id;
- } else {
- pid = object.id;
- }
-
+ pid = object.id;
pid = await activitypub.resolveId(0, pid); // in case wrong id is passed-in; unlikely, but still.
if (!pid) {
return;
diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js
index 3bae10b50f..f2a8446d4a 100644
--- a/src/activitypub/mocks.js
+++ b/src/activitypub/mocks.js
@@ -42,7 +42,7 @@ const sanitizeConfig = {
Mocks._normalize = async (object) => {
// Normalized incoming AP objects into expected types for easier mocking
- let { attributedTo, url, image, content, source } = object;
+ let { type, attributedTo, url, image, content, source, attachment } = object;
switch (true) { // non-string attributedTo handling
case Array.isArray(attributedTo): {
@@ -102,6 +102,30 @@ Mocks._normalize = async (object) => {
if (url) { // Handle url array
if (Array.isArray(url)) {
+ // Special handling for Video type (from PeerTube specifically)
+ if (type === 'Video') {
+ const stream = url.reduce((memo, { type, mediaType, tag }) => {
+ if (!memo) {
+ if (type === 'Link' && mediaType === 'application/x-mpegURL') {
+ memo = tag.reduce((memo, { type, mediaType, href }) => {
+ if (!memo && (type === 'Link' && mediaType === 'video/mp4')) {
+ memo = { type, mediaType, href };
+ }
+
+ return memo;
+ }, null);
+ }
+ }
+
+ return memo;
+ }, null);
+
+ if (stream) {
+ attachment = attachment || [];
+ attachment.push(stream);
+ }
+ }
+
url = url.reduce((valid, cur) => {
if (typeof cur === 'string') {
valid.push(cur);
@@ -126,6 +150,7 @@ Mocks._normalize = async (object) => {
sourceContent,
image,
url,
+ attachment,
};
};
@@ -332,7 +357,7 @@ Mocks.post = async (objects) => {
attributedTo: uid,
inReplyTo: toPid,
published, updated, name, content, sourceContent,
- type, to, cc, audience, attachment, tag, image,
+ to, cc, audience, attachment, tag, image,
} = object;
await activitypub.actors.assert(uid);
@@ -346,21 +371,17 @@ Mocks.post = async (objects) => {
let edited = new Date(updated);
edited = Number.isNaN(edited.valueOf()) ? undefined : edited;
- if (type === 'Video') {
- attachment = attachment || [];
- attachment.push({ url });
- }
-
const payload = {
uid,
pid,
// tid, --> purposely omitted
- name,
content,
sourceContent,
timestamp,
toPid,
+ title: name, // used in post.edit
+
edited,
editor: edited ? uid : undefined,
_activitypub: { to, cc, audience, attachment, tag, url, image },
diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js
index 747eeb54d2..31ca249d3d 100644
--- a/src/activitypub/notes.js
+++ b/src/activitypub/notes.js
@@ -27,6 +27,24 @@ async function unlock(value) {
await db.deleteObjectField('locks', value);
}
+Notes._normalizeTags = async (tag, cid) => {
+ const systemTags = (meta.config.systemTags || '').split(',');
+ const maxTags = await categories.getCategoryField(cid, 'maxTags');
+ const tags = (tag || [])
+ .map((tag) => {
+ tag.name = tag.name.startsWith('#') ? tag.name.slice(1) : tag.name;
+ return tag;
+ })
+ .filter(o => o.type === 'Hashtag' && !systemTags.includes(o.name))
+ .map(t => t.name);
+
+ if (tags.length > maxTags) {
+ tags.length = maxTags;
+ }
+
+ return tags;
+};
+
Notes.assert = async (uid, input, options = { skipChecks: false }) => {
/**
* Given the id or object of any as:Note, either retrieves the full context (if resolvable),
@@ -75,7 +93,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
chain = chain.sort((a, b) => a.timestamp - b.timestamp);
const mainPost = chain[0];
- let { pid: mainPid, tid, uid: authorId, timestamp, name, content, sourceContent, _activitypub } = mainPost;
+ let { pid: mainPid, tid, uid: authorId, timestamp, title, content, sourceContent, _activitypub } = mainPost;
const hasTid = !!tid;
const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1;
@@ -94,32 +112,25 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
return { tid, count: 0 };
}
- let title;
if (hasTid) {
mainPid = await topics.getTopicField(tid, 'mainPid');
} else {
- // Check recipients/audience for category (local or remote)
+ // Check recipients/audience for local category
const set = activitypub.helpers.makeSet(_activitypub, ['to', 'cc', 'audience']);
await activitypub.actors.assert(Array.from(set));
-
- // Local
const resolved = await Promise.all(Array.from(set).map(async id => await activitypub.helpers.resolveLocalId(id)));
const recipientCids = resolved
.filter(Boolean)
.filter(({ type }) => type === 'category')
.map(obj => obj.id);
- // Remote
- const assertedGroups = await db.exists(Array.from(set).map(id => `categoryRemote:${id}`));
- const remoteCid = Array.from(set).filter((_, idx) => assertedGroups[idx]).shift();
-
- if (remoteCid || recipientCids.length) {
+ if (recipientCids.length) {
// Overrides passed-in value, respect addressing from main post over booster
- options.cid = remoteCid || recipientCids.shift();
+ options.cid = recipientCids.shift();
}
// mainPid ok to leave as-is
- title = name || activitypub.helpers.generateTitle(utils.decodeHTMLEntities(content || sourceContent));
+ title = title || activitypub.helpers.generateTitle(utils.decodeHTMLEntities(content || sourceContent));
// Remove any custom emoji from title
if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) {
@@ -166,22 +177,9 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
const count = unprocessed.length;
activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`);
- let tags;
if (!hasTid) {
const { to, cc, attachment } = mainPost._activitypub;
- const systemTags = (meta.config.systemTags || '').split(',');
- const maxTags = await categories.getCategoryField(cid, 'maxTags');
- tags = (mainPost._activitypub.tag || [])
- .map((tag) => {
- tag.name = tag.name.startsWith('#') ? tag.name.slice(1) : tag.name;
- return tag;
- })
- .filter(o => o.type === 'Hashtag' && !systemTags.includes(o.name))
- .map(t => t.name);
-
- if (tags.length > maxTags) {
- tags.length = maxTags;
- }
+ const tags = await Notes._normalizeTags(mainPost._activitypub.tag || []);
await Promise.all([
topics.post({
diff --git a/src/api/activitypub.js b/src/api/activitypub.js
index 2752a50bc6..ce78f12d23 100644
--- a/src/api/activitypub.js
+++ b/src/api/activitypub.js
@@ -52,11 +52,8 @@ activitypubApi.follow = enabledCheck(async (caller, { type, id, actor } = {}) =>
actor = uid || cid;
}
- const [handle, isFollowing] = await Promise.all([
- user.getUserField(actor, 'username'),
- db.isSortedSetMember(type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, actor),
- ]);
+ const isFollowing = await db.isSortedSetMember(type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, actor);
if (isFollowing) { // already following
return;
}
@@ -66,7 +63,7 @@ activitypubApi.follow = enabledCheck(async (caller, { type, id, actor } = {}) =>
await db.sortedSetAdd(`followRequests:${type}.${id}`, timestamp, actor);
try {
await activitypub.send(type, id, [actor], {
- id: `${nconf.get('url')}/${type}/${id}#activity/follow/${handle}/${timestamp}`,
+ id: `${nconf.get('url')}/${type}/${id}#activity/follow/${encodeURIComponent(actor)}/${timestamp}`,
type: 'Follow',
object: actor,
});
@@ -93,8 +90,7 @@ activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => {
actor = uid || cid;
}
- const [handle, isFollowing, isPending] = await Promise.all([
- user.getUserField(actor, 'username'),
+ const [isFollowing, isPending] = await Promise.all([
db.isSortedSetMember(type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, actor),
db.isSortedSetMember(`followRequests:${type === 'uid' ? 'uid' : 'cid'}.${id}`, actor),
]);
@@ -110,7 +106,7 @@ activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => {
const timestamp = timestamps[0] || timestamps[1];
const object = {
- id: `${nconf.get('url')}/${type}/${id}#activity/follow/${handle}/${timestamp}`,
+ id: `${nconf.get('url')}/${type}/${id}#activity/follow/${encodeURIComponent(actor)}/${timestamp}`,
type: 'Follow',
object: actor,
};
@@ -121,7 +117,7 @@ activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => {
}
await activitypub.send(type, id, [actor], {
- id: `${nconf.get('url')}/${type}/${id}#activity/undo:follow/${handle}/${timestamp}`,
+ id: `${nconf.get('url')}/${type}/${id}#activity/undo:follow/${encodeURIComponent(actor)}/${timestamp}`,
type: 'Undo',
object,
});
@@ -314,7 +310,15 @@ activitypubApi.delete.note = enabledCheck(async (caller, { pid }) => {
activitypubApi.like = {};
activitypubApi.like.note = enabledCheck(async (caller, { pid }) => {
- if (!activitypub.helpers.isUri(pid)) { // remote only
+ const payload = {
+ id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`,
+ type: 'Like',
+ actor: `${nconf.get('url')}/uid/${caller.uid}`,
+ object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid,
+ };
+
+ if (!activitypub.helpers.isUri(pid)) { // only 1b12 announce for local likes
+ await activitypub.feps.announce(pid, payload);
return;
}
@@ -323,13 +327,6 @@ activitypubApi.like.note = enabledCheck(async (caller, { pid }) => {
return;
}
- const payload = {
- id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`,
- type: 'Like',
- actor: `${nconf.get('url')}/uid/${caller.uid}`,
- object: pid,
- };
-
await Promise.all([
activitypub.send('uid', caller.uid, [uid], payload),
activitypub.feps.announce(pid, payload),
diff --git a/src/api/helpers.js b/src/api/helpers.js
index e0d3bbc0bb..168e5539b6 100644
--- a/src/api/helpers.js
+++ b/src/api/helpers.js
@@ -137,12 +137,12 @@ async function executeCommand(caller, command, eventName, notification, data) {
}
if (result && command === 'upvote') {
socketHelpers.upvote(result, notification);
- api.activitypub.like.note(caller, { pid: data.pid });
+ await api.activitypub.like.note(caller, { pid: data.pid });
} else if (result && notification) {
socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, command, notification);
} else if (result && command === 'unvote') {
socketHelpers.rescindUpvoteNotification(data.pid, caller.uid);
- api.activitypub.undo.like(caller, { pid: data.pid });
+ await api.activitypub.undo.like(caller, { pid: data.pid });
}
return result;
}
diff --git a/src/controllers/activitypub/actors.js b/src/controllers/activitypub/actors.js
index 85e2fda01c..0f7a348990 100644
--- a/src/controllers/activitypub/actors.js
+++ b/src/controllers/activitypub/actors.js
@@ -105,7 +105,9 @@ Actors.replies = async function (req, res, next) {
}
// Convert pids to urls
- replies.orderedItems = replies.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid));
+ if (replies.orderedItems) {
+ replies.orderedItems = replies.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid));
+ }
const object = {
'@context': 'https://www.w3.org/ns/activitystreams',
@@ -167,7 +169,7 @@ Actors.topic = async function (req, res, next) {
res.set('ETag', digest);
// Convert pids to urls
- if (page || collection.totalItems < meta.config.postsPerPage) {
+ if (page || collection.totalItems < perPage) {
collection.orderedItems = collection.orderedItems || [];
if (!page || page === 1) { // add OP to collection
collection.orderedItems.unshift(mainPid);
diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js
index ccf85b2012..36054d6616 100644
--- a/src/controllers/activitypub/index.js
+++ b/src/controllers/activitypub/index.js
@@ -6,6 +6,7 @@ const winston = require('winston');
const meta = require('../../meta');
const user = require('../../user');
const activitypub = require('../../activitypub');
+const utils = require('../../utils');
const helpers = require('../helpers');
const Controller = module.exports;
@@ -60,71 +61,53 @@ Controller.fetch = async (req, res, next) => {
Controller.getFollowing = async (req, res) => {
const { followingCount, followingRemoteCount } = await user.getUserFields(req.params.uid, ['followingCount', 'followingRemoteCount']);
const totalItems = parseInt(followingCount || 0, 10) + parseInt(followingRemoteCount || 0, 10);
- let orderedItems;
- let next = (totalItems && `${nconf.get('url')}/uid/${req.params.uid}/following?page=`) || null;
- if (totalItems) {
- if (req.query.page) {
- const page = parseInt(req.query.page, 10) || 1;
- const resultsPerPage = 50;
- const start = Math.max(0, page - 1) * resultsPerPage;
- const stop = start + resultsPerPage - 1;
+ const count = totalItems;
+ const collection = await activitypub.helpers.generateCollection({
+ method: user.getFollowing.bind(null, req.params.uid),
+ count,
+ perPage: 50,
+ page: req.query.page,
+ url: `${nconf.get('url')}/uid/${req.params.uid}/following`,
+ });
- orderedItems = await user.getFollowing(req.params.uid, start, stop);
- orderedItems = orderedItems.map(({ userslug }) => `${nconf.get('url')}/user/${userslug}`);
- if (stop < totalItems - 1) {
- next = `${next}${page + 1}`;
- } else {
- next = null;
+ if (collection.hasOwnProperty('orderedItems')) {
+ collection.orderedItems = collection.orderedItems.map(({ uid }) => {
+ if (utils.isNumber(uid)) {
+ return `${nconf.get('url')}/uid/${uid}`;
}
- } else {
- orderedItems = [];
- next = `${next}1`;
- }
+
+ return uid;
+ });
}
- res.status(200).json({
- '@context': 'https://www.w3.org/ns/activitystreams',
- type: 'OrderedCollection',
- totalItems,
- orderedItems,
- next,
- });
+ res.status(200).json(collection);
};
Controller.getFollowers = async (req, res) => {
const { followerCount, followerRemoteCount } = await user.getUserFields(req.params.uid, ['followerCount', 'followerRemoteCount']);
const totalItems = parseInt(followerCount || 0, 10) + parseInt(followerRemoteCount || 0, 10);
- let orderedItems = [];
- let next = (totalItems && `${nconf.get('url')}/uid/${req.params.uid}/followers?page=`) || null;
- if (totalItems) {
- if (req.query.page) {
- const page = parseInt(req.query.page, 10) || 1;
- const resultsPerPage = 50;
- const start = Math.max(0, page - 1) * resultsPerPage;
- const stop = start + resultsPerPage - 1;
+ const count = totalItems;
+ const collection = await activitypub.helpers.generateCollection({
+ method: user.getFollowers.bind(null, req.params.uid),
+ count,
+ perPage: 50,
+ page: req.query.page,
+ url: `${nconf.get('url')}/uid/${req.params.uid}/followers`,
+ });
- orderedItems = await user.getFollowers(req.params.uid, start, stop);
- orderedItems = orderedItems.map(({ userslug }) => `${nconf.get('url')}/user/${userslug}`);
- if (stop < totalItems - 1) {
- next = `${next}${page + 1}`;
- } else {
- next = null;
+ if (collection.hasOwnProperty('orderedItems')) {
+ collection.orderedItems = collection.orderedItems.map(({ uid }) => {
+ if (utils.isNumber(uid)) {
+ return `${nconf.get('url')}/uid/${uid}`;
}
- } else {
- orderedItems = [];
- next = `${next}1`;
- }
+
+ return uid;
+ });
}
- res.status(200).json({
- '@context': 'https://www.w3.org/ns/activitystreams',
- type: 'OrderedCollection',
- totalItems,
- orderedItems,
- next,
- });
+ res.status(200).json(collection);
};
Controller.getOutbox = async (req, res) => {
diff --git a/src/emailer.js b/src/emailer.js
index f280c21399..ad7e762916 100644
--- a/src/emailer.js
+++ b/src/emailer.js
@@ -344,10 +344,7 @@ Emailer.sendToEmail = async (template, email, language, params) => {
const usingFallback = !Plugins.hooks.hasListeners('filter:email.send') &&
!Plugins.hooks.hasListeners('static:email.send');
try {
- if (Plugins.hooks.hasListeners('filter:email.send')) {
- // Deprecated, remove in v1.19.0
- await Plugins.hooks.fire('filter:email.send', data);
- } else if (Plugins.hooks.hasListeners('static:email.send')) {
+ if (Plugins.hooks.hasListeners('static:email.send')) {
await Plugins.hooks.fire('static:email.send', data);
} else {
await Emailer.sendViaFallback(data);
diff --git a/src/flags.js b/src/flags.js
index d19da08d95..8ae9a6c595 100644
--- a/src/flags.js
+++ b/src/flags.js
@@ -96,7 +96,6 @@ Flags.init = async function () {
};
try {
- ({ filters: Flags._filters } = await plugins.hooks.fire('filter:flags.getFilters', hookData));
({ filters: Flags._filters, states: Flags._states } = await plugins.hooks.fire('filter:flags.init', hookData));
} catch (err) {
winston.error(`[flags/init] Could not retrieve filters\n${err.stack}`);
diff --git a/src/middleware/index.js b/src/middleware/index.js
index fd0597fb6e..5a0e69842f 100644
--- a/src/middleware/index.js
+++ b/src/middleware/index.js
@@ -1,6 +1,5 @@
'use strict';
-const async = require('async');
const path = require('path');
const validator = require('validator');
const nconf = require('nconf');
@@ -91,11 +90,6 @@ middleware.pageView = helpers.try(async (req, res, next) => {
});
middleware.pluginHooks = helpers.try(async (req, res, next) => {
- // TODO: Deprecate in v2.0
- await async.each(plugins.loadedHooks['filter:router.page'] || [], (hookObj, next) => {
- hookObj.method(req, res, next);
- });
-
await plugins.hooks.fire('response:router.page', {
req: req,
res: res,
diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js
index 553e90db45..0b79e78f5f 100644
--- a/src/plugins/hooks.js
+++ b/src/plugins/hooks.js
@@ -8,97 +8,11 @@ const als = require('../als');
const Hooks = module.exports;
Hooks._deprecated = new Map([
- ['filter:email.send', {
- new: 'static:email.send',
- since: 'v1.17.0',
- until: 'v2.0.0',
- }],
- ['filter:router.page', {
- new: 'response:router.page',
- since: 'v1.15.3',
- until: 'v2.1.0',
- }],
- ['filter:post.purge', {
- new: 'filter:posts.purge',
- since: 'v1.19.6',
- until: 'v2.1.0',
- }],
- ['action:post.purge', {
- new: 'action:posts.purge',
- since: 'v1.19.6',
- until: 'v2.1.0',
- }],
- ['filter:user.verify.code', {
- new: 'filter:user.verify',
- since: 'v2.2.0',
- until: 'v3.0.0',
- }],
- ['filter:flags.getFilters', {
- new: 'filter:flags.init',
- since: 'v2.7.0',
- until: 'v3.0.0',
- }],
- ['filter:privileges.global.list', {
- new: 'static:privileges.global.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.global.groups.list', {
- new: 'static:privileges.global.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.global.list_human', {
- new: 'static:privileges.global.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.global.groups.list_human', {
- new: 'static:privileges.global.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.list', {
- new: 'static:privileges.categories.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.groups.list', {
- new: 'static:privileges.categories.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.list_human', {
- new: 'static:privileges.categories.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.groups.list_human', {
- new: 'static:privileges.categories.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
-
- ['filter:privileges.admin.list', {
- new: 'static:privileges.admin.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.admin.groups.list', {
- new: 'static:privileges.admin.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.admin.list_human', {
- new: 'static:privileges.admin.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
- ['filter:privileges.admin.groups.list_human', {
- new: 'static:privileges.admin.init',
- since: 'v3.5.0',
- until: 'v4.0.0',
- }],
+ /* ['filter:old.hook.name', {
+ new: 'filter:new.hook.name',
+ since: 'v4.0.0',
+ until: 'v5.0.0',
+ }], */
]);
Hooks.internals = {
diff --git a/src/posts/data.js b/src/posts/data.js
index 688c131b8a..d74a22e69d 100644
--- a/src/posts/data.js
+++ b/src/posts/data.js
@@ -7,7 +7,7 @@ const utils = require('../utils');
const intFields = [
'uid', 'pid', 'tid', 'deleted', 'timestamp',
'upvotes', 'downvotes', 'deleterUid', 'edited',
- 'replies', 'bookmarks',
+ 'replies', 'bookmarks', 'announces',
];
module.exports = function (Posts) {
diff --git a/src/posts/delete.js b/src/posts/delete.js
index 6ea4b55453..5668ac3750 100644
--- a/src/posts/delete.js
+++ b/src/posts/delete.js
@@ -63,10 +63,6 @@ module.exports = function (Posts) {
p.cid = tidToTopic[p.tid] && tidToTopic[p.tid].cid;
});
- // deprecated hook
- await Promise.all(postData.map(p => plugins.hooks.fire('filter:post.purge', { post: p, pid: p.pid, uid: uid })));
-
- // new hook
await plugins.hooks.fire('filter:posts.purge', {
posts: postData,
pids: postData.map(p => p.pid),
@@ -90,10 +86,6 @@ module.exports = function (Posts) {
await resolveFlags(postData, uid);
- // deprecated hook
- Promise.all(postData.map(p => plugins.hooks.fire('action:post.purge', { post: p, uid: uid })));
-
- // new hook
plugins.hooks.fire('action:posts.purge', { posts: postData, uid: uid });
await db.deleteAll(postData.map(p => `post:${p.pid}`));
diff --git a/src/posts/edit.js b/src/posts/edit.js
index 610a659e4f..077616b29e 100644
--- a/src/posts/edit.js
+++ b/src/posts/edit.js
@@ -35,7 +35,7 @@ module.exports = function (Posts) {
await scheduledTopicCheck(data, topicData);
data.content = data.content === null ? postData.content : data.content;
- const oldContent = postData.content; // for diffing purposes
+ const oldContent = postData.sourceContent || postData.content; // for diffing purposes
const editPostData = getEditPostData(data, topicData, postData);
if (data.handle) {
@@ -55,7 +55,7 @@ module.exports = function (Posts) {
]);
await Posts.setPostFields(data.pid, result.post);
- const contentChanged = data.content !== oldContent ||
+ const contentChanged = ((data.sourceContent || data.content) !== oldContent) ||
topic.renamed ||
topic.tagsupdated;
@@ -194,6 +194,7 @@ module.exports = function (Posts) {
function getEditPostData(data, topicData, postData) {
const editPostData = {
content: data.content,
+ sourceContent: data.sourceContent,
editor: data.uid,
};
diff --git a/src/privileges/admin.js b/src/privileges/admin.js
index 958b4cfe49..422e9c1060 100644
--- a/src/privileges/admin.js
+++ b/src/privileges/admin.js
@@ -39,8 +39,8 @@ privsAdmin.init = async () => {
}
};
-privsAdmin.getUserPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.admin.list', Array.from(_privilegeMap.keys()));
-privsAdmin.getGroupPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.admin.groups.list', Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`));
+privsAdmin.getUserPrivilegeList = () => Array.from(_privilegeMap.keys());
+privsAdmin.getGroupPrivilegeList = () => Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`);
privsAdmin.getPrivilegeList = async () => {
const [user, group] = await Promise.all([
privsAdmin.getUserPrivilegeList(),
@@ -149,18 +149,12 @@ privsAdmin.list = async function (uid) {
groupPrivilegeList.splice(idx, 1);
}
- const labels = await utils.promiseParallel({
- users: plugins.hooks.fire('filter:privileges.admin.list_human', privilegeLabels.slice()),
- groups: plugins.hooks.fire('filter:privileges.admin.groups.list_human', privilegeLabels.slice()),
- });
-
const keys = {
users: userPrivilegeList,
groups: groupPrivilegeList,
};
const payload = await utils.promiseParallel({
- labels,
labelData: Array.from(_privilegeMap.values()),
users: helpers.getUserPrivileges(0, keys.users),
groups: helpers.getGroupPrivileges(0, keys.groups),
diff --git a/src/privileges/categories.js b/src/privileges/categories.js
index 6061b28abe..3efa1e2413 100644
--- a/src/privileges/categories.js
+++ b/src/privileges/categories.js
@@ -53,8 +53,8 @@ privsCategories.getType = function (privilege) {
return priv && priv.type ? priv.type : '';
};
-privsCategories.getUserPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.list', Array.from(_privilegeMap.keys()));
-privsCategories.getGroupPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.groups.list', Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`));
+privsCategories.getUserPrivilegeList = () => Array.from(_privilegeMap.keys());
+privsCategories.getGroupPrivilegeList = () => Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`);
privsCategories.getPrivilegeList = async () => {
const [user, group] = await Promise.all([
@@ -72,27 +72,20 @@ privsCategories.getPrivilegesByFilter = function (filter) {
// Method used in admin/category controller to show all users/groups with privs in that given cid
privsCategories.list = async function (cid) {
- let labels = Array.from(_privilegeMap.values()).map(data => data.label);
- labels = await utils.promiseParallel({
- users: plugins.hooks.fire('filter:privileges.list_human', labels.slice()),
- groups: plugins.hooks.fire('filter:privileges.groups.list_human', labels.slice()),
- });
-
const keys = await utils.promiseParallel({
users: privsCategories.getUserPrivilegeList(),
groups: privsCategories.getGroupPrivilegeList(),
});
const payload = await utils.promiseParallel({
- labels,
labelData: Array.from(_privilegeMap.values()),
users: helpers.getUserPrivileges(cid, keys.users),
groups: helpers.getGroupPrivileges(cid, keys.groups),
});
payload.keys = keys;
- payload.columnCountUserOther = payload.labels.users.length - privsCategories._coreSize;
- payload.columnCountGroupOther = payload.labels.groups.length - privsCategories._coreSize;
+ payload.columnCountUserOther = payload.labelData.length - privsCategories._coreSize;
+ payload.columnCountGroupOther = payload.labelData.length - privsCategories._coreSize;
return payload;
};
diff --git a/src/privileges/global.js b/src/privileges/global.js
index aca4d85250..9de1beba8d 100644
--- a/src/privileges/global.js
+++ b/src/privileges/global.js
@@ -54,8 +54,8 @@ privsGlobal.getType = function (privilege) {
return priv && priv.type ? priv.type : '';
};
-privsGlobal.getUserPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.global.list', Array.from(_privilegeMap.keys()));
-privsGlobal.getGroupPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.global.groups.list', Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`));
+privsGlobal.getUserPrivilegeList = () => Array.from(_privilegeMap.keys());
+privsGlobal.getGroupPrivilegeList = () => Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`);
privsGlobal.getPrivilegeList = async () => {
const [user, group] = await Promise.all([
privsGlobal.getUserPrivilegeList(),
@@ -65,21 +65,12 @@ privsGlobal.getPrivilegeList = async () => {
};
privsGlobal.list = async function () {
- async function getLabels() {
- const labels = Array.from(_privilegeMap.values()).map(data => data.label);
- return await utils.promiseParallel({
- users: plugins.hooks.fire('filter:privileges.global.list_human', labels.slice()),
- groups: plugins.hooks.fire('filter:privileges.global.groups.list_human', labels.slice()),
- });
- }
-
const keys = await utils.promiseParallel({
users: privsGlobal.getUserPrivilegeList(),
groups: privsGlobal.getGroupPrivilegeList(),
});
const payload = await utils.promiseParallel({
- labels: getLabels(),
labelData: Array.from(_privilegeMap.values()),
users: helpers.getUserPrivileges(0, keys.users),
groups: helpers.getGroupPrivileges(0, keys.groups),
diff --git a/src/socket.io/admin/analytics.js b/src/socket.io/admin/analytics.js
index bc084b14f5..8af8881873 100644
--- a/src/socket.io/admin/analytics.js
+++ b/src/socket.io/admin/analytics.js
@@ -18,14 +18,18 @@ Analytics.get = async function (socket, data) {
data.amount = 24;
}
}
- const getStats = data.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet;
+ const getStats = data.units === 'days' ?
+ analytics.getDailyStatsForSet :
+ analytics.getHourlyStatsForSet;
+
if (data.graph === 'traffic') {
+ const until = data.until || Date.now();
const result = await utils.promiseParallel({
- uniqueVisitors: getStats('analytics:uniquevisitors', data.until || Date.now(), data.amount),
- pageviews: getStats('analytics:pageviews', data.until || Date.now(), data.amount),
- pageviewsRegistered: getStats('analytics:pageviews:registered', data.until || Date.now(), data.amount),
- pageviewsGuest: getStats('analytics:pageviews:guest', data.until || Date.now(), data.amount),
- pageviewsBot: getStats('analytics:pageviews:bot', data.until || Date.now(), data.amount),
+ uniqueVisitors: getStats('analytics:uniquevisitors', until, data.amount),
+ pageviews: getStats('analytics:pageviews', until, data.amount),
+ pageviewsRegistered: getStats('analytics:pageviews:registered', until, data.amount),
+ pageviewsGuest: getStats('analytics:pageviews:guest', until, data.amount),
+ pageviewsBot: getStats('analytics:pageviews:bot', until, data.amount),
summary: analytics.getSummary(),
});
result.pastDay = result.pageviews.reduce((a, b) => parseInt(a, 10) + parseInt(b, 10));
diff --git a/src/user/email.js b/src/user/email.js
index b485081713..16ec7e04bc 100644
--- a/src/user/email.js
+++ b/src/user/email.js
@@ -145,7 +145,7 @@ UserEmail.sendValidationEmail = async function (uid, options) {
uid,
username,
confirm_link,
- confirm_code: await plugins.hooks.fire('filter:user.verify.code', confirm_code),
+ confirm_code,
email: options.email,
subject: options.subject || '[[email:email.verify-your-email.subject]]',
diff --git a/test/activitypub/actors.js b/test/activitypub/actors.js
index 687bc92a44..37fc74280d 100644
--- a/test/activitypub/actors.js
+++ b/test/activitypub/actors.js
@@ -895,7 +895,7 @@ describe('Pruning', () => {
const { id } = helpers.mocks.note({
cc: [cid],
});
- await activitypub.notes.assert(0, id);
+ await activitypub.notes.assert(0, id, { cid });
const total = await db.sortedSetCard('usersRemote:lastCrawled');
const result = await activitypub.actors.prune();
diff --git a/test/activitypub/feps.js b/test/activitypub/feps.js
index a9b42644d8..65520f0f4e 100644
--- a/test/activitypub/feps.js
+++ b/test/activitypub/feps.js
@@ -12,6 +12,7 @@ const user = require('../../src/user');
const groups = require('../../src/groups');
const categories = require('../../src/categories');
const topics = require('../../src/topics');
+const posts = require('../../src/posts');
const api = require('../../src/api');
const helpers = require('./helpers');
@@ -47,84 +48,258 @@ describe('FEPs', () => {
activitypub._sent.clear();
});
- it('should be called when a topic is moved from uncategorized to another category', async () => {
- const { topicData, postData } = await topics.post({
- uid,
- cid: -1,
- title: utils.generateUUID(),
- content: utils.generateUUID(),
- });
+ describe('local actions (create, reply, vote)', () => {
+ let topicData;
- assert(topicData);
-
- await api.topics.move({ uid: adminUid }, {
- tid: topicData.tid,
- cid,
- });
-
- assert.strictEqual(activitypub._sent.size, 2);
-
- const key = Array.from(activitypub._sent.keys())[0];
- const activity = activitypub._sent.get(key);
-
- assert(activity && activity.object && typeof activity.object === 'object');
- assert.strictEqual(activity.object.id, `${nconf.get('url')}/post/${postData.pid}`);
- });
-
- it('should be called for a newly forked topic', async () => {
- const { topicData } = await topics.post({
- uid,
- cid: -1,
- title: utils.generateUUID(),
- content: utils.generateUUID(),
- });
- const { tid } = topicData;
- const { pid: reply1Pid } = await topics.reply({ uid, tid, content: utils.generateUUID() });
- const { pid: reply2Pid } = await topics.reply({ uid, tid, content: utils.generateUUID() });
- await topics.createTopicFromPosts(
- adminUid, utils.generateUUID(), [reply1Pid, reply2Pid], tid, cid
- );
-
- assert.strictEqual(activitypub._sent.size, 2, activitypub._sent.keys());
-
- const key = Array.from(activitypub._sent.keys())[0];
- const activity = activitypub._sent.get(key);
-
- assert(activity && activity.object && typeof activity.object === 'object');
- assert.strictEqual(activity.object.id, `${nconf.get('url')}/post/${reply1Pid}`);
- });
-
- it('should be called when a post is moved to another topic', async () => {
- const [{ topicData: topic1 }, { topicData: topic2 }] = await Promise.all([
- topics.post({
- uid,
+ before(async () => {
+ topicData = await api.topics.create({ uid }, {
cid,
title: utils.generateUUID(),
content: utils.generateUUID(),
- }),
- topics.post({
- uid,
+ });
+ });
+
+ afterEach(() => {
+ activitypub._sent.clear();
+ });
+
+ it('should have federated out both Announce(Create(Article)) and Announce(Article)', () => {
+ const activities = Array.from(activitypub._sent);
+
+ const test1 = activities.some((activity) => {
+ [, activity] = activity;
+ return activity.type === 'Announce' &&
+ activity.object && activity.object.type === 'Create' &&
+ activity.object.object && activity.object.object.type === 'Article';
+ });
+
+ const test2 = activities.some((activity) => {
+ [, activity] = activity;
+ return activity.type === 'Announce' &&
+ activity.object && activity.object.type === 'Article';
+ });
+
+ assert(test1 && test2);
+ });
+
+ it('should federate out Announce(Create(Note)) on local reply', async () => {
+ await api.topics.reply({ uid }, {
+ tid: topicData.tid,
+ content: utils.generateUUID(),
+ });
+
+ const activities = Array.from(activitypub._sent);
+
+ assert(activities.some((activity) => {
+ [, activity] = activity;
+ return activity.type === 'Announce' &&
+ activity.object && activity.object.type === 'Create' &&
+ activity.object.object && activity.object.object.type === 'Note';
+ }));
+ });
+
+ it('should NOT federate out Announce(Note) on local reply', async () => {
+ await api.topics.reply({ uid }, {
+ tid: topicData.tid,
+ content: utils.generateUUID(),
+ });
+
+ const activities = Array.from(activitypub._sent);
+
+ assert(activities.every((activity) => {
+ [, activity] = activity;
+ if (activity.type === 'Announce' && activity.object && activity.object.type === 'Note') {
+ return false;
+ }
+
+ return true;
+ }));
+ });
+
+ it('should federate out Announce(Like) on local vote', async () => {
+ activitypub._sent.clear();
+ await api.posts.upvote({ uid: adminUid }, { pid: topicData.mainPid, room_id: `topic_${topicData.tid}` });
+ const activities = Array.from(activitypub._sent);
+
+ assert(activities.some((activity) => {
+ [, activity] = activity;
+ return activity.type === 'Announce' &&
+ activity.object && activity.object.type === 'Like';
+ }));
+ });
+ });
+
+ describe('remote actions (create, reply, vote)', () => {
+ let activity;
+ let pid;
+ let topicData;
+
+ before(async () => {
+ topicData = await api.topics.create({ uid }, {
cid,
title: utils.generateUUID(),
content: utils.generateUUID(),
- }),
- ]);
+ });
+ });
- assert(topic1 && topic2);
+ afterEach(() => {
+ activitypub._sent.clear();
+ });
- // Create new reply and move it to topic 2
- const { pid } = await topics.reply({ uid, tid: topic1.tid, content: utils.generateUUID() });
- await api.posts.move({ uid: adminUid }, { pid, tid: topic2.tid });
+ it('should have slotted the note into the test category', async () => {
+ const { id, note } = await helpers.mocks.note({
+ cc: [`${nconf.get('url')}/category/${cid}`],
+ });
+ pid = id;
+ ({ activity } = await helpers.mocks.create(note));
+ await activitypub.inbox.create({ body: activity });
- assert.strictEqual(activitypub._sent.size, 1);
- const activities = Array.from(activitypub._sent.keys()).map(key => activitypub._sent.get(key));
+ const noteCid = await posts.getCidByPid(pid);
+ assert.strictEqual(noteCid, cid);
+ });
- const activity = activities.pop();
- assert.strictEqual(activity.type, 'Announce');
- assert(activity.object && activity.object.type);
- assert.strictEqual(activity.object.type, 'Create');
- assert(activity.object.object && activity.object.object.type);
- assert.strictEqual(activity.object.object.type, 'Note');
+ it('should federate out an Announce(Create(Note)) and Announce(Note) on new topic', async () => {
+ const { id, note } = await helpers.mocks.note({
+ cc: [`${nconf.get('url')}/category/${cid}`],
+ });
+ pid = id;
+ ({ activity } = await helpers.mocks.create(note));
+ await activitypub.inbox.create({ body: activity });
+
+ const activities = Array.from(activitypub._sent);
+
+ const test1 = activities.some((activity) => {
+ [, activity] = activity;
+ return activity.type === 'Announce' &&
+ activity.object && activity.object.type === 'Create' &&
+ activity.object.object && activity.object.object.type === 'Note';
+ });
+
+ const test2 = activities.some((activity) => {
+ [, activity] = activity;
+ return activity.type === 'Announce' &&
+ activity.object && activity.object.type === 'Note';
+ });
+
+ assert(test1 && test2);
+ });
+
+ it('should federate out an Announce(Create(Note)) on reply', async () => {
+ const { id, note } = await helpers.mocks.note({
+ cc: [`${nconf.get('url')}/category/${cid}`],
+ inReplyTo: `${nconf.get('url')}/post/${topicData.mainPid}`,
+ });
+ pid = id;
+ ({ activity } = await helpers.mocks.create(note));
+ await activitypub.inbox.create({ body: activity });
+
+ const activities = Array.from(activitypub._sent);
+
+ assert(activities.some((activity) => {
+ [, activity] = activity;
+ return activity.type === 'Announce' &&
+ activity.object && activity.object.type === 'Create' &&
+ activity.object.object && activity.object.object.type === 'Note';
+ }));
+ });
+
+ it('should federate out an Announce(Like) on vote', async () => {
+ const { activity } = await helpers.mocks.like({
+ object: {
+ id: `${nconf.get('url')}/post/${topicData.mainPid}`,
+ },
+ });
+ await activitypub.inbox.like({ body: activity });
+
+ const activities = Array.from(activitypub._sent);
+ assert(activities.some((activity) => {
+ [, activity] = activity;
+ return activity.type === 'Announce' &&
+ activity.object && activity.object.type === 'Like';
+ }));
+ });
+ });
+
+ describe('extended actions not explicitly specified in 1b12', () => {
+ it('should be called when a topic is moved from uncategorized to another category', async () => {
+ const { topicData, postData } = await topics.post({
+ uid,
+ cid: -1,
+ title: utils.generateUUID(),
+ content: utils.generateUUID(),
+ });
+
+ assert(topicData);
+
+ await api.topics.move({ uid: adminUid }, {
+ tid: topicData.tid,
+ cid,
+ });
+
+ assert.strictEqual(activitypub._sent.size, 2);
+
+ const key = Array.from(activitypub._sent.keys())[0];
+ const activity = activitypub._sent.get(key);
+
+ assert(activity && activity.object && typeof activity.object === 'object');
+ assert.strictEqual(activity.object.id, `${nconf.get('url')}/post/${postData.pid}`);
+ });
+
+ it('should be called for a newly forked topic', async () => {
+ const { topicData } = await topics.post({
+ uid,
+ cid: -1,
+ title: utils.generateUUID(),
+ content: utils.generateUUID(),
+ });
+ const { tid } = topicData;
+ const { pid: reply1Pid } = await topics.reply({ uid, tid, content: utils.generateUUID() });
+ const { pid: reply2Pid } = await topics.reply({ uid, tid, content: utils.generateUUID() });
+ await topics.createTopicFromPosts(
+ adminUid, utils.generateUUID(), [reply1Pid, reply2Pid], tid, cid
+ );
+
+ assert.strictEqual(activitypub._sent.size, 2, activitypub._sent.keys());
+
+ const key = Array.from(activitypub._sent.keys())[0];
+ const activity = activitypub._sent.get(key);
+
+ assert(activity && activity.object && typeof activity.object === 'object');
+ assert.strictEqual(activity.object.id, `${nconf.get('url')}/post/${reply1Pid}`);
+ });
+
+ it('should be called when a post is moved to another topic', async () => {
+ const [{ topicData: topic1 }, { topicData: topic2 }] = await Promise.all([
+ topics.post({
+ uid,
+ cid,
+ title: utils.generateUUID(),
+ content: utils.generateUUID(),
+ }),
+ topics.post({
+ uid,
+ cid,
+ title: utils.generateUUID(),
+ content: utils.generateUUID(),
+ }),
+ ]);
+
+ assert(topic1 && topic2);
+
+ // Create new reply and move it to topic 2
+ const { pid } = await topics.reply({ uid, tid: topic1.tid, content: utils.generateUUID() });
+ await api.posts.move({ uid: adminUid }, { pid, tid: topic2.tid });
+
+ assert.strictEqual(activitypub._sent.size, 1);
+ const activities = Array.from(activitypub._sent.keys()).map(key => activitypub._sent.get(key));
+
+ const activity = activities.pop();
+ assert.strictEqual(activity.type, 'Announce');
+ assert(activity.object && activity.object.type);
+ assert.strictEqual(activity.object.type, 'Create');
+ assert(activity.object.object && activity.object.object.type);
+ assert.strictEqual(activity.object.object.type, 'Note');
+ });
});
});
});
diff --git a/test/activitypub/helpers.js b/test/activitypub/helpers.js
index e4c3a4d689..2690910e06 100644
--- a/test/activitypub/helpers.js
+++ b/test/activitypub/helpers.js
@@ -144,7 +144,7 @@ Helpers.mocks.like = (override = {}) => {
const activity = {
'@context': 'https://www.w3.org/ns/activitystreams',
- id: `${Helpers.mocks._baseUrl}/like/${encodeURIComponent(object)}`,
+ id: `${Helpers.mocks._baseUrl}/like/${encodeURIComponent(object.id || object)}`,
type: 'Like',
actor,
object,
@@ -162,6 +162,8 @@ Helpers.mocks.announce = (override = {}) => {
if (!object) {
({ id: object } = Helpers.mocks.note());
}
+ delete override.actor;
+ delete override.object;
const activity = {
'@context': 'https://www.w3.org/ns/activitystreams',
@@ -171,6 +173,7 @@ Helpers.mocks.announce = (override = {}) => {
cc: [`${actor}/followers`],
actor,
object,
+ ...override,
};
return { activity };
diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js
index 9b4aaf8653..afd24081c5 100644
--- a/test/activitypub/notes.js
+++ b/test/activitypub/notes.js
@@ -83,33 +83,6 @@ describe('Notes', () => {
assert.strictEqual(topic.cid, cid);
});
- it('should slot newly created topic in remote category if addressed', async () => {
- const { id: cid, actor } = helpers.mocks.group();
- await activitypub.actors.assertGroup([cid]);
-
- const { id } = helpers.mocks.note({
- cc: [cid],
- });
-
- const assertion = await activitypub.notes.assert(0, id);
- assert(assertion);
-
- const { tid, count } = assertion;
- assert(tid);
- assert.strictEqual(count, 1);
-
- const topic = await topics.getTopicData(tid);
- assert.strictEqual(topic.cid, cid);
-
- const tids = await db.getSortedSetMembers(`cid:${cid}:tids`);
- assert(tids.includes(tid));
-
- const category = await categories.getCategoryData(cid);
- ['topic_count', 'post_count', 'totalPostCount', 'totalTopicCount'].forEach((prop) => {
- assert.strictEqual(category[prop], 1);
- });
- });
-
it('should add a remote category topic to a user\'s inbox if they are following the category', async () => {
const { id: cid, actor } = helpers.mocks.group();
await activitypub.actors.assertGroup([cid]);
@@ -120,7 +93,7 @@ describe('Notes', () => {
const { id } = helpers.mocks.note({
cc: [cid],
});
- const { tid } = await activitypub.notes.assert(0, id);
+ const { tid } = await activitypub.notes.assert(0, id, { cid });
const inInbox = await db.isSortedSetMember(`uid:${uid}:inbox`, tid);
assert(inInbox);
@@ -161,7 +134,7 @@ describe('Notes', () => {
const { id } = helpers.mocks.note({
cc: [remoteCid],
});
- const assertion = await activitypub.notes.assert(0, id);
+ const assertion = await activitypub.notes.assert(0, id, { cid: remoteCid });
assert(assertion);
const unread = await topics.getTotalUnread(uid);
@@ -180,7 +153,7 @@ describe('Notes', () => {
const { id, note } = helpers.mocks.note({
cc: [remoteCid],
});
- const assertion = await activitypub.notes.assert(0, id);
+ const assertion = await activitypub.notes.assert(0, id, { cid: remoteCid });
assert(assertion);
const unread = await topics.getTotalUnread(uid);
@@ -203,7 +176,7 @@ describe('Notes', () => {
const { id, note } = helpers.mocks.note({
cc: [remoteCid],
});
- const assertion = await activitypub.notes.assert(0, id);
+ const assertion = await activitypub.notes.assert(0, id, { cid: remoteCid });
assert(assertion);
const unread = await topics.getTotalUnread(uid);
@@ -457,6 +430,44 @@ describe('Notes', () => {
});
});
+ describe('Create', () => {
+ let uid;
+
+ before(async () => {
+ uid = await user.create({ username: utils.generateUUID() });
+ });
+
+ describe('(Note)', () => {
+ it('should create a new topic in cid -1', async () => {
+ const { note, id } = helpers.mocks.note();
+ const { activity } = helpers.mocks.create(note);
+
+ await db.sortedSetAdd(`followersRemote:${note.attributedTo}`, Date.now(), uid);
+ await activitypub.inbox.create({ body: activity });
+
+ assert(await posts.exists(id));
+
+ const cid = await posts.getCidByPid(id);
+ assert.strictEqual(cid, -1);
+ });
+
+ it('should create a new topic in cid -1 even if a remote category is addressed', async () => {
+ const { id: remoteCid } = helpers.mocks.group();
+ const { note, id } = helpers.mocks.note({
+ audience: [remoteCid],
+ });
+ const { activity } = helpers.mocks.create(note);
+
+ await activitypub.inbox.create({ body: activity });
+
+ assert(await posts.exists(id));
+
+ const cid = await posts.getCidByPid(id);
+ assert.strictEqual(cid, -1);
+ });
+ });
+ });
+
describe('Announce', () => {
let cid;
@@ -464,6 +475,101 @@ describe('Notes', () => {
({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) }));
});
+ describe('(Create)', () => {
+ it('should create a new topic in a remote category if addressed', async () => {
+ const { id: remoteCid } = helpers.mocks.group();
+ const { id, note } = helpers.mocks.note({
+ audience: [remoteCid],
+ });
+ let { activity } = helpers.mocks.create(note);
+ ({ activity } = helpers.mocks.announce({ actor: remoteCid, object: activity }));
+
+ await activitypub.inbox.announce({ body: activity });
+
+ assert(await posts.exists(id));
+
+ const cid = await posts.getCidByPid(id);
+ assert.strictEqual(cid, remoteCid);
+ });
+ });
+
+ describe('(Create) or (Note) referencing local post', () => {
+ let uid;
+ let topicData;
+ let postData;
+ let localNote;
+ let announces = 0;
+
+ before(async () => {
+ uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
+ ({ topicData, postData } = await topics.post({
+ cid,
+ uid,
+ title: utils.generateUUID(),
+ content: utils.generateUUID(),
+ }));
+ localNote = await activitypub.mocks.notes.public(postData);
+ });
+
+ it('should increment announces counter when a remote user shares', async () => {
+ const { id } = helpers.mocks.person();
+ const { activity } = helpers.mocks.announce({
+ actor: id,
+ object: localNote,
+ cc: [`${nconf.get('url')}/uid/${topicData.uid}`],
+ });
+
+ await activitypub.inbox.announce({ body: activity });
+ announces += 1;
+
+ const count = await posts.getPostField(topicData.mainPid, 'announces');
+ assert.strictEqual(count, announces);
+ });
+
+ it('should contain the remote user announcer id in the post announces zset', async () => {
+ const { id } = helpers.mocks.person();
+ const { activity } = helpers.mocks.announce({
+ actor: id,
+ object: localNote,
+ cc: [`${nconf.get('url')}/uid/${topicData.uid}`],
+ });
+
+ await activitypub.inbox.announce({ body: activity });
+ announces += 1;
+
+ const exists = await db.isSortedSetMember(`pid:${topicData.mainPid}:announces`, id);
+ assert(exists);
+ });
+
+ it('should NOT increment announces counter when a remote category shares', async () => {
+ const { id } = helpers.mocks.group();
+ const { activity } = helpers.mocks.announce({
+ actor: id,
+ object: localNote,
+ cc: [`${nconf.get('url')}/uid/${topicData.uid}`],
+ });
+
+ await activitypub.inbox.announce({ body: activity });
+
+ const count = await posts.getPostField(topicData.mainPid, 'announces');
+ assert.strictEqual(count, announces);
+ });
+
+ it('should NOT contain the remote category announcer id in the post announces zset', async () => {
+ const { id } = helpers.mocks.group();
+ const { activity } = helpers.mocks.announce({
+ actor: id,
+ object: localNote,
+ cc: [`${nconf.get('url')}/uid/${topicData.uid}`],
+ });
+
+ await activitypub.inbox.announce({ body: activity });
+
+ const exists = await db.isSortedSetMember(`pid:${topicData.mainPid}:announces`, id);
+ assert(!exists);
+ });
+ });
+
describe('(Note)', () => {
it('should create a new topic in cid -1 if category not addressed', async () => {
const { note } = helpers.mocks.note();
diff --git a/test/categories.js b/test/categories.js
index 5309bd1545..46972873e9 100644
--- a/test/categories.js
+++ b/test/categories.js
@@ -476,9 +476,7 @@ describe('Categories', () => {
it('should get privilege settings', async () => {
const data = await apiCategories.getPrivileges({ uid: adminUid }, categoryObj.cid);
- assert(data.labels);
- assert(data.labels.users);
- assert(data.labels.groups);
+ assert(data.labelData);
assert(data.keys.users);
assert(data.keys.groups);
assert(data.users);
diff --git a/test/controllers.js b/test/controllers.js
index c0c7b22a7f..b2174d8cf9 100644
--- a/test/controllers.js
+++ b/test/controllers.js
@@ -1442,44 +1442,44 @@ describe('Controllers', () => {
});
it('should handle CSRF error', async () => {
- plugins.loadedHooks['filter:router.page'] = plugins.loadedHooks['filter:router.page'] || [];
- plugins.loadedHooks['filter:router.page'].push({
- method: function (req, res, next) {
+ plugins.loadedHooks['response:router.page'] = plugins.loadedHooks['response:router.page'] || [];
+ plugins.loadedHooks['response:router.page'].push({
+ method: function () {
const err = new Error('csrf-error');
err.code = 'EBADCSRFTOKEN';
- next(err);
+ throw err;
},
});
const { response } = await request.get(`${nconf.get('url')}/users`);
- plugins.loadedHooks['filter:router.page'] = [];
+ plugins.loadedHooks['response:router.page'] = [];
assert.equal(response.statusCode, 403);
});
it('should handle black-list error', async () => {
- plugins.loadedHooks['filter:router.page'] = plugins.loadedHooks['filter:router.page'] || [];
- plugins.loadedHooks['filter:router.page'].push({
- method: function (req, res, next) {
+ plugins.loadedHooks['response:router.page'] = plugins.loadedHooks['response:router.page'] || [];
+ plugins.loadedHooks['response:router.page'].push({
+ method: function () {
const err = new Error('blacklist error message');
err.code = 'blacklisted-ip';
- next(err);
+ throw err;
},
});
const { response, body } = await request.get(`${nconf.get('url')}/users`);
- plugins.loadedHooks['filter:router.page'] = [];
+ plugins.loadedHooks['response:router.page'] = [];
assert.equal(response.statusCode, 403);
assert.equal(body, 'blacklist error message');
});
it('should handle page redirect through error', async () => {
- plugins.loadedHooks['filter:router.page'] = plugins.loadedHooks['filter:router.page'] || [];
- plugins.loadedHooks['filter:router.page'].push({
- method: function (req, res, next) {
+ plugins.loadedHooks['response:router.page'] = plugins.loadedHooks['response:router.page'] || [];
+ plugins.loadedHooks['response:router.page'].push({
+ method: function () {
const err = new Error('redirect');
err.status = 302;
err.path = '/popular';
- plugins.loadedHooks['filter:router.page'] = [];
- next(err);
+ plugins.loadedHooks['response:router.page'] = [];
+ throw err;
},
});
const { response, body } = await request.get(`${nconf.get('url')}/users`);
@@ -1488,14 +1488,14 @@ describe('Controllers', () => {
});
it('should handle api page redirect through error', async () => {
- plugins.loadedHooks['filter:router.page'] = plugins.loadedHooks['filter:router.page'] || [];
- plugins.loadedHooks['filter:router.page'].push({
- method: function (req, res, next) {
+ plugins.loadedHooks['response:router.page'] = plugins.loadedHooks['response:router.page'] || [];
+ plugins.loadedHooks['response:router.page'].push({
+ method: function () {
const err = new Error('redirect');
err.status = 308;
err.path = '/api/popular';
- plugins.loadedHooks['filter:router.page'] = [];
- next(err);
+ plugins.loadedHooks['response:router.page'] = [];
+ throw err;
},
});
const { response, body } = await request.get(`${nconf.get('url')}/api/users`);
@@ -1505,15 +1505,15 @@ describe('Controllers', () => {
});
it('should handle error page', async () => {
- plugins.loadedHooks['filter:router.page'] = plugins.loadedHooks['filter:router.page'] || [];
- plugins.loadedHooks['filter:router.page'].push({
- method: function (req, res, next) {
+ plugins.loadedHooks['response:router.page'] = plugins.loadedHooks['response:router.page'] || [];
+ plugins.loadedHooks['response:router.page'].push({
+ method: function () {
const err = new Error('regular error');
- next(err);
+ throw err;
},
});
const { response, body } = await request.get(`${nconf.get('url')}/users`);
- plugins.loadedHooks['filter:router.page'] = [];
+ plugins.loadedHooks['response:router.page'] = [];
assert.equal(response.statusCode, 500);
assert(body);
});