Merge branch master into develop

v1.18.x
Peter Jaszkowiak 8 years ago
commit 484a800327

@ -38,11 +38,9 @@ There is a chance that the issue you are experiencing may have already been fixe
You can find the NodeBB version number in the Admin Control Panel (ACP), as well as the first line output to the shell when running NodeBB
``` plaintext
info: NodeBB v0.5.2-dev Copyright (C) 2013-2014 NodeBB Inc.
info: This program comes with ABSOLUTELY NO WARRANTY.
info: This is free software, and you are welcome to redistribute it under certain conditions.
info:
info: Time: Tue Oct 07 2014 20:25:20 GMT-0400 (EDT)
3/4 12:38:57 [10752] - info: NodeBB v1.4.5 Copyright (C) 2013-2017 NodeBB Inc.
3/4 12:38:57 [10752] - info: This program comes with ABSOLUTELY NO WARRANTY.
3/4 12:38:57 [10752] - info: This is free software, and you are welcome to redistribute it under certain conditions.
```
If you are running NodeBB via git, it is also helpful to let the maintainers know what commit hash you are on. To find the commit hash, execute the following command:

@ -19,6 +19,12 @@
'use strict';
if (require.main !== module) {
require.main.require = function (path) {
return require(path);
};
}
var nconf = require('nconf');
nconf.argv().env('__');

@ -7,6 +7,7 @@ var path = require('path');
var fork = require('child_process').fork;
var async = require('async');
var logrotate = require('logrotate-stream');
var file = require('./src/file');
var pkg = require('./package.json');
@ -23,6 +24,7 @@ var workers = [];
var Loader = {
timesStarted: 0,
};
var appPath = path.join(__dirname, 'app.js');
Loader.init = function (callback) {
if (silent) {
@ -114,7 +116,7 @@ function forkWorker(index, isPrimary) {
process.env.isCluster = ports.length > 1;
process.env.port = ports[index];
var worker = fork('app.js', args, {
var worker = fork(appPath, args, {
silent: silent,
env: process.env,
});

@ -2,16 +2,25 @@
'use strict';
var cproc;
var args;
var fs;
var path;
var request;
var semver;
var prompt;
var async;
try {
require('colors');
var cproc = require('child_process');
var args = require('minimist')(process.argv.slice(2));
var fs = require('fs');
var path = require('path');
var request = require('request');
var semver = require('semver');
var prompt = require('prompt');
var async = require('async');
cproc = require('child_process');
args = require('minimist')(process.argv.slice(2));
fs = require('fs');
path = require('path');
request = require('request');
semver = require('semver');
prompt = require('prompt');
async = require('async');
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
process.stdout.write('NodeBB could not be started because it\'s dependencies have not been installed.\n');
@ -23,12 +32,15 @@ try {
}
}
var loaderPath = path.join(__dirname, 'loader.js');
var appPath = path.join(__dirname, 'app.js');
if (args.dev) {
process.env.NODE_ENV = 'development';
}
function getRunningPid(callback) {
fs.readFile(__dirname + '/pidfile', {
fs.readFile(path.join(__dirname, 'pidfile'), {
encoding: 'utf-8',
}, function (err, pid) {
if (err) {
@ -58,7 +70,7 @@ function getCurrentVersion(callback) {
});
}
function fork(args) {
return cproc.fork('app.js', args, {
return cproc.fork(appPath, args, {
cwd: __dirname,
silent: false,
});
@ -72,7 +84,7 @@ function getInstalledPlugins(callback) {
return callback(err);
}
var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w\-]+$/;
var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w-]+$/;
var moduleName;
var isGitRepo;
@ -170,21 +182,21 @@ function checkPlugins(standalone, callback) {
body = [body];
}
var current,
suggested,
upgradable = body.map(function (suggestObj) {
current = payload.plugins[suggestObj.package];
suggested = suggestObj.version;
if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) {
return {
name: suggestObj.package,
current: current,
suggested: suggested,
};
}
return null;
}).filter(Boolean);
var current;
var suggested;
var upgradable = body.map(function (suggestObj) {
current = payload.plugins[suggestObj.package];
suggested = suggestObj.version;
if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) {
return {
name: suggestObj.package,
current: current,
suggested: suggested,
};
}
return null;
}).filter(Boolean);
next(null, upgradable);
});
@ -200,7 +212,7 @@ function upgradePlugins(callback) {
checkPlugins(standalone, function (err, found) {
if (err) {
process.stdout.write('\Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability\n'.reset);
process.stdout.write('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability\n'.reset);
return callback(err);
}
@ -280,7 +292,7 @@ var commands = {
process.stdout.write(' "' + './nodebb restart'.yellow + '" to restart NodeBB\n\n'.reset);
// Spawn a new NodeBB process
cproc.fork(__dirname + '/loader.js', {
cproc.fork(loaderPath, {
env: process.env,
});
},
@ -334,7 +346,7 @@ var commands = {
process.stdout.write('\n\n'.reset);
// Spawn a new NodeBB process
cproc.fork(__dirname + '/loader.js', {
cproc.fork(loaderPath, {
env: process.env,
});
cproc.spawn('tail', ['-F', './logs/output.log'], {
@ -348,7 +360,7 @@ var commands = {
usage: 'Usage: ' + './nodebb dev'.yellow,
handler: function () {
process.env.NODE_ENV = 'development';
cproc.fork(__dirname + '/loader.js', ['--no-daemon', '--no-silent'], {
cproc.fork(loaderPath, ['--no-daemon', '--no-silent'], {
env: process.env,
});
},

@ -11,7 +11,7 @@
"main": "app.js",
"scripts": {
"start": "node loader.js",
"lint": "eslint --cache .",
"lint": "eslint --cache ./nodebb .",
"pretest": "npm run lint",
"test": "istanbul cover node_modules/mocha/bin/_mocha -- -R dot",
"coveralls": "istanbul cover _mocha --report lcovonly -- -R dot && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
@ -95,7 +95,8 @@
"validator": "^6.1.0",
"winston": "^2.1.0",
"xml": "^1.0.1",
"xregexp": "~3.1.0"
"xregexp": "~3.1.0",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"coveralls": "^2.11.14",

@ -37,6 +37,7 @@
"min-username-length": "Minimum Username Length",
"max-username-length": "Maximum Username Length",
"min-password-length": "Minimum Password Length",
"min-password-strength": "Minimum Password Strength",
"max-about-me-length": "Maximum About Me Length",
"terms-of-use": "Forum Terms of Use <small>(Leave blank to disable)</small>",
"user-search": "User Search",

@ -63,6 +63,7 @@
"username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as <strong>%1</strong>",
"password_same_as_username": "Your password is the same as your username, please select another password.",
"password_same_as_email": "Your password is the same as your email, please select another password.",
"weak_password": "Weak password.",
"upload_picture": "Upload picture",
"upload_a_picture": "Upload a picture",

@ -61,7 +61,7 @@
"reputation": "Hírnév",
"read_more": "tovább olvas",
"more": "Több",
"posted_ago_by_guest": "Vendég hozzászólás %1",
"posted_ago_by_guest": "%1 vendég hozzászólás",
"posted_ago_by": "%2 hozzászólás %1",
"posted_ago": "%1 hozzászólás",
"posted_in": "hozzászólt itt: %1",
@ -103,5 +103,5 @@
"cookies.message": "A weboldal sütiket használ, a legjobb weboldalas élmény érdekében.",
"cookies.accept": "Értem!",
"cookies.learn_more": "Tudnivalók",
"edited": "Edited"
"edited": "Szerkesztett"
}

@ -4,7 +4,7 @@
"week": "Hét",
"month": "Hónap",
"year": "Év",
"alltime": "Minden idők",
"alltime": "Bármikor",
"no_recent_topics": "Nincs friss témakör.",
"no_popular_topics": "Nincs népszerű témakör.",
"there-is-a-new-topic": "Van egy új témakör.",

@ -103,5 +103,5 @@
"cookies.message": "Deze website gebruikt cookies om je ervan te verzekeren dat je de beste ervaring krijgt tijdens het gebruik van onze website.",
"cookies.accept": "Begrepen",
"cookies.learn_more": "Meer",
"edited": "Edited"
"edited": "Bewerkt"
}

@ -27,7 +27,7 @@
"details.disableJoinRequests": "Groepsverzoeken uitschakelen",
"details.grant": "Toekennen/herroepen van eigendom",
"details.kick": "Kick",
"details.kick_confirm": "Are you sure you want to remove this member from the group?",
"details.kick_confirm": "Weet u zeker dat u de gebruiker wilt verwijderen uit de groep?",
"details.owner_options": "Groepsadministratie",
"details.group_name": "Groepsnaam",
"details.member_count": "Ledentelling",
@ -54,5 +54,5 @@
"upload-group-cover": "Upload groepscover",
"bulk-invite-instructions": "Vul een lijst is met gebruikersnamen gescheiden met komma's om deze uit te nodigen voor deze groep",
"bulk-invite": "Massa uitnodiging",
"remove_group_cover_confirm": "Are you sure you want to remove the cover picture?"
"remove_group_cover_confirm": "Weet u zeker dat u de cover foto wilt verwijderen?"
}

@ -13,7 +13,7 @@
"chat.contacts": "Contacten",
"chat.message-history": "Berichtengeschiedenis",
"chat.pop-out": "Chatvenster opbrengen bij chat",
"chat.minimize": "Minimize",
"chat.minimize": "Verkleinen",
"chat.maximize": "Maximaliseren",
"chat.seven_days": "7 dagen",
"chat.thirty_days": "30 dagen",

@ -10,14 +10,14 @@
"return_to": "Terug naar %1",
"new_notification": "Nieuwe notificatie",
"you_have_unread_notifications": "Je hebt nieuwe notificaties.",
"all": "All",
"topics": "Topics",
"replies": "Replies",
"all": "Alles",
"topics": "Onderwerpen",
"replies": "Antwoorden",
"chat": "Chats",
"follows": "Follows",
"follows": "Volgt",
"upvote": "Upvotes",
"new-flags": "New Flags",
"my-flags": "Flags assigned to me",
"new-flags": "Nieuwe markeringen",
"my-flags": "Markeringen toegewezen aan mij",
"bans": "Bans",
"new_message_from": "Nieuw bericht van <strong>%1</strong>",
"upvoted_your_post_in": "<strong>%1</strong> heeft voor een bericht gestemd in <strong>%2</strong>.",
@ -28,9 +28,9 @@
"user_flagged_post_in": "<strong>%1</strong> rapporteerde een bericht in <strong>%2</strong>",
"user_flagged_post_in_dual": "<strong>%1</strong> en <strong>%2</strong> rapporteerde een bericht in <strong>%3</strong>",
"user_flagged_post_in_multiple": "<strong>%1</strong> en %2 andere rapporteede een bericht in <strong>%3</strong>",
"user_flagged_user": "<strong>%1</strong> flagged a user profile (%2)",
"user_flagged_user_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a user profile (%3)",
"user_flagged_user_multiple": "<strong>%1</strong> and %2 others flagged a user profile (%3)",
"user_flagged_user": "<strong>%1</strong> markeerde een gebruikersprofiel (%2)",
"user_flagged_user_dual": "<strong>%1</strong> en <strong>%2</strong> markeerden een gebruikersprofiel (%3)",
"user_flagged_user_multiple": "<strong>%1</strong> en %2 anderen markeerde een gebruikersprofiel (%3)",
"user_posted_to": "<strong>%1</strong> heeft een reactie geplaatst in <strong>%2</strong>",
"user_posted_to_dual": "<strong>%1</strong> en <strong>%2</strong> hebben een reactie geplaatst in: <strong>%3</strong>",
"user_posted_to_multiple": "<strong>%1</strong> en %2 hebben een reactie geplaatst in: <strong>%3</strong>",
@ -40,7 +40,7 @@
"user_started_following_you_multiple": "<strong%1>%1</strong> en %2 andere volgen jou nu.",
"new_register": "<strong>%1</strong> heeft een registratie verzoek aangevraagd.",
"new_register_multiple": "Er is/zijn <strong>%1</strong> registratieverzoek(en) die wacht(en) op goedkeuring.",
"flag_assigned_to_you": "<strong>Flag %1</strong> has been assigned to you",
"flag_assigned_to_you": "<strong>Flag %1</strong> is aan u toegewezen",
"email-confirmed": "E-mailadres bevestigd",
"email-confirmed-message": "Bedankt voor het bevestigen van je e-mailadres. Je account is nu volledig geactiveerd.",
"email-confirm-error-message": "Er was een probleem met het bevestigen van dit e-mailadres. Misschien is de code niet goed ingevoerd of was de beschikbare tijd inmiddels verstreken.",

@ -6,7 +6,7 @@
"popular-month": "De populaire onderwerpen van deze maand",
"popular-alltime": "De populaire onderwerpen",
"recent": "Recente onderwerpen",
"flagged-content": "Flagged Content",
"flagged-content": "Gemarkeerde content",
"ip-blacklist": "IP zwarte lijst",
"users/online": "Online Gebruikers",
"users/latest": "Meest recente gebruikers",
@ -27,7 +27,7 @@
"group": "%1's groep",
"chats": "Chats",
"chat": "Chatten met %1",
"flags": "Flags",
"flags": "Markeringen",
"flag-details": "Flag %1 Details",
"account/edit": "\"%1\" aanpassen",
"account/edit/password": "Wachtwoord van \"%1\" aanpassen",

@ -10,17 +10,17 @@
"return_to": "Geri dön.",
"new_notification": "Yeni bildirim",
"you_have_unread_notifications": "Okunmamış bildirimleriniz var.",
"all": "All",
"topics": "Topics",
"replies": "Replies",
"chat": "Chats",
"follows": "Follows",
"all": "Hepsi",
"topics": "Başlıklar",
"replies": "Yanıtlar",
"chat": "Sohbetler",
"follows": "Takip ediyor",
"upvote": "Upvotes",
"new-flags": "New Flags",
"new-flags": "Yeni Bayrak",
"my-flags": "Flags assigned to me",
"bans": "Bans",
"bans": "Yasaklamalar",
"new_message_from": "<strong>%1</strong> size bir mesaj gönderdi",
"upvoted_your_post_in": "<strong>%1</strong> iletinizi beğendi. <strong>%2</strong>",
"upvoted_your_post_in": "<strong>%1</strong> iletinizi beğendi. <strong>%2</strong>.",
"upvoted_your_post_in_dual": "<strong>%1</strong> ve <strong>%2</strong> <strong>%3</strong> içindeki gönderini beğendi.",
"upvoted_your_post_in_multiple": "<strong>%1</strong> ve %2 iki kişi daha <strong>%3</strong> içindeki gönderini beğendi.",
"moved_your_post": "<strong>%1</strong> senin iletin <strong>%2</strong> taşındı",

@ -131,5 +131,5 @@
"info.email-history": "Email Geçmişi",
"info.moderation-note": "Moderasyon Notu",
"info.moderation-note.success": "Moderasyon notu kaydedildi",
"info.moderation-note.add": "Add note"
"info.moderation-note.add": "Not ekle"
}

@ -1,19 +0,0 @@
{
"daily": "Щоденні скарги",
"by-user": "Скарги за користувачем",
"by-user-search": "Пошук оскаржених постів за іменем користувача",
"category": "Категорія",
"sort-by": "Сортувати за",
"sort-by.most-flags": "Найбільше скарг",
"sort-by.most-recent": "Найсвіжіші",
"search": "Шукати",
"dismiss-all": "Відхилити всі",
"none-flagged": "Скарг немає!",
"posted-in": "Запощено в %1",
"read-more": "Читати далі",
"flagged-x-times": "Цей пост було оскаржено %1 раз(ів):",
"dismiss": "Відхилити цю скаргу",
"delete-post": "Видалити цей пост",
"alerts.confirm-delete-post": "Ви точно бажаєте видалити цей пост?"
}

@ -131,5 +131,5 @@
"info.email-history": "Історія електронної пошти",
"info.moderation-note": "Коментар модератора",
"info.moderation-note.success": "Коментар модератора збережено",
"info.moderation-note.add": "Add note"
"info.moderation-note.add": "Додати коментар"
}

@ -26,7 +26,7 @@
"redis.blocked-clients": "阻止的客户端",
"redis.used-memory": "已使用内存",
"redis.memory-frag-ratio": "内存碎片比率",
"redis.total-connections-recieved": "已接收连接总数",
"redis.total-connections-recieved": "已接收连接总数",
"redis.total-commands-processed": "已执行命令总数",
"redis.iops": "每秒实时操作数",
"redis.keyspace-hits": "Keyspace 命中",

@ -2,5 +2,5 @@
"events": "事件",
"no-events": "暂无事件。",
"control-panel": "事件控制面板",
"delete-events": "除事件"
"delete-events": "除事件"
}

@ -1,6 +1,6 @@
{
"installed": "已安装",
"active": "生效中",
"active": "激活",
"inactive": "未生效",
"out-of-date": "已过期",
"none-found": "无插件。",

@ -13,7 +13,7 @@
"container.alert": "警报",
"alert.confirm-delete": "确认删除此窗口部件?",
"alert.updated": "窗口部件升级",
"alert.update-success": "已成功升级窗口部件"
"alert.updated": "窗口部件更新",
"alert.update-success": "已成功更新窗口部件"
}

@ -33,7 +33,7 @@
"control-panel": "系统控制",
"reload": "重载",
"restart": "重启",
"restart-warning": "重新载入或重启 NodeBB 会丢弃数秒内所有的连接。",
"restart-warning": "重载或重启 NodeBB 会丢失数秒内所有的连接。",
"maintenance-mode": "维护模式",
"maintenance-mode-title": "点击此处设置 NodeBB 的维护模式",
"realtime-chart-updates": "实时图表更新",

@ -12,57 +12,57 @@
"ext-link": "外部链接",
"upload-image": "上传图片",
"delete-image": "移除",
"category-image": "块图片",
"parent-category": "父块",
"optional-parent-category": "(可选)父块",
"category-image": "块图片",
"parent-category": "父块",
"optional-parent-category": "(可选)父块",
"parent-category-none": "(无)",
"copy-settings": "复制设置",
"optional-clone-settings": "(可选) 从块复制设置",
"purge": "删除块",
"optional-clone-settings": "(可选) 从块复制设置",
"purge": "删除块",
"enable": "启用",
"disable": "禁用",
"edit": "编辑",
"select-category": "选择块",
"set-parent-category": "设置父块",
"select-category": "选择块",
"set-parent-category": "设置父块",
"privileges.description": "您可以在此部分中配置此块的访问控制权限。 可以根据每个用户或每个组授予权限。 您可以通过在下面的表格中搜索,将新用户添加到此表中。",
"privileges.warning": "<strong>注意</ strong>:权限设置会立即生效。 调整这些设置后,无需保存。",
"privileges.description": "您可以在此部分中配置此块的访问控制权限。 可以根据每个用户或每个组授予权限。 您可以通过在下面的表格中搜索,将新用户添加到此表中。",
"privileges.warning": "<strong>注意</strong>:权限设置会立即生效。 调整这些设置后,无需保存。",
"privileges.section-viewing": "查看权限",
"privileges.section-posting": "发帖权限",
"privileges.section-moderation": "审核权限",
"privileges.section-user": "用户",
"privileges.search-user": "添加用户",
"privileges.no-users": "此类别中没有用户特定的权限。",
"privileges.section-group": "用户组",
"privileges.group-private": "这个用户组是私密的",
"privileges.search-group": "添加用户组",
"privileges.no-users": "此版块中没有用户特定的权限。",
"privileges.section-group": "组",
"privileges.group-private": "这个组是私密的",
"privileges.search-group": "添加组",
"privileges.copy-to-children": "复制到子版块",
"privileges.copy-from-category": "从块复制",
"privileges.inherit": "如果 <code>registered-users</ code> 组被授予特定权限,所有其他组都会收到<strong>隐式权限</ strong>,即使它们未被明确定义/检查。 将显示此隐式权限,因为所有用户都是 <code>registered-users</ code> 用户组的一部分,因此无需显式授予其他组的权限。",
"privileges.copy-from-category": "从块复制",
"privileges.inherit": "如果 <code>registered-users</code> 组被授予特定权限,所有其他组都会收到<strong>隐式权限</strong>,即使它们未被明确定义/检查。 将显示此隐式权限,因为所有用户都是 <code>registered-users</code> 群组的一部分,因此无需显式授予其他组的权限。",
"analytics.back": "返回块列表",
"analytics.title": "“%1”块的统计",
"analytics.pageviews-hourly": "<strong>图1 </ strong> &ndash; 此板块的每小时页面浏览量</ small>",
"analytics.pageviews-daily": "<strong>图2 </ strong> &ndash; 此板块的每日页面浏览量</small>",
"analytics.topics-daily": "<strong>图3 </ strong> &ndash; 每日在此板块中创建的主题</ small>",
"analytics.posts-daily": "<strong>图4 </ strong> &ndash; 每日在此板块中每日发布的帖子</ small>",
"analytics.back": "返回块列表",
"analytics.title": "“%1”块的统计",
"analytics.pageviews-hourly": "<strong>图1</strong> &ndash; 此版块的每小时页面浏览量</small>",
"analytics.pageviews-daily": "<strong>图2</strong> &ndash; 此版块的每日页面浏览量</small>",
"analytics.topics-daily": "<strong>图3</strong> &ndash; 每日在此版块中创建的主题</small>",
"analytics.posts-daily": "<strong>图4</strong> &ndash; 每日在此版块中每日发布的帖子</small>",
"alert.created": "创建",
"alert.create-success": "块创建成功!",
"alert.none-active": "您没有有效的块。",
"alert.create": "创建一个块",
"alert.confirm-moderate": "<strong>您确定要将审核权限授予此用户组吗?</ strong>此群组是公开的,任何用户都可以随意加入。",
"alert.confirm-purge": "<p class =“lead”>您确定要清除此板块“%1”吗</ p> <h5> <strong class =“text-danger”>警告!</ strong> 板块将被清除!</ h5> <p class =“help-block”>清除板块将删除所有主题和帖子,并从数据库中删除板块。 如果您想<em>暂时</ em>移除板块,请使用停用板块。</ p>",
"alert.purge-success": "块已删除!",
"alert.create-success": "块创建成功!",
"alert.none-active": "您没有有效的块。",
"alert.create": "创建一个块",
"alert.confirm-moderate": "<strong>您确定要将审核权限授予此群组吗?</strong>此群组是公开的,任何用户都可以随意加入。",
"alert.confirm-purge": "<p class =“lead”>您确定要清除此版块“%1”吗</p> <h5> <strong class =“text-danger”>警告!</strong> 版块将被清除!</h5> <p class =“help-block”>清除版块将删除所有主题和帖子,并从数据库中删除版块。 如果您想<em>暂时</em>移除版块,请使用停用版块。</p>",
"alert.purge-success": "块已删除!",
"alert.copy-success": "设置已复制!",
"alert.set-parent-category": "设置父块",
"alert.updated": "块已更新",
"alert.updated-success": "块ID %1 成功更新。",
"alert.upload-image": "上传块图片",
"alert.set-parent-category": "设置父块",
"alert.updated": "块已更新",
"alert.updated-success": "块ID %1 成功更新。",
"alert.upload-image": "上传块图片",
"alert.find-user": "查找用户",
"alert.user-search": "在这里查找用户…",
"alert.find-group": "查找用户组",
"alert.group-search": "在此处搜索用户组..."
"alert.find-group": "查找组",
"alert.group-search": "在此处搜索组..."
}

@ -1,31 +1,31 @@
{
"name": "用户组名",
"description": "用户组描述",
"system": "系统用户组",
"name": "组名",
"description": "组描述",
"system": "系统组",
"edit": "编辑",
"search-placeholder": "索",
"create": "创建用户组",
"description-placeholder": "一个关于你的用户组的简短描述",
"search-placeholder": "索",
"create": "创建组",
"description-placeholder": "一个关于你的组的简短描述",
"create-button": "创建",
"alerts.create-failure": "<strong>哦不!</strong><p>创建您的用户组时出现问题。 请稍后再试!</p>",
"alerts.confirm-delete": "确认要删除这个用户组么?",
"alerts.create-failure": "<strong>哦不!</strong><p>创建您的组时出现问题。 请稍后再试!</p>",
"alerts.confirm-delete": "确认要删除这个组么?",
"edit.name": "名字",
"edit.description": "描述",
"edit.user-title": "成员标题",
"edit.icon": "用户组标志",
"edit.icon": "组标志",
"edit.label-color": "群组标签颜色",
"edit.show-badge": "显示徽章",
"edit.private-details": "启用此选项后,加入用户组的请求将需要组长审批。",
"edit.private-override": "警告:系统禁用了私有用户组,优先于该选项。",
"edit.private-details": "启用此选项后,加入群组的请求将需要群组所有者审批。",
"edit.private-override": "警告:系统已禁用了私有群组,优先级高于该选项。",
"edit.disable-requests": "禁止加入请求",
"edit.hidden": "隐藏",
"edit.hidden-details": "启用此选项后,此用户组将不在用户组列表展现,并且用户只能被手动邀请加入",
"edit.add-user": "向此组添加成员",
"edit.hidden-details": "启用此选项后,此群组将不在群组列表展现,并且用户只能被手动邀请加入",
"edit.add-user": "向此组添加成员",
"edit.add-user-search": "搜索用户",
"edit.members": "成员列表",
"control-panel": "组控制面板",
"control-panel": "组控制面板",
"revert": "重置",
"edit.no-users-found": "没有找到用户",

@ -1,6 +1,6 @@
{
"queue": "队列",
"description": "注册队列里面没有用户。<br>要开启这项功能,请去<a href=\"%1\">设置 &rarr; 用户 &rarr; 用户注册</a> 并设置<strong>注册类型</strong>为“管理员批准”。",
"queue": "申请",
"description": "注册申请队列里面没有用户申请。<br>要开启这项功能,请去<a href=\"%1\">设置 &rarr; 用户 &rarr; 用户注册</a> 并设置<strong>注册类型</strong>为“管理员批准”。",
"list.name": "姓名",
"list.email": "邮件",

@ -30,9 +30,9 @@
"search.username": "通过用户名",
"search.username-placeholder": "输入你想找的用户名",
"search.email": "通过邮箱",
"search.email-placeholder": "输入你想的邮箱地址",
"search.email-placeholder": "输入你想查询的邮箱地址",
"search.ip": "通过IP地址",
"search.ip-placeholder": "输入你想的IP",
"search.ip-placeholder": "输入你想查询的IP",
"search.not-found": "未找到用户!",
"inactive.3-months": "3个月",

@ -11,8 +11,8 @@
"manage/categories": "版块",
"manage/tags": "话题",
"manage/users": "用户",
"manage/registration": "注册队列",
"manage/groups": "用户组",
"manage/registration": "注册申请",
"manage/groups": "组",
"manage/ip-blacklist": "IP 黑名单",
"section-settings": "设置",
@ -20,7 +20,7 @@
"settings/reputation": "声望",
"settings/email": "邮件",
"settings/user": "用户",
"settings/group": "用户组",
"settings/group": "组",
"settings/guest": "游客",
"settings/uploads": "上传",
"settings/post": "发帖",

@ -10,7 +10,7 @@
"headers.acam": "Access-Control-Allow-Methods",
"headers.acah": "Access-Control-Allow-Headers",
"traffic-management": "流量管理",
"traffic.help": "NodeBB 拥有在高流量情况下自动拒绝请求的模块。 您可以在这里调整这些设置,虽然默认值就很棒。",
"traffic.help": "NodeBB 拥有在高流量情况下自动拒绝请求的模块。尽管默认值就很棒,但您可以在这里调整这些设置。",
"traffic.enable": "启用流量管理",
"traffic.event-lag": "事件循环滞后阈值(毫秒)",
"traffic.event-lag-help": "降低此值会减少页面加载的等待时间,但也会向更多用户显示“过载”消息。(需要重新启动)",

@ -6,6 +6,6 @@
"consent.link-text": "政策链接文本",
"consent.blank-localised-default": "留空以便使用 NodeBB 本地默认值",
"settings": "设置",
"cookie-domain": "会话 cookie 域名",
"cookie-domain": "Session cookie 域名",
"blank-default": "留空以保持默认"
}

@ -1,12 +1,12 @@
{
"general": "通用",
"private-groups": "私有用户组",
"private-groups.help": "启用此选项后,加入用户组需要组长审批<em>(默认启用)</em>。",
"private-groups.warning": "<strong>注意!</strong>如果这个选项未启用并且你有私有用户组,那么你的用户组将变为公共的。",
"allow-creation": "允许创建用户组",
"allow-creation-help": "如果启用,用户就可以创建用户组<em>(默认:不启用)</em>",
"max-name-length": "用户组名字的最大长度",
"cover-image": "用户组封面图片",
"private-groups": "私有组",
"private-groups.help": "启用此选项后,加入用户组需要群组所有者审批<em>(默认启用)</em>。",
"private-groups.warning": "<strong>注意!</strong>如果这个选项未启用并且你有私有群组,那么你的群组将变为公共的。",
"allow-creation": "允许创建组",
"allow-creation-help": "如果启用,用户就可以创建组<em>(默认:不启用)</em>",
"max-name-length": "组名字的最大长度",
"cover-image": "组封面图片",
"default-cover": "默认封面图片",
"default-cover-help": "为没有上传封面图片的群组添加以逗号分隔的默认封面图片"
}

@ -3,7 +3,7 @@
"enable": "在主题和帖子使用分页替代无限滚动浏览。",
"topics": "话题分页",
"posts-per-page": "每页帖子数",
"categories": "块分页",
"categories": "块分页",
"topics-per-page": "每页主题数",
"initial-num-load": "最初加载未读,最新,热门的话题"
}

@ -6,21 +6,21 @@
"sorting.most-votes": "最多投票",
"sorting.topic-default": "默认主题排序",
"restrictions": "发帖限制",
"restrictions.seconds-between": "发帖间隔",
"restrictions.seconds-between-new": "对于新用户的发帖间隔",
"restrictions.seconds-between": "发帖间隔(单位:秒)",
"restrictions.seconds-between-new": "对于新用户的发帖间隔(单位:秒)",
"restrictions.rep-threshold": "取消发帖限制所需的声望值",
"restrictions.seconds-defore-new": "见习时间",
"restrictions.seconds-edit-after": "用户在发布后允许编辑帖子的秒数。 (0为禁用) ",
"restrictions.seconds-delete-after": "允许在发布后删除帖子的秒数。 (0为禁用) ",
"restrictions.seconds-defore-new": "见习时间(单位:秒)",
"restrictions.seconds-edit-after": "用户在发布后允许编辑帖子的时间0为禁用单位",
"restrictions.seconds-delete-after": "用户在发布后允许删除帖子的时间0为禁用单位",
"restrictions.replies-no-delete": "在用户被禁止删除自己的主题后的回复数。 (0为禁用) ",
"restrictions.min-title-length": "最小标题长度",
"restrictions.max-title-length": "最大标题长度",
"restrictions.min-post-length": "最小帖子长度",
"restrictions.max-post-length": "最大帖子长度",
"restrictions.days-until-stale": "主题过期时间",
"restrictions.days-until-stale": "主题过期时间(单位:天)",
"restrictions.stale-help": "如果某个主题被视为“过时”,则会向尝试回复该主题的用户显示警告。",
"timestamp": "时间戳",
"timestamp.cut-off": "日期截止日期 (天) ",
"timestamp.cut-off": "日期截止日期(单位:天)",
"timestamp.cut-off-help": "日期&amp;时间将以相对方式 (例如“3小时前” / “5天前”) 显示,并且会依照访客语言时区转换。在某一时刻之后,可以切换该文本以显示本地化日期本身 (例如2016年11月5日15:30) 。<br /> <em> (默认值:<code> 30 </code>或一个月) 。 设置为0可始终显示日期留空以始终显示相对时间。</em>",
"teaser": "预览帖子",
"teaser.last-post": "最后&ndash; 显示最新的帖子,包括原帖,如果没有回复",
@ -35,7 +35,7 @@
"signature.no-images": "禁用签名中的图片",
"signature.max-length": "签名最大长度",
"composer": "编辑器设置",
"composer-help": "以下设置控制所示后期编辑器的功能和/或外观\n\\t\\t\\t\\t当用户创建新主题或回复现有主题时。",
"composer-help": "以下设置控制所示后期编辑器的功能和/或外观\n\t\t\t\t当用户创建新主题或回复现有主题时。",
"composer.show-help": "显示“帮助”选项卡",
"composer.enable-plugin-help": "允许插件将内容添加到帮助选项卡",
"composer.custom-help": "自定义帮助文本",

@ -3,7 +3,7 @@
"disable": "禁用声望系统",
"disable-down-voting": "禁用 踩",
"votes-are-public": "所有投票是公开的",
"thresholds": "活动值",
"thresholds": "活动值",
"min-rep-downvote": "踩帖子所需要声望的最小值",
"min-rep-flag": "举报帖子需要的最小声望"
}

@ -3,7 +3,7 @@
"allow-files": "允许用户上传普通文件",
"private": "使上传的文件私有化",
"max-image-width": "缩小图片到指定宽度(单位像素)",
"max-image-width-help": "(像素单位,默认760像素设置为0以禁用)",
"max-image-width-help": "(像素单位,默认 760 px设置为0以禁用)",
"max-file-size": "最大文件尺寸(单位 KiB)",
"max-file-size-help": "(单位 KiB默认2048KiB)",
"allow-topic-thumbnails": "允许用户上传主题缩略图",
@ -16,11 +16,11 @@
"default-avatar": "访客默认头像",
"upload": "上传",
"profile-image-dimension": "个人资料相片尺寸",
"profile-image-dimension-help": "(使用像素作为单位默认128px)",
"profile-image-dimension-help": "(使用 px 作为单位默认128px)",
"max-profile-image-size": "个人资料相片最大大小",
"max-profile-image-size-help": "(单位KiB默认256KiB)",
"max-profile-image-size-help": "(单位 KiB 默认256KiB)",
"max-cover-image-size": "最大封面图片文件大小",
"max-cover-image-size-help": "(单位kb,默认:2048KiB)",
"max-cover-image-size-help": "(单位 KiB ,默认:2048KiB)",
"keep-all-user-images": "在服务器上保留旧头像和旧的资料封面",
"profile-covers": "资料封面",
"default-covers": "默认封面图片",

@ -33,11 +33,11 @@
"registration-type.help": "通常 - 用户可以通过/register页面注册<br/>\n管理员批准 - 用户注册请求会被放入 <a href=\"%1/admin/manage/registration\">请求队列</a> 待管理员批准。<br/>\n管理员批准 IP地址 - 新用户不受影响已存在帐户的IP地址注册需要管理员批准。<br/>\n邀请制 - 用户可以通过 <a href=\"%1/users\" target=\"_blank\">用户</a> 页面邀请其它用户。<br/>\n管理员邀请制 - 只有管理员可以通过 <a href=\"%1/users\" target=\"_blank\">用户</a> 和 <a href=\"%1/admin/manage/users\">admin/manage/users</a> 页面邀请其它用户。<br/>\n无注册 - 不开放用户注册。<br/>",
"registration.max-invites": "每个用户最大邀请数",
"max-invites": "每个用户最大邀请数",
"max-invites-help": "无限制填0。管理员没有邀请限制<br>仅在邀请制时可用",
"max-invites-help": "无限制填 0 。管理员没有邀请限制<br>仅在邀请制时可用",
"min-username-length": "最小用户名长度",
"max-username-length": "最大用户名长度",
"min-password-length": "最小密码长度",
"max-about-me-length": "最大自我介绍长度",
"max-about-me-length": "自我介绍的最大长度",
"terms-of-use": "论坛使用条款 <small>(留空即可禁用)</small>",
"user-search": "用户搜索",
"user-search-results-per-page": "展示的结果数量",

@ -8,7 +8,7 @@
"invalid-pid": "无效帖子 ID",
"invalid-uid": "无效用户 ID",
"invalid-username": "无效用户名",
"invalid-email": "无效电子邮箱",
"invalid-email": "无效电子邮箱",
"invalid-title": "无效标题!",
"invalid-user-data": "无效用户数据",
"invalid-password": "无效密码",
@ -29,14 +29,14 @@
"username-too-long": "用户名太长",
"password-too-long": "密码太长",
"user-banned": "用户已禁止",
"user-banned-reason": "抱歉,此帐号已经被封 (原因:%1)",
"user-banned-reason": "抱歉,此帐号已经被封 (原因:%1)",
"user-too-new": "抱歉,您需要等待 %1 秒后,才可以发帖!",
"blacklisted-ip": "对不起,您的 IP 地址已被社区禁用。如果您认为这是一个错误,请与管理员联系。",
"ban-expiry-missing": "请提供此次禁言结束日期",
"no-category": "版块不存在",
"no-topic": "主题不存在",
"no-post": "帖子不存在",
"no-group": "用户组不存在",
"no-group": "组不存在",
"no-user": "用户不存在",
"no-teaser": "主题预览不存在",
"no-privileges": "您没有权限执行此操作。",
@ -80,13 +80,13 @@
"invalid-image-type": "无效的图像类型。允许的类型有:%1",
"invalid-image-extension": "无效的图像扩展",
"invalid-file-type": "无效文件格式,允许的格式有:%1",
"group-name-too-short": "用户组名太短",
"group-name-too-long": "用户组名太长",
"group-already-exists": "用户组已存在",
"group-name-change-not-allowed": "不允许更改用户组名称",
"group-already-member": "已经是此用户组的成员",
"group-not-member": "不是此用户组的成员",
"group-needs-owner": "用户组需要指定至少一名组长",
"group-name-too-short": "组名太短",
"group-name-too-long": "组名太长",
"group-already-exists": "组已存在",
"group-name-change-not-allowed": "不允许更改组名称",
"group-already-member": "已经是此组的成员",
"group-not-member": "不是此组的成员",
"group-needs-owner": "群组需要指定至少一名群组所有者",
"group-already-invited": "您已邀请该用户",
"group-already-requested": "已提交您的请求",
"post-already-deleted": "此帖已被删除",

@ -31,7 +31,7 @@
"header.tags": "话题",
"header.popular": "热门",
"header.users": "会员",
"header.groups": "用户组",
"header.groups": "组",
"header.chats": "聊天",
"header.notifications": "通知",
"header.search": "搜索",
@ -45,7 +45,7 @@
"alert.success": "成功",
"alert.error": "错误",
"alert.banned": "封禁",
"alert.banned.message": "您刚刚被封禁,现在您将退出登录。",
"alert.banned.message": "您刚刚被封禁了,现在您将登出站点。",
"alert.unfollow": "您已取消关注 %1",
"alert.follow": "您已关注 %1",
"online": "在线",

@ -1,35 +1,35 @@
{
"groups": "用户组",
"view_group": "查看用户组",
"owner": "组长",
"new_group": "创建用户组",
"no_groups_found": "尚无用户组信息",
"groups": "组",
"view_group": "查看组",
"owner": "群组所有者",
"new_group": "创建组",
"no_groups_found": "尚无组信息",
"pending.accept": "接受",
"pending.reject": "拒绝",
"pending.accept_all": "接受全部",
"pending.reject_all": "拒绝全部",
"pending.accept_all": "全部同意",
"pending.reject_all": "全部拒绝",
"pending.none": "暂时没有待加入的成员",
"invited.none": "暂时没有接受邀请的成员",
"invited.uninvite": "取消邀请",
"invited.search": "选择用户加入用户组",
"invited.search": "选择用户加入组",
"invited.notification_title": "您已被邀请加入 <strong>%1</strong>",
"request.notification_title": "来自 <strong>%1</strong> 的用户组成员请求",
"request.notification_title": "来自 <strong>%1</strong> 的组成员请求",
"request.notification_text": "<strong>%1</strong> 已被邀请加入 <strong>%2</strong>",
"cover-save": "保存",
"cover-saving": "正在保存",
"details.title": "用户组信息",
"details.title": "组信息",
"details.members": "成员列表",
"details.pending": "待加入成员",
"details.invited": "已邀请成员",
"details.has_no_posts": "此用户组的会员尚未发表任何帖子。",
"details.has_no_posts": "此组的会员尚未发表任何帖子。",
"details.latest_posts": "最新帖子",
"details.private": "私有",
"details.disableJoinRequests": "禁止申请加入用户组",
"details.grant": "授予/取消管理权",
"details.kick": "踢出用户组",
"details.kick_confirm": "您确定要将此成员从用户组中移除吗?",
"details.owner_options": "用户组管理",
"details.group_name": "用户组名",
"details.kick_confirm": "您确定要将此成员从组中移除吗?",
"details.owner_options": "组管理",
"details.group_name": "组名",
"details.member_count": "用户组成员数",
"details.creation_date": "创建时间",
"details.description": "描述",
@ -38,21 +38,21 @@
"details.change_colour": "更改颜色",
"details.badge_text": "徽章文本",
"details.userTitleEnabled": "显示组内称号",
"details.private_help": "启用此选项后,加入用户组需要组长审批。",
"details.private_help": "启用此选项后,加入组需要组长审批。",
"details.hidden": "隐藏",
"details.hidden_help": "启用此选项后,用户组将不在用户组列表中展现,成员只能通过邀请加入。",
"details.delete_group": "删除用户组",
"details.private_system_help": "系统禁用了私有用户组,这个选项不起任何作用",
"event.updated": "用户组信息已更新",
"event.deleted": "用户组 \"%1\" 已被删除",
"details.hidden_help": "启用此选项后,群组将不在群组列表中展现,成员只能通过邀请加入。",
"details.delete_group": "删除组",
"details.private_system_help": "系统禁用了私有组,这个选项不起任何作用",
"event.updated": "组信息已更新",
"event.deleted": "组 \"%1\" 已被删除",
"membership.accept-invitation": "接受邀请",
"membership.invitation-pending": "邀请中",
"membership.join-group": "加入用户组",
"membership.leave-group": "退出用户组",
"membership.join-group": "加入组",
"membership.leave-group": "退出组",
"membership.reject": "拒绝",
"new-group.group_name": "组名: ",
"upload-group-cover": "上传组封面",
"bulk-invite-instructions": "输入您要邀请加入此用户组的用户名,多个用户以逗号分隔",
"new-group.group_name": "组名: ",
"upload-group-cover": "上传组封面",
"bulk-invite-instructions": "输入您要邀请加入此组的用户名,多个用户以逗号分隔",
"bulk-invite": "批量邀请",
"remove_group_cover_confirm": "确定要移除封面图片吗?"
}

@ -20,7 +20,7 @@
"chat.three_months": "3个月",
"chat.delete_message_confirm": "确认删除此消息吗?",
"chat.add-users-to-room": "向此聊天室中添加成员",
"composer.compose": "编写",
"composer.compose": "编写帮助",
"composer.show_preview": "显示预览",
"composer.hide_preview": "隐藏预览",
"composer.user_said_in": "%1 在 %2 中说:",
@ -38,7 +38,7 @@
"composer.upload-picture": "上传图片",
"composer.upload-file": "上传文件",
"composer.zen_mode": "无干扰模式",
"composer.select_category": "选择一个块",
"composer.select_category": "选择一个块",
"bootbox.ok": "确认",
"bootbox.cancel": "取消",
"bootbox.confirm": "确认",

@ -24,7 +24,7 @@
"upvoted_your_post_in_dual": "<strong>%1</strong> 和 <strong>%2</strong> 在 <strong>%3</strong> 赞了您的帖子。",
"upvoted_your_post_in_multiple": "<strong>%1</strong> 和 %2 个其他人在 <strong>%3</strong> 赞了您的帖子。",
"moved_your_post": "您的帖子已被 <strong>%1</strong> 移动到了 <strong>%2</strong>",
"moved_your_topic": "<strong>%1</strong> 移动了 <strong>%2</strong>",
"moved_your_topic": "<strong>%1</strong> 移动了 <strong>%2</strong>",
"user_flagged_post_in": "<strong>%1</strong> 在 <strong>%2</strong> 标记了一个帖子",
"user_flagged_post_in_dual": "<strong>%1</strong> 和 <strong>%2</strong> 在 <strong>%3</strong> 举报了一个帖子",
"user_flagged_post_in_multiple": "<strong>%1</strong> 和 %2 个其他人在 <strong>%3</strong> 举报了一个帖子",
@ -42,7 +42,7 @@
"new_register_multiple": "有 <strong>%1</strong> 条注册申请等待批准。",
"flag_assigned_to_you": "<strong>举报 %1</strong> 已经被指派给你",
"email-confirmed": "电子邮箱已确认",
"email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已激活。",
"email-confirm-error-message": "验证您电子邮箱地址时出现了问题。可能是因为验证码无效或已过期。",
"email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已全激活。",
"email-confirm-error-message": "验证您电子邮箱地址时出现了问题。可能是因为验证码无效或已过期。",
"email-confirm-sent": "确认邮件已发送。"
}

@ -22,9 +22,9 @@
"registration-complete": "注册完成",
"login": "登录帐号",
"reset": "重置帐户密码",
"categories": "块",
"groups": "用户组",
"group": "%1 的用户组",
"categories": "块",
"groups": "组",
"group": "%1 的组",
"chats": "聊天",
"chat": "与 %1 聊天",
"flags": "举报",
@ -38,7 +38,7 @@
"account/followers": "关注 %1 的人",
"account/posts": "%1 发布的帖子",
"account/topics": "%1 创建的主题",
"account/groups": "%1 的用户组",
"account/groups": "%1 的组",
"account/bookmarks": "%1 收藏的帖子",
"account/settings": "用户设置",
"account/watched": "主题已被 %1 关注",

@ -12,7 +12,7 @@
"password_placeholder": "输入密码",
"confirm_password": "确认密码",
"confirm_password_placeholder": "再次输入密码",
"register_now_button": "马上注册",
"register_now_button": "立即注册",
"alternative_registration": "其他方式注册",
"terms_of_use": "使用条款",
"agree_to_terms_of_use": "我同意使用条款",

@ -6,8 +6,8 @@
"titles": "标题",
"titles-posts": "标题和回帖",
"posted-by": "发表",
"in-categories": "在版",
"search-child-categories": "搜索子版",
"in-categories": "在版",
"search-child-categories": "搜索子版",
"has-tags": "有标签",
"reply-count": "回复数",
"at-least": "至少",
@ -31,7 +31,7 @@
"number-of-views": "查看数",
"topic-start-date": "主题开始日期",
"username": "用户名",
"category": "块",
"category": "块",
"descending": "倒序",
"ascending": "顺序",
"save-preferences": "保存设置",

@ -38,7 +38,7 @@
"login_to_subscribe": "请注册或登录后,再订阅此主题。",
"markAsUnreadForAll.success": "将全部主题标为未读。",
"mark_unread": "标记为未读",
"mark_unread.success": "未读话题",
"mark_unread.success": "主题已被标记为未读。",
"watch": "关注",
"unwatch": "取消关注",
"watch.title": "当此主题有新回复时,通知我",
@ -48,7 +48,7 @@
"not-watching": "未关注",
"ignoring": "忽略中",
"watching.description": "有新回复时通知我。<br/>在未读主题中显示。",
"not-watching.description": "不要在有新回复时通知我。<br/>如果这个分类未被忽略则在未读主题中显示。",
"not-watching.description": "不要在有新回复时通知我。<br/>如果这个版块未被忽略则在未读主题中显示。",
"ignoring.description": "不要在有新回复时通知我。<br/>不要在未读主题中显示该主题。",
"thread_tools.title": "主题工具",
"thread_tools.markAsUnreadForAll": "标记全部未读",
@ -70,8 +70,8 @@
"post_delete_confirm": "确定删除此帖吗?",
"post_restore_confirm": "确定恢复此帖吗?",
"post_purge_confirm": "确认清除此回帖吗?",
"load_categories": "正在载入块",
"disabled_categories_note": "停用的块为灰色",
"load_categories": "正在载入块",
"disabled_categories_note": "停用的块为灰色",
"confirm_move": "移动",
"confirm_fork": "分割",
"bookmark": "书签",

@ -1,5 +1,5 @@
{
"banned": "封禁",
"banned": "封禁",
"offline": "离线",
"username": "用户名",
"joindate": "注册日期",
@ -8,7 +8,7 @@
"confirm_email": "确认电子邮箱",
"account_info": "账户信息",
"ban_account": "封禁账户",
"ban_account_confirm": "您确定封禁这位用户吗?",
"ban_account_confirm": "您确定封禁这位用户吗?",
"unban_account": "解禁账户",
"delete_account": "删除帐号",
"delete_account_confirm": "确认要删除您的帐户吗?<br /><strong>此操作是不可逆转的,您将无法恢复您的任何数据</strong><br /><br />请输入您的用户名,确认您想要删除此帐户。",
@ -108,8 +108,8 @@
"scroll_to_my_post": "在提交回复之后显示新回复",
"follow_topics_you_reply_to": "关注你回复过的主题",
"follow_topics_you_create": "关注你创建的主题",
"grouptitle": "用户组标题",
"no-group-title": "不展示用户组称号",
"grouptitle": "组标题",
"no-group-title": "不展示组称号",
"select-skin": "选择皮肤",
"select-homepage": "选择首页",
"homepage": "首页",
@ -117,8 +117,8 @@
"custom_route": "自定义首页路由",
"custom_route_help": "输入路由名称,前面不需要斜杠 ( 例如, \"recent\" 或 \"popular\" )",
"sso.title": "单点登录服务",
"sso.associated": "关联到",
"sso.not-associated": "点击这里关联",
"sso.associated": "关联到",
"sso.not-associated": "点击这里关联",
"info.latest-flags": "最新举报",
"info.no-flags": "没有找到被举报的帖子",
"info.ban-history": "最近封禁历史",
@ -129,7 +129,7 @@
"info.banned-no-reason": "没有原因",
"info.username-history": "历史用户名",
"info.email-history": "历史邮箱",
"info.moderation-note": "版主留言",
"info.moderation-note.success": "修改未保存",
"info.moderation-note.add": "添加"
"info.moderation-note": "版主备注",
"info.moderation-note.success": "版主备注已保存",
"info.moderation-note.add": "添加注"
}

@ -15,7 +15,7 @@
"recent_topics": "最新主题",
"popular_topics": "热门主题",
"unread_topics": "未读主题",
"categories": "版",
"categories": "版",
"tags": "话题",
"no-users-found": "未找到匹配的用户!"
}

@ -1,6 +1,5 @@
'use strict';
define('admin/modules/search', ['mousetrap'], function (mousetrap) {
var search = {};
@ -11,16 +10,17 @@ define('admin/modules/search', ['mousetrap'], function (mousetrap) {
var namespace = params.namespace;
var translations = params.translations;
var title = params.title;
var escaped = utils.escapeRegexChars(term);
var results = translations
// remove all lines without a match
.replace(new RegExp('^(?:(?!' + term + ').)*$', 'gmi'), '')
.replace(new RegExp('^(?:(?!' + escaped + ').)*$', 'gmi'), '')
// remove lines that only match the title
.replace(new RegExp('(^|\\n).*?' + title + '.*?(\\n|$)', 'g'), '')
// get up to 25 characters of context on both sides of the match
// and wrap the match in a `.search-match` element
.replace(
new RegExp('^[\\s\\S]*?(.{0,25})(' + term + ')(.{0,25})[\\s\\S]*?$', 'gmi'),
new RegExp('^[\\s\\S]*?(.{0,25})(' + escaped + ')(.{0,25})[\\s\\S]*?$', 'gmi'),
'...$1<span class="search-match">$2</span>$3...<br>'
)
// collapse whitespace
@ -28,7 +28,7 @@ define('admin/modules/search', ['mousetrap'], function (mousetrap) {
.trim();
title = title.replace(
new RegExp('(^.*?)(' + term + ')(.*?$)', 'gi'),
new RegExp('(^.*?)(' + escaped + ')(.*?$)', 'gi'),
'$1<span class="search-match">$2</span>$3'
);
@ -123,7 +123,7 @@ define('admin/modules/search', ['mousetrap'], function (mousetrap) {
menu.children('.result').remove();
var len = value.length;
var len = /\W/.test(value) ? 3 : value.length;
var results;
menu.toggleClass('state-start-typing', len === 0);

@ -1,7 +1,7 @@
'use strict';
define('forum/account/edit/password', ['forum/account/header', 'translator'], function (header, translator) {
define('forum/account/edit/password', ['forum/account/header', 'translator', 'zxcvbn'], function (header, translator, zxcvbn) {
var AccountEditPassword = {};
AccountEditPassword.init = function () {
@ -20,6 +20,7 @@ define('forum/account/edit/password', ['forum/account/header', 'translator'], fu
var passwordsmatch = false;
function onPasswordChanged() {
var passwordStrength = zxcvbn(password.val());
passwordvalid = false;
if (password.val().length < ajaxify.data.minimumPasswordLength) {
showError(password_notify, '[[user:change_password_error_length]]');
@ -29,6 +30,8 @@ define('forum/account/edit/password', ['forum/account/header', 'translator'], fu
showError(password_notify, '[[user:password_same_as_username]]');
} else if (password.val() === ajaxify.data.email) {
showError(password_notify, '[[user:password_same_as_email]]');
} else if (passwordStrength.score < ajaxify.data.minimumPasswordStrength) {
showError(password_notify, '[[user:weak_password]]');
} else {
showSuccess(password_notify);
passwordvalid = true;

@ -98,7 +98,11 @@ define('forum/chats', [
}
loading = true;
var start = parseInt($('.chat-content').children('[data-index]').first().attr('data-index'), 10) + 1;
socket.emit('modules.chats.getMessages', { roomId: roomId, uid: uid, start: start }, function (err, data) {
socket.emit('modules.chats.getMessages', {
roomId: roomId,
uid: uid,
start: start,
}, function (err, data) {
if (err) {
return app.alertError(err.message);
}
@ -122,7 +126,7 @@ define('forum/chats', [
Chats.addEditDeleteHandler = function (element, roomId) {
element.on('click', '[data-action="edit"]', function () {
var messageId = $(this).parents('[data-mid]').attr('data-mid');
var inputEl = components.get('chat/input');
var inputEl = $('[data-roomid="' + roomId + '"] [component="chat/input"]');
messages.prepEdit(inputEl, messageId, roomId);
}).on('click', '[data-action="delete"]', function () {
var messageId = $(this).parents('[data-mid]').attr('data-mid');
@ -170,7 +174,10 @@ define('forum/chats', [
if (oldName === newName) {
return;
}
socket.emit('modules.chats.renameRoom', { roomId: roomId, newName: newName }, function (err) {
socket.emit('modules.chats.renameRoom', {
roomId: roomId,
newName: newName,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
@ -235,10 +242,15 @@ define('forum/chats', [
if (event.item === app.user.username) {
return;
}
socket.emit('modules.chats.addUserToRoom', { roomId: data.roomId, username: event.item }, function (err) {
socket.emit('modules.chats.addUserToRoom', {
roomId: data.roomId,
username: event.item,
}, function (err) {
if (err) {
app.alertError(err.message);
tagEl.tagsinput('remove', event.item, { nouser: true });
tagEl.tagsinput('remove', event.item, {
nouser: true,
});
}
});
});
@ -262,7 +274,10 @@ define('forum/chats', [
if (event.options && event.options.nouser) {
return;
}
socket.emit('modules.chats.removeUserFromRoom', { roomId: data.roomId, username: event.item }, function (err) {
socket.emit('modules.chats.removeUserFromRoom', {
roomId: data.roomId,
username: event.item,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
@ -325,7 +340,12 @@ define('forum/chats', [
} else {
var recentEl = components.get('chat/recent');
templates.parse('partials/chats/recent_room', {
rooms: { roomId: data.roomId, lastUser: data.message.fromUser, usernames: data.message.fromUser.username, unread: true },
rooms: {
roomId: data.roomId,
lastUser: data.message.fromUser,
usernames: data.message.fromUser.username,
unread: true,
},
}, function (html) {
translator.translate(html, function (translated) {
recentEl.prepend(translated);
@ -347,7 +367,7 @@ define('forum/chats', [
};
Chats.resizeMainWindow = function () {
var messagesList = $('.expanded-chat .chat-content');
var messagesList = $('.expanded-chat .chat-content');
if (messagesList.length) {
var margin = $('.expanded-chat ul').outerHeight(true) - $('.expanded-chat ul').height();

@ -1,7 +1,7 @@
'use strict';
define('forum/register', ['translator'], function (translator) {
define('forum/register', ['translator', 'zxcvbn'], function (translator, zxcvbn) {
var Register = {};
var validationError = false;
var successIcon = '';
@ -170,6 +170,7 @@ define('forum/register', ['translator'], function (translator) {
function validatePassword(password, password_confirm) {
var password_notify = $('#password-notify');
var password_confirm_notify = $('#password-confirm-notify');
var passwordStrength = zxcvbn(password);
if (password.length < ajaxify.data.minimumPasswordLength) {
showError(password_notify, '[[user:change_password_error_length]]');
@ -181,6 +182,8 @@ define('forum/register', ['translator'], function (translator) {
showError(password_notify, '[[user:password_same_as_username]]');
} else if (password === $('#email').val()) {
showError(password_notify, '[[user:password_same_as_email]]');
} else if (passwordStrength.score < ajaxify.data.minimumPasswordStrength) {
showError(password_notify, '[[user:weak_password]]');
} else {
showSuccess(password_notify, successIcon);
}

@ -75,7 +75,9 @@ define('chat', [
});
}
} else {
socket.emit('modules.chats.loadRoom', { roomId: data.roomId }, function (err, roomData) {
socket.emit('modules.chats.loadRoom', {
roomId: data.roomId,
}, function (err, roomData) {
if (err) {
return app.alertError(err.message);
}
@ -111,7 +113,10 @@ define('chat', [
};
module.loadChatsDropdown = function (chatsListEl) {
socket.emit('modules.chats.getRecentChats', { uid: app.user.uid, after: 0 }, function (err, data) {
socket.emit('modules.chats.getRecentChats', {
uid: app.user.uid,
after: 0,
}, function (err, data) {
if (err) {
return app.alertError(err.message);
}
@ -163,7 +168,7 @@ define('chat', [
var dragged = false;
chatModal.attr('id', 'chat-modal-' + data.roomId);
chatModal.attr('roomId', data.roomId);
chatModal.attr('data-roomid', data.roomId);
chatModal.attr('intervalId', 0);
chatModal.attr('UUID', uuid);
chatModal.css('position', 'fixed');
@ -211,7 +216,7 @@ define('chat', [
components.get('chat/input').val(text);
});
ajaxify.go('user/' + app.user.userslug + '/chats/' + chatModal.attr('roomId'));
ajaxify.go('user/' + app.user.userslug + '/chats/' + chatModal.attr('data-roomid'));
module.close(chatModal);
}
@ -252,14 +257,14 @@ define('chat', [
messagesEl.css('height', module.calculateChatListHeight(chatModal));
});
Chats.addRenameHandler(chatModal.attr('roomId'), chatModal.find('[component="chat/room/name"]'));
Chats.addRenameHandler(chatModal.attr('data-roomid'), chatModal.find('[component="chat/room/name"]'));
Chats.addSendHandlers(chatModal.attr('roomId'), chatModal.find('#chat-message-input'), chatModal.find('#chat-message-send-btn'));
Chats.addSendHandlers(chatModal.attr('data-roomid'), chatModal.find('#chat-message-input'), chatModal.find('#chat-message-send-btn'));
Chats.createTagsInput(chatModal.find('.users-tag-input'), data);
Chats.createAutoComplete(chatModal.find('[component="chat/input"]'));
Chats.addScrollHandler(chatModal.attr('roomId'), data.uid, chatModal.find('.chat-content'));
Chats.addScrollHandler(chatModal.attr('data-roomid'), data.uid, chatModal.find('.chat-content'));
taskbar.push('chat', chatModal.attr('UUID'), {
title: data.roomName || (data.users.length ? data.users[0].username : ''),
@ -314,7 +319,7 @@ define('chat', [
ChatsMessages.scrollToBottom(chatModal.find('.chat-content'));
module.bringModalToTop(chatModal);
module.focusInput(chatModal);
socket.emit('modules.chats.markRead', chatModal.attr('roomId'));
socket.emit('modules.chats.markRead', chatModal.attr('data-roomid'));
var env = utils.findBootstrapEnvironment();
if (env === 'xs' || env === 'sm') {

@ -97,6 +97,9 @@
// and the strings of untranslated text in between
var toTranslate = [];
// to store the state of if we're currently in a top-level token for later
var inToken = false;
// split a translator string into an array of tokens
// but don't split by commas inside other translator strings
function split(text) {
@ -141,6 +144,8 @@
// set the last break to our current
// spot since we just broke the string
lastBreak = cursor;
// we're in a token now
inToken = true;
// the current level of nesting of the translation strings
var level = 0;
@ -176,6 +181,8 @@
invalidTextRegex.test(sliced[0])) {
cursor += 1;
lastBreak -= 2;
// no longer in a token
inToken = false;
if (level > 0) {
level -= 1;
} else {
@ -191,18 +198,26 @@
// if we're at the base level, then this is the end
if (level === 0) {
// so grab the name and args
var result = split(str.slice(lastBreak, cursor));
var currentSlice = str.slice(lastBreak, cursor);
var result = split(currentSlice);
var name = result[0];
var args = result.slice(1);
// make a backup based on the raw string of the token
// if there are arguments to the token
var backup = '';
if (args && args.length) {
backup = this.translate('&lsqb;&lsqb;' + currentSlice + '&lsqb;&lsqb;');
}
// add the translation promise to the array
toTranslate.push(this.translateKey(name, args));
toTranslate.push(this.translateKey(name, args, backup));
// skip past the ending brackets
cursor += 2;
// set this as our last break
lastBreak = cursor;
// and we're no longer in a translation string,
// so continue with the main loop
inToken = false;
break;
}
// otherwise we lower the level
@ -219,8 +234,16 @@
cursor += 1;
}
// ending string of source
var last = str.slice(lastBreak);
// if we were mid-token, treat it as invalid
if (inToken) {
last = this.translate('&lsqb;&lsqb;' + last);
}
// add the remaining text after the last translation string
toTranslate.push(str.slice(lastBreak, cursor + 2));
toTranslate.push(last);
// and return a promise for the concatenated translated string
return Promise.all(toTranslate).then(function (translated) {
@ -232,9 +255,10 @@
* Translates a specific key and array of arguments
* @param {string} name - Translation key (ex. 'global:home')
* @param {string[]} args - Arguments for `%1`, `%2`, etc
* @param {string|Promise<string>} backup - Text to use in case the key can't be found
* @returns {Promise<string>}
*/
Translator.prototype.translateKey = function translateKey(name, args) {
Translator.prototype.translateKey = function translateKey(name, args, backup) {
var self = this;
var result = name.split(':', 2);
@ -251,29 +275,27 @@
}
var translation = this.getTranslation(namespace, key);
var argsToTranslate = args.map(function (arg) {
return string(arg).collapseWhitespace().decodeHTMLEntities().escapeHTML().s;
}).map(function (arg) {
return self.translate(arg);
});
// so we can await all promises at once
argsToTranslate.unshift(translation);
return Promise.all(argsToTranslate).then(function (result) {
var translated = result[0];
var translatedArgs = result.slice(1);
return translation.then(function (translated) {
// check if the translation is missing first
if (!translated) {
warn('Missing translation "' + name + '"');
return key;
return backup || key;
}
var out = translated;
translatedArgs.forEach(function (arg, i) {
var escaped = arg.replace(/%(?=\d)/g, '&#37;').replace(/\\,/g, '&#44;');
out = out.replace(new RegExp('%' + (i + 1), 'g'), escaped);
var argsToTranslate = args.map(function (arg) {
return string(arg).collapseWhitespace().decodeHTMLEntities().escapeHTML().s;
}).map(function (arg) {
return self.translate(arg);
});
return Promise.all(argsToTranslate).then(function (translatedArgs) {
var out = translated;
translatedArgs.forEach(function (arg, i) {
var escaped = arg.replace(/%(?=\d)/g, '&#37;').replace(/\\,/g, '&#44;');
out = out.replace(new RegExp('%' + (i + 1), 'g'), escaped);
});
return out;
});
return out;
});
};
@ -281,7 +303,7 @@
* Load translation file (or use a cached version), and optionally return the translation of a certain key
* @param {string} namespace - The file name of the translation namespace
* @param {string} [key] - The key of the specific translation to getJSON
* @returns {Promise<Object|string>}
* @returns {Promise<Object>|Promise<string>}
*/
Translator.prototype.getTranslation = function getTranslation(namespace, key) {
var translation;

@ -86,7 +86,7 @@ module.exports = function (Categories) {
privileges.topics.filterTids('read', tids, uid, next);
},
function (tids, next) {
getTopics(tids, next);
getTopics(tids, uid, next);
},
function (topics, next) {
assignTopicsToCategories(categoryData, topics);
@ -98,7 +98,7 @@ module.exports = function (Categories) {
], callback);
};
function getTopics(tids, callback) {
function getTopics(tids, uid, callback) {
var topicData;
async.waterfall([
function (next) {
@ -119,7 +119,7 @@ module.exports = function (Categories) {
async.parallel({
categoryData: async.apply(Categories.getCategoriesFields, cids, ['cid', 'parentCid']),
teasers: async.apply(topics.getTeasers, _topicData),
teasers: async.apply(topics.getTeasers, _topicData, uid),
}, next);
},
function (results, next) {

@ -80,6 +80,7 @@ function renderRoute(name, req, res, next) {
if (name === 'password') {
userData.minimumPasswordLength = parseInt(meta.config.minimumPasswordLength, 10);
userData.minimumPasswordStrength = parseInt(meta.config.minimumPasswordStrength || 0, 10);
}
userData.title = '[[pages:account/edit/' + name + ', ' + userData.username + ']]';

@ -43,13 +43,14 @@ exports.handleErrors = function (err, req, res, next) { // eslint-disable-line n
return res.status(403).type('text/plain').send(err.message);
}
if (parseInt(err.status, 10) === 302 && err.path) {
return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path);
var status = parseInt(err.status, 10);
if ((status === 302 || status === 308) && err.path) {
return res.locals.isAPI ? res.status(status).json(err.path) : res.redirect(err.path);
}
winston.error(req.path + '\n', err.stack);
res.status(err.status || 500);
res.status(status || 500);
var path = String(req.path || '');
if (res.locals.isAPI) {

@ -48,7 +48,11 @@ Controllers.home = function (req, res, next) {
var hook = 'action:homepage.get:' + route;
if (plugins.hasListeners(hook)) {
return plugins.fireHook(hook, { req: req, res: res, next: next });
return plugins.fireHook(hook, {
req: req,
res: res,
next: next,
});
}
if (route === 'categories' || route === '/') {
@ -85,7 +89,15 @@ Controllers.reset = function (req, res, next) {
displayExpiryNotice: req.session.passwordExpired,
code: req.params.code,
minimumPasswordLength: parseInt(meta.config.minimumPasswordLength, 10),
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[reset_password:reset_password]]', url: '/reset' }, { text: '[[reset_password:update_password]]' }]),
breadcrumbs: helpers.buildBreadcrumbs([
{
text: '[[reset_password:reset_password]]',
url: '/reset',
},
{
text: '[[reset_password:update_password]]',
},
]),
title: '[[pages:reset]]',
});
@ -94,7 +106,9 @@ Controllers.reset = function (req, res, next) {
} else {
res.render('reset', {
code: null,
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[reset_password:reset_password]]' }]),
breadcrumbs: helpers.buildBreadcrumbs([{
text: '[[reset_password:reset_password]]',
}]),
title: '[[pages:reset]]',
});
}
@ -124,7 +138,9 @@ Controllers.login = function (req, res, next) {
data.allowLocalLogin = parseInt(meta.config.allowLocalLogin, 10) === 1 || parseInt(req.query.local, 10) === 1;
data.allowRegistration = registrationType === 'normal' || registrationType === 'admin-approval' || registrationType === 'admin-approval-ip';
data.allowLoginWith = '[[login:' + allowLoginWith + ']]';
data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[global:login]]' }]);
data.breadcrumbs = helpers.buildBreadcrumbs([{
text: '[[global:login]]',
}]);
data.error = req.flash('error')[0] || errorText;
data.title = '[[pages:login]]';
@ -171,7 +187,11 @@ Controllers.register = function (req, res, next) {
}
},
function (next) {
plugins.fireHook('filter:parse.post', { postData: { content: meta.config.termsOfUse || '' } }, next);
plugins.fireHook('filter:parse.post', {
postData: {
content: meta.config.termsOfUse || '',
},
}, next);
},
], function (err, termsOfUse) {
if (err) {
@ -188,8 +208,11 @@ Controllers.register = function (req, res, next) {
data.minimumUsernameLength = parseInt(meta.config.minimumUsernameLength, 10);
data.maximumUsernameLength = parseInt(meta.config.maximumUsernameLength, 10);
data.minimumPasswordLength = parseInt(meta.config.minimumPasswordLength, 10);
data.minimumPasswordStrength = parseInt(meta.config.minimumPasswordStrength || 0, 10);
data.termsOfUse = termsOfUse.postData.content;
data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[register:register]]' }]);
data.breadcrumbs = helpers.buildBreadcrumbs([{
text: '[[register:register]]',
}]);
data.regFormEntry = [];
data.error = req.flash('error')[0] || errorText;
data.title = '[[pages:register]]';
@ -333,7 +356,9 @@ Controllers.outgoing = function (req, res, next) {
res.render('outgoing', {
outgoing: validator.escape(String(url)),
title: meta.config.title,
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[notifications:outgoing_link]]' }]),
breadcrumbs: helpers.buildBreadcrumbs([{
text: '[[notifications:outgoing_link]]',
}]),
});
};
@ -341,7 +366,9 @@ Controllers.termsOfUse = function (req, res, next) {
if (!meta.config.termsOfUse) {
return next();
}
res.render('tos', { termsOfUse: meta.config.termsOfUse });
res.render('tos', {
termsOfUse: meta.config.termsOfUse,
});
};
Controllers.ping = function (req, res) {

@ -67,6 +67,7 @@ topicsController.get = function (req, res, callback) {
settings = results.settings;
var postCount = parseInt(results.topic.postcount, 10);
pageCount = Math.max(1, Math.ceil(postCount / settings.postsPerPage));
results.topic.postcount = postCount;
if (utils.isNumber(req.params.post_index) && (req.params.post_index < 1 || req.params.post_index > postCount)) {
return helpers.redirect(res, '/topic/' + req.params.topic_id + '/' + req.params.slug + (req.params.post_index > postCount ? '/' + postCount : ''));

@ -166,7 +166,7 @@
};
module.checkCompatibility = function (callback) {
var mongoPkg = require.main.require('./node_modules/mongodb/package.json');
var mongoPkg = require('mongodb/package.json');
if (semver.lt(mongoPkg.version, '2.0.0')) {
return callback(new Error('The `mongodb` package is out-of-date, please run `./nodebb setup` again.'));

@ -101,16 +101,21 @@ module.exports = function (Meta) {
}
function updateConfig(config) {
updateLocalConfig(config);
pubsub.publish('config:update', config);
}
function updateLocalConfig(config) {
for (var field in config) {
if (config.hasOwnProperty(field)) {
Meta.config[field] = config[field];
}
}
}
pubsub.on('config:update', function onConfigReceived(config) {
if (typeof config === 'object' && Meta.config) {
for (var field in config) {
if (config.hasOwnProperty(field)) {
Meta.config[field] = config[field];
}
}
updateLocalConfig(config);
}
});

@ -20,8 +20,8 @@ module.exports = function (Meta) {
target: {},
scripts: {
base: [
'./node_modules/jquery/dist/jquery.js',
'./node_modules/socket.io-client/dist/socket.io.js',
'node_modules/jquery/dist/jquery.js',
'node_modules/socket.io-client/dist/socket.io.js',
'public/vendor/jquery/timeago/jquery.timeago.js',
'public/vendor/jquery/js/jquery.form.min.js',
'public/vendor/visibility/visibility.min.js',
@ -35,14 +35,14 @@ module.exports = function (Meta) {
'public/vendor/tinycon/tinycon.js',
'public/vendor/xregexp/xregexp.js',
'public/vendor/xregexp/unicode/unicode-base.js',
'./node_modules/templates.js/lib/templates.js',
'node_modules/templates.js/lib/templates.js',
'public/src/utils.js',
'public/src/sockets.js',
'public/src/app.js',
'public/src/ajaxify.js',
'public/src/overrides.js',
'public/src/widgets.js',
'./node_modules/promise-polyfill/promise.js',
'node_modules/promise-polyfill/promise.js',
],
// files listed below are only available client-side, or are bundled in to reduce # of network requests on cold load
@ -84,11 +84,12 @@ module.exports = function (Meta) {
// modules listed below are built (/src/modules) so they can be defined anonymously
modules: {
'Chart.js': './node_modules/chart.js/dist/Chart.min.js',
'mousetrap.js': './node_modules/mousetrap/mousetrap.min.js',
'Chart.js': 'node_modules/chart.js/dist/Chart.min.js',
'mousetrap.js': 'node_modules/mousetrap/mousetrap.min.js',
'jqueryui.js': 'public/vendor/jquery/js/jquery-ui.js',
'buzz.js': 'public/vendor/buzz/buzz.js',
'cropper.js': './node_modules/cropperjs/dist/cropper.min.js',
'cropper.js': 'node_modules/cropperjs/dist/cropper.min.js',
'zxcvbn.js': 'node_modules/zxcvbn/dist/zxcvbn.js',
},
},
};
@ -110,7 +111,9 @@ module.exports = function (Meta) {
}
if (filePath.endsWith('.min.js')) {
minified = { code: buffer.toString() };
minified = {
code: buffer.toString(),
};
return cb();
}
@ -193,10 +196,10 @@ module.exports = function (Meta) {
function clearModules(callback) {
var builtPaths = moduleDirs.map(function (p) {
return '../../build/public/src/' + p;
return path.join(__dirname, '../../build/public/src', p);
});
async.each(builtPaths, function (builtPath, next) {
rimraf(path.join(__dirname, builtPath), next);
rimraf(builtPath, next);
}, function (err) {
callback(err);
});
@ -311,7 +314,7 @@ module.exports = function (Meta) {
}
Meta.js.target[target].scripts = Meta.js.target[target].scripts.map(function (script) {
return path.relative(basePath, script).replace(/\\/g, '/');
return path.resolve(basePath, script).replace(/\\/g, '/');
});
callback();
@ -325,7 +328,7 @@ module.exports = function (Meta) {
};
Meta.js.commitToFile = function (target, callback) {
fs.writeFile(path.join(__dirname, '../../build/public/' + target), Meta.js.target[target].cache, function (err) {
fs.writeFile(path.join(__dirname, '../../build/public', target), Meta.js.target[target].cache, function (err) {
callback(err);
});
};
@ -345,7 +348,9 @@ module.exports = function (Meta) {
/**
* otherwise, just clean up --debug/--debug-brk options which are set up by default from the parent one
*/
forkProcessParams = { execArgv: [] };
forkProcessParams = {
execArgv: [],
};
}
return forkProcessParams;

@ -2,6 +2,7 @@
(function (module) {
var fork = require('child_process').fork;
var path = require('path');
module.hash = function (rounds, password, callback) {
forkChild({ type: 'hash', rounds: rounds, password: password }, callback);
@ -16,7 +17,7 @@
if (global.v8debug || parseInt(process.execArgv.indexOf('--debug'), 10) !== -1) {
forkProcessParams = { execArgv: ['--debug=' + (5859), '--nolazy'] };
}
var child = fork('./bcrypt', [], forkProcessParams);
var child = fork(path.join(__dirname, 'bcrypt'), [], forkProcessParams);
child.on('message', function (msg) {
if (msg.err) {

@ -1,5 +1,6 @@
'use strict';
var path = require('path');
var winston = require('winston');
var nconf = require('nconf');
var async = require('async');
@ -83,7 +84,7 @@ function resetTheme(themeId, callback) {
var meta = require('./meta');
var fs = require('fs');
fs.access('node_modules/' + themeId + '/package.json', function (err) {
fs.access(path.join(__dirname, '../node_modules', themeId, 'package.json'), function (err) {
if (err) {
winston.warn('[reset] Theme `%s` is not installed on this forum', themeId);
callback(new Error('theme-not-found'));

@ -104,7 +104,6 @@ function setupConfigs() {
function printStartupInfo() {
if (nconf.get('isPrimary') === 'true') {
winston.info('Time: %s', (new Date()).toString());
winston.info('Initializing NodeBB v%s', nconf.get('version'));
var host = nconf.get(nconf.get('database') + ':host');

@ -126,7 +126,7 @@ var social = require('./social');
Topics.getUserBookmarks(tids, uid, next);
},
teasers: function (next) {
Topics.getTeasers(topics, next);
Topics.getTeasers(topics, uid, next);
},
tags: function (next) {
Topics.getTopicsTagsObjects(tids, next);

@ -63,7 +63,7 @@ module.exports = function (Topics) {
var uids = [];
postData.forEach(function (postData) {
if (postData && postData[field] && uids.indexOf(postData[field]) === -1) {
if (postData && parseInt(postData[field], 10) >= 0 && uids.indexOf(postData[field]) === -1) {
uids.push(postData[field]);
}
});

@ -12,7 +12,11 @@ var plugins = require('../plugins');
var utils = require('../../public/src/utils');
module.exports = function (Topics) {
Topics.getTeasers = function (topics, callback) {
Topics.getTeasers = function (topics, uid, callback) {
if (typeof uid === 'function') {
callback = uid;
uid = 0;
}
if (!Array.isArray(topics) || !topics.length) {
return callback(null, []);
}
@ -94,7 +98,7 @@ module.exports = function (Topics) {
return tidToPost[topic.tid];
});
plugins.fireHook('filter:teasers.get', { teasers: teasers }, next);
plugins.fireHook('filter:teasers.get', { teasers: teasers, uid: uid }, next);
},
function (data, next) {
next(null, data.teasers);
@ -102,7 +106,11 @@ module.exports = function (Topics) {
], callback);
};
Topics.getTeasersByTids = function (tids, callback) {
Topics.getTeasersByTids = function (tids, uid, callback) {
if (typeof uid === 'function') {
callback = uid;
uid = 0;
}
if (!Array.isArray(tids) || !tids.length) {
return callback(null, []);
}
@ -111,13 +119,17 @@ module.exports = function (Topics) {
Topics.getTopicsFields(tids, ['tid', 'postcount', 'teaserPid'], next);
},
function (topics, next) {
Topics.getTeasers(topics, next);
Topics.getTeasers(topics, uid, next);
},
], callback);
};
Topics.getTeaser = function (tid, callback) {
Topics.getTeasersByTids([tid], function (err, teasers) {
Topics.getTeaser = function (tid, uid, callback) {
if (typeof uid === 'function') {
callback = uid;
uid = 0;
}
Topics.getTeasersByTids([tid], uid, function (err, teasers) {
callback(err, Array.isArray(teasers) && teasers.length ? teasers[0] : null);
});
};

@ -199,20 +199,21 @@ var privileges = require('../privileges');
db.getObjectsFields(keys, ['mergeId'], next);
},
], function (err, mergeIds) {
// A missing (null) mergeId means that notification is counted separately.
mergeIds = mergeIds.map(function (set) {
return set.mergeId;
});
function (mergeIds, next) {
mergeIds = mergeIds.map(function (set) {
return set.mergeId;
});
callback(err, mergeIds.reduce(function (count, cur, idx, arr) {
if (cur === null || idx === arr.indexOf(cur)) {
count += 1;
}
next(null, mergeIds.reduce(function (count, mergeId, idx, arr) {
// A missing (null) mergeId means that notification is counted separately.
if (mergeId === null || idx === arr.indexOf(mergeId)) {
count += 1;
}
return count;
}, 0));
});
return count;
}, 0));
},
], callback);
};
UserNotifications.getUnreadByField = function (uid, field, values, callback) {

@ -167,6 +167,16 @@
<label>[[admin/settings/user:min-password-length]]</label>
<input type="text" class="form-control" value="6" data-field="minimumPasswordLength">
</div>
<div class="form-group">
<label>[[admin/settings/user:min-password-strength]]</label>
<select class="form-control" data-field="minimumPasswordStrength">
<option value="0">0 - too guessable: risky password</option>
<option value="1">1 - very guessable</option>
<option value="2">2 - somewhat guessable</option>
<option value="3">3 - safely unguessable</option>
<option value="4">4 - very unguessable</option>
</select>
</div>
<div class="form-group">
<label>[[admin/settings/user:max-about-me-length]]</label>
<input type="text" class="form-control" value="500" data-field="maximumAboutMeLength">

@ -156,6 +156,27 @@ describe('new Translator(language)', function () {
assert.strictEqual(translated, 'Latest Users');
});
});
it('should use key for unknown keys without arguments', function () {
var translator = Translator.create('en-GB');
return translator.translate('[[unknown:key.without.args]]').then(function (translated) {
assert.strictEqual(translated, 'key.without.args');
});
});
it('should use backup for unknown keys with arguments', function () {
var translator = Translator.create('en-GB');
return translator.translate('[[unknown:key.with.args, arguments are here, derpity, derp]]').then(function (translated) {
assert.strictEqual(translated, '&lsqb;&lsqb;unknown:key.with.args, arguments are here, derpity, derp&lsqb;&lsqb;');
});
});
it('should ignore unclosed tokens', function () {
var translator = Translator.create('en-GB');
return translator.translate('here is some stuff and other things [[abc:xyz, other random stuff should be fine here [[global:home]] and more things [[pages:users/latest]]').then(function (translated) {
assert.strictEqual(translated, 'here is some stuff and other things &lsqb;&lsqb;abc:xyz, other random stuff should be fine here Home and more things Latest Users');
});
});
});
});

Loading…
Cancel
Save