mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-16 21:40:23 +01:00
Compare commits
95 Commits
protocol-v
...
v1.13.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a476d7261b | ||
|
|
71f4607db4 | ||
|
|
d6ac2ba396 | ||
|
|
a5ef6b53b8 | ||
|
|
c35a21d7f0 | ||
|
|
1e50616c13 | ||
|
|
157832131d | ||
|
|
d5b3d56296 | ||
|
|
976e26a958 | ||
|
|
eb4a1a5772 | ||
|
|
cdfbcbb9ce | ||
|
|
87225a90c3 | ||
|
|
5ed7fc0ffa | ||
|
|
16ab641dd1 | ||
|
|
726ba71c57 | ||
|
|
f07b4bfa62 | ||
|
|
b370333c6a | ||
|
|
b959c24a2b | ||
|
|
111ed802cf | ||
|
|
6d7131fbc5 | ||
|
|
8c48f94b96 | ||
|
|
9969dd6335 | ||
|
|
d927b763c1 | ||
|
|
66febb8071 | ||
|
|
3cca929a88 | ||
|
|
df2c785127 | ||
|
|
0ae1eb4f6e | ||
|
|
dd440ce902 | ||
|
|
027f3f2256 | ||
|
|
23810cc64b | ||
|
|
30c503611c | ||
|
|
cd1fa27a8b | ||
|
|
ee4304b443 | ||
|
|
f799f017ab | ||
|
|
418c174d56 | ||
|
|
51236df4ed | ||
|
|
1f13ab8a19 | ||
|
|
236a173009 | ||
|
|
82ace391cb | ||
|
|
3077eb9428 | ||
|
|
ecc579a29c | ||
|
|
594cd7e176 | ||
|
|
106c141ff5 | ||
|
|
c510a2c4f2 | ||
|
|
0e49cfb98d | ||
|
|
66992a556c | ||
|
|
1b08f37612 | ||
|
|
bfaba89557 | ||
|
|
8bb5e71ebe | ||
|
|
3fac09b1ab | ||
|
|
8e5a2276af | ||
|
|
ddce77b343 | ||
|
|
6a63c1a100 | ||
|
|
c8fb7f9246 | ||
|
|
8c6a7954cf | ||
|
|
d74eecfbe8 | ||
|
|
153b1a0eaa | ||
|
|
1656738359 | ||
|
|
01d1ae78c8 | ||
|
|
b0f3e48ac2 | ||
|
|
3e52557689 | ||
|
|
09d55581d8 | ||
|
|
16e8f49655 | ||
|
|
e3952674ba | ||
|
|
b7a57996f7 | ||
|
|
ca10f8f073 | ||
|
|
842916ea42 | ||
|
|
4eb9652a2a | ||
|
|
740de034fa | ||
|
|
879acc85ae | ||
|
|
e06c1bfcd2 | ||
|
|
df5e3a7394 | ||
|
|
61da8c29ac | ||
|
|
48f086279c | ||
|
|
5a8217de01 | ||
|
|
527745310d | ||
|
|
5e6233969e | ||
|
|
81c5ca15eb | ||
|
|
ac567bc10a | ||
|
|
5410b5d6da | ||
|
|
223225378e | ||
|
|
10989cccaa | ||
|
|
0aae421417 | ||
|
|
fd056b58fb | ||
|
|
bb1515ce56 | ||
|
|
c1b1ee61f3 | ||
|
|
9d074731f4 | ||
|
|
b9679df784 | ||
|
|
f6d7a24a67 | ||
|
|
9a4a48bc45 | ||
|
|
2edc6960d0 | ||
|
|
cc6758a0f1 | ||
|
|
d1e0672fa6 | ||
|
|
bf7cab0e4f | ||
|
|
c6ef1486de |
71
CHANGELOG.md
71
CHANGELOG.md
@@ -1,3 +1,74 @@
|
||||
#### 1.13.1 (2019-12-19)
|
||||
|
||||
##### Chores
|
||||
|
||||
* incrementing version number - v1.13.1 (d1e0672f)
|
||||
* incrementing version number - v1.13.0 (c38b2d23)
|
||||
* **deps:**
|
||||
* update dependency husky to v3.1.0 (#8046) (c3418c26)
|
||||
* update dependency coveralls to v3.0.9 (#8067) (0aeee144)
|
||||
* update dependency eslint to v6.7.0 (32cfe96f)
|
||||
* update dependency coveralls to v3.0.8 (#8054) (8ba26104)
|
||||
|
||||
##### Documentation Changes
|
||||
|
||||
* updated changelog (94499da3)
|
||||
|
||||
##### New Features
|
||||
|
||||
* better output for cli plugins list, closes #8075 (4fc69443)
|
||||
* #5272, allow changing user groups from manage users page (05c9fe27)
|
||||
* merge social authentication into plugins menu in ACP (f9a8ebfc)
|
||||
* convert middleware.isAdmin to async/await (efd1e88b)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* #8085, fix cookie name (dec157d6)
|
||||
* #8058, fix incorrect digest setting display in ACP (1b992d82)
|
||||
* remove select version (6a17e32d)
|
||||
* travis config (3ae98300)
|
||||
* travis :dog: (3731dc4e)
|
||||
* #8078, dont mark notifications read without a mergeId (a8df6d62)
|
||||
* #8077, show continue chat on all profile pages (7af1c873)
|
||||
* profile showing posts from deleted topics (2679f37d)
|
||||
* #8073, configurable necroThreshold (4d669783)
|
||||
* allow members to search as well (b323df2f)
|
||||
* #8069, dont show hidden groups in search (c2cd7de8)
|
||||
* missing await (33fd4a1c)
|
||||
* #8064, break-word on post-queue (1bda92e3)
|
||||
* #6711 (7ed002a1)
|
||||
* #8061, don't crash if there is a network problem (de404102)
|
||||
* #8059, properly mark topic unread when using mark unread for all (a688aaae)
|
||||
* #8042, dont show errors after clearing form (3811e0a3)
|
||||
* unhandled promise rejection error on reset error (51073772)
|
||||
* #8050, fix redirect after registration (366ad5cd)
|
||||
* make _csrf a secure cookie if the website is using https (#8045) (0efe27b1)
|
||||
* #8034 (0a96c923)
|
||||
* serialize (a2545204)
|
||||
* show login fields if user has local password (1eca5b3d)
|
||||
* use the correct attribute name for widgets (6c404b81)
|
||||
* **deps:**
|
||||
* update dependency semver to v7 (483d7535)
|
||||
* update dependency nodebb-theme-vanilla to v11.1.12 (610ecf35)
|
||||
* update dependency sharp to v0.23.4 (#8076) (eb18c182)
|
||||
* update dependency nodebb-theme-persona to v10.1.30 (0514383a)
|
||||
* update dependency nodebb-plugin-markdown to v8.11.0 (702ca164)
|
||||
* update dependency connect-mongo to v3.2.0 (2aef7a5b)
|
||||
* update dependency mongodb to v3.3.5 (#8065) (68118e43)
|
||||
* update dependency nodebb-theme-persona to v10.1.29 (#8057) (34933091)
|
||||
* update dependency sharp to v0.23.3 (#8044) (6fa88823)
|
||||
* update dependency validator to v12.1.0 (#8055) (488ea394)
|
||||
* update dependency nodebb-theme-slick to v1.2.28 (#8041) (b3511f71)
|
||||
* update dependency nodebb-theme-vanilla to v11.1.11 (#8040) (d567c4ae)
|
||||
* update dependency nodebb-theme-persona to v10.1.28 (#8039) (6c87bed5)
|
||||
* update dependency nodebb-plugin-dbsearch to v4.0.7 (#8038) (1e2e16b4)
|
||||
|
||||
##### Refactors
|
||||
|
||||
* async/await middleware (a227cbe3)
|
||||
* change to const/let (3454a24b)
|
||||
* shorter returns (cec00795)
|
||||
|
||||
### 1.13.0 (2019-11-13)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "1.13.0",
|
||||
"version": "1.13.2",
|
||||
"homepage": "http://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -49,7 +49,7 @@
|
||||
"connect-mongo": "3.2.0",
|
||||
"connect-multiparty": "^2.1.0",
|
||||
"connect-pg-simple": "^6.0.0",
|
||||
"connect-redis": "4.0.3",
|
||||
"connect-redis": "4.0.4",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"cron": "^1.3.0",
|
||||
"cropperjs": "^1.2.2",
|
||||
@@ -63,7 +63,7 @@
|
||||
"helmet": "^3.11.0",
|
||||
"html-to-text": "^5.0.0",
|
||||
"ipaddr.js": "^1.5.4",
|
||||
"jquery": "^3.2.1",
|
||||
"jquery": "3.4.1",
|
||||
"jsesc": "2.5.2",
|
||||
"json-2-csv": "^3.0.0",
|
||||
"jsonwebtoken": "^8.4.0",
|
||||
@@ -73,43 +73,43 @@
|
||||
"lru-cache": "5.1.1",
|
||||
"material-design-lite": "^1.3.0",
|
||||
"mime": "^2.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mongodb": "3.4.0",
|
||||
"mkdirp": "^1.0.3",
|
||||
"mongodb": "3.5.2",
|
||||
"morgan": "^1.9.1",
|
||||
"mousetrap": "^1.6.1",
|
||||
"mubsub-nbb": "^1.5.1",
|
||||
"nconf": "^0.10.0",
|
||||
"nodebb-plugin-composer-default": "6.3.20",
|
||||
"nodebb-plugin-composer-default": "6.3.21",
|
||||
"nodebb-plugin-dbsearch": "4.0.7",
|
||||
"nodebb-plugin-emoji": "^3.0.0",
|
||||
"nodebb-plugin-emoji-android": "2.0.0",
|
||||
"nodebb-plugin-markdown": "8.11.0",
|
||||
"nodebb-plugin-mentions": "2.7.3",
|
||||
"nodebb-plugin-mentions": "2.7.4",
|
||||
"nodebb-plugin-soundpack-default": "1.0.0",
|
||||
"nodebb-plugin-spam-be-gone": "0.6.7",
|
||||
"nodebb-rewards-essentials": "0.1.2",
|
||||
"nodebb-theme-lavender": "5.0.11",
|
||||
"nodebb-theme-persona": "10.1.30",
|
||||
"nodebb-theme-persona": "10.1.34",
|
||||
"nodebb-theme-slick": "1.2.28",
|
||||
"nodebb-theme-vanilla": "11.1.12",
|
||||
"nodebb-widget-essentials": "4.0.17",
|
||||
"nodebb-theme-vanilla": "11.1.15",
|
||||
"nodebb-widget-essentials": "4.0.18",
|
||||
"nodemailer": "^6.0.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-local": "1.0.0",
|
||||
"pg": "^7.4.0",
|
||||
"pg-cursor": "^2.0.0",
|
||||
"postcss": "7.0.21",
|
||||
"postcss": "7.0.26",
|
||||
"postcss-clean": "1.1.0",
|
||||
"promise-polyfill": "^8.0.0",
|
||||
"prompt": "^1.0.0",
|
||||
"redis": "2.8.0",
|
||||
"request": "2.88.0",
|
||||
"rimraf": "3.0.0",
|
||||
"rimraf": "3.0.1",
|
||||
"rss": "^1.2.2",
|
||||
"sanitize-html": "^1.16.3",
|
||||
"semver": "^7.0.0",
|
||||
"serve-favicon": "^2.4.5",
|
||||
"sharp": "0.23.4",
|
||||
"sharp": "0.24.0",
|
||||
"sitemap": "^5.0.0",
|
||||
"socket.io": "2.3.0",
|
||||
"socket.io-adapter-cluster": "^1.0.1",
|
||||
@@ -124,27 +124,27 @@
|
||||
"textcomplete.contenteditable": "^0.1.1",
|
||||
"toobusy-js": "^0.5.1",
|
||||
"uglify-es": "^3.3.9",
|
||||
"validator": "12.1.0",
|
||||
"validator": "12.2.0",
|
||||
"winston": "3.2.1",
|
||||
"xml": "^1.0.1",
|
||||
"xregexp": "^4.1.1",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "8.2.0",
|
||||
"@commitlint/config-angular": "8.2.0",
|
||||
"@commitlint/cli": "8.3.5",
|
||||
"@commitlint/config-angular": "8.3.4",
|
||||
"coveralls": "3.0.9",
|
||||
"eslint": "6.7.0",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-config-airbnb-base": "14.0.0",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"grunt": "1.0.4",
|
||||
"grunt-contrib-watch": "1.1.0",
|
||||
"husky": "3.1.0",
|
||||
"husky": "4.2.1",
|
||||
"jsdom": "15.2.1",
|
||||
"lint-staged": "9.4.2",
|
||||
"mocha": "6.2.2",
|
||||
"lint-staged": "10.0.7",
|
||||
"mocha": "7.0.1",
|
||||
"mocha-lcov-reporter": "1.3.0",
|
||||
"nyc": "14.1.1",
|
||||
"nyc": "15.0.0",
|
||||
"smtp-server": "3.5.0"
|
||||
},
|
||||
"bugs": {
|
||||
@@ -170,4 +170,4 @@
|
||||
"url": "https://github.com/barisusakli"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"manage/groups": "Gruppen",
|
||||
"manage/ip-blacklist": "IP Blacklist",
|
||||
"manage/uploads": "Uploads",
|
||||
"manage/digest": "Digests",
|
||||
"manage/digest": "Zusammenfassungen",
|
||||
|
||||
"section-settings": "Einstellungen",
|
||||
"settings/general": "Allgemein",
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"testing.select": "Wählen Sie die E-Mail Vorlage",
|
||||
"testing.send": "Test-E-Mail versenden",
|
||||
"testing.send-help": "Die Test-E-Mail wird an die E-Mail Adresse des momentan eingeloggten Nutzers geschickt.",
|
||||
"subscriptions": "Email Digests",
|
||||
"subscriptions.disable": "Disable email digests",
|
||||
"subscriptions": "Email Zusammenfassungen",
|
||||
"subscriptions.disable": "Deaktivierung der Email Zusammenfassungen",
|
||||
"subscriptions.hour": "Sende Zeit",
|
||||
"subscriptions.hour-help": "Bitte geben Sie eine Nummer ein, welche die Stunde repräsentiert zu welcher geplante Emails versandt werden sollen (z.B. <code>0</code> für Mitternacht, <code>17</code> für 5 Uhr Nachmittags). Beachten Sie, dass die Zeit auf der Serverzeit basiert und daher nicht umbedingt mit ihrer Systemzeit übereinstimmen muss.<br>Die ungefähre Serverzeit ist: <span id=\"serverTime\"></span><br>Die nächste tägliche Sendung ist um <span id=\"nextDigestTime\"></span> geplant"
|
||||
}
|
||||
@@ -27,9 +27,9 @@
|
||||
"digest.week": "der letzten Woche",
|
||||
"digest.month": "des letzen Monats",
|
||||
"digest.subject": "Zusammenfassung für %1",
|
||||
"digest.title.day": "Your Daily Digest",
|
||||
"digest.title.week": "Your Weekly Digest",
|
||||
"digest.title.month": "Your Monthly Digest",
|
||||
"digest.title.day": "Deine tägliche Zusammenfassung",
|
||||
"digest.title.week": "Deine wöchentliche Zusammenfassung",
|
||||
"digest.title.month": "Deine monatliche Zusammenfassung",
|
||||
"notif.chat.subject": "Neue Chatnachricht von %1 erhalten",
|
||||
"notif.chat.cta": "Klicke hier, um die Unterhaltung fortzusetzen",
|
||||
"notif.chat.unsub.info": "Diese Chat-Benachrichtigung wurde dir aufgrund deiner Abonnement-Einstellungen gesendet.",
|
||||
|
||||
@@ -134,6 +134,6 @@
|
||||
"diffs.no-revisions-description": "Dieser Beitrag ha <strong>%1</strong> Revisionen.",
|
||||
"diffs.current-revision": "Aktuelle Revision",
|
||||
"diffs.original-revision": "Ursprüngliche Revision",
|
||||
"timeago_later": "%1 later",
|
||||
"timeago_later": "%1 später",
|
||||
"timeago_earlier": "%1 earlier"
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"events": "ארועים",
|
||||
"no-events": "אין ארועים",
|
||||
"control-panel": "בקרת ארועים",
|
||||
"filters": "Filters",
|
||||
"filters-apply": "Apply Filters",
|
||||
"filter-type": "Event Type",
|
||||
"filter-start": "Start Date",
|
||||
"filter-end": "End Date",
|
||||
"filter-perPage": "Per Page"
|
||||
"control-panel": "בקרת ארועים\n ",
|
||||
"filters": "מסננים",
|
||||
"filters-apply": "החל מסננים",
|
||||
"filter-type": "סוג אירוע",
|
||||
"filter-start": "מתאריך",
|
||||
"filter-end": "עד תאריך",
|
||||
"filter-perPage": "פריטים בכל דף"
|
||||
}
|
||||
@@ -18,13 +18,13 @@
|
||||
"last_reply_time": "תגובה אחרונה",
|
||||
"reply-as-topic": "הגב כנושא",
|
||||
"guest-login-reply": "התחבר כדי לפרסם תגובה",
|
||||
"login-to-view": "🔒 Log in to view",
|
||||
"login-to-view": "🔒 התחבר כדי לצפות",
|
||||
"edit": "עריכה",
|
||||
"delete": "מחק",
|
||||
"purge": "מחק לצמיתות",
|
||||
"restore": "שחזר",
|
||||
"move": "הזז",
|
||||
"change-owner": "Change Owner",
|
||||
"change-owner": "שנה מחבר הודעה",
|
||||
"fork": "פורק",
|
||||
"link": "לינק",
|
||||
"share": "שתף",
|
||||
@@ -56,7 +56,7 @@
|
||||
"ignoring": "מתעלם",
|
||||
"watching.description": "הודע לי על תגובות חדשות. <br/>הצג נושא חדש ברשימת הלא נקראו.",
|
||||
"not-watching.description": "אל תיידע אותי על תגובות חדשות. <br/>הצג נושא חדש ברשימת הלא נקראו במידה ובחרתי לא להתעלם מקבוצת הדיון",
|
||||
"ignoring.description": "Do not notify me of new replies.<br/>Do not show topic in unread.",
|
||||
"ignoring.description": "אל תתריע לי על תגובות חדשות. <br/>אל תראה את הנושא בנושאים שלא נקראו ",
|
||||
"thread_tools.title": "כלי נושא",
|
||||
"thread_tools.markAsUnreadForAll": "סמן לא נקרא לכולם",
|
||||
"thread_tools.pin": "נעץ נושא",
|
||||
@@ -66,7 +66,7 @@
|
||||
"thread_tools.move": "הזז נושא",
|
||||
"thread_tools.move-posts": "הזז פוסטים",
|
||||
"thread_tools.move_all": "הזז הכל",
|
||||
"thread_tools.change_owner": "Change Owner",
|
||||
"thread_tools.change_owner": "שנה את כותב ההודעה",
|
||||
"thread_tools.select_category": "בחר קטגוריה",
|
||||
"thread_tools.fork": "שכפל נושא",
|
||||
"thread_tools.delete": "מחק נושא",
|
||||
@@ -101,7 +101,7 @@
|
||||
"delete_posts_instruction": "לחץ על הפוסטים שברצונך למחוק",
|
||||
"merge_topics_instruction": "לחץ על הנושאים שתרצה למזג",
|
||||
"move_posts_instruction": "לחץ על הפוסטים שאתה רוצה להזיז",
|
||||
"change_owner_instruction": "Click the posts you want to assign to another user",
|
||||
"change_owner_instruction": "לחץ על ההודעה שהנך רוצה לשנות את בעל ההודעה",
|
||||
"composer.title_placeholder": "הכנס את כותרת הנושא כאן...",
|
||||
"composer.handle_placeholder": "שם",
|
||||
"composer.discard": "ביטול",
|
||||
@@ -130,10 +130,10 @@
|
||||
"stale.reply_anyway": "הגב לנושא זה בכל מקרה",
|
||||
"link_back": "תגובה: [%1](%2)",
|
||||
"diffs.title": "היסטוריית עריכת הפוסט",
|
||||
"diffs.description": "This post has <strong>%1</strong> revisions. Click one of the revisions below to see the post content at that point in time.",
|
||||
"diffs.description": "להודעה זו יש <strong>%1</strong> עריכות. לחץ על אחת מהעריכות להלן כדי לראות את תוכן ההודעה בנקודת זמן זו.",
|
||||
"diffs.no-revisions-description": "לפוסט זה יש <strong>%1</strong>גרסאות",
|
||||
"diffs.current-revision": "current revision",
|
||||
"diffs.original-revision": "original revision",
|
||||
"timeago_later": "%1 later",
|
||||
"timeago_earlier": "%1 earlier"
|
||||
"diffs.current-revision": "גירסה נוכחית",
|
||||
"diffs.original-revision": "גירסה מקורית",
|
||||
"timeago_later": "אחרי %1:",
|
||||
"timeago_earlier": "לפני %1 "
|
||||
}
|
||||
@@ -30,16 +30,16 @@
|
||||
"select-category": "Seleziona Categoria",
|
||||
"set-parent-category": "Imposta la Categoria Padre",
|
||||
|
||||
"privileges.description": "You can configure the access control privileges for portions of the site in this section. Privileges can be granted on a per-user or a per-group basis. Select the domain of effect from the dropdown below.",
|
||||
"privileges.description": "In questa sezione è possibile configurare i privilegi di controllo dell'accesso per parti del sito. I privilegi possono essere concessi per utente o per gruppo. Seleziona il dominio dell'effetto dal menu a discesa in basso.",
|
||||
"privileges.category-selector": "Configura privilegi per",
|
||||
"privileges.warning": "<strong>Note</strong>: Privilege settings take effect immediately. It is not necessary to save the category after adjusting these settings.",
|
||||
"privileges.warning": "<strong>Nota</strong>: Le impostazioni dei privilegi hanno effetto immediato. Non è necessario salvare la categoria dopo aver regolato queste impostazioni.",
|
||||
"privileges.section-viewing": "Visualizzazione dei Privilegi",
|
||||
"privileges.section-posting": "Posting Privileges",
|
||||
"privileges.section-moderation": "Moderation Privileges",
|
||||
"privileges.section-posting": "Privilegi di pubblicazione",
|
||||
"privileges.section-moderation": "Privilegi di Moderazione",
|
||||
"privileges.section-other": "Altro",
|
||||
"privileges.section-user": "Utente",
|
||||
"privileges.search-user": "Aggiungi Utente",
|
||||
"privileges.no-users": "No user-specific privileges in this category.",
|
||||
"privileges.no-users": "Nessun privilegio specifico dell'utente in questa categoria.",
|
||||
"privileges.section-group": "Gruppo",
|
||||
"privileges.group-private": "Questo gruppo è privato",
|
||||
"privileges.search-group": "Aggiungi gruppo",
|
||||
@@ -49,27 +49,27 @@
|
||||
"privileges.copy-group-privileges-to-children": "Copia i privilegi di questo gruppo dai figli di questa categoria.",
|
||||
"privileges.copy-group-privileges-to-all-categories": "Copia i privilegi di questo gruppo in tutte le categorie.",
|
||||
"privileges.copy-group-privileges-from": "Copia questo gruppo di privilegi da un altra categoria.",
|
||||
"privileges.inherit": "If the <code>registered-users</code> group is granted a specific privilege, all other groups receive an <strong>implicit privilege</strong>, even if they are not explicitly defined/checked. This implicit privilege is shown to you because all users are part of the <code>registered-users</code> user group, and so, privileges for additional groups need not be explicitly granted.",
|
||||
"privileges.inherit": "Se l' <code>utente registrato</code> al gruppo viene concesso un privilegio specifico, tutti gli altri gruppi ricevono un<strong>privilegio implicito</strong>,anche se non sono esplicitamente definiti / controllati. Questo privilegio implicito ti viene mostrato perché tutti gli utenti fanno parte di<code>gruppo di utenti</code> registrati e quindi i privilegi per gruppi aggiuntivi non devono essere esplicitamente concessi.",
|
||||
"privileges.copy-success": "Privilegi copiati!",
|
||||
|
||||
"analytics.back": "Back to Categories List",
|
||||
"analytics.back": "Torna all'Elenco delle Categorie",
|
||||
"analytics.title": "Statistiche per la categoria \"%1\"",
|
||||
"analytics.pageviews-hourly": "<strong>Figura 1</strong> – Vista delle visualizzazioni orarie per questa categoria</small>",
|
||||
"analytics.pageviews-daily": "<strong>Figura 2</strong> – Vista delle visualizzazioni giornaliere per questa categoria</small>",
|
||||
"analytics.topics-daily": "<strong>Figura 3</strong> – Argomenti giornalieri creati in questa categoria</small>",
|
||||
"analytics.posts-daily": "<strong>Figure 4</strong> – Daily posts made in this category</small>",
|
||||
"analytics.posts-daily": "<strong>Figura 4</strong>dash; Post giornalieri pubblicati in questa categoria</small>",
|
||||
|
||||
"alert.created": "Creato",
|
||||
"alert.create-success": "Categoria creata con successo!",
|
||||
"alert.none-active": "Hai una categoria non attiva.",
|
||||
"alert.create": "Crea una Categoria",
|
||||
"alert.confirm-moderate": "<strong>Are you sure you wish to grant the moderation privilege to this user group?</strong> This group is public, and any users can join at will.",
|
||||
"alert.confirm-moderate": "<strong>Sei sicuro di voler concedere il privilegio di moderazione a questo gruppo di utenti?</strong> Questo gruppo è pubblico e tutti gli utenti possono aderire a piacimento.",
|
||||
"alert.confirm-purge": "<p class=\"lead\">Vuoi davvero eliminare definitivamente questa categoria \"%1\"?</p><h5><strong class=\"text-danger\">Attenzione!</strong>Tutte le discussioni e i posti in questa categoria saranno eliminati definitivamente!</h5> <p class=\"help-block\">Eliminare definitivamente una categoria rimuoverà tutte le discussioni e i post ed eliminerà la categoria dal database. Se vuoi rimuovere una categoria <em>temporaneamente</em>, puoi invece \"disabilitare\" la categoria.",
|
||||
"alert.purge-success": "Categoria eliminata definitivamente!",
|
||||
"alert.copy-success": "Settings Copied!",
|
||||
"alert.set-parent-category": "Set Parent Category",
|
||||
"alert.updated": "Updated Categories",
|
||||
"alert.updated-success": "Category IDs %1 successfully updated.",
|
||||
"alert.copy-success": "Impostazioni copiate!",
|
||||
"alert.set-parent-category": "Imposta la Categoria Genitore",
|
||||
"alert.updated": "Categorie aggiornate",
|
||||
"alert.updated-success": "ID categoria %1 aggiornati correttamente.",
|
||||
"alert.upload-image": "Carica immagine categoria",
|
||||
"alert.find-user": "Trova un Utente",
|
||||
"alert.user-search": "Cerca un utente qui...",
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"lead": "A listing of digest delivery stats and times is displayed below.",
|
||||
"disclaimer": "Please be advised that email delivery is not guaranteed, due to the nature of email technology. Many variables factor into whether an email sent to the recipient server is ultimately delivered into the user's inbox, including server reputation, blacklisted IP addresses, and whether DKIM/SPF/DMARC is configured.",
|
||||
"disclaimer-continued": "A successful delivery means the message was sent successfully by NodeBB and acknowledged by the recipient server. It does not mean the email landed in the inbox. For best results, we recommend using a third-party email delivery service such as <a href=\"https://sendgrid.com/why-sendgrid/\">SendGrid</a>.",
|
||||
"lead": "Di seguito viene visualizzato un elenco di statistiche e tempi di consegna del digest.",
|
||||
"disclaimer": "Si informa che la consegna della posta non è garantita a causa della natura della tecnologia di posta elettronica. Molte variabili determinano se una e-mail inviata al server del destinatario viene infine recapitata nella posta in arrivo dell'utente, inclusa la reputazione del server, gli indirizzi IP nella lista nera e se DKIM/SPF/DMARC è configurato.",
|
||||
"disclaimer-continued": "Una consegna corretta indica che il messaggio è stato inviato correttamente da NodeBB e riconosciuto dal server destinatario. Non significa che l'email è arrivata nella posta in arrivo. Per risultati ottimali, si consiglia di utilizzare un servizio di consegna e-mail di terze parti come <a href=\"https://sendgrid.com/why-sendgrid/\">SendGrid</a>.",
|
||||
|
||||
"user": "User",
|
||||
"subscription": "Subscription Type",
|
||||
"last-delivery": "Last successful delivery",
|
||||
"default": "System default",
|
||||
"default-help": "<em>System default</em> means the user has not explicitly overridden the global forum setting for digests, which is currently: "<strong>%1</strong>"",
|
||||
"resend": "Resend Digest",
|
||||
"resend-all-confirm": "Are you sure you wish to mnually execute this digest run?",
|
||||
"resent-single": "Manual digest resend completed",
|
||||
"resent-day": "Daily digest resent",
|
||||
"resent-week": "Weekly digest resent",
|
||||
"resent-month": "Monthly digest resent",
|
||||
"null": "<em>Never</em>",
|
||||
"manual-run": "Manual digest run:",
|
||||
"user": "Utente",
|
||||
"subscription": "Tipo di Abbonamento",
|
||||
"last-delivery": "Ultima consegna riuscita",
|
||||
"default": "Sistema predefinito",
|
||||
"default-help": "<em>Sistema predefinito</em> significa che l'utente non ha esplicitamente sovrascritto l'impostazione del forum globale per i digest, che attualmente è: & quot;<strong>%1</strong>"",
|
||||
"resend": "Rinvia Digest",
|
||||
"resend-all-confirm": "Sei sicuro di voler eseguire manualmente questa corsa digest?",
|
||||
"resent-single": "Invio del digest manuale completato",
|
||||
"resent-day": "Rinvio digest giornaliero",
|
||||
"resent-week": "Rinvio del digest settimanale",
|
||||
"resent-month": "Rinvio del digest mensile",
|
||||
"null": "<em>Mai</em>",
|
||||
"manual-run": "Esecuzione digest manuale:",
|
||||
|
||||
"no-delivery-data": "No delivery data found"
|
||||
"no-delivery-data": "Nessun dato di consegna trovato"
|
||||
}
|
||||
@@ -15,8 +15,8 @@
|
||||
"delete": "Rimuovi Utente(i)",
|
||||
"purge": "Rimuovi Utente(i) e Contenuto",
|
||||
"download-csv": "Scarica CSV",
|
||||
"manage-groups": "Manage Groups",
|
||||
"add-group": "Add Group",
|
||||
"manage-groups": "Gestisci Gruppi",
|
||||
"add-group": "Aggiungi Gruppo",
|
||||
"invite": "Invito",
|
||||
"new": "Nuovo utente",
|
||||
|
||||
|
||||
@@ -27,33 +27,33 @@
|
||||
"restrictions.max-title-length": "Lunghezza Massima Titolo",
|
||||
"restrictions.min-post-length": "Lunghezza Minima Post",
|
||||
"restrictions.max-post-length": "Lunghezza Massima Post",
|
||||
"restrictions.days-until-stale": "Days until topic is considered stale",
|
||||
"restrictions.stale-help": "If a topic is considered \"stale\", then a warning will be shown to users who attempt to reply to that topic.",
|
||||
"timestamp": "Timestamp",
|
||||
"timestamp.cut-off": "Date cut-off (in days)",
|
||||
"timestamp.cut-off-help": "Dates & times will be shown in a relative manner (e.g. \"3 hours ago\" / \"5 days ago\"), and localised into various\n\t\t\t\t\tlanguages. After a certain point, this text can be switched to display the localised date itself\n\t\t\t\t\t(e.g. 5 Nov 2016 15:30).<br /><em>(Default: <code>30</code>, or one month). Set to 0 to always display dates, leave blank to always display relative times.</em>",
|
||||
"timestamp.necro-threshold": "Necro Threshold (in days)",
|
||||
"timestamp.necro-threshold-help": "A message will be shown between posts if the time between them is longer than the necro threshold. (Default: <code>7</code>, or one week). Set to 0 to disable.</em>",
|
||||
"teaser": "Teaser Post",
|
||||
"teaser.last-post": "Last – Show the latest post, including the original post, if no replies",
|
||||
"teaser.last-reply": "Last – Show the latest reply, or a \"No replies\" placeholder if no replies",
|
||||
"restrictions.days-until-stale": "Giorni prima che l'argomento sia considerato vecchio",
|
||||
"restrictions.stale-help": "Se un argomento è considerato \"non aggiornato\", verrà mostrato un avviso agli utenti che tentano di rispondere a tale argomento.",
|
||||
"timestamp": "Data e Ora",
|
||||
"timestamp.cut-off": "Data di interruzione (in giorni)",
|
||||
"timestamp.cut-off-help": "I tempi delle date verranno visualizzati in modo relativo (ad es. \"3 ore fa\" / \"5 giorni fa\") e localizzati in varie\n\t\t\t\t\tlingue. Dopo un certo punto, questo testo può essere cambiato per visualizzare la data localizzata\n\t\t\t\t\t(es. 5 Nov 2016 15:30).<br /><em>(Predefinito: <code>30</code>, o un mese).Impostare su 0 per visualizzare sempre le date, lasciare vuoto per visualizzare sempre i tempi relativi.</em>",
|
||||
"timestamp.necro-threshold": "Necro Threshold (in giorni)",
|
||||
"timestamp.necro-threshold-help": "Un messaggio verrà mostrato tra i post se il tempo tra loro è più lungo della soglia necro. (Predefinito: <code>7</code>, o una settimana). Impostare su 0 per disabilitare.</em>",
|
||||
"teaser": "Post Inopportuno",
|
||||
"teaser.last-post": "Ultimo – Mostra l'ultimo post, incluso il post originale, se non ci sono risposte",
|
||||
"teaser.last-reply": "Ultimo – Mostra l'ultima risposta o un segnaposto \"Nessuna risposta\" se non risposto",
|
||||
"teaser.first": "Primo",
|
||||
"unread": "Unread Settings",
|
||||
"unread.cutoff": "Unread cutoff days",
|
||||
"unread.min-track-last": "Minimum posts in topic before tracking last read",
|
||||
"unread": "Impostazioni non Lette",
|
||||
"unread.cutoff": "Giorni di interruzione non letti",
|
||||
"unread.min-track-last": "Post minimi nell'argomento prima del monitoraggio dell'ultima lettura",
|
||||
"recent": "Impostazioni Recenti",
|
||||
"recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page",
|
||||
"signature": "Signature Settings",
|
||||
"signature.disable": "Disable signatures",
|
||||
"signature.no-links": "Disable links in signatures",
|
||||
"signature.no-images": "Disable images in signatures",
|
||||
"signature.max-length": "Maximum Signature Length",
|
||||
"composer": "Composer Settings",
|
||||
"composer-help": "The following settings govern the functionality and/or appearance of the post composer shown\n\t\t\t\tto users when they create new topics, or reply to existing topics.",
|
||||
"composer.show-help": "Show \"Help\" tab",
|
||||
"composer.enable-plugin-help": "Allow plugins to add content to the help tab",
|
||||
"composer.custom-help": "Custom Help Text",
|
||||
"ip-tracking": "IP Tracking",
|
||||
"ip-tracking.each-post": "Track IP Address for each post",
|
||||
"enable-post-history": "Enable Post History"
|
||||
"recent.categoryFilter.disable": "Disabilita il filtro degli argomenti nelle categorie ignorate nella pagina/recente",
|
||||
"signature": "Impostazioni della Firma",
|
||||
"signature.disable": "Disabilita le firme",
|
||||
"signature.no-links": "Disabilita i collegamenti nelle firme",
|
||||
"signature.no-images": "Disabilita le immagini nelle firme",
|
||||
"signature.max-length": "Lunghezza massima della firma",
|
||||
"composer": "Impostazioni del compositore",
|
||||
"composer-help": "Le seguenti impostazioni regolano la funzionalità e/o l'aspetto del post compositore mostrato\n\t\t\t\tagli utenti quando creano nuovi argomenti o rispondono ad argomenti esistenti.",
|
||||
"composer.show-help": "Mostra la scheda \"Aiuto\"",
|
||||
"composer.enable-plugin-help": "Consenti ai plug-in di aggiungere contenuti alla scheda Guida",
|
||||
"composer.custom-help": "Testo di aiuto personalizzato",
|
||||
"ip-tracking": "Monitoraggio IP",
|
||||
"ip-tracking.each-post": "Traccia l'indirizzo IP per ogni post",
|
||||
"enable-post-history": "Abilita Cronologia post"
|
||||
}
|
||||
@@ -41,11 +41,11 @@
|
||||
"registration-type.invite-only": "Solo Invito",
|
||||
"registration-type.admin-invite-only": "Solo invito per Amministratori",
|
||||
"registration-type.disabled": "Niente registrazione",
|
||||
"registration-type.help": "Normal - Users can register from the /register page.<br/>\nInvite Only - Users can invite others from the <a href=\"%1/users\" target=\"_blank\">users</a> page.<br/>\nAdmin Invite Only - Only administrators can invite others from <a href=\"%1/users\" target=\"_blank\">users</a> and <a href=\"%1/admin/manage/users\">admin/manage/users</a> pages.<br/>\nNo registration - No user registration.<br/>",
|
||||
"registration-approval-type.help": "Normal - Users are registered immediately.<br/>\nAdmin Approval - User registrations are placed in an <a href=\"%1/admin/manage/registration\">approval queue</a> for administrators.<br/>\nAdmin Approval for IPs - Normal for new users, Admin Approval for IP addresses that already have an account.<br/>",
|
||||
"registration-type.help": "Normale: gli utenti possono registrarsi dalla pagina/di registrazione.<br/>\nSolo invito: gli utenti possono invitare altri dalla <a href=\"%1/users\" target=\"_blank\">pagina</a> utenti.<br/>\nSolo su invito amministratore: solo gli amministratori possono invitare altri <a href=\"%1/users\" target=\"_blank\">utenti</a> e<a href=\"%1/admin/manage/users\">dalle pagine</a> amministratore/gestione/utenti.<br/>\nNessuna registrazione - Nessuna registrazione dell'utente.<br/>",
|
||||
"registration-approval-type.help": "Normale: gli utenti vengono registrati immediatamente.<br/>\nApprovazione amministratore - Le registrazioni degli utenti vengono inserite in una <a href=\"%1/admin/manage/registration\">coda di approvazione</a> per amministratori.<br/>\nApprovazione amministratore per IP - Normale per i nuovi utenti, Approvazione amministratore per indirizzi IP che dispongono già di un account.<br/>",
|
||||
"registration.max-invites": "Numero massimo di inviti per Utente",
|
||||
"max-invites": "Numero massimo di inviti per Utente",
|
||||
"max-invites-help": "0 for no restriction. Admins get infinite invitations<br>Only applicable for \"Invite Only\"",
|
||||
"max-invites-help": "0 per nessuna restrizione. Gli amministratori ricevono infiniti inviti<br>Applicabile solo per \"Solo invito\"",
|
||||
"invite-expiration": "Invito scaduto",
|
||||
"invite-expiration-help": "Il tuo invito scadrà tra %1 giorni.",
|
||||
"min-username-length": "Lunghezza Minima Username",
|
||||
@@ -63,7 +63,7 @@
|
||||
"outgoing-new-tab": "Apri link esterni in una nuova scheda",
|
||||
"topic-search": "Abilita ricerca nella Discussione",
|
||||
"digest-freq": "Iscriviti al Riepilogo",
|
||||
"digest-freq.off": "Off",
|
||||
"digest-freq.off": "Spento",
|
||||
"digest-freq.daily": "Quotidiano",
|
||||
"digest-freq.weekly": "Settimanale",
|
||||
"digest-freq.monthly": "Mensile",
|
||||
@@ -72,8 +72,8 @@
|
||||
"follow-created-topics": "Segui le discussioni che tu crei",
|
||||
"follow-replied-topics": "Segui discussioni a cui rispondi tu",
|
||||
"default-notification-settings": "Impostazioni di notifica predefinite",
|
||||
"categoryWatchState": "Default category watch state",
|
||||
"categoryWatchState.watching": "Watching",
|
||||
"categoryWatchState.notwatching": "Not Watching",
|
||||
"categoryWatchState": "Stato predefinito della categoria di controllo",
|
||||
"categoryWatchState.watching": "Guardare",
|
||||
"categoryWatchState.notwatching": "Non Guardare",
|
||||
"categoryWatchState.ignoring": "Ignorato"
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
"notif.chat.cta": "Clicca qui per continuare la conversazione",
|
||||
"notif.chat.unsub.info": "Questa notifica di chat ti è stata inviata perché l'hai sottoscritta nelle impostazioni.",
|
||||
"notif.post.unsub.info": "Questa notifica di discussione ti è stata inviata perché l'hai sottoscritta nelle impostazioni.",
|
||||
"notif.post.unsub.one-click": "Alternatively, unsubscribe from future emails like this, by clicking",
|
||||
"notif.post.unsub.one-click": "In alternativa, annulla l'iscrizione a future email come questa, facendo clic",
|
||||
"notif.cta": "Al forum",
|
||||
"notif.cta-new-reply": "Visualizza Post",
|
||||
"notif.cta-new-chat": "Visualizza Chat",
|
||||
@@ -42,8 +42,8 @@
|
||||
"notif.test.long": "Questo è un test delle notifiche email. Invia aiuto!",
|
||||
"test.text1": "Questa è una email di prova per verificare che il servizio di invio email è configurato correttamente sul tuo NodeBB.",
|
||||
"unsub.cta": "Clicca qui per modificare queste impostazioni",
|
||||
"unsubscribe": "unsubscribe",
|
||||
"unsub.success": "You will no longer receive emails from the <strong>%1</strong> mailing list",
|
||||
"unsubscribe": "Annulla l'iscrizione",
|
||||
"unsub.success": "Non riceverai più email dalla <strong>%1</strong> mailing list",
|
||||
"banned.subject": "Sei stato bannato da %1",
|
||||
"banned.text1": "L'utente %1 è stato bannato da %2",
|
||||
"banned.text2": "Questo ban durerà fino a %1.",
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
"invalid-pagination-value": "Valore di impaginazione non valido, deve essere almeno %1 ed al massimo %2",
|
||||
"username-taken": "Nome utente già esistente",
|
||||
"email-taken": "Email già esistente",
|
||||
"email-not-confirmed": "You are unable to post until your email is confirmed, please click here to confirm your email.",
|
||||
"email-not-confirmed": "Non puoi pubblicare post finché la tua email non è confermata, fai clic qui per confermare la tua email",
|
||||
"email-not-confirmed-chat": "Non puoi chattare finché non confermi la tua email, per favore clicca qui per confermare la tua email.",
|
||||
"email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email. You won't be able to post or chat until your email is confirmed.",
|
||||
"email-not-confirmed-email-sent": "La tua email non è stata ancora confermata, controlla la tua casella di posta per l'email di conferma. Non potrai pubblicare post o chattare fino a quando la tua email non sarà confermata.",
|
||||
"no-email-to-confirm": "Questo forum richiede una conferma via email, clicca qui per inserire un'email",
|
||||
"email-confirm-failed": "Non abbiamo potuto confermare la tua email, per favore riprovaci più tardi.",
|
||||
"confirm-email-already-sent": "Email di conferma già inviata, per favore attendere %1 minuto(i) per inviarne un'altra.",
|
||||
"sendmail-not-found": "Impossibile trovare l'eseguibile di sendmail, per favore assicurati che sia installato ed eseguibile dall'utente che esegue NodeBB.",
|
||||
"digest-not-enabled": "This user does not have digests enabled, or the system default is not configured to send digests",
|
||||
"digest-not-enabled": "Questo utente non ha digest attivi o l'impostazione predefinita del sistema non è configurata per l'invio di digest",
|
||||
"username-too-short": "Nome utente troppo corto",
|
||||
"username-too-long": "Nome utente troppo lungo",
|
||||
"password-too-long": "Password troppo lunga",
|
||||
|
||||
@@ -159,17 +159,17 @@
|
||||
"consent.email_intro": "Occasionalmente, potremmo inviare email al tuo indirizzo email registrato per fornirti aggiornamenti e/o per informarti di nuove attività che ti riguardano. Puoi personalizzare la frequenza del riepilogo della comunità (compresa la disabilitazione definitiva), così come selezionare quali tipi di notifiche ricevere via email, tramite la pagina delle impostazioni utente.",
|
||||
"consent.digest_frequency": "A meno che non sia stato modificato esplicitamente nelle impostazioni utente, questa comunità fornisce email riepilogative ogni %1.",
|
||||
"consent.digest_off": "A meno che non sia stato modificato esplicitamente nelle impostazioni utente, questa comunità non invia email riepilogative",
|
||||
"consent.received": "You have provided consent for this website to collect and process your information. No additional action is required.",
|
||||
"consent.not_received": "You have not provided consent for data collection and processing. At any time this website's administration may elect to delete your account in order to become compliant with the General Data Protection Regulation.",
|
||||
"consent.received": "Hai fornito il consenso a questo sito Web per raccogliere ed elaborare le tue informazioni. Non è richiesta alcuna azione aggiuntiva.",
|
||||
"consent.not_received": "Non hai fornito il consenso per la raccolta e l'elaborazione dei dati. In qualsiasi momento l'amministrazione di questo sito Web può decidere di eliminare il tuo account per renderlo conforme al regolamento generale sulla protezione dei dati.",
|
||||
"consent.give": "Consenti",
|
||||
"consent.right_of_access": "Hai i privilegi di accesso",
|
||||
"consent.right_of_access_description": "You have the right to access any data collected by this website upon request. You can retrieve a copy of this data by clicking the appropriate button below.",
|
||||
"consent.right_of_access_description": "Hai il diritto di accedere a tutti i dati raccolti da questo sito Web su richiesta. È possibile recuperare una copia di questi dati facendo clic sul pulsante appropriato di seguito.",
|
||||
"consent.right_to_rectification": "Hai i privilegi alla rettifica",
|
||||
"consent.right_to_rectification_description": "You have the right to change or update any inaccurate data provided to us. Your profile can be updated by editing your profile, and post content can always be edited. If this is not the case, please contact this site's administrative team.",
|
||||
"consent.right_to_rectification_description": "Hai il diritto di modificare o aggiornare i dati inesatti forniti a noi. Il tuo profilo può essere aggiornato modificando il tuo profilo e il contenuto dei post può sempre essere modificato. In caso contrario, contattare questo team amministrativo del sito.",
|
||||
"consent.right_to_erasure": "Hai i privilegi per cancellare",
|
||||
"consent.right_to_erasure_description": "At any time, you are able to revoke your consent to data collection and/or processing by deleting your account. Your individual profile can be deleted, although your posted content will remain. If you wish to delete both your account <strong>and</strong> your content, please contact the administrative team for this website.",
|
||||
"consent.right_to_erasure_description": "In qualsiasi momento, puoi revocare il tuo consenso alla raccolta e / o al trattamento dei dati eliminando il tuo account. Il tuo profilo individuale può essere eliminato, anche se i contenuti pubblicati rimarranno. Se desideri eliminare entrambi i tuoi account <strong>e</strong> i tuoi contenuti, contatta il team amministrativo per questo sito Web.",
|
||||
"consent.right_to_data_portability": "Hai i privilegi alla portabilità dei dati",
|
||||
"consent.right_to_data_portability_description": "You may request from us a machine-readable export of any collected data about you and your account. You can do so by clicking the appropriate button below.",
|
||||
"consent.right_to_data_portability_description": "Puoi richiedere da noi un'esportazione leggibile meccanicamente di tutti i dati raccolti su di te e sul tuo account. Puoi farlo facendo clic sul pulsante appropriato in basso.",
|
||||
"consent.export_profile": "Esporta i profili (.csv)",
|
||||
"consent.export_uploads": "Esporta i contenuti caricati (.zip)",
|
||||
"consent.export_posts": "Esporta i post (.csv)"
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
|
||||
"error.select-clone": "Proszę wybrać stronę do sklonowania",
|
||||
|
||||
"title": "Title",
|
||||
"title.placeholder": "Title (only shown on some containers)",
|
||||
"container": "Container",
|
||||
"container.placeholder": "Drag and drop a container or enter HTML here.",
|
||||
"show-to-groups": "Show to groups",
|
||||
"hide-from-groups": "Hide from groups",
|
||||
"hide-on-mobile": "Hide on mobile"
|
||||
"title": "Tytuł",
|
||||
"title.placeholder": "Tytuł (wyświetlany tylko w niektórych kontenerach)",
|
||||
"container": "Kontener",
|
||||
"container.placeholder": "Przeciągnij i upuść kontener lub wpisz tutaj HTML.",
|
||||
"show-to-groups": "Pokaż dla grup",
|
||||
"hide-from-groups": "Ukryj dla grup",
|
||||
"hide-on-mobile": "Ukraj na urządzeniach mobilnych"
|
||||
}
|
||||
@@ -3,19 +3,19 @@
|
||||
"disclaimer": "Please be advised that email delivery is not guaranteed, due to the nature of email technology. Many variables factor into whether an email sent to the recipient server is ultimately delivered into the user's inbox, including server reputation, blacklisted IP addresses, and whether DKIM/SPF/DMARC is configured.",
|
||||
"disclaimer-continued": "A successful delivery means the message was sent successfully by NodeBB and acknowledged by the recipient server. It does not mean the email landed in the inbox. For best results, we recommend using a third-party email delivery service such as <a href=\"https://sendgrid.com/why-sendgrid/\">SendGrid</a>.",
|
||||
|
||||
"user": "User",
|
||||
"subscription": "Subscription Type",
|
||||
"user": "Użytkownik",
|
||||
"subscription": "Typ subskrypcji",
|
||||
"last-delivery": "Last successful delivery",
|
||||
"default": "System default",
|
||||
"default": "Domyślne ustawienie systemowe",
|
||||
"default-help": "<em>System default</em> means the user has not explicitly overridden the global forum setting for digests, which is currently: "<strong>%1</strong>"",
|
||||
"resend": "Resend Digest",
|
||||
"resend-all-confirm": "Are you sure you wish to mnually execute this digest run?",
|
||||
"resent-single": "Manual digest resend completed",
|
||||
"resent-day": "Daily digest resent",
|
||||
"resent-week": "Weekly digest resent",
|
||||
"resent-month": "Monthly digest resent",
|
||||
"null": "<em>Never</em>",
|
||||
"manual-run": "Manual digest run:",
|
||||
"resend": "Wyślij ponownie podsumowanie",
|
||||
"resend-all-confirm": "Czy na pewno chcesz ręcznie wykonać włącznie podsumowania?",
|
||||
"resent-single": "Ręczne wysyłanie podsumowania zakończone",
|
||||
"resent-day": "Codzienne podsumowanie",
|
||||
"resent-week": "Tygodniowe podsumowanie",
|
||||
"resent-month": "Miesięczne podsumowanie",
|
||||
"null": "<em>Nigdy</em>",
|
||||
"manual-run": "Włącz ręcznie podsumowania",
|
||||
|
||||
"no-delivery-data": "No delivery data found"
|
||||
"no-delivery-data": "Nie znaleziono danych"
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"global": "Globalny",
|
||||
"global.no-users": "Brak globalnych uprawnień zdefiniowanych dla użytkownika",
|
||||
"group-privileges": "Group Privileges",
|
||||
"user-privileges": "User Privileges",
|
||||
"group-privileges": "Uprawnienia grup",
|
||||
"user-privileges": "Uprawnienia użytkownika",
|
||||
"chat": "Dostęp do czatu",
|
||||
"upload-images": "Przesyłanie zdjęć",
|
||||
"upload-files": "Przesyłanie plików",
|
||||
@@ -16,7 +16,7 @@
|
||||
"view-groups": "Wyświetlanie grup",
|
||||
"allow-local-login": "Logowanie lokalne",
|
||||
"allow-group-creation": "Tworzenie grup",
|
||||
"view-users-info": "View Users Info",
|
||||
"view-users-info": "Pokaż dane użytkownika",
|
||||
"find-category": "Szukanie kategorii",
|
||||
"access-category": "Dostęp do kategorii",
|
||||
"access-topics": "Dostęp do tematów",
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"delete": "Usuń użytkownika(-ów)",
|
||||
"purge": "Usuń użytkownika(-ów) oraz zawartość",
|
||||
"download-csv": "Pobierz CSV",
|
||||
"manage-groups": "Manage Groups",
|
||||
"add-group": "Add Group",
|
||||
"manage-groups": "Zarządzaj grupami",
|
||||
"add-group": "Dodaj grupę",
|
||||
"invite": "Zaproś",
|
||||
"new": "Nowy użytkownik",
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"manage/groups": "Grupy",
|
||||
"manage/ip-blacklist": "Czarna lista IP",
|
||||
"manage/uploads": "Przesłane pliki",
|
||||
"manage/digest": "Digests",
|
||||
"manage/digest": "Podsumownia",
|
||||
|
||||
"section-settings": "Ustawienia",
|
||||
"settings/general": "Ogólne",
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"testing.select": "Wybierz szablon e-maila",
|
||||
"testing.send": "Wyślij testowy e-mail",
|
||||
"testing.send-help": "Testowy e-mail zostanie wysłany na adres aktualnie zalogowanego użytkownika.",
|
||||
"subscriptions": "Email Digests",
|
||||
"subscriptions.disable": "Disable email digests",
|
||||
"subscriptions": "Podsumowania e-mail",
|
||||
"subscriptions.disable": "Wyłącz podsumowania e-maili",
|
||||
"subscriptions.hour": "Godzina podsumowania",
|
||||
"subscriptions.hour-help": "Wprowadź liczbę odpowiadającą godzinie, o której mają być wysyłane regularne e-maile z podsumowaniem (np. <code>0</code> dla północy lub <code>17</code> dla 17:00). Pamiętaj, że godzina jest godziną serwera i nie musi zgadzać się z czasem lokalnym administratora. Przybliżony czas serwera to: <span id=\"serverTime\"></span><br /> Wysłanie kolejnego e-maila z podsumowaniem zaplanowano na <span id=\"nextDigestTime\"></span>"
|
||||
}
|
||||
@@ -7,12 +7,12 @@
|
||||
"sorting.most-posts": "Najwięcej postów",
|
||||
"sorting.topic-default": "Domyślne sortowanie tematów",
|
||||
"length": "Długość postu",
|
||||
"post-queue": "Post Queue",
|
||||
"post-queue": "Kolejka postów",
|
||||
"restrictions": "Restrykcje postowania",
|
||||
"restrictions-new": "Restrykcje dla nowych użytkowników",
|
||||
"restrictions.post-queue": "Włącz kolejkę postów",
|
||||
"restrictions.post-queue-rep-threshold": "Reputation required to bypass post queue",
|
||||
"restrictions.groups-exempt-from-post-queue": "Select groups that should be exempt from the post queue",
|
||||
"restrictions.post-queue-rep-threshold": "Reputacja wymagana do ominięcia kolejki postów",
|
||||
"restrictions.groups-exempt-from-post-queue": "Wybierz grupy, które powinny być zwolnione z kolejki postów",
|
||||
"restrictions-new.post-queue": "Włącz restrykcje dla nowych użytkowników",
|
||||
"restrictions.post-queue-help": "Włączenie kolejki postów spowoduje umieszczenie postów nowych użytkowników w kolejce do zatwierdzenia",
|
||||
"restrictions-new.post-queue-help": "Włączenie restrykcji dla nowych użytkowników ustawi restrykcje na ich wpisy.",
|
||||
@@ -32,8 +32,8 @@
|
||||
"timestamp": "Znacznik czasowy",
|
||||
"timestamp.cut-off": "Termin odcięcia (w dniach)",
|
||||
"timestamp.cut-off-help": "Daty oraz godziny będą wyświetlane w sposób relatywny (np. \"3 godziny temu\" / \"5 dni temu\"), oraz przetłumaczone na różne\n\t\t\t\t\tjęzyki. Po określonym czasie, ten tekst może zostać zmieniony, aby wyświetlać sformatowane daty.\n\t\t\t\t\t(np. 4 Lut 2017 12:45).<br /><em>(domyślnie: <code>30</code>, lub jeden miesiąc). Ustaw 0, aby zawsze wyświetlać daty; pozostaw puste, aby korzystać z tylko z relatywnych opisów.</em>",
|
||||
"timestamp.necro-threshold": "Necro Threshold (in days)",
|
||||
"timestamp.necro-threshold-help": "A message will be shown between posts if the time between them is longer than the necro threshold. (Default: <code>7</code>, or one week). Set to 0 to disable.</em>",
|
||||
"timestamp.necro-threshold": "Próg nekro (w dniach)",
|
||||
"timestamp.necro-threshold-help": "Komunikat zostanie wyświetlony między postami, jeśli czas między nimi jest dłuższy niż próg nekro. (Domyślnie: <code>7</code> lub jeden tydzień). Ustaw na 0, aby wyłączyć.</em>",
|
||||
"teaser": "Zwiastun postu",
|
||||
"teaser.last-post": "Ostatni – Pokaż ostatni post, włączając pierwszy post, w razie braku odpowiedzi",
|
||||
"teaser.last-reply": "Ostatni – Pokaż ostatnią odpowiedź lub komunikat „Brak odpowiedzi” w razie ich braku",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"notif.chat.cta": "Kliknij tutaj, aby kontynuować rozmowę",
|
||||
"notif.chat.unsub.info": "To powiadomienie o czacie zostało wysłane zgodnie z Twoimi ustawieniami.",
|
||||
"notif.post.unsub.info": "To powiadomienie o poście zostało wysłane zgodnie z Twoimi ustawieniami.",
|
||||
"notif.post.unsub.one-click": "Alternatively, unsubscribe from future emails like this, by clicking",
|
||||
"notif.post.unsub.one-click": "Możesz zrezygnować z otrzymywania takich e-maili w przyszłości, klikając",
|
||||
"notif.cta": "Na forum",
|
||||
"notif.cta-new-reply": "Pokaż wpisy",
|
||||
"notif.cta-new-chat": "Pokaż czat",
|
||||
@@ -42,8 +42,8 @@
|
||||
"notif.test.long": "To jest email testowy z powiadomieniami. Wyślij pomoc!",
|
||||
"test.text1": "To jest e-mail testowy wysyłany w celu sprawdzenia konfiguracji e-mailera w NodeBB.",
|
||||
"unsub.cta": "Kliknij tutaj, aby zmienić te ustawienia",
|
||||
"unsubscribe": "unsubscribe",
|
||||
"unsub.success": "You will no longer receive emails from the <strong>%1</strong> mailing list",
|
||||
"unsubscribe": "Wypisz się",
|
||||
"unsub.success": "Nie będziesz już otrzymywać wiadomości e-mail z <strong>%1</strong>",
|
||||
"banned.subject": "Zostałeś zbanowany na %1",
|
||||
"banned.text1": "Użytkownik %1 został zbanowany na %2.",
|
||||
"banned.text2": "Ban potrwa do %1",
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
"account-locked": "Twoje konto zostało tymczasowo zablokowane",
|
||||
"search-requires-login": "Wyszukiwanie wymaga konta - zaloguj się lub zarejestruj.",
|
||||
"goback": "Wciśnij wstecz, aby powrócić do poprzedniej strony",
|
||||
"invalid-cid": "Błędne ID kategorii",
|
||||
"invalid-tid": "Błędne ID tematu",
|
||||
"invalid-pid": "Błędne ID posta",
|
||||
"invalid-uid": "Błędne ID użytkownika",
|
||||
"invalid-username": "Błędny login",
|
||||
"invalid-email": "Błędny e-mail",
|
||||
"invalid-fullname": "Invalid Fullname",
|
||||
"invalid-location": "Invalid Location",
|
||||
"invalid-birthday": "Invalid Birthday",
|
||||
"invalid-cid": "Nieprawidłowy ID kategorii",
|
||||
"invalid-tid": "Nieprawidłowy ID tematu",
|
||||
"invalid-pid": "Nieprawidłowy ID posta",
|
||||
"invalid-uid": "Nieprawidłowy ID użytkownika",
|
||||
"invalid-username": "Nieprawidłowy login",
|
||||
"invalid-email": "Nieprawidłowy adres e-mail",
|
||||
"invalid-fullname": "Nieprawidłowa nazwa",
|
||||
"invalid-location": "Nieprawidłowa lokalizacja",
|
||||
"invalid-birthday": "Nieprawidłowa data urodzenia",
|
||||
"invalid-title": "Błędna nazwa",
|
||||
"invalid-user-data": "Błędne dane użytkownika",
|
||||
"invalid-password": "Błędne hasło",
|
||||
@@ -26,14 +26,14 @@
|
||||
"invalid-pagination-value": "Błędna wartość paginacji, zakres od %1 do %2",
|
||||
"username-taken": "Login zajęty",
|
||||
"email-taken": "Email zajęty",
|
||||
"email-not-confirmed": "You are unable to post until your email is confirmed, please click here to confirm your email.",
|
||||
"email-not-confirmed": "Nie możesz opublikować, dopóki Twój adres e-mail nie zostanie potwierdzony, kliknij tutaj, aby potwierdzić swój adres e-mail.",
|
||||
"email-not-confirmed-chat": "Nie możesz prowadzić rozmów, dopóki twój email nie zostanie potwierdzony. Kliknij tutaj, aby potwierdzić swój email.",
|
||||
"email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email. You won't be able to post or chat until your email is confirmed.",
|
||||
"email-not-confirmed-email-sent": "Twój e-mail nie został jeszcze potwierdzony, sprawdź swoją skrzynkę pocztową, aby znaleźć e-mail z potwierdzeniem. Nie będziesz mógł dodawać postów ani czatować, dopóki Twój adres e-mail nie zostanie potwierdzony.",
|
||||
"no-email-to-confirm": "To forum wymaga weryfikacji przez email. Proszę kliknąć tutaj, aby wprowadzić adres.",
|
||||
"email-confirm-failed": "Nie byliśmy w stanie potwierdzić Twojego adresu e-mail. Spróbuj później.",
|
||||
"confirm-email-already-sent": "Email potwierdzający został już wysłany, proszę odczekaj jeszcze %1 minut(y), aby wysłać kolejny.",
|
||||
"sendmail-not-found": "Program sendmail nie został znaleziony, proszę upewnij się, że jest zainstalowany i możliwy do uruchomienia przez użytkownika uruchamiającego NodeBB.",
|
||||
"digest-not-enabled": "This user does not have digests enabled, or the system default is not configured to send digests",
|
||||
"digest-not-enabled": "Ten użytkownik nie ma włączonych skrótów lub system nie jest skonfigurowany do wysyłania skrótów",
|
||||
"username-too-short": "Nazwa użytkownika za krótka",
|
||||
"username-too-long": "Zbyt długa nazwa użytkownika",
|
||||
"password-too-long": "Hasło jest za długie",
|
||||
@@ -103,8 +103,8 @@
|
||||
"group-needs-owner": "Ta grupa musi mieć przynajmniej jednego właściciela",
|
||||
"group-already-invited": "Ten użytkownik został już zaproszony",
|
||||
"group-already-requested": "Twoje podanie o członkostwo zostało już wysłane",
|
||||
"group-join-disabled": "You are not able to join this group at this time",
|
||||
"group-leave-disabled": "You are not able to leave this group at this time",
|
||||
"group-join-disabled": "Nie możesz teraz dołączyć do tej grupy",
|
||||
"group-leave-disabled": "Obecnie nie możesz opuścić tej grupy",
|
||||
"post-already-deleted": "Ten post został już skasowany",
|
||||
"post-already-restored": "Ten post został już przywrócony",
|
||||
"topic-already-deleted": "Ten temat został już skasowany",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"details.latest_posts": "Ostatnie posty",
|
||||
"details.private": "Prywatna",
|
||||
"details.disableJoinRequests": "Wyłączono prośbę o dołączenie",
|
||||
"details.disableLeave": "Disallow users from leaving the group",
|
||||
"details.disableLeave": "Wyłącz możliwość opuszczania użytkowników z grupy",
|
||||
"details.grant": "Nadaj/Cofnij prawa Właściciela",
|
||||
"details.kick": "Wykop",
|
||||
"details.kick_confirm": "Jesteś pewny, że chcesz wyrzucić tego użytkownika z grupy?",
|
||||
@@ -49,11 +49,11 @@
|
||||
"event.updated": "Dane grupy zostały zaktualizowane",
|
||||
"event.deleted": "Grupa \"%1\" została usunięta",
|
||||
"membership.accept-invitation": "Przyjmij zaproszenie",
|
||||
"membership.accept.notification_title": "You are now a member of <strong>%1</strong>",
|
||||
"membership.accept.notification_title": "Jesteś teraz członkiem <strong>%1</strong>",
|
||||
"membership.invitation-pending": "Oczekujące zaproszenie",
|
||||
"membership.join-group": "Dołącz do grupy",
|
||||
"membership.leave-group": "Opuść grupę",
|
||||
"membership.leave.notification_title": "<strong>%1</strong> has left group <strong>%2</strong>",
|
||||
"membership.leave.notification_title": "<strong>%1</strong> opuścił grupę <strong>%2</strong>",
|
||||
"membership.reject": "Odrzuć",
|
||||
"new-group.group_name": "Nazwa grupy:",
|
||||
"upload-group-cover": "Prześlij zdjęcie tła grupy",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"reset_password": "Zresetuj hasło",
|
||||
"update_password": "Zmień hasło",
|
||||
"update_password": "Zaktualizuj hasło",
|
||||
"password_changed.title": "Hasło zmienione",
|
||||
"password_changed.message": "<p>Hasło zostało zmienione. <a href=\"/login\">Zaloguj się ponownie</a>.",
|
||||
"wrong_reset_code.title": "Nieprawidłowy kod resetujący",
|
||||
@@ -9,7 +9,7 @@
|
||||
"repeat_password": "Powtórz hasło",
|
||||
"enter_email": "Podaj swój <strong>adres e-mail</strong>, by otrzymać wiadomość z instrukcjami, jak zresetować hasło.",
|
||||
"enter_email_address": "Wpisz swój adres e-mail",
|
||||
"password_reset_sent": "If the specified address corresponds to an existing user account, a password reset email was sent. Please note that only one email will be sent per minute.",
|
||||
"password_reset_sent": "Jeśli podany adres odpowiada istniejącemu kontu użytkownika, to zostanie wysłana wiadomość e-mail dotyczącą resetowania hasła. Pamiętaj, że na minutę zostanie wysłany tylko jeden e-mail.",
|
||||
"invalid_email": "Nieprawidłowy adres e-mail.",
|
||||
"password_too_short": "Wprowadzone hasło jest zbyt krótkie, wybierz inne hasło.",
|
||||
"passwords_do_not_match": "Wprowadzone hasła nie pasują do siebie",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"moved": "Przeniesiony",
|
||||
"copy-ip": "Kopiuj IP",
|
||||
"ban-ip": "Blokuj IP",
|
||||
"view-history": "Edytuj historię",
|
||||
"view-history": "Historia edycji",
|
||||
"bookmark_instructions": "Kliknij tutaj, by powrócić do ostatniego przeczytanego postu w tym temacie.",
|
||||
"flag_title": "Zgłoś post do moderacji",
|
||||
"merged_message": "Ten temat został połączony z <a href=\"/topic/%1\">%2</a>",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"reputation": "Reputacja",
|
||||
"bookmarks": "Zakładki",
|
||||
"watched_categories": "Obserwowane kategorie",
|
||||
"change_all": "Change All",
|
||||
"change_all": "Zmień wszystko",
|
||||
"watched": "Obserwowane",
|
||||
"ignored": "Zignorowane",
|
||||
"default-category-watch-state": "Domyślny stan oglądania kategorii",
|
||||
@@ -125,7 +125,7 @@
|
||||
"follow_topics_you_reply_to": "Obserwuj tematy, w których uczestniczysz",
|
||||
"follow_topics_you_create": "Obserwuj tematy, które utworzyłeś",
|
||||
"grouptitle": "Nazwa grupy",
|
||||
"group-order-help": "Select a group and use the arrows to order titles",
|
||||
"group-order-help": "Wybierz grupę i użyj strzałek, aby zamówić tytuł",
|
||||
"no-group-title": "Brak nazwy grupy",
|
||||
"select-skin": "Wybierz skórkę",
|
||||
"select-homepage": "Wybierz stronę startową",
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"plugin-item.install": "Установить",
|
||||
"plugin-item.uninstall": "Удалить",
|
||||
"plugin-item.settings": "Настройки",
|
||||
"plugin-item.installed": "Установленные",
|
||||
"plugin-item.latest": "Недавние",
|
||||
"plugin-item.installed": "Установленная версия",
|
||||
"plugin-item.latest": "Последняя версия",
|
||||
"plugin-item.upgrade": "Обновить",
|
||||
"plugin-item.more-info": "Дополнительная информация:",
|
||||
"plugin-item.unknown": "Неизвестно",
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
"watch": "Стежити",
|
||||
"ignore": "Ігнорувати",
|
||||
"watching": "Відстежується",
|
||||
"not-watching": "Not Watching",
|
||||
"not-watching": "Не спостерігається",
|
||||
"ignoring": "Ігнорувати",
|
||||
"watching.description": "Show topics in unread and recent",
|
||||
"not-watching.description": "Do not show topics in unread, show in recent",
|
||||
"ignoring.description": "Do not show topics in unread and recent",
|
||||
"watching.message": "You are now watching updates from this category and all subcategories",
|
||||
"notwatching.message": "You are not watching updates from this category and all subcategories",
|
||||
"ignoring.message": "You are now ignoring updates from this category and all subcategories",
|
||||
"watching.description": "Показати теми в непрочитаних та останніх",
|
||||
"not-watching.description": "Не показувати теми в непрочитаних, показувати в останніх",
|
||||
"ignoring.description": "Не показувати теми в непрочитаних і останніх",
|
||||
"watching.message": "Ви зараз спостерігаєте за оновленнями з цієї категорії та всіх її підкатегорій",
|
||||
"notwatching.message": "Зараз ви не спостерігаєте за оновленнями з цієї категорії та всіх її підкатегорій",
|
||||
"ignoring.message": "Зараз ви ігноруєте оновлення з цієї категорії та всіх її підкатегорій",
|
||||
"watched-categories": "Переглянуті категорії"
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"test-email.subject": "Test Email",
|
||||
"password-reset-requested": "Password Reset Requested!",
|
||||
"test-email.subject": "Тестове поштове повідомлення",
|
||||
"password-reset-requested": "Отримано запит на скидання пароля!",
|
||||
"welcome-to": "Ласкаво просимо до %1",
|
||||
"invite": "Запрошення від %1",
|
||||
"greeting_no_name": "Привіт",
|
||||
"greeting_with_name": "Привіт %1",
|
||||
"email.verify-your-email.subject": "Please verify your email",
|
||||
"email.verify.text1": "Your email address has changed!",
|
||||
"email.verify-your-email.subject": "Будь-ласка перевірте вашу електронну адресу",
|
||||
"email.verify.text1": "Ваша електронна адреса змінилась!",
|
||||
"welcome.text1": "Дякуємо за реєстрацію з %1!",
|
||||
"welcome.text2": "Щоб повністю активувати ваш акаунт, нам потрібно перевірити, що вам належить електронна адреса, яку ви вказали при реєстрації ",
|
||||
"welcome.text3": "Адміністратор схвалив ваш запит на реєстрацію. Ви можете залогінитись, використовуючи свій пароль та назву акаунту",
|
||||
"welcome.cta": "Натисніть тут, щоб підтвердити вашу електронну адресу",
|
||||
"invitation.text1": "%1 запросив вас приєднатися до %2",
|
||||
"invitation.text2": "Термін дії вашого запрошення закінчиться за %1 днів.",
|
||||
"invitation.cta": "Click here to create your account.",
|
||||
"invitation.cta": "Натисніть тут щоб створити акаунт.",
|
||||
"reset.text1": "Ми отримали запит на відновлення вашого паролю, можливо тому, что ви його забули. Якщо вам це не потрібно - проігноруйте цей лист",
|
||||
"reset.text2": "Щоб продовжити відновлення паролю, будь ласка, перейдіть за посиланням",
|
||||
"reset.cta": "Натисніть тут щоб скинути Ваш пароль",
|
||||
@@ -27,23 +27,23 @@
|
||||
"digest.week": "тиждень",
|
||||
"digest.month": "місяць",
|
||||
"digest.subject": "Дайджест для %1",
|
||||
"digest.title.day": "Your Daily Digest",
|
||||
"digest.title.week": "Your Weekly Digest",
|
||||
"digest.title.month": "Your Monthly Digest",
|
||||
"digest.title.day": "Ваш щоденний дайджест",
|
||||
"digest.title.week": "Ваш тижневий дайджест",
|
||||
"digest.title.month": "Ваш місячний дайджест",
|
||||
"notif.chat.subject": "Отримане нове повідомлення чату від %1",
|
||||
"notif.chat.cta": "Натисніть тут, щоб продовжити розмову",
|
||||
"notif.chat.unsub.info": "Це повідомлення чату було вислано вам, згідно ваших налаштувань підписки",
|
||||
"notif.post.unsub.info": "Це поштове повідомлення було вислано вам, згідно ваших налаштувань підписки",
|
||||
"notif.post.unsub.one-click": "Alternatively, unsubscribe from future emails like this, by clicking",
|
||||
"notif.cta": "To the forum",
|
||||
"notif.cta-new-reply": "View Post",
|
||||
"notif.cta-new-chat": "View Chat",
|
||||
"notif.test.short": "Testing Notifications",
|
||||
"notif.test.long": "This is a test of the notifications email. Send help!",
|
||||
"notif.post.unsub.one-click": "Ви також можете відписатись від схожих майбутніх повідомлень, натиснувши тут",
|
||||
"notif.cta": "На форум",
|
||||
"notif.cta-new-reply": "Переглянути допис",
|
||||
"notif.cta-new-chat": "Переглянути чат",
|
||||
"notif.test.short": "Перевірка сповіщень",
|
||||
"notif.test.long": "Це перевірка повідомлення про сповіщення.",
|
||||
"test.text1": "Це пробний лист для верифікації поштової служби. Всі налаштування вірні для NodeBB.",
|
||||
"unsub.cta": "Натисніть тут, щоб змінити ці налаштування",
|
||||
"unsubscribe": "unsubscribe",
|
||||
"unsub.success": "You will no longer receive emails from the <strong>%1</strong> mailing list",
|
||||
"unsubscribe": "відписатись",
|
||||
"unsub.success": "Ви більше не будете отримувати повідомлення з <strong>%1</strong> поштової розсилки",
|
||||
"banned.subject": "Ви були забанені на %1",
|
||||
"banned.text1": "Користувач %1 був забанений на %2.",
|
||||
"banned.text2": "Тривалість бану - до %1.",
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
"invalid-uid": "Невірний ID користувача",
|
||||
"invalid-username": "Невірне ім'я користувача",
|
||||
"invalid-email": "Невірна електронна адреса",
|
||||
"invalid-fullname": "Invalid Fullname",
|
||||
"invalid-location": "Invalid Location",
|
||||
"invalid-birthday": "Invalid Birthday",
|
||||
"invalid-fullname": "Невірне повне ім'я",
|
||||
"invalid-location": "Невірне місцезнаходження",
|
||||
"invalid-birthday": "Невірна дата народження",
|
||||
"invalid-title": "Невірний заголовок",
|
||||
"invalid-user-data": "Невірні користувацькі дані",
|
||||
"invalid-password": "Невірний пароль",
|
||||
@@ -26,18 +26,18 @@
|
||||
"invalid-pagination-value": "Невірне значення сторінки, має бути щонайменше %1 та щонайбільше %2",
|
||||
"username-taken": "Це ім'я зайняте",
|
||||
"email-taken": "Ця електронна пошта зайнята",
|
||||
"email-not-confirmed": "You are unable to post until your email is confirmed, please click here to confirm your email.",
|
||||
"email-not-confirmed": "Ви не зможете постити до підтвердження вашої електронної адреси, будь-ласка натисніть тут щоб підтвердити свій емейл.",
|
||||
"email-not-confirmed-chat": "Ви не можете користуватися чатом поки ваша електронна пошта не буде підтверджена, натисніть тут, щоб це зробити.",
|
||||
"email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email. You won't be able to post or chat until your email is confirmed.",
|
||||
"email-not-confirmed-email-sent": "Ваша електронна адреса ще не була підтверджена, будь-ласка перевірте свою поштову скриньку. Ви не зможете постити або чатитись до того як ваш емейл підтверджено.",
|
||||
"no-email-to-confirm": "Цей форум вимагає підтвердження електронної пошти, будь-ласка, натисніть тут, щоб його ввести.",
|
||||
"email-confirm-failed": "Ми не можемо підтвердити вашу електронну пошту, будь ласка, спробуйте пізніше.",
|
||||
"confirm-email-already-sent": "Підтвердження по електронній пошті вже було надіслано, зачекайте, будь ласка, %1 хвилин(и), щоб відправити ще одне. ",
|
||||
"sendmail-not-found": "Виконуваний файл sendmail не знайдено, переконайтесь, будь ласка, що його встановлено та що він виконується власником процесу NodeBB.",
|
||||
"digest-not-enabled": "This user does not have digests enabled, or the system default is not configured to send digests",
|
||||
"digest-not-enabled": "Цей користувач не має активних дайджестів, або налаштування по замовчанню не включають надсилання дайджестів.",
|
||||
"username-too-short": "Ім'я користувача закоротке",
|
||||
"username-too-long": "Ім'я користувача задовге",
|
||||
"password-too-long": "Пароль задовгий",
|
||||
"reset-rate-limited": "Too many password reset requests (rate limited)",
|
||||
"reset-rate-limited": "Занадто багато запитів на скидання паролю (кількість за період часу обмежена)",
|
||||
"user-banned": "Користувача забанено",
|
||||
"user-banned-reason": "Вибачте, але цей акаунт було забанено (Причина: %1)",
|
||||
"user-banned-reason-until": "Вибачте, цей акаунт забанений до %1 (Причина: %2)",
|
||||
@@ -83,7 +83,7 @@
|
||||
"still-uploading": "Зачекайте, будь ласка, доки завантаження завершиться.",
|
||||
"file-too-big": "Максимальний розмір файлу %1 кБ — завантажте менший файл, будь ласка.",
|
||||
"guest-upload-disabled": "Гостьове завантаження вимкнено.",
|
||||
"cors-error": "Unable to upload image due to misconfigured CORS",
|
||||
"cors-error": "Неможливо завантажити зображення через неправильно налаштований CORS",
|
||||
"already-bookmarked": "Ви вже додали цей пост собі в закладки",
|
||||
"already-unbookmarked": "Ви вже видалили цей пост із закладок",
|
||||
"cant-ban-other-admins": "Ви не можете банити інших адмінів!",
|
||||
@@ -93,7 +93,7 @@
|
||||
"invalid-image-type": "Невірний тип зображення. Дозволені типи: %1",
|
||||
"invalid-image-extension": "Невірне розширення зображення",
|
||||
"invalid-file-type": "Невірний тип файлу. Дозволені типи: %1",
|
||||
"invalid-image-dimensions": "Image dimensions are too big",
|
||||
"invalid-image-dimensions": "Зображення занадто велике",
|
||||
"group-name-too-short": "Ім'я групи занадто коротке",
|
||||
"group-name-too-long": "Ім'я групи занадто довге",
|
||||
"group-already-exists": "Група вже існує",
|
||||
@@ -103,8 +103,8 @@
|
||||
"group-needs-owner": "Ця група потребує щонайменше одного власника",
|
||||
"group-already-invited": "Користувача вже було запрошено",
|
||||
"group-already-requested": "Ваша заявка на вступ вже подана",
|
||||
"group-join-disabled": "You are not able to join this group at this time",
|
||||
"group-leave-disabled": "You are not able to leave this group at this time",
|
||||
"group-join-disabled": "Ви не можете приєднатись до цієї групи зараз",
|
||||
"group-leave-disabled": "Ви не можете покинути цю групу зараз",
|
||||
"post-already-deleted": "Цей пост вже видалено",
|
||||
"post-already-restored": "Цей пост вже відновлено",
|
||||
"topic-already-deleted": "Ця тема вже була видалена",
|
||||
@@ -127,7 +127,7 @@
|
||||
"chat-edit-duration-expired": "Ви можете редагувати повідомлення чату лише через %1 секунд після публікації",
|
||||
"chat-delete-duration-expired": "Ви можете видаляти повідомлення чату лише через %1 секунд після публікації",
|
||||
"chat-deleted-already": "Це повідомлення чату вже було видалено.",
|
||||
"chat-restored-already": "This chat message has already been restored.",
|
||||
"chat-restored-already": "Це чат повідомлення вже було відновлене",
|
||||
"already-voting-for-this-post": "Ви вже проголосували за цей пост.",
|
||||
"reputation-system-disabled": "Система репутацій вимкнена.",
|
||||
"downvoting-disabled": "Голосування проти вимкнено",
|
||||
@@ -159,8 +159,8 @@
|
||||
"cant-move-to-same-topic": "Ви не можете перемістити пост до тієї ж самої теми!",
|
||||
"cannot-block-self": "Ви не можете заблокувати самого себе!",
|
||||
"cannot-block-privileged": "Ви не можете заблокувати адміністраторів або глобальних модераторів",
|
||||
"cannot-block-guest": "Guest are not able to block other users",
|
||||
"already-blocked": "This user is already blocked",
|
||||
"already-unblocked": "This user is already unblocked",
|
||||
"cannot-block-guest": "Гості не можуть блокувати інших користувачів",
|
||||
"already-blocked": "Цей користувач вже заблокований",
|
||||
"already-unblocked": "Цей користувач вже розблокований",
|
||||
"no-connection": "Схоже, виникла проблема з вашим Інтернет-з'єднанням"
|
||||
}
|
||||
@@ -59,8 +59,8 @@
|
||||
"downvoted": "Проти",
|
||||
"views": "Перегляди",
|
||||
"reputation": "Репутація",
|
||||
"lastpost": "Last post",
|
||||
"firstpost": "First post",
|
||||
"lastpost": "Останній допис",
|
||||
"firstpost": "Перший допис",
|
||||
"read_more": "читати далі",
|
||||
"more": "Більше",
|
||||
"posted_ago_by_guest": "запостив Гість %1",
|
||||
@@ -87,7 +87,7 @@
|
||||
"language": "Мова",
|
||||
"guest": "Гість",
|
||||
"guests": "Гості",
|
||||
"former_user": "A Former User",
|
||||
"former_user": "Колишній користувач",
|
||||
"updated.title": "Форум оновлено",
|
||||
"updated.message": "Форум було щойно оновлено до останньої версії. Клікніть тут, щоб оновити сторінку.",
|
||||
"privacy": "Приватність",
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
"details.latest_posts": "Останні пости",
|
||||
"details.private": "Приватна",
|
||||
"details.disableJoinRequests": "Вимкнути запити на приєднання",
|
||||
"details.disableLeave": "Disallow users from leaving the group",
|
||||
"details.disableLeave": "Забороніть користувачам покидати групу",
|
||||
"details.grant": "Надати/забрати права адміністратора",
|
||||
"details.kick": "Вигнати",
|
||||
"details.kick_confirm": "Ви впевнені, що бажаєте видалити цього користувача з групи?",
|
||||
"details.add-member": "Add Member",
|
||||
"details.add-member": "Додати члена групи",
|
||||
"details.owner_options": "Адміністрація групи",
|
||||
"details.group_name": "Назва групи",
|
||||
"details.member_count": "Кількість учасників",
|
||||
@@ -37,8 +37,8 @@
|
||||
"details.description": "Опис",
|
||||
"details.badge_preview": "Попередній перегляд бейджа",
|
||||
"details.change_icon": "Змінити іконку",
|
||||
"details.change_label_colour": "Change Label Colour",
|
||||
"details.change_text_colour": "Change Text Colour",
|
||||
"details.change_label_colour": "Змінити колір позначки",
|
||||
"details.change_text_colour": "Змінити колір тексту",
|
||||
"details.badge_text": "Текст бейджа",
|
||||
"details.userTitleEnabled": "Показати бейдж",
|
||||
"details.private_help": "Якщо увімкнено, приєднання до групи вимагає підтвердження власника.",
|
||||
@@ -49,11 +49,11 @@
|
||||
"event.updated": "Деталі групи оновлено",
|
||||
"event.deleted": "Група \"%1\" видалена",
|
||||
"membership.accept-invitation": "Прийняти запрошення",
|
||||
"membership.accept.notification_title": "You are now a member of <strong>%1</strong>",
|
||||
"membership.accept.notification_title": "Тепер ви є членом <strong>%1</strong>",
|
||||
"membership.invitation-pending": "Запрошення в черзі",
|
||||
"membership.join-group": "Приєднатися до групи",
|
||||
"membership.leave-group": "Покинути групу",
|
||||
"membership.leave.notification_title": "<strong>%1</strong> has left group <strong>%2</strong>",
|
||||
"membership.leave.notification_title": "<strong>%1</strong> покинув групу <strong>%2</strong>",
|
||||
"membership.reject": "Відхилити",
|
||||
"new-group.group_name": "Назва групи:",
|
||||
"upload-group-cover": "Завантажити обкладинку групи",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"chat.delete_message_confirm": "Ви впевнені, що хочете видалити це повідомлення?",
|
||||
"chat.retrieving-users": "Отримання користувачів...",
|
||||
"chat.manage-room": "Управління чат кімнатами",
|
||||
"chat.add-user-help": "Search for users here. When selected, the user will be added to the chat. The new user will not be able to see chat messages written before they were added to the conversation. Only room owners (<i class=\"fa fa-star text-warning\"></i>) may remove users from chat rooms.",
|
||||
"chat.add-user-help": "Шукайте користувачів тут. Користувача можна додати до чату, обравши його. Нові користувачі не можуть бачити повідомлення, написані до того, як їх додали до розмови. Тільки власники кімнат можуть видаляти користувачів з кімнат.",
|
||||
"chat.confirm-chat-with-dnd-user": "Користувач змінив свій статус на DnD (Не турбувати). Ви дійсно бажаєте надіслати йому повідомлення в чат?",
|
||||
"chat.rename-room": "Перейменувати Кімнату",
|
||||
"chat.rename-placeholder": "Введіть назву своєї кімнати тут",
|
||||
@@ -33,10 +33,10 @@
|
||||
"chat.in-room": "У цій кімнаті",
|
||||
"chat.kick": "Штурхнути",
|
||||
"chat.show-ip": "Показати IP",
|
||||
"chat.owner": "Room Owner",
|
||||
"chat.system.user-join": "%1 has joined the room",
|
||||
"chat.system.user-leave": "%1 has left the room",
|
||||
"chat.system.room-rename": "%2 has renamed this room: %1",
|
||||
"chat.owner": "Власник кімнати",
|
||||
"chat.system.user-join": "%1 зайшов в кімнату",
|
||||
"chat.system.user-leave": "%1 покинув кімнату",
|
||||
"chat.system.room-rename": "%2 перейменував кімнату на: %1",
|
||||
"composer.compose": "Редактор повідомлень",
|
||||
"composer.show_preview": "Показати попередній перегляд",
|
||||
"composer.hide_preview": "Сховати попередній перегляд",
|
||||
@@ -50,7 +50,7 @@
|
||||
"composer.formatting.italic": "Курсив",
|
||||
"composer.formatting.list": "Список",
|
||||
"composer.formatting.strikethrough": "Закреслений",
|
||||
"composer.formatting.code": "Code",
|
||||
"composer.formatting.code": "Код",
|
||||
"composer.formatting.link": "Посилання",
|
||||
"composer.formatting.picture": "Зображення",
|
||||
"composer.upload-picture": "Завантажити зображення",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"outgoing_link_message": "Ви залишаєте %1",
|
||||
"continue_to": "Перейти до %1",
|
||||
"return_to": "Повернутись до %1",
|
||||
"new_notification": "You have a new notification",
|
||||
"new_notification": "У вас нове сповіщення",
|
||||
"you_have_unread_notifications": "У вас немає непрочитаних сповіщень",
|
||||
"all": "Всі",
|
||||
"topics": "Теми",
|
||||
@@ -56,7 +56,7 @@
|
||||
"notificationType_follow": "Коли хтось починає слідкувати за вами",
|
||||
"notificationType_new-chat": "Коли ви отримуєте повідомлення чату",
|
||||
"notificationType_group-invite": "Коли ви отримуєте запрошення до групи",
|
||||
"notificationType_group-request-membership": "When someone requests to join a group you own",
|
||||
"notificationType_group-request-membership": "Коли хтось подає запит на приєднання до групи, якою ви володієте",
|
||||
"notificationType_new-register": "Коли когось додано до черги на реєстрацію",
|
||||
"notificationType_post-queue": "Коли новий пост знаходиться в черзі",
|
||||
"notificationType_new-post-flag": "Коли повідомлення позначено",
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"popular-month": "Популярні теми цього місяця",
|
||||
"popular-alltime": "Популярні теми за весь час",
|
||||
"recent": "Свіжі теми",
|
||||
"top-day": "Top voted topics today",
|
||||
"top-week": "Top voted topics this week",
|
||||
"top-month": "Top voted topics this month",
|
||||
"top-alltime": "Top Voted Topics",
|
||||
"top-day": "Найрейтинговіші теми сьогодні",
|
||||
"top-week": "Найрейтинговіші теми цього тижня",
|
||||
"top-month": "Найрейтинговіші теми цього місяця",
|
||||
"top-alltime": "Найрейтинговіші теми",
|
||||
"moderator-tools": "Інструменти Модератора",
|
||||
"flagged-content": "Оскаржений вміст",
|
||||
"ip-blacklist": "Чорний список IP адрес",
|
||||
@@ -43,10 +43,10 @@
|
||||
"account/following": "Люди за котрими стежить %1",
|
||||
"account/followers": "Люди котрі стежать за %1",
|
||||
"account/posts": "Пости написані %1",
|
||||
"account/latest-posts": "Latest posts made by %1",
|
||||
"account/latest-posts": "Останні дописи від %1",
|
||||
"account/topics": "Теми створені %1",
|
||||
"account/groups": "Групи %1",
|
||||
"account/watched_categories": "%1's Watched Categories",
|
||||
"account/watched_categories": "Категорії, за якими спостерігає %1",
|
||||
"account/bookmarks": "Закладки %1",
|
||||
"account/settings": "Налаштування користувача",
|
||||
"account/watched": "Теми за якими стежить %1",
|
||||
@@ -56,7 +56,7 @@
|
||||
"account/best": "Найкращі пости %1",
|
||||
"account/blocks": "Заблоковані користувачі для %1",
|
||||
"account/uploads": "Завантаження від %1",
|
||||
"account/sessions": "Login Sessions",
|
||||
"account/sessions": "Логін-сесії",
|
||||
"confirm": "Електронну пошту підтверджено",
|
||||
"maintenance.text": "%1 в данний час на технічному обслуговувані. Завітайте, будь ласка, пізніше.",
|
||||
"maintenance.messageIntro": "Крім того, адміністратор залишив це повідомлення:",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"repeat_password": "Підтвердіть пароль",
|
||||
"enter_email": "Будь ласка, введіть свою <strong>електронну пошту</strong> і ми надішлемо вам листа с інструкцією як скинути ваш обліковий запис.",
|
||||
"enter_email_address": "Введіть електронну пошту",
|
||||
"password_reset_sent": "If the specified address corresponds to an existing user account, a password reset email was sent. Please note that only one email will be sent per minute.",
|
||||
"password_reset_sent": "Якщо зазначена електронна адреса належить існуючому користувачеві, повідомлення для скидання паролю було надіслане на цю адресу. Майте на увазі, що тільки одне повідомлення може бути надіслане за хвилину.",
|
||||
"invalid_email": "Невірна або неіснуюча електронна пошта!",
|
||||
"password_too_short": "Уведений пароль закороткий, оберіть, будь ласка, інший.",
|
||||
"passwords_do_not_match": "Паролі що ви ввели не співпадають.",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"at-most": "Щонайбільше",
|
||||
"relevance": "Релевантність",
|
||||
"post-time": "Час посту",
|
||||
"votes": "Votes",
|
||||
"votes": "Голоси",
|
||||
"newer-than": "Новіші за",
|
||||
"older-than": "Старіші за",
|
||||
"any-date": "Будь-яка дата",
|
||||
@@ -31,7 +31,7 @@
|
||||
"sort-by": "Сортувати за",
|
||||
"last-reply-time": "Час останньої відповіді",
|
||||
"topic-title": "Заголовок теми",
|
||||
"topic-votes": "Topic votes",
|
||||
"topic-votes": "Голоси за тему",
|
||||
"number-of-replies": "Кількість відповідей",
|
||||
"number-of-views": "Кількість переглядів",
|
||||
"topic-start-date": "Час початку теми",
|
||||
@@ -44,5 +44,5 @@
|
||||
"search-preferences-saved": "Налаштування пошуку збережено",
|
||||
"search-preferences-cleared": "Налаштування пошуку очищені",
|
||||
"show-results-as": "Показати результати як",
|
||||
"see-more-results": "See more results (%1)"
|
||||
"see-more-results": "Дивитись більше результатів (%1)"
|
||||
}
|
||||
@@ -18,13 +18,13 @@
|
||||
"last_reply_time": "Остання відповідь",
|
||||
"reply-as-topic": "Відповісти темою",
|
||||
"guest-login-reply": "Увійти для відповіді",
|
||||
"login-to-view": "🔒 Log in to view",
|
||||
"login-to-view": "🔒 Увійдіть щоб переглянути",
|
||||
"edit": "Редагувати",
|
||||
"delete": "Видалити",
|
||||
"purge": "Стерти",
|
||||
"restore": "Відновити",
|
||||
"move": "Перемістити",
|
||||
"change-owner": "Change Owner",
|
||||
"change-owner": "Змінити Власника",
|
||||
"fork": "Відгалужити",
|
||||
"link": "Зв'язати",
|
||||
"share": "Поширити",
|
||||
@@ -66,7 +66,7 @@
|
||||
"thread_tools.move": "Перемістити тему",
|
||||
"thread_tools.move-posts": "Перемістити Пости",
|
||||
"thread_tools.move_all": "Перемістити всі",
|
||||
"thread_tools.change_owner": "Change Owner",
|
||||
"thread_tools.change_owner": "Змінити Власника",
|
||||
"thread_tools.select_category": "Обрати Категорію",
|
||||
"thread_tools.fork": "Відгалужити тему",
|
||||
"thread_tools.delete": "Видалити тему",
|
||||
@@ -101,7 +101,7 @@
|
||||
"delete_posts_instruction": "Тисніть пости які ви бажаєте видалити/стерти",
|
||||
"merge_topics_instruction": "Натисніть на теми, які потрібно об'єднати",
|
||||
"move_posts_instruction": "Натисніть на пости, які ви хочете перемістити",
|
||||
"change_owner_instruction": "Click the posts you want to assign to another user",
|
||||
"change_owner_instruction": "Клікніть на дописи які ви хочете призначити іншому користувачу",
|
||||
"composer.title_placeholder": "Уведіть заголовок теми...",
|
||||
"composer.handle_placeholder": "Ім'я",
|
||||
"composer.discard": "Скасувати",
|
||||
@@ -134,6 +134,6 @@
|
||||
"diffs.no-revisions-description": "Цей пост має <strong>%1</strong> версій.",
|
||||
"diffs.current-revision": "поточна ревізія",
|
||||
"diffs.original-revision": "початкова ревізія",
|
||||
"timeago_later": "%1 later",
|
||||
"timeago_earlier": "%1 earlier"
|
||||
"timeago_later": "%1 пізніше",
|
||||
"timeago_earlier": "%1 раніше"
|
||||
}
|
||||
@@ -25,17 +25,17 @@
|
||||
"profile_views": "Переглядів профілю",
|
||||
"reputation": "Репутація",
|
||||
"bookmarks": "Закладки",
|
||||
"watched_categories": "Watched categories",
|
||||
"change_all": "Change All",
|
||||
"watched_categories": "Категорії, за якими ви спостерігаєте",
|
||||
"change_all": "Змінити Всі",
|
||||
"watched": "Переглянуті",
|
||||
"ignored": "Ігнорується",
|
||||
"default-category-watch-state": "Default category watch state",
|
||||
"default-category-watch-state": "Спостереження за категоріями за замовчанням",
|
||||
"followers": "Відстежувачі",
|
||||
"following": "Відстежувані",
|
||||
"blocks": "Блокування",
|
||||
"block_toggle": "Увімкнути Блокування",
|
||||
"block_user": "Block User",
|
||||
"unblock_user": "Unblock User",
|
||||
"block_user": "Заблокувати Користувача",
|
||||
"unblock_user": "Розблокувати Користувача",
|
||||
"aboutme": "Про мене",
|
||||
"signature": "Підпис",
|
||||
"birthday": "День народження",
|
||||
@@ -50,7 +50,7 @@
|
||||
"change_picture": "Змінити зображення",
|
||||
"change_username": "Змінити ім'я користувача",
|
||||
"change_email": "Змінити електронну пошту",
|
||||
"email_same_as_password": "Please enter your current password to continue – you've entered your new email again",
|
||||
"email_same_as_password": "Будь-ласка введіть ваш поточний пароль щоб продовжити – ви ввели ваш новий емейл знову",
|
||||
"edit": "Редагувати",
|
||||
"edit-profile": "Редагувати профіль",
|
||||
"default_picture": "Стандартна іконка",
|
||||
@@ -112,9 +112,9 @@
|
||||
"no-sound": "Без звуку",
|
||||
"upvote-notif-freq": "Частота сповіщень позитивних відгуків",
|
||||
"upvote-notif-freq.all": "Всі позитивні відгуки",
|
||||
"upvote-notif-freq.first": "First Per Post",
|
||||
"upvote-notif-freq.first": "Перше в дописі",
|
||||
"upvote-notif-freq.everyTen": "Кожні 10 позитивних відгуків",
|
||||
"upvote-notif-freq.threshold": "On 1, 5, 10, 25, 50, 100, 150, 200...",
|
||||
"upvote-notif-freq.threshold": "На 1, 5, 10, 25, 50, 100, 150, 200...",
|
||||
"upvote-notif-freq.logarithmic": "На 10, 100, 1000...",
|
||||
"upvote-notif-freq.disabled": "Вимкнено",
|
||||
"browsing": "Налаштування перегляду",
|
||||
@@ -125,7 +125,7 @@
|
||||
"follow_topics_you_reply_to": "Підписуватися на теми в котрих ви відповідаєте",
|
||||
"follow_topics_you_create": "Підписуватися на теми які ви створюєте",
|
||||
"grouptitle": "Заголовок групи",
|
||||
"group-order-help": "Select a group and use the arrows to order titles",
|
||||
"group-order-help": "Оберіть групу і використовуйте стрілки для зміни порядку заголовків",
|
||||
"no-group-title": "Немає заголовка групи",
|
||||
"select-skin": "Обрати стиль сайту",
|
||||
"select-homepage": "Обрати домашню сторінку",
|
||||
@@ -152,7 +152,7 @@
|
||||
"info.moderation-note": "Коментар модератора",
|
||||
"info.moderation-note.success": "Коментар модератора збережено",
|
||||
"info.moderation-note.add": "Додати коментар",
|
||||
"sessions.description": "This page allows you to view any active sessions on this forum and revoke them if necessary. You can revoke your own session by logging out of your account.",
|
||||
"sessions.description": "Ця сторінка дозволяє вам переглядати будь-які активні сесії на цьому форумі та видаляти їх якщо потрібно. Ви можете видалити вашу власну сесію, якщо вийдете зі свого акаунта.",
|
||||
"consent.title": "Ваші Права & Згода",
|
||||
"consent.lead": "Цей форум збирає та обробляє вашу особисту інформацію.",
|
||||
"consent.intro": "Ми використовуємо цю інформацію виключно з метою персоналізації вашої активності у цій спільноті, а також для з'єднання ваших постів з вашим особистим акаунтом. На етапі реєстрації ми просили вас надати ім'я користувача та електронну пошту, також ви можете (необов'язково) надати нам додаткову інформацію, щоб завершити створення свого користувацького профілю на цьому сайті.<br /><br />Ми зберігаємо цю інформацію протягом всього періоду життя вашого акаунту, і ви можете відкликати свою згоду у будь-який час, якщо видалите акаунт. У будь-який час ви можете отримати копію ваших особистих даних та внеску на цьому сайті через свою сторінку Права & Згода.<br /><br />Якщо у вас виникли будь-які питання або зауваження, ми заохочуємо вас звернутись до команди Адміністраторів цього форуму.",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"filter-by": "Фільтрувати за",
|
||||
"online-only": "Лише в мережі",
|
||||
"invite": "Запросити",
|
||||
"prompt-email": "Emails:",
|
||||
"prompt-email": "Емейли:",
|
||||
"invitation-email-sent": "Лист із запрошенням відправлено %1",
|
||||
"user_list": "Список користувачів",
|
||||
"recent_topics": "Нещодавні теми",
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"forum-traffic": "Forum Traffic",
|
||||
"forum-traffic": "Lưu lượng truy cập",
|
||||
"page-views": "Lượt xem trang",
|
||||
"unique-visitors": "Khách truy cập duy nhất",
|
||||
"new-users": "New Users",
|
||||
"new-users": "Người dùng mới",
|
||||
"posts": "Bài viết",
|
||||
"topics": "Chủ đề",
|
||||
"page-views-seven": "7 ngày trước",
|
||||
"page-views-thirty": "30 ngày trước",
|
||||
"page-views-last-day": "24 giờ trước",
|
||||
"page-views-custom": "Custom Date Range",
|
||||
"page-views-custom-start": "Range Start",
|
||||
"page-views-custom-end": "Range End",
|
||||
"page-views-custom-help": "Enter a date range of page views you would like to view. If no date picker is available, the accepted format is <code>YYYY-MM-DD</code>",
|
||||
"page-views-custom-error": "Please enter a valid date range in the format <code>YYYY-MM-DD</code>",
|
||||
"page-views-custom": "Tùy chỉnh phạm vi ngày",
|
||||
"page-views-custom-start": "Phạm vi bắt đầu",
|
||||
"page-views-custom-end": "Phạm vi kết thúc",
|
||||
"page-views-custom-help": "Nhập phạm vi ngày của lượt xem trang bạn muốn xem. Nếu không có bộ chọn ngày, định dạng được chấp nhận là <code>YYYY-MM-DD</code>",
|
||||
"page-views-custom-error": "Vui lòng nhập một phạm vi ngày hợp lệ trong định dạng <code>YYYY-MM-DD</code>",
|
||||
|
||||
"stats.yesterday": "Yesterday",
|
||||
"stats.today": "Today",
|
||||
"stats.last-week": "Last Week",
|
||||
"stats.this-week": "This Week",
|
||||
"stats.last-month": "Last Month",
|
||||
"stats.this-month": "This Month",
|
||||
"stats.all": "All Time",
|
||||
"stats.yesterday": "Hôm qua",
|
||||
"stats.today": "Hôm nay",
|
||||
"stats.last-week": "Tuần trước",
|
||||
"stats.this-week": "Tuần này",
|
||||
"stats.last-month": "Tháng trước",
|
||||
"stats.this-month": "Tháng này",
|
||||
"stats.all": "Mọi lúc",
|
||||
|
||||
"updates": "Updates",
|
||||
"running-version": "You are running <strong>NodeBB v<span id=\"version\">%1</span></strong>.",
|
||||
"keep-updated": "Always make sure that your NodeBB is up to date for the latest security patches and bug fixes.",
|
||||
"up-to-date": "<p>You are <strong>up-to-date</strong> <i class=\"fa fa-check\"></i></p>",
|
||||
"upgrade-available": "<p>A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/configuring/upgrade/\" target=\"_blank\">upgrading your NodeBB</a>.</p>",
|
||||
"updates": "Cập nhật",
|
||||
"running-version": "Bạn đang chạy <strong>NodeBB v<span id=\"version\">%1</span></strong>.",
|
||||
"keep-updated": "Luôn đảm bảo rằng NodeBB của bạn được cập nhật cho các bản vá bảo mật và sửa lỗi mới nhất.",
|
||||
"up-to-date": "<p>Bạn đang <strong>bản mới nhất</strong> <i class=\"fa fa-check\"></i></p>",
|
||||
"upgrade-available": "<p>Phiên bản mới (v%1) đã được phát hành. Xem xét <a href=\"https://docs.nodebb.org/configuring/upgrade/\" target=\"_blank\">nâng cấp NodeBB của bạn</a>.</p>",
|
||||
"prerelease-upgrade-available": "<p>This is an outdated pre-release version of NodeBB. A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/configuring/upgrade/\" target=\"_blank\">upgrading your NodeBB</a>.</p>",
|
||||
"prerelease-warning": "<p>This is a <strong>pre-release</strong> version of NodeBB. Unintended bugs may occur. <i class=\"fa fa-exclamation-triangle\"></i></p>",
|
||||
"running-in-development": "<span>Forum is running in development mode. The forum may be open to potential vulnerabilities; please contact your system administrator.</span>",
|
||||
@@ -39,7 +39,7 @@
|
||||
"search-plugin-not-installed": "Search Plugin not installed",
|
||||
"search-plugin-tooltip": "Install a search plugin from the plugin page in order to activate search functionality",
|
||||
|
||||
"control-panel": "System Control",
|
||||
"control-panel": "Điều khiển hệ thống",
|
||||
"rebuild-and-restart": "Rebuild & Restart",
|
||||
"restart": "Restart",
|
||||
"restart-warning": "Rebuilding or Restarting your NodeBB will drop all existing connections for a few seconds.",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"reorder-plugins": "重新排序插件",
|
||||
"order-active": "排序生效插件",
|
||||
"dev-interested": "有兴趣为NodeBB开发插件?",
|
||||
"docs-info": "有关插件创作的完整文档可以在 <a target=\"_blank\" href=\"https://docs.nodebb.org/development/plugins/\">NodeBB 文档</a>中找到。",
|
||||
"docs-info": "有关插件创作的完整文档可以在 <a target=\"_blank\" href=\"https://docs.nodebb-cn.org/development\">NodeBB 文档</a>中找到。",
|
||||
|
||||
"order.description": "部分插件需要在其它插件启用之后才能完美运作。",
|
||||
"order.explanation": "插件将按照以下顺序载入,从上至下。",
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"delete": "删除用户",
|
||||
"purge": "删除用户和内容",
|
||||
"download-csv": "下载CSV",
|
||||
"manage-groups": "Manage Groups",
|
||||
"add-group": "Add Group",
|
||||
"manage-groups": "管理用户组",
|
||||
"add-group": "添加至群组",
|
||||
"invite": "邀请",
|
||||
"new": "新建用户",
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"timestamp": "时间戳",
|
||||
"timestamp.cut-off": "截止日期(天)",
|
||||
"timestamp.cut-off-help": "日期&时间将以相对方式 (例如,“3小时前” / “5天前”) 显示,并且会依照访客语言时区转换。在某一时刻之后,可以切换该文本以显示本地化日期本身 (例如2016年11月5日15:30) 。<br /> <em> (默认值:<code> 30 </code>或一个月) 。 设置为0可始终显示日期,留空以始终显示相对时间。</em>",
|
||||
"timestamp.necro-threshold": "Necro Threshold (in days)",
|
||||
"timestamp.necro-threshold-help": "A message will be shown between posts if the time between them is longer than the necro threshold. (Default: <code>7</code>, or one week). Set to 0 to disable.</em>",
|
||||
"timestamp.necro-threshold": "挖坟警告(单位:天)",
|
||||
"timestamp.necro-threshold-help": "若进行回复的帖子最后回复的时间早于挖坟警告设定的天数,则在尝试回复前显示挖坟警告(默认:<code>7</code>天)。可以设置为 0 来禁用。</em>",
|
||||
"teaser": "预览帖子",
|
||||
"teaser.last-post": "最后– 显示最新的帖子,包括原帖,如果没有回复",
|
||||
"teaser.last-reply": "最后– 显示最新回复,如果没有回复,则显示“无回复”占位符",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"category": "版块",
|
||||
"subcategories": "子版块",
|
||||
"new_topic_button": "新主题",
|
||||
"new_topic_button": "发表主题",
|
||||
"guest-login-post": "登录以发表",
|
||||
"no_topics": "<strong>此版块还没有任何内容。</strong><br />赶紧来发帖吧!",
|
||||
"browsing": "正在浏览",
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
"greeting_no_name": "您好",
|
||||
"greeting_with_name": "%1,您好",
|
||||
"email.verify-your-email.subject": "请验证你的电子邮箱",
|
||||
"email.verify.text1": "你的电子邮箱地址已经成功更改!",
|
||||
"email.verify.text1": "你的电子邮箱地址已成功更改!",
|
||||
"welcome.text1": "感谢您注册 %1 帐户!",
|
||||
"welcome.text2": "我们需要在校验您注册时填写的电子邮箱地址后,才能激活您的帐户。",
|
||||
"welcome.text3": "管理员接受了您的注册请求,请用您的用户名和密码登陆。",
|
||||
"welcome.text2": "在您验证您绑定的邮箱地址之后,您的账户才能激活。",
|
||||
"welcome.text3": "管理员批准了您的注册申请,现在您可以登录您的账户了。",
|
||||
"welcome.cta": "点击这里确认您的电子邮箱地址",
|
||||
"invitation.text1": "%1 邀请您加入 %2",
|
||||
"invitation.text2": "您的邀请将在 %1 天后过期。",
|
||||
"invitation.cta": "点击这里新建账号",
|
||||
"reset.text1": "可能由于您忘记了密码,我们收到了重置您帐户密码的申请。 如果您没有提交密码重置的请求,请忽略这封邮件。",
|
||||
"reset.text1": "很可能是您忘记了密码,我们收到了重置您帐户密码的申请。 如果您没有申请密码重置,请忽略这封邮件。",
|
||||
"reset.text2": "如需继续重置密码,请点击下面的链接:",
|
||||
"reset.cta": "点击这里重置您的密码",
|
||||
"reset.notify.subject": "更改密码成功",
|
||||
@@ -43,10 +43,10 @@
|
||||
"test.text1": "这是一封测试邮件,用来验证 NodeBB 的邮件配置是否设置正确。",
|
||||
"unsub.cta": "点击这里修改这些设置",
|
||||
"unsubscribe": "退订",
|
||||
"unsub.success": "你将不再从<strong>%1</strong>邮寄名单接受邮件",
|
||||
"banned.subject": "您已被封禁从 %1",
|
||||
"banned.text1": "用户 %1 已被封禁从 %2.",
|
||||
"banned.text2": "封禁将持续到 %1.",
|
||||
"unsub.success": "您将不再收到来自<strong>%1</strong>邮寄名单的邮件",
|
||||
"banned.subject": "您在 %1 的账户已被封禁",
|
||||
"banned.text1": "您在 %2 的账户 %1 已被封禁。",
|
||||
"banned.text2": "本次封禁将在 %1 结束。",
|
||||
"banned.text3": "这是您被封禁的原因:",
|
||||
"closing": "谢谢!"
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
"username-email": "用户名 / 邮箱",
|
||||
"username": "用户名",
|
||||
"email": "邮件",
|
||||
"remember_me": "记住我?",
|
||||
"remember_me": "保持登录信息?",
|
||||
"forgot_password": "忘记密码?",
|
||||
"alternative_logins": "使用合作网站帐号登录",
|
||||
"failed_login_attempt": "登录失败",
|
||||
"login_successful": "您已经成功登录!",
|
||||
"login_successful": "您已成功登录!",
|
||||
"dont_have_account": "没有帐号?",
|
||||
"logged-out-due-to-inactivity": "由于长时间不活动,您的账号已被管理员从控制面板中注销"
|
||||
}
|
||||
@@ -465,7 +465,7 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress'
|
||||
// Update the View as JSON button url
|
||||
var apiEl = $('#view-as-json');
|
||||
var newHref = $.param({
|
||||
units: units,
|
||||
units: units || 'hours',
|
||||
until: until,
|
||||
count: amount,
|
||||
});
|
||||
|
||||
@@ -227,6 +227,22 @@ define('admin/manage/category', [
|
||||
$('button[data-action="setParent"]').removeClass('hide');
|
||||
});
|
||||
});
|
||||
$('button[data-action="toggle"]').on('click', function () {
|
||||
var payload = {};
|
||||
var $this = $(this);
|
||||
var disabled = $this.attr('data-disabled') === '1';
|
||||
payload[ajaxify.data.category.cid] = {
|
||||
disabled: disabled ? 0 : 1,
|
||||
};
|
||||
socket.emit('admin.categories.update', payload, function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
$this.translateText(!disabled ? '[[admin/manage/categories:enable]]' : '[[admin/manage/categories:disable]]');
|
||||
$this.toggleClass('btn-primary', !disabled).toggleClass('btn-danger', disabled);
|
||||
$this.attr('data-disabled', disabled ? 0 : 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function modified(el) {
|
||||
|
||||
@@ -110,6 +110,7 @@ define('admin/manage/group', [
|
||||
private: $('#group-private').is(':checked'),
|
||||
hidden: $('#group-hidden').is(':checked'),
|
||||
disableJoinRequests: $('#group-disableJoinRequests').is(':checked'),
|
||||
disableLeave: $('#group-disableLeave').is(':checked'),
|
||||
},
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
|
||||
@@ -308,6 +308,7 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], funct
|
||||
},
|
||||
});
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -398,7 +398,7 @@ app.cacheBuster = null;
|
||||
}
|
||||
if (registerMessage) {
|
||||
$(document).ready(function () {
|
||||
showAlert('register', decodeURIComponent(registerMessage));
|
||||
showAlert('register', utils.escapeHTML(decodeURIComponent(registerMessage)));
|
||||
registerMessage = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,13 +70,10 @@ define('chat', [
|
||||
roomData.silent = true;
|
||||
roomData.uid = app.user.uid;
|
||||
roomData.isSelf = isSelf;
|
||||
module.createModal(roomData, function (modal) {
|
||||
module.createModal(roomData, function () {
|
||||
if (!isSelf) {
|
||||
updateTitleAndPlaySound(data.message.mid, username);
|
||||
}
|
||||
if (!modal) {
|
||||
addMessageToModal(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -87,7 +84,10 @@ define('chat', [
|
||||
var username = data.message.fromUser.username;
|
||||
var isSelf = data.self === 1;
|
||||
require(['forum/chats/messages'], function (ChatsMessages) {
|
||||
ChatsMessages.appendChatMessage(modal.find('.chat-content'), data.message);
|
||||
// don't add if already added
|
||||
if (!modal.find('[data-mid="' + data.message.messageId + '"]').length) {
|
||||
ChatsMessages.appendChatMessage(modal.find('.chat-content'), data.message);
|
||||
}
|
||||
|
||||
if (modal.is(':visible')) {
|
||||
taskbar.updateActive(modal.attr('data-uuid'));
|
||||
@@ -145,7 +145,7 @@ define('chat', [
|
||||
require(['scrollStop', 'forum/chats', 'forum/chats/messages'], function (scrollStop, Chats, ChatsMessages) {
|
||||
app.parseAndTranslate('chat', data, function (chatModal) {
|
||||
if (module.modalExists(data.roomId)) {
|
||||
return callback(null);
|
||||
return callback(module.getModal(data.roomId));
|
||||
}
|
||||
var uuid = utils.generateUUID();
|
||||
var dragged = false;
|
||||
|
||||
@@ -155,7 +155,7 @@ define('taskbar', ['benchpress', 'translator'], function (Benchpress, translator
|
||||
|
||||
var taskbarEl = $('<li />')
|
||||
.addClass(data.options.className)
|
||||
.html('<a href="#"' + (data.options.image ? ' style="background-image: url(\'' + data.options.image + '\');"' : '') + '>' +
|
||||
.html('<a href="#"' + (data.options.image ? ' style="background-image: url(\'' + data.options.image + '\'); background-size: cover;"' : '') + '>' +
|
||||
(data.options.icon ? '<i class="fa ' + data.options.icon + '"></i> ' : '') +
|
||||
'<span component="taskbar/title">' + title + '</span>' +
|
||||
'</a>')
|
||||
|
||||
@@ -361,7 +361,7 @@
|
||||
|
||||
var nodes = descendantTextNodes(element);
|
||||
var text = nodes.map(function (node) {
|
||||
return node.nodeValue;
|
||||
return utils.escapeHTML(node.nodeValue);
|
||||
}).join(' || ');
|
||||
|
||||
var attrNodes = attributes.reduce(function (prev, attr) {
|
||||
|
||||
@@ -123,14 +123,16 @@ if (typeof window !== 'undefined') {
|
||||
$.timeago.settings.allowFuture = true;
|
||||
var userLang = config.userLang.replace('_', '-');
|
||||
var options = { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||
var formatFn;
|
||||
if (typeof Intl === 'undefined') {
|
||||
formatFn = function (date) {
|
||||
return date.toLocaleString(userLang, options);
|
||||
};
|
||||
} else {
|
||||
var dtFormat = new Intl.DateTimeFormat(userLang, options);
|
||||
formatFn = dtFormat.format;
|
||||
var formatFn = function (date) {
|
||||
return date.toLocaleString(userLang, options);
|
||||
};
|
||||
try {
|
||||
if (typeof Intl !== 'undefined') {
|
||||
var dtFormat = new Intl.DateTimeFormat(userLang, options);
|
||||
formatFn = dtFormat.format;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
var iso;
|
||||
|
||||
@@ -8,7 +8,7 @@ const _ = require('lodash');
|
||||
const versions = require('../../admin/versions');
|
||||
const db = require('../../database');
|
||||
const meta = require('../../meta');
|
||||
const analytics = require('../../analytics').async;
|
||||
const analytics = require('../../analytics');
|
||||
const plugins = require('../../plugins');
|
||||
const user = require('../../user');
|
||||
const utils = require('../../utils');
|
||||
@@ -93,7 +93,7 @@ dashboardController.getAnalytics = async (req, res, next) => {
|
||||
}
|
||||
|
||||
const method = req.query.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet;
|
||||
let payload = await Promise.all(sets.map(async set => method('analytics:' + set, until, count)));
|
||||
let payload = await Promise.all(sets.map(set => method('analytics:' + set, until, count)));
|
||||
payload = _.zipObject(sets, payload);
|
||||
|
||||
res.json({
|
||||
|
||||
@@ -86,8 +86,8 @@ apiController.loadConfig = async function (req) {
|
||||
config.usePagination = settings.usePagination;
|
||||
config.topicsPerPage = settings.topicsPerPage;
|
||||
config.postsPerPage = settings.postsPerPage;
|
||||
config.userLang = (req.query.lang ? validator.escape(String(req.query.lang)) : null) || settings.userLang || config.defaultLang;
|
||||
config.acpLang = (req.query.lang ? validator.escape(String(req.query.lang)) : null) || settings.acpLang;
|
||||
config.userLang = validator.escape(String((req.query.lang ? req.query.lang : null) || settings.userLang || config.defaultLang));
|
||||
config.acpLang = validator.escape(String((req.query.lang ? req.query.lang : null) || settings.acpLang));
|
||||
config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab;
|
||||
config.topicPostSort = settings.topicPostSort || config.topicPostSort;
|
||||
config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort;
|
||||
|
||||
@@ -316,8 +316,12 @@ authenticationController.doLogin = async function (req, uid) {
|
||||
};
|
||||
|
||||
authenticationController.onSuccessfulLogin = async function (req, uid) {
|
||||
// If already called once, return prematurely
|
||||
if (req.res.locals.user) {
|
||||
/*
|
||||
* Older code required that this method be called from within the SSO plugin.
|
||||
* That behaviour is no longer required, onSuccessfulLogin is now automatically
|
||||
* called in NodeBB core. However, if already called, return prematurely
|
||||
*/
|
||||
if (req.loggedIn && !req.session.forceLogin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ exports.get = async function (req, res, callback) {
|
||||
templateData: {},
|
||||
});
|
||||
|
||||
if (!data || !data.templateData) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
if (data.templateData.disabled) {
|
||||
res.render('', {
|
||||
title: '[[modules:composer.compose]]',
|
||||
|
||||
@@ -166,9 +166,9 @@ async function buildBreadcrumbs(topicData) {
|
||||
}
|
||||
|
||||
async function addTags(topicData, req, res) {
|
||||
var postAtIndex = topicData.posts.find(p => parseInt(p.index, 10) === parseInt(Math.max(0, req.params.post_index - 1), 10));
|
||||
|
||||
var description = '';
|
||||
const postIndex = parseInt(req.params.post_index, 10) || 0;
|
||||
const postAtIndex = topicData.posts.find(p => parseInt(p.index, 10) === parseInt(Math.max(0, postIndex - 1), 10));
|
||||
let description = '';
|
||||
if (postAtIndex && postAtIndex.content) {
|
||||
description = utils.stripHTMLTags(utils.decodeHTMLEntities(postAtIndex.content));
|
||||
}
|
||||
@@ -329,10 +329,10 @@ topicsController.pagination = async function (req, res, callback) {
|
||||
return helpers.notAllowed(req, res);
|
||||
}
|
||||
|
||||
var postCount = topic.postcount;
|
||||
var pageCount = Math.max(1, Math.ceil(postCount / settings.postsPerPage));
|
||||
const postCount = topic.postcount;
|
||||
const pageCount = Math.max(1, Math.ceil(postCount / settings.postsPerPage));
|
||||
|
||||
var paginationData = pagination.create(currentPage, pageCount);
|
||||
const paginationData = pagination.create(currentPage, pageCount);
|
||||
paginationData.rel.forEach(function (rel) {
|
||||
rel.href = nconf.get('url') + '/topic/' + topic.slug + rel.href;
|
||||
});
|
||||
|
||||
@@ -25,6 +25,7 @@ events.types = [
|
||||
'post-delete',
|
||||
'post-restore',
|
||||
'post-purge',
|
||||
'post-change-owner',
|
||||
'topic-delete',
|
||||
'topic-restore',
|
||||
'topic-purge',
|
||||
|
||||
@@ -10,7 +10,6 @@ const graceful = require('graceful-fs');
|
||||
const util = require('util');
|
||||
|
||||
const readdirAsync = util.promisify(fs.readdir);
|
||||
const mkdirpAsync = util.promisify(mkdirp);
|
||||
const copyFileAsync = util.promisify(fs.copyFile);
|
||||
const writeFleAsync = util.promisify(fs.writeFile);
|
||||
const statAsync = util.promisify(fs.stat);
|
||||
@@ -33,7 +32,7 @@ file.saveFileToLocal = async function (filename, folder, tempPath) {
|
||||
const uploadPath = path.join(nconf.get('upload_path'), folder, filename);
|
||||
|
||||
winston.verbose('Saving file ' + filename + ' to : ' + uploadPath);
|
||||
await mkdirpAsync(path.dirname(uploadPath));
|
||||
await mkdirp(path.dirname(uploadPath));
|
||||
await copyFileAsync(tempPath, uploadPath);
|
||||
return {
|
||||
url: '/assets/uploads/' + (folder ? folder + '/' : '') + filename,
|
||||
|
||||
68
src/flags.js
68
src/flags.js
@@ -19,6 +19,16 @@ const utils = require('../public/src/utils');
|
||||
|
||||
const Flags = module.exports;
|
||||
|
||||
Flags._constants = {
|
||||
states: ['open', 'wip', 'resolved', 'rejected'],
|
||||
state_class: {
|
||||
open: 'info',
|
||||
wip: 'warning',
|
||||
resolved: 'success',
|
||||
rejected: 'danger',
|
||||
},
|
||||
};
|
||||
|
||||
Flags.init = async function () {
|
||||
// Query plugins for custom filter strategies and merge into core filter strategies
|
||||
function prepareSets(sets, orSets, prefix, value) {
|
||||
@@ -162,13 +172,7 @@ Flags.list = async function (filters, uid) {
|
||||
'icon:text': userObj['icon:text'],
|
||||
},
|
||||
};
|
||||
const stateToLabel = {
|
||||
open: 'info',
|
||||
wip: 'warning',
|
||||
resolved: 'success',
|
||||
rejected: 'danger',
|
||||
};
|
||||
flagObj.labelClass = stateToLabel[flagObj.state];
|
||||
flagObj.labelClass = Flags._constants.state_class[flagObj.state];
|
||||
|
||||
return Object.assign(flagObj, {
|
||||
description: validator.escape(String(flagObj.description)),
|
||||
@@ -247,18 +251,21 @@ Flags.create = async function (type, id, uid, reason, timestamp) {
|
||||
timestamp = Date.now();
|
||||
doHistoryAppend = true;
|
||||
}
|
||||
const [exists, targetExists, targetUid, targetCid] = await Promise.all([
|
||||
const [flagExists, targetExists, canFlag, targetUid, targetCid] = await Promise.all([
|
||||
// Sanity checks
|
||||
Flags.exists(type, id, uid),
|
||||
Flags.targetExists(type, id),
|
||||
Flags.canFlag(type, id, uid),
|
||||
// Extra data for zset insertion
|
||||
Flags.getTargetUid(type, id),
|
||||
Flags.getTargetCid(type, id),
|
||||
]);
|
||||
if (exists) {
|
||||
if (flagExists) {
|
||||
throw new Error('[[error:already-flagged]]');
|
||||
} else if (!targetExists) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
} else if (!canFlag) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
const flagId = await db.incrObjectField('global', 'nextFlagId');
|
||||
|
||||
@@ -303,6 +310,16 @@ Flags.exists = async function (type, id, uid) {
|
||||
return await db.isSortedSetMember('flags:hash', [type, id, uid].join(':'));
|
||||
};
|
||||
|
||||
Flags.canFlag = async function (type, id, uid) {
|
||||
if (type === 'user') {
|
||||
return true;
|
||||
}
|
||||
if (type === 'post') {
|
||||
return await privileges.posts.can('topics:read', id, uid);
|
||||
}
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
};
|
||||
|
||||
Flags.getTarget = async function (type, id, uid) {
|
||||
if (type === 'user') {
|
||||
const userData = await user.getUserData(id);
|
||||
@@ -344,6 +361,7 @@ Flags.getTargetCid = async function (type, id) {
|
||||
};
|
||||
|
||||
Flags.update = async function (flagId, uid, changeset) {
|
||||
const current = await db.getObjectFields('flag:' + flagId, ['state', 'assignee', 'type', 'targetId']);
|
||||
const now = changeset.datetime || Date.now();
|
||||
const notifyAssignee = async function (assigneeId) {
|
||||
if (assigneeId === '' || parseInt(uid, 10) === parseInt(assigneeId, 10)) {
|
||||
@@ -359,20 +377,40 @@ Flags.update = async function (flagId, uid, changeset) {
|
||||
});
|
||||
await notifications.push(notifObj, [assigneeId]);
|
||||
};
|
||||
const isAssignable = async function (assigneeId) {
|
||||
let allowed = false;
|
||||
allowed = await user.isAdminOrGlobalMod(assigneeId);
|
||||
|
||||
// Retrieve existing flag data to compare for history-saving purposes
|
||||
const current = await db.getObjectFields('flag:' + flagId, ['state', 'assignee']);
|
||||
// Mods are also allowed to be assigned, if flag target is post in uid's moderated cid
|
||||
if (!allowed && current.type === 'post') {
|
||||
const cid = await posts.getCidByPid(current.targetId);
|
||||
allowed = await user.isModerator(assigneeId, cid);
|
||||
}
|
||||
|
||||
return allowed;
|
||||
};
|
||||
|
||||
// Retrieve existing flag data to compare for history-saving/reference purposes
|
||||
const tasks = [];
|
||||
for (var prop in changeset) {
|
||||
if (changeset.hasOwnProperty(prop)) {
|
||||
if (current[prop] === changeset[prop]) {
|
||||
delete changeset[prop];
|
||||
} else if (prop === 'state') {
|
||||
tasks.push(db.sortedSetAdd('flags:byState:' + changeset[prop], now, flagId));
|
||||
tasks.push(db.sortedSetRemove('flags:byState:' + current[prop], flagId));
|
||||
if (!Flags._constants.states.includes(changeset[prop])) {
|
||||
delete changeset[prop];
|
||||
} else {
|
||||
tasks.push(db.sortedSetAdd('flags:byState:' + changeset[prop], now, flagId));
|
||||
tasks.push(db.sortedSetRemove('flags:byState:' + current[prop], flagId));
|
||||
}
|
||||
} else if (prop === 'assignee') {
|
||||
tasks.push(db.sortedSetAdd('flags:byAssignee:' + changeset[prop], now, flagId));
|
||||
tasks.push(notifyAssignee(changeset[prop]));
|
||||
/* eslint-disable-next-line */
|
||||
if (!await isAssignable(parseInt(changeset[prop], 10))) {
|
||||
delete changeset[prop];
|
||||
} else {
|
||||
tasks.push(db.sortedSetAdd('flags:byAssignee:' + changeset[prop], now, flagId));
|
||||
tasks.push(notifyAssignee(changeset[prop]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ module.exports = function (Groups) {
|
||||
const disableLeave = parseInt(data.disableLeave, 10) === 1 ? 1 : 0;
|
||||
const isHidden = parseInt(data.hidden, 10) === 1;
|
||||
|
||||
validateGroupName(data.name);
|
||||
Groups.validateGroupName(data.name);
|
||||
|
||||
const exists = await meta.userOrGroupExists(data.name);
|
||||
if (exists) {
|
||||
@@ -72,11 +72,15 @@ module.exports = function (Groups) {
|
||||
Groups.isPrivilegeGroup(data.name);
|
||||
}
|
||||
|
||||
function validateGroupName(name) {
|
||||
Groups.validateGroupName = function (name) {
|
||||
if (!name) {
|
||||
throw new Error('[[error:group-name-too-short]]');
|
||||
}
|
||||
|
||||
if (typeof name !== 'string') {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
|
||||
if (!Groups.isPrivilegeGroup(name) && name.length > meta.config.maximumGroupNameLength) {
|
||||
throw new Error('[[error:group-name-too-long]]');
|
||||
}
|
||||
@@ -88,5 +92,5 @@ module.exports = function (Groups) {
|
||||
if (name.includes('/') || !utils.slugify(name)) {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -53,6 +53,11 @@ module.exports = function (Groups) {
|
||||
return Array.isArray(groupsData) && groupsData[0] ? groupsData[0] : null;
|
||||
};
|
||||
|
||||
Groups.getGroupField = async function (groupName, field) {
|
||||
const groupData = await Groups.getGroupFields(groupName, [field]);
|
||||
return groupData ? groupData[field] : null;
|
||||
};
|
||||
|
||||
Groups.getGroupFields = async function (groupName, fields) {
|
||||
const groups = await Groups.getGroupsFields([groupName], fields);
|
||||
return groups ? groups[0] : null;
|
||||
|
||||
@@ -78,7 +78,8 @@ Groups.getGroupsBySort = async function (sort, start, stop) {
|
||||
Groups.getNonPrivilegeGroups = async function (set, start, stop) {
|
||||
let groupNames = await db.getSortedSetRevRange(set, start, stop);
|
||||
groupNames = groupNames.concat(Groups.ephemeralGroups).filter(groupName => !Groups.isPrivilegeGroup(groupName));
|
||||
return await Groups.getGroupsData(groupNames);
|
||||
const groupsData = await Groups.getGroupsData(groupNames);
|
||||
return groupsData.filter(Boolean);
|
||||
};
|
||||
|
||||
Groups.getGroups = async function (set, start, stop) {
|
||||
|
||||
@@ -54,7 +54,10 @@ module.exports = function (Groups) {
|
||||
payload.disableLeave = values.disableLeave ? '1' : '0';
|
||||
}
|
||||
|
||||
await checkNameChange(groupName, values.name);
|
||||
if (values.hasOwnProperty('name')) {
|
||||
await checkNameChange(groupName, values.name);
|
||||
}
|
||||
|
||||
if (values.hasOwnProperty('private')) {
|
||||
await updatePrivacy(groupName, values.private);
|
||||
}
|
||||
@@ -125,6 +128,10 @@ module.exports = function (Groups) {
|
||||
}
|
||||
|
||||
async function checkNameChange(currentName, newName) {
|
||||
Groups.validateGroupName(newName);
|
||||
if (Groups.isPrivilegeGroup(newName)) {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
const currentSlug = utils.slugify(currentName);
|
||||
const newSlug = utils.slugify(newName);
|
||||
if (currentName === newName || currentSlug === newSlug) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var meta = require('../meta');
|
||||
var plugins = require('../plugins');
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
const meta = require('../meta');
|
||||
const plugins = require('../plugins');
|
||||
const db = require('../database');
|
||||
const user = require('../user');
|
||||
|
||||
module.exports = function (Messaging) {
|
||||
Messaging.sendMessage = async (data) => {
|
||||
@@ -21,7 +21,7 @@ module.exports = function (Messaging) {
|
||||
throw new Error('[[error:invalid-chat-message]]');
|
||||
}
|
||||
|
||||
const maximumChatMessageLength = (meta.config.maximumChatMessageLength || 1000);
|
||||
const maximumChatMessageLength = meta.config.maximumChatMessageLength || 1000;
|
||||
const data = await plugins.fireHook('filter:messaging.checkContent', { content: content });
|
||||
content = String(data.content).trim();
|
||||
if (!content) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
var utils = require('../utils');
|
||||
var plugins = require('../plugins');
|
||||
const validator = require('validator');
|
||||
|
||||
const db = require('../database');
|
||||
const user = require('../user');
|
||||
const utils = require('../utils');
|
||||
const plugins = require('../plugins');
|
||||
|
||||
const intFields = ['timestamp', 'edited', 'fromuid', 'roomId', 'deleted', 'system'];
|
||||
|
||||
@@ -79,6 +81,7 @@ module.exports = function (Messaging) {
|
||||
|
||||
messages = await Promise.all(messages.map(async (message) => {
|
||||
if (message.system) {
|
||||
message.content = validator.escape(String(message.content));
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@ module.exports = function (Messaging) {
|
||||
throw new Error('[[error:chat-' + field + '-already]]');
|
||||
}
|
||||
|
||||
return await Messaging.setMessageField(mid, 'deleted', state);
|
||||
await Messaging.setMessageField(mid, 'deleted', state);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var meta = require('../meta');
|
||||
var user = require('../user');
|
||||
const meta = require('../meta');
|
||||
const user = require('../user');
|
||||
|
||||
var sockets = require('../socket.io');
|
||||
const sockets = require('../socket.io');
|
||||
|
||||
|
||||
module.exports = function (Messaging) {
|
||||
@@ -57,18 +57,18 @@ module.exports = function (Messaging) {
|
||||
|
||||
const [isAdmin, messageData] = await Promise.all([
|
||||
user.isAdministrator(uid),
|
||||
Messaging.getMessageFields(messageId, ['fromuid', 'timestamp']),
|
||||
Messaging.getMessageFields(messageId, ['fromuid', 'timestamp', 'system']),
|
||||
]);
|
||||
|
||||
if (isAdmin) {
|
||||
if (isAdmin && !messageData.system) {
|
||||
return;
|
||||
}
|
||||
var chatConfigDuration = meta.config[durationConfig];
|
||||
const chatConfigDuration = meta.config[durationConfig];
|
||||
if (chatConfigDuration && Date.now() - messageData.timestamp > chatConfigDuration * 1000) {
|
||||
throw new Error('[[error:chat-' + type + '-duration-expired, ' + meta.config[durationConfig] + ']]');
|
||||
}
|
||||
|
||||
if (messageData.fromuid === parseInt(uid, 10)) {
|
||||
if (messageData.fromuid === parseInt(uid, 10) && !messageData.system) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,14 +139,8 @@ Messaging.getRecentChats = async (callerUid, uid, start, stop) => {
|
||||
});
|
||||
};
|
||||
|
||||
Messaging.generateUsernames = (users, excludeUid) => {
|
||||
users = users.filter(function (user) {
|
||||
return user && parseInt(user.uid, 10) !== excludeUid;
|
||||
});
|
||||
return users.map(function (user) {
|
||||
return user.username;
|
||||
}).join(', ');
|
||||
};
|
||||
Messaging.generateUsernames = (users, excludeUid) => users.filter(user => user && parseInt(user.uid, 10) !== excludeUid)
|
||||
.map(user => user.username).join(', ');
|
||||
|
||||
Messaging.getTeaser = async (uid, roomId) => {
|
||||
const mid = await Messaging.getLatestUndeletedMessage(uid, roomId);
|
||||
|
||||
@@ -20,10 +20,7 @@ module.exports = function (Messaging) {
|
||||
};
|
||||
|
||||
Messaging.getRoomsData = async (roomIds) => {
|
||||
const roomData = await db.getObjects(roomIds.map(function (roomId) {
|
||||
return 'chat:room:' + roomId;
|
||||
}));
|
||||
|
||||
const roomData = await db.getObjects(roomIds.map(roomId => 'chat:room:' + roomId));
|
||||
modifyRoomData(roomData);
|
||||
return roomData;
|
||||
};
|
||||
@@ -53,6 +50,7 @@ module.exports = function (Messaging) {
|
||||
db.sortedSetAdd('chat:room:' + roomId + ':uids', now, uid),
|
||||
]);
|
||||
await Promise.all([
|
||||
Messaging.addSystemMessage('user-join', uid, roomId), // chat owner should also get the user-join system message
|
||||
Messaging.addUsersToRoom(uid, toUids, roomId),
|
||||
Messaging.addRoomToUsers(roomId, [uid].concat(toUids), now),
|
||||
]);
|
||||
@@ -61,7 +59,7 @@ module.exports = function (Messaging) {
|
||||
};
|
||||
|
||||
Messaging.isUserInRoom = async (uid, roomId) => {
|
||||
const inRoom = db.isSortedSetMember('chat:room:' + roomId + ':uids', uid);
|
||||
const inRoom = await db.isSortedSetMember('chat:room:' + roomId + ':uids', uid);
|
||||
const data = await plugins.fireHook('filter:messaging.isUserInRoom', { uid: uid, roomId: roomId, inRoom: inRoom });
|
||||
return data.inRoom;
|
||||
};
|
||||
@@ -113,6 +111,9 @@ module.exports = function (Messaging) {
|
||||
};
|
||||
|
||||
Messaging.leaveRoom = async (uids, roomId) => {
|
||||
const isInRoom = await Promise.all(uids.map(uid => Messaging.isUserInRoom(uid, roomId)));
|
||||
uids = uids.filter((uid, index) => isInRoom[index]);
|
||||
|
||||
const keys = uids
|
||||
.map(uid => 'uid:' + uid + ':chat:rooms')
|
||||
.concat(uids.map(uid => 'uid:' + uid + ':chat:rooms:unread'));
|
||||
@@ -127,6 +128,9 @@ module.exports = function (Messaging) {
|
||||
};
|
||||
|
||||
Messaging.leaveRooms = async (uid, roomIds) => {
|
||||
const isInRoom = await Promise.all(roomIds.map(roomId => Messaging.isUserInRoom(uid, roomId)));
|
||||
roomIds = roomIds.filter((roomId, index) => isInRoom[index]);
|
||||
|
||||
const roomKeys = roomIds.map(roomId => 'chat:room:' + roomId + ':uids');
|
||||
await Promise.all([
|
||||
db.sortedSetsRemove(roomKeys, uid),
|
||||
@@ -192,7 +196,7 @@ module.exports = function (Messaging) {
|
||||
};
|
||||
|
||||
Messaging.canReply = async (roomId, uid) => {
|
||||
const inRoom = db.isSortedSetMember('chat:room:' + roomId + ':uids', uid);
|
||||
const inRoom = await db.isSortedSetMember('chat:room:' + roomId + ':uids', uid);
|
||||
const data = await plugins.fireHook('filter:messaging.canReply', { uid: uid, roomId: roomId, inRoom: inRoom, canReply: inRoom });
|
||||
return data.canReply;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const ipaddr = require('ipaddr.js');
|
||||
const winston = require('winston');
|
||||
const _ = require('lodash');
|
||||
const validator = require('validator');
|
||||
|
||||
const db = require('../database');
|
||||
const pubsub = require('../pubsub');
|
||||
@@ -128,7 +129,7 @@ Blacklist.validate = function (rules) {
|
||||
}
|
||||
|
||||
if (!addr || whitelist.includes(rule)) {
|
||||
invalid.push(rule);
|
||||
invalid.push(validator.escape(rule));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const os = require('os');
|
||||
const async = require('async');
|
||||
const winston = require('winston');
|
||||
const nconf = require('nconf');
|
||||
@@ -150,7 +151,14 @@ exports.build = function (targets, options, callback) {
|
||||
targets = targets.split(',');
|
||||
}
|
||||
|
||||
var parallel = !nconf.get('series') && !options.series;
|
||||
let series = nconf.get('series') || options.series;
|
||||
if (series === undefined) {
|
||||
// Detect # of CPUs and select strategy as appropriate
|
||||
winston.verbose('[build] Querying CPU core count for build strategy');
|
||||
const cpus = os.cpus();
|
||||
series = cpus.length < 4;
|
||||
winston.verbose('[build] System returned ' + cpus.length + ' cores, opting for ' + (series ? 'series' : 'parallel') + ' build strategy');
|
||||
}
|
||||
|
||||
targets = targets
|
||||
// get full target name
|
||||
@@ -195,14 +203,14 @@ exports.build = function (targets, options, callback) {
|
||||
require('./minifier').maxThreads = threads - 1;
|
||||
}
|
||||
|
||||
if (parallel) {
|
||||
if (!series) {
|
||||
winston.info('[build] Building in parallel mode');
|
||||
} else {
|
||||
winston.info('[build] Building in series mode');
|
||||
}
|
||||
|
||||
startTime = Date.now();
|
||||
buildTargets(targets, parallel, next);
|
||||
buildTargets(targets, !series, next);
|
||||
},
|
||||
function (next) {
|
||||
totalTime = (Date.now() - startTime) / 1000;
|
||||
|
||||
@@ -5,7 +5,6 @@ const path = require('path');
|
||||
const mkdirp = require('mkdirp');
|
||||
const winston = require('winston');
|
||||
const util = require('util');
|
||||
const mkdirpAsync = util.promisify(mkdirp);
|
||||
const writeFileAsync = util.promisify(fs.writeFile);
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
|
||||
@@ -19,7 +18,7 @@ function generate() {
|
||||
}
|
||||
|
||||
exports.write = async function write() {
|
||||
await mkdirpAsync(path.dirname(filePath));
|
||||
await mkdirp(path.dirname(filePath));
|
||||
await writeFileAsync(filePath, generate());
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,16 @@
|
||||
var path = require('path');
|
||||
var async = require('async');
|
||||
var fs = require('fs');
|
||||
const util = require('util');
|
||||
var mkdirp = require('mkdirp');
|
||||
var mkdirpCallback;
|
||||
if (mkdirp.hasOwnProperty('native')) {
|
||||
mkdirpCallback = util.callbackify(mkdirp);
|
||||
} else {
|
||||
mkdirpCallback = mkdirp;
|
||||
mkdirp = util.promisify(mkdirp);
|
||||
}
|
||||
|
||||
var rimraf = require('rimraf');
|
||||
|
||||
var file = require('../file');
|
||||
@@ -119,7 +128,7 @@ function minifyModules(modules, fork, callback) {
|
||||
return prev;
|
||||
}, []);
|
||||
|
||||
async.each(moduleDirs, mkdirp, function (err) {
|
||||
async.each(moduleDirs, mkdirpCallback, function (err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -156,7 +165,7 @@ function linkModules(callback) {
|
||||
|
||||
async.parallel({
|
||||
dir: function (cb) {
|
||||
mkdirp(path.dirname(destPath), function (err) {
|
||||
mkdirpCallback(path.dirname(destPath), function (err) {
|
||||
cb(err);
|
||||
});
|
||||
},
|
||||
@@ -272,7 +281,7 @@ JS.linkStatics = function (callback) {
|
||||
var sourceDir = plugins.staticDirs[mappedPath];
|
||||
var destDir = path.join(__dirname, '../../build/public/plugins', mappedPath);
|
||||
|
||||
mkdirp(path.dirname(destDir), function (err) {
|
||||
mkdirpCallback(path.dirname(destDir), function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -343,7 +352,7 @@ JS.buildBundle = function (target, fork, callback) {
|
||||
getBundleScriptList(target, next);
|
||||
},
|
||||
function (files, next) {
|
||||
mkdirp(path.join(__dirname, '../../build/public'), function (err) {
|
||||
mkdirpCallback(path.join(__dirname, '../../build/public'), function (err) {
|
||||
next(err, files);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const mkdirp = require('mkdirp');
|
||||
const util = require('util');
|
||||
let mkdirp = require('mkdirp');
|
||||
mkdirp = mkdirp.hasOwnProperty('native') ? mkdirp : util.promisify(mkdirp);
|
||||
const rimraf = require('rimraf');
|
||||
const _ = require('lodash');
|
||||
|
||||
const util = require('util');
|
||||
const mkdirpAsync = util.promisify(mkdirp);
|
||||
const rimrafAsync = util.promisify(rimraf);
|
||||
const writeFileAsync = util.promisify(fs.writeFile);
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
@@ -46,7 +46,7 @@ async function getTranslationMetadata() {
|
||||
|
||||
// save a list of languages to `${buildLanguagesPath}/metadata.json`
|
||||
// avoids readdirs later on
|
||||
await mkdirpAsync(buildLanguagesPath);
|
||||
await mkdirp(buildLanguagesPath);
|
||||
const result = {
|
||||
languages: languages,
|
||||
namespaces: namespaces,
|
||||
@@ -59,7 +59,7 @@ async function writeLanguageFile(language, namespace, translations) {
|
||||
const dev = global.env === 'development';
|
||||
const filePath = path.join(buildLanguagesPath, language, namespace + '.json');
|
||||
|
||||
await mkdirpAsync(path.dirname(filePath));
|
||||
await mkdirp(path.dirname(filePath));
|
||||
await writeFileAsync(filePath, JSON.stringify(translations, null, dev ? 2 : 0));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const rimraf = require('rimraf');
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
const util = require('util');
|
||||
|
||||
const rimraf = require('rimraf');
|
||||
let mkdirp = require('mkdirp');
|
||||
mkdirp = mkdirp.hasOwnProperty('native') ? mkdirp : util.promisify(mkdirp);
|
||||
|
||||
const readdirAsync = util.promisify(fs.readdir);
|
||||
const rimrafAsync = util.promisify(rimraf);
|
||||
const mkdirpAsync = util.promisify(mkdirp);
|
||||
const writeFileAsync = util.promisify(fs.writeFile);
|
||||
|
||||
const file = require('../file');
|
||||
@@ -70,7 +70,7 @@ Sounds.build = async function build() {
|
||||
map.unshift({});
|
||||
map = Object.assign.apply(null, map);
|
||||
await rimrafAsync(soundsPath);
|
||||
await mkdirpAsync(soundsPath);
|
||||
await mkdirp(soundsPath);
|
||||
|
||||
await writeFileAsync(path.join(soundsPath, 'fileMap.json'), JSON.stringify(map));
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const mkdirp = require('mkdirp');
|
||||
const util = require('util');
|
||||
let mkdirp = require('mkdirp');
|
||||
mkdirp = mkdirp.hasOwnProperty('native') ? mkdirp : util.promisify(mkdirp);
|
||||
const rimraf = require('rimraf');
|
||||
const winston = require('winston');
|
||||
const path = require('path');
|
||||
|
||||
const util = require('util');
|
||||
const fs = require('fs');
|
||||
const fsReadFile = util.promisify(fs.readFile);
|
||||
const fsWriteFile = util.promisify(fs.writeFile);
|
||||
@@ -123,10 +123,9 @@ Templates.compileTemplate = compileTemplate;
|
||||
|
||||
async function compile() {
|
||||
const _rimraf = util.promisify(rimraf);
|
||||
const _mkdirp = util.promisify(mkdirp);
|
||||
|
||||
await _rimraf(viewsPath);
|
||||
await _mkdirp(viewsPath);
|
||||
await mkdirp(viewsPath);
|
||||
|
||||
let files = await db.getSortedSetRange('plugins:active', 0, -1);
|
||||
files = await getTemplateDirs(files);
|
||||
@@ -137,7 +136,7 @@ async function compile() {
|
||||
let imported = await fsReadFile(filePath, 'utf8');
|
||||
imported = await processImports(files, name, imported);
|
||||
|
||||
await _mkdirp(path.join(viewsPath, path.dirname(name)));
|
||||
await mkdirp(path.join(viewsPath, path.dirname(name)));
|
||||
|
||||
await fsWriteFile(path.join(viewsPath, name), imported);
|
||||
const compiled = await Benchpress.precompile(imported, { minify: global.env !== 'development' });
|
||||
|
||||
@@ -148,40 +148,40 @@ module.exports = function (Plugins) {
|
||||
if (!Array.isArray(hookList) || !hookList.length) {
|
||||
return;
|
||||
}
|
||||
// don't bubble errors from these hooks, so bad plugins don't stop startup
|
||||
const noErrorHooks = ['static:app.load', 'static:assets.prepare', 'static:app.preload'];
|
||||
await async.each(hookList, function (hookObj, next) {
|
||||
if (typeof hookObj.method === 'function') {
|
||||
let timedOut = false;
|
||||
const timeoutId = setTimeout(function () {
|
||||
winston.warn('[plugins] Callback timed out, hook \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
|
||||
timedOut = true;
|
||||
next();
|
||||
}, 5000);
|
||||
if (typeof hookObj.method !== 'function') {
|
||||
return next();
|
||||
}
|
||||
|
||||
const onError = (err) => {
|
||||
winston.error('[plugins] Error executing \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
|
||||
winston.error(err);
|
||||
clearTimeout(timeoutId);
|
||||
next();
|
||||
};
|
||||
const callback = (...args) => {
|
||||
clearTimeout(timeoutId);
|
||||
if (!timedOut) {
|
||||
next(...args);
|
||||
}
|
||||
};
|
||||
try {
|
||||
const returned = hookObj.method(params, callback);
|
||||
if (utils.isPromise(returned)) {
|
||||
returned.then(
|
||||
payload => setImmediate(callback, null, payload),
|
||||
err => setImmediate(onError, err)
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
onError(err);
|
||||
}
|
||||
} else {
|
||||
let timedOut = false;
|
||||
const timeoutId = setTimeout(function () {
|
||||
winston.warn('[plugins] Callback timed out, hook \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
|
||||
timedOut = true;
|
||||
next();
|
||||
}, 5000);
|
||||
|
||||
const callback = (err) => {
|
||||
clearTimeout(timeoutId);
|
||||
if (err) {
|
||||
winston.error('[plugins] Error executing \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
|
||||
winston.error(err.stack);
|
||||
}
|
||||
if (!timedOut) {
|
||||
next(noErrorHooks.includes(hook) ? null : err);
|
||||
}
|
||||
};
|
||||
try {
|
||||
const returned = hookObj.method(params, callback);
|
||||
if (utils.isPromise(returned)) {
|
||||
returned.then(
|
||||
payload => setImmediate(callback, null, payload),
|
||||
err => setImmediate(callback, err)
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ module.exports = function (Posts) {
|
||||
};
|
||||
|
||||
Posts.changeOwner = async function (pids, toUid) {
|
||||
const exists = user.exists(toUid);
|
||||
const exists = await user.exists(toUid);
|
||||
if (!exists) {
|
||||
throw new Error('[[error:no-user]]');
|
||||
}
|
||||
@@ -163,6 +163,7 @@ module.exports = function (Posts) {
|
||||
reduceCounters(postsByUser),
|
||||
updateTopicPosters(postData, toUid),
|
||||
]);
|
||||
return postData;
|
||||
};
|
||||
|
||||
async function reduceCounters(postsByUser) {
|
||||
|
||||
@@ -110,6 +110,7 @@ module.exports = function (privileges) {
|
||||
return await utils.promiseParallel({
|
||||
categories: categories.getCategoriesFields(cids, ['disabled']),
|
||||
allowedTo: helpers.isUserAllowedTo(privilege, uid, cids),
|
||||
view_deleted: helpers.isUserAllowedTo('posts:view_deleted', uid, cids),
|
||||
isAdmin: user.isAdministrator(uid),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -88,16 +88,17 @@ module.exports = function (privileges) {
|
||||
cids = _.uniq(cids);
|
||||
|
||||
const results = await privileges.categories.getBase(privilege, cids, uid);
|
||||
cids = cids.filter(function (cid, index) {
|
||||
const allowedCids = cids.filter(function (cid, index) {
|
||||
return !results.categories[index].disabled &&
|
||||
(results.allowedTo[index] || results.isAdmin);
|
||||
});
|
||||
|
||||
const cidsSet = new Set(cids);
|
||||
const cidsSet = new Set(allowedCids);
|
||||
const canViewDeleted = _.zipObject(cids, results.view_deleted);
|
||||
|
||||
pids = postData.filter(function (post) {
|
||||
return post.topic && cidsSet.has(post.topic.cid) &&
|
||||
((!post.topic.deleted && !post.deleted) || results.isAdmin);
|
||||
((!post.topic.deleted && !post.deleted) || canViewDeleted[post.topic.cid] || results.isAdmin);
|
||||
}).map(post => post.pid);
|
||||
|
||||
const data = await plugins.fireHook('filter:privileges.posts.filter', {
|
||||
|
||||
@@ -68,14 +68,15 @@ module.exports = function (privileges) {
|
||||
}
|
||||
|
||||
const topicsData = await topics.getTopicsFields(tids, ['tid', 'cid', 'deleted']);
|
||||
let cids = _.uniq(topicsData.map(topic => topic.cid));
|
||||
const cids = _.uniq(topicsData.map(topic => topic.cid));
|
||||
const results = await privileges.categories.getBase(privilege, cids, uid);
|
||||
|
||||
cids = cids.filter((cid, index) => !results.categories[index].disabled && (results.allowedTo[index] || results.isAdmin));
|
||||
const allowedCids = cids.filter((cid, index) => !results.categories[index].disabled && (results.allowedTo[index] || results.isAdmin));
|
||||
|
||||
const cidsSet = new Set(cids);
|
||||
const cidsSet = new Set(allowedCids);
|
||||
const canViewDeleted = _.zipObject(cids, results.view_deleted);
|
||||
|
||||
tids = topicsData.filter(t => cidsSet.has(t.cid) && (!t.deleted || results.isAdmin)).map(t => t.tid);
|
||||
tids = topicsData.filter(t => cidsSet.has(t.cid) && (!t.deleted || canViewDeleted[t.cid] || results.isAdmin)).map(t => t.tid);
|
||||
|
||||
const data = await plugins.fireHook('filter:privileges.topics.filter', {
|
||||
privilege: privilege,
|
||||
@@ -115,7 +116,7 @@ module.exports = function (privileges) {
|
||||
};
|
||||
|
||||
privileges.topics.canDelete = async function (tid, uid) {
|
||||
const topicData = await topics.getTopicFields(tid, ['cid', 'postcount']);
|
||||
const topicData = await topics.getTopicFields(tid, ['uid', 'cid', 'postcount', 'deleterUid']);
|
||||
const [isModerator, isAdministrator, isOwner, allowedTo] = await Promise.all([
|
||||
user.isModerator(uid, topicData.cid),
|
||||
user.isAdministrator(uid),
|
||||
@@ -135,7 +136,8 @@ module.exports = function (privileges) {
|
||||
throw new Error(langKey);
|
||||
}
|
||||
|
||||
return allowedTo[0] && (isOwner || isModerator);
|
||||
const deleterUid = topicData.deleterUid;
|
||||
return allowedTo[0] && ((isOwner && (deleterUid === 0 || deleterUid === topicData.uid)) || isModerator);
|
||||
};
|
||||
|
||||
privileges.topics.canEdit = async function (tid, uid) {
|
||||
|
||||
@@ -21,7 +21,7 @@ SocketFlags.create = async function (socket, data) {
|
||||
|
||||
const flagObj = await flags.create(data.type, data.id, socket.uid, data.reason);
|
||||
await flags.notify(flagObj, socket.uid);
|
||||
return flagObj;
|
||||
return flagObj.flagId;
|
||||
};
|
||||
|
||||
SocketFlags.update = async function (socket, data) {
|
||||
|
||||
@@ -22,6 +22,10 @@ SocketGroups.join = async (socket, data) => {
|
||||
throw new Error('[[error:invalid-uid]]');
|
||||
}
|
||||
|
||||
if (typeof data.groupName !== 'string') {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
|
||||
if (data.groupName === 'administrators' || groups.isPrivilegeGroup(data.groupName)) {
|
||||
throw new Error('[[error:not-allowed]]');
|
||||
}
|
||||
@@ -66,6 +70,10 @@ SocketGroups.leave = async (socket, data) => {
|
||||
throw new Error('[[error:invalid-uid]]');
|
||||
}
|
||||
|
||||
if (typeof data.groupName !== 'string') {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
|
||||
if (data.groupName === 'administrators') {
|
||||
throw new Error('[[error:cant-remove-self-as-admin]]');
|
||||
}
|
||||
@@ -104,6 +112,9 @@ SocketGroups.addMember = async (socket, data) => {
|
||||
};
|
||||
|
||||
async function isOwner(socket, data) {
|
||||
if (typeof data.groupName !== 'string') {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
const results = await utils.promiseParallel({
|
||||
isAdmin: await user.isAdministrator(socket.uid),
|
||||
isGlobalModerator: await user.isGlobalModerator(socket.uid),
|
||||
@@ -118,6 +129,9 @@ async function isOwner(socket, data) {
|
||||
}
|
||||
|
||||
async function isInvited(socket, data) {
|
||||
if (typeof data.groupName !== 'string') {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
const invited = await groups.isInvited(socket.uid, data.groupName);
|
||||
if (!invited) {
|
||||
throw new Error('[[error:not-invited]]');
|
||||
@@ -171,6 +185,9 @@ SocketGroups.rejectAll = async (socket, data) => {
|
||||
};
|
||||
|
||||
async function acceptRejectAll(method, socket, data) {
|
||||
if (typeof data.groupName !== 'string') {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
const uids = await groups.getPending(data.groupName);
|
||||
await Promise.all(uids.map(async (uid) => {
|
||||
await method(socket, { groupName: data.groupName, toUid: uid });
|
||||
@@ -251,7 +268,7 @@ SocketGroups.kick = async (socket, data) => {
|
||||
SocketGroups.create = async (socket, data) => {
|
||||
if (!socket.uid) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
} else if (groups.isPrivilegeGroup(data.name)) {
|
||||
} else if (typeof data.name !== 'string' || groups.isPrivilegeGroup(data.name)) {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
|
||||
@@ -260,6 +277,7 @@ SocketGroups.create = async (socket, data) => {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
data.ownerUid = socket.uid;
|
||||
data.system = false;
|
||||
const groupData = await groups.create(data);
|
||||
logGroupEvent(socket, 'group-create', {
|
||||
groupName: data.name,
|
||||
@@ -338,7 +356,6 @@ SocketGroups.cover.update = async (socket, data) => {
|
||||
if (!socket.uid) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
|
||||
await canModifyGroup(socket.uid, data.groupName);
|
||||
return await groups.updateCover(socket.uid, data);
|
||||
};
|
||||
@@ -353,12 +370,17 @@ SocketGroups.cover.remove = async (socket, data) => {
|
||||
};
|
||||
|
||||
async function canModifyGroup(uid, groupName) {
|
||||
if (typeof groupName !== 'string') {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
const results = await utils.promiseParallel({
|
||||
isOwner: groups.ownership.isOwner(uid, groupName),
|
||||
isAdminOrGlobalMod: user.isAdminOrGlobalMod(uid),
|
||||
system: groups.getGroupField(groupName, 'system'),
|
||||
isAdmin: user.isAdministrator(uid),
|
||||
isGlobalMod: user.isGlobalModerator(uid),
|
||||
});
|
||||
|
||||
if (!results.isOwner && !results.isAdminOrGlobalMod) {
|
||||
if (!(results.isOwner || results.isAdmin || (results.isGlobalMod && !results.system))) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,11 +113,14 @@ SocketModules.chats.getUsersInRoom = async function (socket, data) {
|
||||
if (!data || !data.roomId) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
const [userData, isOwner] = await Promise.all([
|
||||
Messaging.getUsersInRoom(data.roomId, 0, -1),
|
||||
const [isUserInRoom, isOwner, userData] = await Promise.all([
|
||||
Messaging.isUserInRoom(socket.uid, data.roomId),
|
||||
Messaging.isRoomOwner(socket.uid, data.roomId),
|
||||
Messaging.getUsersInRoom(data.roomId, 0, -1),
|
||||
]);
|
||||
|
||||
if (!isUserInRoom) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
userData.forEach((user) => {
|
||||
user.canKick = (parseInt(user.uid, 10) !== parseInt(socket.uid, 10)) && isOwner;
|
||||
});
|
||||
|
||||
@@ -167,11 +167,22 @@ module.exports = function (SocketPosts) {
|
||||
if (!data || !Array.isArray(data.pids) || !data.toUid) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
const isAdminOrGlobalMod = user.isAdminOrGlobalMod(socket.uid);
|
||||
const isAdminOrGlobalMod = await user.isAdminOrGlobalMod(socket.uid);
|
||||
if (!isAdminOrGlobalMod) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
|
||||
await posts.changeOwner(data.pids, data.toUid);
|
||||
var postData = await posts.changeOwner(data.pids, data.toUid);
|
||||
var logs = postData.map(({ pid, uid, cid }) => (events.log({
|
||||
type: 'post-change-owner',
|
||||
uid: socket.uid,
|
||||
ip: socket.ip,
|
||||
targetUid: data.toUid,
|
||||
pid: pid,
|
||||
originalUid: uid,
|
||||
cid: cid,
|
||||
})));
|
||||
|
||||
await Promise.all(logs);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ const intFields = [
|
||||
'tid', 'cid', 'uid', 'mainPid', 'postcount',
|
||||
'viewcount', 'deleted', 'locked', 'pinned',
|
||||
'timestamp', 'upvotes', 'downvotes', 'lastposttime',
|
||||
'deleterUid',
|
||||
];
|
||||
|
||||
module.exports = function (Topics) {
|
||||
@@ -90,6 +91,10 @@ function modifyTopic(topic, fields) {
|
||||
|
||||
escapeTitle(topic);
|
||||
|
||||
if (topic.hasOwnProperty('thumb')) {
|
||||
topic.thumb = validator.escape(String(topic.thumb));
|
||||
}
|
||||
|
||||
if (topic.hasOwnProperty('timestamp')) {
|
||||
topic.timestampISO = utils.toISOString(topic.timestamp);
|
||||
}
|
||||
|
||||
@@ -50,8 +50,7 @@ Topics.getTopics = async function (tids, options) {
|
||||
}
|
||||
|
||||
tids = await privileges.topics.filterTids('topics:read', tids, uid);
|
||||
const topics = await Topics.getTopicsByTids(tids, options);
|
||||
return topics;
|
||||
return await Topics.getTopicsByTids(tids, options);
|
||||
};
|
||||
|
||||
Topics.getTopicsByTids = async function (tids, options) {
|
||||
|
||||
@@ -127,7 +127,7 @@ module.exports = function (User) {
|
||||
};
|
||||
|
||||
User.isPasswordValid = function (password, minStrength) {
|
||||
minStrength = minStrength || meta.config.minimumPasswordStrength;
|
||||
minStrength = (minStrength || minStrength === 0) ? minStrength : meta.config.minimumPasswordStrength;
|
||||
|
||||
// Sanity checks: Checks if defined and is string
|
||||
if (!password || !utils.isPasswordValid(password)) {
|
||||
|
||||
@@ -30,11 +30,8 @@ module.exports = function (User) {
|
||||
const now = Date.now();
|
||||
const isArray = Array.isArray(uid);
|
||||
uid = isArray ? uid : [uid];
|
||||
const lastonline = db.sortedSetScores('users:online', uid);
|
||||
const isOnline = uid.map(function (uid, index) {
|
||||
return (now - lastonline[index]) < (meta.config.onlineCutoff * 60000);
|
||||
});
|
||||
|
||||
const lastonline = await db.sortedSetScores('users:online', uid);
|
||||
const isOnline = uid.map((uid, index) => (now - lastonline[index]) < (meta.config.onlineCutoff * 60000));
|
||||
return isArray ? isOnline : isOnline[0];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ module.exports = function (User) {
|
||||
hashedPassword = '';
|
||||
}
|
||||
|
||||
User.isPasswordValid(password);
|
||||
User.isPasswordValid(password, 0);
|
||||
await User.auth.logAttempt(uid, ip);
|
||||
const ok = await Password.compare(password, hashedPassword);
|
||||
if (ok) {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const validator = require('validator');
|
||||
|
||||
const meta = require('../meta');
|
||||
const db = require('../database');
|
||||
const plugins = require('../plugins');
|
||||
const notifications = require('../notifications');
|
||||
const languages = require('../languages');
|
||||
|
||||
module.exports = function (User) {
|
||||
User.getSettings = async function (uid) {
|
||||
@@ -55,7 +58,8 @@ module.exports = function (User) {
|
||||
settings.upvoteNotifFreq = getSetting(settings, 'upvoteNotifFreq', 'all');
|
||||
settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
|
||||
settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
|
||||
settings.bootswatchSkin = settings.bootswatchSkin || '';
|
||||
settings.bootswatchSkin = validator.escape(String(settings.bootswatchSkin || ''));
|
||||
settings.homePageRoute = validator.escape(String(settings.homePageRoute || ''));
|
||||
settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1;
|
||||
settings.categoryWatchState = getSetting(settings, 'categoryWatchState', 'notwatching');
|
||||
|
||||
@@ -87,6 +91,13 @@ module.exports = function (User) {
|
||||
throw new Error('[[error:invalid-pagination-value, 2, ' + maxTopicsPerPage + ']]');
|
||||
}
|
||||
|
||||
const languageCodes = await languages.listCodes();
|
||||
if (data.userLang && !languageCodes.includes(data.userLang)) {
|
||||
throw new Error('[[error:invalid-language]]');
|
||||
}
|
||||
if (data.acpLang && !languageCodes.includes(data.acpLang)) {
|
||||
throw new Error('[[error:invalid-language]]');
|
||||
}
|
||||
data.userLang = data.userLang || meta.config.defaultLang;
|
||||
|
||||
plugins.fireHook('action:user.saveSettings', { uid: uid, settings: data });
|
||||
|
||||
@@ -18,10 +18,14 @@ module.exports = function (User) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
|
||||
const finalPath = path.join(nconf.get('upload_path'), uploadName);
|
||||
if (!finalPath.startsWith(nconf.get('upload_path'))) {
|
||||
throw new Error('[[error:invalid-path]]');
|
||||
}
|
||||
winston.verbose('[user/deleteUpload] Deleting ' + uploadName);
|
||||
await Promise.all([
|
||||
file.delete(path.join(nconf.get('upload_path'), uploadName)),
|
||||
file.delete(path.join(nconf.get('upload_path'), path.dirname(uploadName), path.basename(uploadName, path.extname(uploadName)) + '-resized' + path.extname(uploadName))),
|
||||
file.delete(finalPath),
|
||||
file.delete(file.appendToFileName(finalPath, '-resized')),
|
||||
]);
|
||||
await db.sortedSetRemove('uid:' + uid + ':uploads', uploadName);
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user