Compare commits

..

30 Commits

Author SHA1 Message Date
Julian Lam
4de7529241 Fix ./nodebb upgrade process to not use programmatic npm
- Closes #3451
- Apparently, programmatically invoking npm is like opening Pandora's
box. No thanks.
2015-08-18 16:47:05 -04:00
Julian Lam
944fc6e2f8 0.7.3 2015-08-18 11:48:43 -04:00
Julian Lam
ffa0544036 updated shrinkwrap file 2015-08-18 11:43:06 -04:00
Julian Lam
2a99721d01 Merge branch 'master' into v0.7.x 2015-08-18 11:35:41 -04:00
Julian Lam
6fff745c5c updated shrinkwrap file 2015-08-17 17:12:59 -04:00
Julian Lam
c93781c38a Merge branch 'master' into v0.7.x 2015-08-17 17:10:06 -04:00
Barış Soner Uşaklı
4bcf7792f8 xss fix 2015-07-30 14:39:28 -04:00
Julian Lam
93fb36b99d re-generated shrinkwrap file 2015-07-29 12:36:58 -04:00
Julian Lam
56b79a974a 0.7.2 2015-07-28 10:40:21 -04:00
Julian Lam
418b9bf02f Merge branch 'master' into v0.7.x 2015-07-28 10:37:50 -04:00
Julian Lam
258d562c3e updated shrinkwrap file 2015-07-28 10:34:46 -04:00
Julian Lam
f1b097ded6 Merge branch 'master' into v0.7.x 2015-07-28 10:29:42 -04:00
Julian Lam
ef750e2ebd updated shrinkwrap file 2015-07-21 15:26:24 -04:00
Julian Lam
67d9209b99 fixed regression of pagination caused by changes to ajaxification logic 2015-07-21 15:25:12 -04:00
Julian Lam
98c1390e7f updated theme minvers 2015-07-21 15:25:06 -04:00
Julian Lam
a3d17d14fd 0.7.1 2015-07-21 14:55:29 -04:00
Julian Lam
307eb19d01 updated shrinkwrap file 2015-07-21 14:54:27 -04:00
Julian Lam
f475d6e3b8 switching default theme on installation to Persona, from Lavender 2015-07-21 14:54:14 -04:00
Julian Lam
b4465b7df1 Merge branch 'master' into v0.7.x 2015-07-21 14:45:45 -04:00
Julian Lam
aa001b5926 Merge pull request #3287 from exo-do/v0.7.x
Update user.json
2015-07-02 22:52:48 -04:00
.marty
5c253c1efa Update user.json
fixed some missing translations
2015-07-03 04:42:47 +02:00
Barış Soner Uşaklı
59b14669c4 fix composer on mobile 2015-06-29 13:32:01 -04:00
Barış Soner Uşaklı
3e386e7668 fix xss on website 2015-06-21 02:15:34 -04:00
Julian Lam
43b90c5679 fixed #3218
Conflicts:
	src/database/mongo.js
2015-06-05 13:35:58 -04:00
Barış Soner Uşaklı
58b8c32fe9 crash fix 2015-05-29 00:47:37 -04:00
Julian Lam
1b88a6a523 upped mentions minver 2015-05-28 12:16:59 -04:00
Julian Lam
8ecd90729e latest translations and fallbacks 2015-05-28 12:16:31 -04:00
barisusakli
a5b8a656e9 fix minSchemaDate 2015-05-23 19:30:56 -04:00
Julian Lam
2cae7995e7 0.7.0 2015-05-23 18:56:50 -04:00
Julian Lam
19ca796915 🚀 2015-05-23 18:56:42 -04:00
4962 changed files with 85764 additions and 271289 deletions

View File

@@ -1,30 +0,0 @@
# Save as .codeclimate.yml (note leading .) in project root directory
version: "2"
languages:
Ruby: false
JavaScript: true
PHP: false
checks:
file-lines:
config:
threshold: 500
method-lines:
config:
threshold: 75
method-complexity:
config:
threshold: 10
similar-code:
config:
threshold: 65
plugins:
duplication:
enabled: true
config:
languages:
javascript:
mass_threshold: 110
count_threshold: 3
exclude_paths:
- "public/vendor/*"
- "test/*"

View File

@@ -1,29 +0,0 @@
node_modules/
*.sublime-project
*.sublime-workspace
.project
.vagrant
.DS_Store
logs/
/public/templates
/public/uploads
/public/vendor
/public/src/modules/string.js
.idea/
.vscode/
*.ipr
*.iws
/coverage
/build
.eslintrc
test/files
*.min.js
/public/src/app.js
/public/src/client.js
/public/src/admin/admin.js
/public/src/modules/translator.common.js
/public/src/modules/pictureCropper.js
/public/src/modules/ace-editor.js
/public/src/client/account/header.js
/public/src/client/test.js

View File

@@ -1,3 +0,0 @@
{
"extends": "nodebb"
}

View File

@@ -1,69 +0,0 @@
# Submitting a Pull Request to NodeBB?
First of all, thank you! Before submission, please run `npm test` to lint and run the automated NodeBB tests. If everything passes, you're good to go. If you have any errors, please fix them and re-run `npm test` to make sure there aren't any others.
## Styleguide and linting
NodeBB mostly conforms to the [AirBnB Javascript style guide](https://github.com/airbnb/javascript#readme). If you're running into a lot of ESlint errors, you may want to install an editor plugin to display them in real time.
## Contributor License Agreement
Thank you for considering contributing to NodeBB. **Before you are able to submit a pull request, please take a moment to read our [contributor license agreement](https://gist.github.com/psychobunny/65946d7aa8854b12fab9)** and agree to it on the pull request page on GitHub. In summary, signing this document means that 1) you own the code that you are contributing and 2) you give permission to NodeBB Inc. to license the code to others. This agreement applies to any repository under the NodeBB organization.
If you are writing contributions as part of employment from another company / individual, then your employer will need to sign a separate agreement. Please [contact us](mailto:accounts@nodebb.org) so that we can send this additional agreement to your employer.
# Having problems installing NodeBB?
Chances are somebody has run into this problem before. After consulting our [documentation](https://docs.nodebb.org/installing/os/), please head over to our [community support forum](https://community.nodebb.org) for advice.
# Found a Security Vulnerability?
If you believe you have identified a security vulnerability with NodeBB, report it as soon as possible via email to **security@nodebb.org**.
A member of the NodeBB security team will respond to the issue.
Please do not post it to the public bug tracker.
# Issues & Bugs
Thanks for reporting an issue with NodeBB! Please follow these guidelines in order to streamline the debugging process. The more guidelines you follow, the easier it will be for us to reproduce your problem.
In general, if we can't reproduce it, we can't fix it!
## Try the latest version of NodeBB
There is a chance that the issue you are experiencing may have already been fixed.
## Provide the NodeBB version number and git hash
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
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:
``` bash
$ cd /path/to/my/nodebb
$ git rev-parse HEAD
```
If you have downloaded the `.zip` or `.tar.gz` packages from GitHub (or elsewhere), please let us know.
## Provide theme versions if issue is related to the theme/display
Use `npm ls` to list the versions of the theme you're using. In this example, we're running the Persona theme, which depends on the Vanilla theme.
``` bash
$ npm ls nodebb-theme-vanilla nodebb-theme-persona
nodebb@1.4.3 /path/to/nodebb
+-- nodebb-theme-persona@4.2.4
`-- nodebb-theme-vanilla@5.2.0
```
## Attempt to use `git bisect`
If you have installed NodeBB via GitHub clone, are familiar with utilising git, and are willing to help us narrow down the specific commit that causes a bug, consider running `git bisect`.
A full guide can be found here: [Debugging with Git/Binary Search](http://git-scm.com/book/en/Git-Tools-Debugging-with-Git#Binary-Search)

View File

@@ -1,32 +0,0 @@
<!--
== Github Issues are for bug reports and feature requests only ==
== Please visit https://community.nodebb.org for other support ==
== Found a security exploit? Please email us at security@nodebb.org instead for immediate attention ==
== → DO NOT SUBMIT VULNERABILITIES TO THE PUBLIC BUG TRACKER ==
-->
<!-- ++ Please include the following information when submitting a bug report ++ -->
- **NodeBB version:**
- **NodeBB git hash:**
- **NodeJS version:**
<!-- (to find your git hash, execute `git rev-parse HEAD` from the main NodeBB directory) -->
- **Installed NodeBB Plugins:**
<!-- (to find installed plugins run ./nodebb plugins) -->
- **Database type:** mongo, redis, or postgres
- **Database version:**
<!-- `mongod --version`, `redis-server --version`, or `postgres --version` -->
- **Exact steps to cause this issue:**
<!--
1. First I did this...
2. Then, I clicked on this item...
A quick note: MP4 and MOV formatted video files are now allowed to be uploaded to GH.
Please upload if reproduction steps are hard to describe or reproduce reliably.
-->
- **What you expected:**
<!-- e.g. I expected *abc* to *xyz* -->
- **What happened instead:**
<!-- e.g. Instead, I got *zyx* and NodeBB set fire to my house -->
<!-- Thank you! -->

9
.github/SECURITY.md vendored
View File

@@ -1,9 +0,0 @@
# Reporting a security vulnerability
NodeBB's security policy is based around a private bug bounty program. Users are invited to explore NodeBB for vulnerabilities, and report them to the NodeBB team so that they can be patched.
If you have found a security vulnerability, **do not post it onto our GitHub tracker**. Some security vulnerabilities are quite severe and discretion is recommended. Email the NodeBB Security Team at security@nodebb.org, instead, even if you are not sure whether something qualifies.
# Bug Bounty Program
Security vulnerability reports may be eligible for a bounty based on severity and confirmation from NodeBB team members. For full details regarding our bug bounty program, including the bounty amounts, please consult the [dedicated page for our Bug Bounty Program](https://nodebb.org/bounty).

View File

@@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/install"
schedule:
interval: daily

View File

@@ -1,51 +0,0 @@
name: Run Docker
# Controls when the workflow will run
on:
push:
branches:
- 'master'
- 'v*.x'
tags:
- 'v*'
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: nodebb/docker
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
- name: Build and push Docker images
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -1,204 +0,0 @@
name: Lint and test
on:
push:
branches:
- master
- develop
pull_request:
branches:
- master
- develop
defaults:
run:
shell: bash
jobs:
test:
name: Lint and test
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
node: [12, 14, 16]
database: [mongo-dev, mongo, redis, postgres]
include:
# only run coverage once
- os: ubuntu-latest
node: 14
coverage: true
# test under development once
- database: mongo-dev
test_env: development
# only run eslint once
- os: ubuntu-latest
node: 14
database: mongo-dev
lint: true
runs-on: ${{ matrix.os }}
env:
TEST_ENV: ${{ matrix.test_env || 'production' }}
services:
postgres:
image: 'postgres:10-alpine'
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps port 5432 on service container to the host
- 5432:5432
redis:
image: 'redis:2.8.9'
# Set health checks to wait until redis has started
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps port 6379 on service container to the host
- 6379:6379
mongo:
image: 'mongo:3.6'
ports:
# Maps port 27017 on service container to the host
- 27017:27017
steps:
- uses: actions/checkout@v2
- run: cp install/package.json package.json
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- name: NPM Install
uses: bahmutov/npm-install@v1
with:
useLockFile: false
- name: Setup on MongoDB
if: startsWith(matrix.database, 'mongo')
env:
SETUP: >-
{
"url": "http://127.0.0.1:4567",
"secret": "abcdef",
"admin:username": "admin",
"admin:email": "test@example.org",
"admin:password": "hAN3Eg8W",
"admin:password:confirm": "hAN3Eg8W",
"database": "mongo",
"mongo:host": "127.0.0.1",
"mongo:port": 27017,
"mongo:username": "",
"mongo:password": "",
"mongo:database": "nodebb"
}
CI: >-
{
"host": "127.0.0.1",
"port": 27017,
"database": "ci_test"
}
run: |
node app --setup="${SETUP}" --ci="${CI}"
- name: Setup on PostgreSQL
if: startsWith(matrix.database, 'postgres')
env:
SETUP: >-
{
"url": "http://127.0.0.1:4567",
"secret": "abcdef",
"admin:username": "admin",
"admin:email": "test@example.org",
"admin:password": "hAN3Eg8W",
"admin:password:confirm": "hAN3Eg8W",
"database": "postgres",
"postgres:host": "127.0.0.1",
"postgres:port": 5432,
"postgres:username": "postgres",
"postgres:password": "postgres",
"postgres:database": "nodebb"
}
CI: >-
{
"host": "127.0.0.1",
"database": "ci_test",
"port": 5432,
"username": "postgres",
"password": "postgres"
}
run: |
node -e "const { Client } = require('pg'); const c = new Client({ host: '127.0.0.1', port: 5432, user: 'postgres', password: 'postgres' }); c.connect().then(() => c.query('CREATE DATABASE nodebb')).then(() => c.query('CREATE DATABASE ci_test')).then(() => c.end())"
node app --setup="${SETUP}" --ci="${CI}"
- name: Setup on Redis
if: startsWith(matrix.database, 'redis')
env:
SETUP: >-
{
"url": "http://127.0.0.1:4567/forum",
"secret": "abcdef",
"admin:username": "admin",
"admin:email": "test@example.org",
"admin:password": "hAN3Eg8W",
"admin:password:confirm": "hAN3Eg8W",
"database": "redis",
"redis:host": "127.0.0.1",
"redis:port": 6379,
"redis:password": "",
"redis:database": 0
}
CI: >-
{
"host": "127.0.0.1",
"database": 1,
"port": 6379
}
run: |
node app --setup="${SETUP}" --ci="${CI}"
- name: Run ESLint
if: matrix.lint
run: npm run lint
- name: Node tests
run: npm test
- name: Extract coverage info
run: npm run coverage
- name: Test coverage
uses: coverallsapp/github-action@1.1.3
if: matrix.coverage
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: ${{ matrix.os }}-node-${{ matrix.node }}-db-${{ matrix.database }}
parallel: true
finish:
needs: test
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@1.1.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true

33
.gitignore vendored
View File

@@ -1,10 +1,7 @@
dist/
yarn.lock
npm-debug.log
node_modules/
sftp-config.json
config.json
jsconfig.json
public/src/nodebb.min.js
!src/views/config.json
public/css/*.css
@@ -18,8 +15,6 @@ provision.sh
*.komodoproject
.DS_Store
feeds/recent.rss
.eslintcache
.svn
logs/
@@ -27,46 +22,22 @@ pidfile
# templates
/public/templates
/public/sounds
/public/uploads
/test/uploads
# compiled files
/public/stylesheet.css
/public/admin.css
/public/nodebb.min.js
/public/nodebb.min.js.map
/public/acp.min.js
/public/acp.min.js.map
/public/installer.css
/public/installer.min.js
/public/bootstrap.min.css
/public/logo.png
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
.vscode/
## File-based project format:
*.ipr
*.iws
## Transifex
tx.exe
.transifexrc
##Coverage output
coverage
.nyc_output
*.log
test/files/normalise.jpg.png
test/files/normalise-resized.jpg
package-lock.json
/package.json
*.mongodb
link-plugins.sh
test.sh
*.iws

1
.husky/.gitignore vendored
View File

@@ -1 +0,0 @@
_

View File

@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1

View File

@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install lint-staged

17
.jsbeautifyrc Normal file
View File

@@ -0,0 +1,17 @@
{
"indent_size": 4,
"indent_char": " ",
"indent_level": 0,
"indent_with_tabs": true,
"preserve_newlines": true,
"max_preserve_newlines": 10,
"jslint_happy": true,
"brace_style": "collapse",
"keep_array_indentation": false,
"keep_function_indentation": false,
"space_before_conditional": true,
"break_chained_methods": false,
"eval_code": false,
"unescape_strings": false,
"wrap_line_length": 0
}

86
.jshintrc Normal file
View File

@@ -0,0 +1,86 @@
{
// JSHint Default Configuration File (as on JSHint website)
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : false, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 4, // {int} Number of spaces to use for indentation
"latedef" : false, // true: Require variables/functions to be defined before being used
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : false, // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : false, // true: Require all defined variables be used TODO: Set this to true, update codebase.
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
"trailing" : false, // true: Prohibit trailing whitespaces
"maxparams" : false, // {int} Max number of formal params allowed per function
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
"maxstatements" : false, // {int} Max number statements per function
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
"maxlen" : false, // {int} Max number of characters per line
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements"
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : false, // true: Tolerate multi-line strings
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : true, // Web Browser (window, document, etc)
"couch" : false, // CouchDB
"devel" : true, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jquery" : true, // jQuery
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// Legacy
"nomen" : false, // true: Prohibit dangling `_` in variables
"onevar" : false, // true: Allow only one `var` statement per function
"passfail" : false, // true: Stop on first error
"white" : false, // true: Check against strict whitespace and indentation rules
// Custom Globals
"globals" : {} // additional predefined global variables
}

View File

@@ -1,4 +0,0 @@
reporter: dot
timeout: 25000
exit: true
bail: true

18
.travis.yml Normal file
View File

@@ -0,0 +1,18 @@
services:
- redis-server
before_install:
- "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10"
- "echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list"
- "sudo apt-get update"
- "sudo apt-get install mongodb-org-server"
- npm i --production
- node app --setup="{\"url\":\"http://127.0.0.1:4567/\",\"secret\":\"abcdef\",\"database\":\"mongo\",\"mongo:host\":\"127.0.0.1\",\"mongo:port\":27017,\"mongo:username\":\"\",\"mongo:password\":\"\",\"mongo:database\":0,\"redis:host\":\"127.0.0.1\",\"redis:port\":6379,\"redis:password\":\"\",\"redis:database\":0,\"admin:username\":\"admin\",\"admin:email\":\"test@example.org\",\"admin:password\":\"abcdef\",\"admin:password:confirm\":\"abcdef\"}" --ci="{\"host\":\"127.0.0.1\",\"port\":27017,\"database\":0}"
before_script:
- "until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done"
language: node_js
node_js:
- "0.11"
- "0.10"
branches:
only:
- master

3084
.tx/config

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

66
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,66 @@
# Submitting a Pull Request to NodeBB?
First of all, thank you! Please consider this [style guide](https://docs.nodebb.org/en/latest/contributing/style-guide.html) when submitting your changes. Also, please join our [community](https://community.nodebb.org) to meet other NodeBB developers and designers :)
## Contributor License Agreement
Thank you for considering contributing to NodeBB. **Before we can accept any pull requests, please take a moment to read and sign our [license agreement](https://www.clahub.com/agreements/NodeBB/NodeBB)**. In summary, signing this document means that 1) you own the code that you are contributing and 2) you give permission to NodeBB Inc. to license the code to others. This agreement applies to any repository under the NodeBB organization.
If you are writing contributions as part of employment from another company / individual, then your employer will need to sign a separate agreement. Please [contact us](mailto:accounts@nodebb.org) so that we can send this additional agreement to your employer.
# Having problems installing NodeBB?
Chances are somebody has run into this problem before. After consulting our [documentation](https://docs.nodebb.org/en/latest/installing/os.html), please head over to our [community support forum](https://community.nodebb.org) for advice.
# Found a Security Vulnerability?
If you believe you have identified a security vulnerability with NodeBB, report it as soon as possible via email to **security@nodebb.org**.
A member of the NodeBB security team will respond to the issue.
Please do not post it to the public bug tracker.
# Issues & Bugs
Thanks for reporting an issue with NodeBB! Please follow these guidelines in order to streamline the debugging process. The more guidelines you follow, the easier it will be for us to reproduce your problem.
In general, if we can't reproduce it, we can't fix it!
## Try the latest version of NodeBB
There is a chance that the issue you are experiencing may have already been fixed.
## Provide the NodeBB version number and git hash
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)
```
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:
``` bash
$ cd /path/to/my/nodebb
$ git rev-parse HEAD
```
If you have downloaded the `.zip` or `.tar.gz` packages from GitHub (or elsewhere), please let us know.
## Provide theme versions if issue is related to the theme/display
``` bash
$ npm ls nodebb-theme-vanilla nodebb-theme-lavender
nodebb@0.7.0-dev /home/julian/Projects/nodebb/forum
├── nodebb-theme-lavender@0.2.13
└── nodebb-theme-vanilla@0.2.35
```
## Attempt to use `git bisect`
If you have installed NodeBB via GitHub clone, are familiar with utilising git, and are willing to help us narrow down the specific commit that causes a bug, consider running `git bisect`.
A full guide can be found here: [Debugging with Git/Binary Search](http://git-scm.com/book/en/Git-Tools-Debugging-with-Git#Binary-Search)

View File

@@ -1,25 +0,0 @@
FROM node:lts
RUN mkdir -p /usr/src/app && \
chown -R node:node /usr/src/app
WORKDIR /usr/src/app
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
COPY --chown=node:node install/package.json /usr/src/app/package.json
USER node
RUN npm install --only=prod && \
npm cache clean --force
COPY --chown=node:node . /usr/src/app
ENV NODE_ENV=production \
daemon=false \
silent=false
EXPOSE 4567
CMD test -n "${SETUP}" && ./nodebb setup || node ./nodebb build; node ./nodebb start

View File

@@ -1,217 +1,78 @@
'use strict';
"use strict";
const path = require('path');
const nconf = require('nconf');
var fork = require('child_process').fork,
env = process.env,
worker,
incomplete = [];
nconf.argv().env({
separator: '__',
});
const winston = require('winston');
const { fork } = require('child_process');
const { env } = process;
let worker;
module.exports = function(grunt) {
function update(action, filepath, target) {
var args = [],
fromFile = '',
compiling = '',
time = Date.now();
env.NODE_ENV = env.NODE_ENV || 'development';
if (!grunt.option('verbose')) {
args.push('--log-level=info');
}
if (target === 'lessUpdated') {
fromFile = ['js','tpl'];
compiling = 'less';
} else if (target === 'clientUpdated') {
fromFile = ['less','tpl'];
compiling = 'js';
} else if (target === 'templatesUpdated') {
fromFile = ['js','less'];
compiling = 'tpl';
} else if (target === 'serverUpdated') {
fromFile = ['less','js','tpl'];
}
const configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json');
const prestart = require('./src/prestart');
fromFile = fromFile.filter(function(ext) {
return incomplete.indexOf(ext) === -1;
});
prestart.loadConfig(configFile);
args.push('--from-file=' + fromFile.join(','));
incomplete.push(compiling);
const db = require('./src/database');
worker.kill();
worker = fork('app.js', args, { env: env });
module.exports = function (grunt) {
const args = [];
worker.on('message', function() {
if (incomplete.length) {
incomplete = [];
if (!grunt.option('verbose')) {
args.push('--log-level=info');
nconf.set('log-level', 'info');
if (grunt.option('verbose')) {
grunt.log.writeln('NodeBB restarted in ' + (Date.now() - time) + ' ms');
}
}
});
}
prestart.setupWinston();
grunt.initConfig({
watch: {},
watch: {
lessUpdated: {
files: ['public/**/*.less', 'node_modules/nodebb-*/*.less', 'node_modules/nodebb-*/*/*.less', 'node_modules/nodebb-*/*/*/*.less', 'node_modules/nodebb-*/*/*/*/*.less']
},
clientUpdated: {
files: ['public/src/**/*.js', 'node_modules/nodebb-*/*.js', 'node_modules/nodebb-*/*/*.js', 'node_modules/nodebb-*/*/*/*.js', 'node_modules/nodebb-*/*/*/*/*.js', 'node_modules/templates.js/lib/templates.js']
},
serverUpdated: {
files: ['*.js', 'install/*.js', 'src/**/*.js']
},
templatesUpdated: {
files: ['src/views/**/*.tpl', 'node_modules/nodebb-*/*.tpl', 'node_modules/nodebb-*/*/*.tpl', 'node_modules/nodebb-*/*/*/*.tpl', 'node_modules/nodebb-*/*/*/*/*.tpl', 'node_modules/nodebb-*/*/*/*/*/*.tpl']
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['watch']);
grunt.registerTask('init', async function () {
const done = this.async();
let plugins = [];
if (!process.argv.includes('--core')) {
await db.init();
plugins = await db.getSortedSetRange('plugins:active', 0, -1);
addBaseThemes(plugins);
if (!plugins.includes('nodebb-plugin-composer-default')) {
plugins.push('nodebb-plugin-composer-default');
}
if (!plugins.includes('nodebb-theme-persona')) {
plugins.push('nodebb-theme-persona');
}
}
env.NODE_ENV = 'development';
const styleUpdated_Client = plugins.map(p => `node_modules/${p}/*.less`)
.concat(plugins.map(p => `node_modules/${p}/*.css`))
.concat(plugins.map(p => `node_modules/${p}/+(public|static|less)/**/*.less`))
.concat(plugins.map(p => `node_modules/${p}/+(public|static)/**/*.css`));
const styleUpdated_Admin = plugins.map(p => `node_modules/${p}/*.less`)
.concat(plugins.map(p => `node_modules/${p}/*.css`))
.concat(plugins.map(p => `node_modules/${p}/+(public|static|less)/**/*.less`))
.concat(plugins.map(p => `node_modules/${p}/+(public|static)/**/*.css`));
const clientUpdated = plugins.map(p => `node_modules/${p}/+(public|static)/**/*.js`);
const serverUpdated = plugins.map(p => `node_modules/${p}/*.js`)
.concat(plugins.map(p => `node_modules/${p}/+(lib|src)/**/*.js`));
const templatesUpdated = plugins.map(p => `node_modules/${p}/+(public|static|templates)/**/*.tpl`);
const langUpdated = plugins.map(p => `node_modules/${p}/+(public|static|languages)/**/*.json`);
grunt.config(['watch'], {
styleUpdated_Client: {
files: [
'public/less/**/*.less',
...styleUpdated_Client,
],
options: {
interval: 1000,
},
},
styleUpdated_Admin: {
files: [
'public/less/**/*.less',
...styleUpdated_Admin,
],
options: {
interval: 1000,
},
},
clientUpdated: {
files: [
'public/src/**/*.js',
'public/vendor/**/*.js',
...clientUpdated,
'node_modules/benchpressjs/build/benchpress.js',
],
options: {
interval: 1000,
},
},
serverUpdated: {
files: [
'app.js',
'install/*.js',
'src/**/*.js',
'public/src/modules/translator.common.js',
'public/src/modules/helpers.common.js',
'public/src/utils.common.js',
serverUpdated,
'!src/upgrades/**',
],
options: {
interval: 1000,
},
},
templatesUpdated: {
files: [
'src/views/**/*.tpl',
...templatesUpdated,
],
options: {
interval: 1000,
},
},
langUpdated: {
files: [
'public/language/en-GB/*.json',
'public/language/en-GB/**/*.json',
...langUpdated,
],
options: {
interval: 1000,
},
},
});
const build = require('./src/meta/build');
if (!grunt.option('skip')) {
await build.build(true, { webpack: false });
}
run();
await build.webpack({ watch: true });
done();
});
function run() {
if (worker) {
worker.kill();
}
const execArgv = [];
const inspect = process.argv.find(a => a.startsWith('--inspect'));
if (inspect) {
execArgv.push(inspect);
}
worker = fork('app.js', args, {
env,
execArgv,
});
}
grunt.task.run('init');
grunt.event.removeAllListeners('watch');
grunt.event.on('watch', (action, filepath, target) => {
let compiling;
if (target === 'styleUpdated_Client') {
compiling = 'clientCSS';
} else if (target === 'styleUpdated_Admin') {
compiling = 'acpCSS';
} else if (target === 'clientUpdated') {
compiling = 'js';
} else if (target === 'templatesUpdated') {
compiling = 'tpl';
} else if (target === 'langUpdated') {
compiling = 'lang';
} else if (target === 'serverUpdated') {
// empty require cache
const paths = ['./src/meta/build.js', './src/meta/index.js'];
paths.forEach(p => delete require.cache[require.resolve(p)]);
return run();
}
require('./src/meta/build').build([compiling], { webpack: false }, (err) => {
if (err) {
winston.error(err.stack);
}
if (worker) {
worker.send({ compiling: compiling });
}
});
});
};
function addBaseThemes(plugins) {
let themeId = plugins.find(p => p.includes('nodebb-theme-'));
if (!themeId) {
return plugins;
}
let baseTheme;
do {
try {
baseTheme = require(`${themeId}/theme`).baseTheme;
} catch (err) {
console.log(err);
}
if (baseTheme) {
plugins.push(baseTheme);
themeId = baseTheme;
}
} while (baseTheme);
return plugins;
}
worker = fork('app.js', [], { env: env });
grunt.event.on('watch', update);
};

View File

@@ -1,19 +1,26 @@
# ![NodeBB](public/images/sm-card.png)
# <img alt="NodeBB" src="http://i.imgur.com/mYxPPtB.png" />
[![Workflow](https://github.com/NodeBB/NodeBB/actions/workflows/test.yaml/badge.svg)](https://github.com/NodeBB/NodeBB/actions/workflows/test.yaml)
[![Coverage Status](https://coveralls.io/repos/github/NodeBB/NodeBB/badge.svg?branch=master)](https://coveralls.io/github/NodeBB/NodeBB?branch=master)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NodeBB/NodeBB?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://travis-ci.org/NodeBB/NodeBB.svg?branch=master)](https://travis-ci.org/NodeBB/NodeBB)
[![Dependency Status](https://david-dm.org/nodebb/nodebb.svg)](https://david-dm.org/nodebb/nodebb)
[![Code Climate](https://codeclimate.com/github/NodeBB/NodeBB/badges/gpa.svg)](https://codeclimate.com/github/NodeBB/NodeBB)
[![Documentation Status](https://readthedocs.org/projects/nodebb/badge/?version=latest)](https://readthedocs.org/projects/nodebb/?badge=latest)
[**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and supports either Redis, MongoDB, or a PostgreSQL database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB takes the best of the modern web: real-time streaming discussions, mobile responsiveness, and rich RESTful read/write APIs, while staying true to the original bulletin board/forum format &rarr; categorical hierarchies, local user accounts, and asynchronous messaging.
**NodeBB Forum Software** is powered by Node.js and built on either a Redis or MongoDB database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB is compatible down to IE8 and has many modern features out of the box such as social network integration and streaming discussions.
NodeBB by itself contains a "common core" of basic functionality, while additional functionality and integrations are enabled through the use of third-party plugins.
Additional functionality is enabled through the use of third-party plugins.
### [Try it now](//try.nodebb.org) | [Documentation](//docs.nodebb.org)
* [Get NodeBB](http://www.nodebb.org/ "NodeBB")
* [Demo & Meta Discussion](http://community.nodebb.org)
* [Documentation & Installation Instructions](http://docs.nodebb.org)
* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/)
* [NodeBB Blog](http://blog.nodebb.org)
* [Join us on IRC](https://kiwiirc.com/client/irc.freenode.net/nodebb) - #nodebb on Freenode
* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
## Screenshots
NodeBB's theming engine is highly flexible and does not restrict your design choices. Check out some themed installs in these screenshots below:
[![](http://i.imgur.com/VCoOFyqb.png)](http://i.imgur.com/VCoOFyq.png)
[![](http://i.imgur.com/FLOUuIqb.png)](http://i.imgur.com/FLOUuIq.png)
[![](http://i.imgur.com/Ud1LrfIb.png)](http://i.imgur.com/Ud1LrfI.png)
@@ -23,17 +30,14 @@ NodeBB's theming engine is highly flexible and does not restrict your design cho
[![](http://i.imgur.com/LmHtPhob.png)](http://i.imgur.com/LmHtPho.png)
[![](http://i.imgur.com/paiJPJkb.jpg)](http://i.imgur.com/paiJPJk.jpg)
Our minimalist "Persona" theme gets you going right away, no coding experience required.
[![](http://i.imgur.com/HwNEXGu.png)](http://i.imgur.com/HwNEXGu.png)
[![](http://i.imgur.com/II1byYs.png)](http://i.imgur.com/II1byYs.png)
[![](http://i.imgur.com/8OLssij.png)](http://i.imgur.com/8OLssij.png)
[![](http://i.imgur.com/JKOc0LZ.png)](http://i.imgur.com/JKOc0LZ.png)
## How can I follow along/contribute?
* Our feature roadmap is hosted on the project wiki's [Version History / Roadmap](https://github.com/NodeBB/NodeBB/wiki/Version-History-%26-Roadmap)
* If you are a developer, feel free to check out the source and submit pull requests. We also have a wide array of [plugins](http://community.nodebb.org/category/7/nodebb-plugins) which would be a great starting point for learning the codebase.
* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extension of the base templates as well as styling via LESS or CSS. NodeBB's base theme utilizes [Bootstrap 3](http://getbootstrap.com/) but themes can choose to use a different framework altogether.
* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extention of the base templates as well as styling via LESS or CSS. NodeBB's base theme utilizes [Bootstrap 3](http://getbootstrap.com/) but themes can choose to use a different framework altogether.
* If you know languages other than English you can help us translate NodeBB. We use [Transifex](https://www.transifex.com/projects/p/nodebb/) for internationalization.
* Please don't forget to **like**, **follow**, and **star our repo**! Join our growing [community](http://community.nodebb.org) to keep up to date with the latest NodeBB development.
@@ -41,14 +45,13 @@ Our minimalist "Persona" theme gets you going right away, no coding experience r
NodeBB requires the following software to be installed:
* A version of Node.js at least 12 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions))
* MongoDB, version 3.6 or greater **or** Redis, version 2.8.9 or greater
* If you are using [clustering](https://docs.nodebb.org/configuring/scaling/) you need Redis installed and configured.
* A version of Node.js at least 0.10 or greater
* Redis, version 2.8.9 or greater **or** MongoDB, version 2.6 or greater
* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
## Installation
[Please refer to platform-specific installation documentation](https://docs.nodebb.org/installing/os)
[Please refer to platform-specific installation documentation](http://docs.nodebb.org/en/latest/installing/os.html)
## Securing NodeBB
@@ -63,22 +66,10 @@ It is important to ensure that your NodeBB and database servers are secured. Bea
## Upgrading NodeBB
Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.nodebb.org/configuring/upgrade/)
Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.nodebb.org/en/latest/upgrading/index.html)
## License
NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html).
Interested in a sublicense agreement for use of NodeBB in a non-free/restrictive environment? Contact us at sales@nodebb.org.
## More Information/Links
* [Demo](https://try.nodebb.org)
* [Developer Community](http://community.nodebb.org)
* [Documentation & Installation Instructions](http://docs.nodebb.org)
* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/)
* [NodeBB Blog](http://blog.nodebb.org)
* [Premium Hosting for NodeBB](http://www.nodebb.org/ "NodeBB")
* Unofficial IRC community &ndash; channel `#nodebb` on Libera.chat
* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
Interested in a sublicense agreement for use of NodeBB in a non-free/restrictive environment? Contact us at sales@nodebb.org.

437
app.js
View File

@@ -1,7 +1,7 @@
/*
NodeBB - A better forum platform for the modern web
https://github.com/NodeBB/NodeBB/
Copyright (C) 2013-2021 NodeBB Inc.
Copyright (C) 2013-2014 NodeBB Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,66 +17,401 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
"use strict";
/*global require, global, process*/
require('./require-main');
var nconf = require('nconf');
nconf.argv().env('__');
const nconf = require('nconf');
var fs = require('fs'),
os = require('os'),
url = require('url'),
async = require('async'),
semver = require('semver'),
winston = require('winston'),
colors = require('colors'),
path = require('path'),
pkg = require('./package.json'),
utils = require('./public/src/utils.js');
nconf.argv().env({
separator: '__',
});
const winston = require('winston');
const path = require('path');
const file = require('./src/file');
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
global.env = process.env.NODE_ENV || 'production';
// Alternate configuration file support
const configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json');
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {
colorize: true,
timestamp: function() {
var date = new Date();
return date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0,5) + ' [' + global.process.pid + ']';
},
level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose')
});
const configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
const prestart = require('./src/prestart');
prestart.loadConfig(configFile);
prestart.setupWinston();
prestart.versionCheck();
winston.verbose('* using configuration stored in: %s', configFile);
if (!process.send) {
// If run using `node app`, log GNU copyright info along with server info
winston.info(`NodeBB v${nconf.get('version')} Copyright (C) 2013-${(new Date()).getFullYear()} NodeBB Inc.`);
winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
winston.info('');
if(os.platform() === 'linux') {
require('child_process').exec('/usr/bin/which convert', function(err, stdout, stderr) {
if(err || !stdout) {
winston.warn('Couldn\'t find convert. Did you install imagemagick?');
}
});
}
if (nconf.get('setup') || nconf.get('install')) {
require('./src/cli/setup').setup();
// Alternate configuration file support
var configFile = path.join(__dirname, '/config.json'),
configExists;
if (nconf.get('config')) {
configFile = path.resolve(__dirname, nconf.get('config'));
}
configExists = fs.existsSync(configFile);
if (!nconf.get('setup') && !nconf.get('install') && !nconf.get('upgrade') && !nconf.get('reset') && configExists) {
start();
} else if (nconf.get('setup') || nconf.get('install')) {
setup();
} else if (!configExists) {
require('./install/web').install(nconf.get('port'));
} else if (nconf.get('upgrade')) {
require('./src/cli/upgrade').upgrade(true);
upgrade();
} else if (nconf.get('reset')) {
require('./src/cli/reset').reset({
theme: nconf.get('t'),
plugin: nconf.get('p'),
widgets: nconf.get('w'),
settings: nconf.get('s'),
all: nconf.get('a'),
});
} else if (nconf.get('activate')) {
require('./src/cli/manage').activate(nconf.get('activate'));
} else if (nconf.get('plugins')) {
require('./src/cli/manage').listPlugins();
} else if (nconf.get('build')) {
require('./src/cli/manage').build(nconf.get('build'));
} else if (nconf.get('events')) {
require('./src/cli/manage').listEvents();
} else {
require('./src/start').start();
reset();
}
function loadConfig() {
nconf.file({
file: configFile
});
nconf.defaults({
base_dir: __dirname,
themes_path: path.join(__dirname, 'node_modules'),
views_dir: path.join(__dirname, 'public/templates'),
version: pkg.version
});
if (!nconf.get('isCluster')) {
nconf.set('isPrimary', 'true');
nconf.set('isCluster', 'false');
}
// Ensure themes_path is a full filepath
nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path')));
nconf.set('core_templates_path', path.join(__dirname, 'src/views'));
nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-vanilla/templates'));
if (!process.send) {
// If run using `node app`, log GNU copyright info along with server info
winston.info('NodeBB v' + nconf.get('version') + ' Copyright (C) 2013-2014 NodeBB Inc.');
winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
winston.info('');
}
}
function start() {
loadConfig();
var db = require('./src/database');
// nconf defaults, if not set in config
if (!nconf.get('upload_path')) {
nconf.set('upload_path', '/public/uploads');
}
// Parse out the relative_url and other goodies from the configured URL
var urlObject = url.parse(nconf.get('url'));
var relativePath = urlObject.pathname !== '/' ? urlObject.pathname : '';
nconf.set('base_url', urlObject.protocol + '//' + urlObject.host);
nconf.set('secure', urlObject.protocol === 'https');
nconf.set('use_port', !!urlObject.port);
nconf.set('relative_path', relativePath);
nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || 4567);
nconf.set('upload_url', '/uploads/');
if (nconf.get('isPrimary') === 'true') {
winston.info('Time: %s', (new Date()).toString());
winston.info('Initializing NodeBB v%s', nconf.get('version'));
winston.verbose('* using configuration stored in: %s', configFile);
var host = nconf.get(nconf.get('database') + ':host'),
storeLocation = host ? 'at ' + host + (host.indexOf('/') === -1 ? ':' + nconf.get(nconf.get('database') + ':port') : '') : '';
winston.verbose('* using %s store %s', nconf.get('database'), storeLocation);
winston.verbose('* using themes stored in: %s', nconf.get('themes_path'));
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
process.on('SIGHUP', restart);
process.on('message', function(message) {
if (typeof message !== 'object') {
return;
}
var meta = require('./src/meta');
var emitter = require('./src/emitter');
switch (message.action) {
case 'reload':
meta.reload();
break;
case 'js-propagate':
meta.js.cache = message.cache;
meta.js.map = message.map;
meta.js.hash = message.hash;
emitter.emit('meta:js.compiled');
winston.verbose('[cluster] Client-side javascript and mapping propagated to worker %s', process.pid);
break;
case 'css-propagate':
meta.css.cache = message.cache;
meta.css.acpCache = message.acpCache;
meta.css.hash = message.hash;
emitter.emit('meta:css.compiled');
winston.verbose('[cluster] Stylesheets propagated to worker %s', process.pid);
break;
case 'templates:compiled':
emitter.emit('templates:compiled');
break;
}
});
process.on('uncaughtException', function(err) {
winston.error(err.stack);
console.log(err.stack);
require('./src/meta').js.killMinifier();
shutdown(1);
});
async.waterfall([
async.apply(db.init),
async.apply(db.checkCompatibility),
function(next) {
require('./src/meta').configs.init(next);
},
function(next) {
require('./src/meta').dependencies.check(next);
},
function(next) {
require('./src/upgrade').check(next);
},
function(next) {
var webserver = require('./src/webserver');
require('./src/socket.io').init(webserver.server);
if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) {
require('./src/notifications').init();
require('./src/user').startJobs();
}
webserver.listen();
}
], function(err) {
if (err) {
switch(err.message) {
case 'schema-out-of-date':
winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
winston.warn(' ./nodebb upgrade');
break;
case 'dependencies-out-of-date':
winston.warn('One or more of NodeBB\'s dependent packages are out-of-date. Please run the following command to update them:');
winston.warn(' ./nodebb upgrade');
break;
default:
if (err.stacktrace !== false) {
winston.error(err.stack);
} else {
winston.error(err.message);
}
break;
}
// Either way, bad stuff happened. Abort start.
process.exit();
}
});
}
function setup() {
loadConfig();
winston.info('NodeBB Setup Triggered via Command Line');
var install = require('./src/install');
process.stdout.write('\nWelcome to NodeBB!\n');
process.stdout.write('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.\n');
process.stdout.write('Press enter to accept the default setting (shown in brackets).\n');
install.setup(function (err, data) {
var separator = ' ';
if (process.stdout.columns > 10) {
for(var x=0,cols=process.stdout.columns-10;x<cols;x++) {
separator += '=';
}
}
process.stdout.write('\n' + separator + '\n\n');
if (err) {
winston.error('There was a problem completing NodeBB setup: ', err.message);
} else {
if (data.hasOwnProperty('password')) {
process.stdout.write('An administrative user was automatically created for you:\n');
process.stdout.write(' Username: ' + data.username + '\n');
process.stdout.write(' Password: ' + data.password + '\n');
process.stdout.write('\n');
}
process.stdout.write('NodeBB Setup Completed. Run \'./nodebb start\' to manually start your NodeBB server.\n');
// If I am a child process, notify the parent of the returned data before exiting (useful for notifying
// hosts of auto-generated username/password during headless setups)
if (process.send) {
process.send(data);
}
}
process.exit();
});
}
function upgrade() {
loadConfig();
require('./src/database').init(function(err) {
if (err) {
winston.error(err.stack);
process.exit();
}
require('./src/meta').configs.init(function () {
require('./src/upgrade').upgrade();
});
});
}
function reset() {
loadConfig();
require('./src/database').init(function(err) {
if (err) {
winston.error(err.message);
process.exit();
}
if (nconf.get('t')) {
resetThemes();
} else if (nconf.get('p')) {
if (nconf.get('p') === true) {
resetPlugins();
} else {
resetPlugin(nconf.get('p'));
}
} else if (nconf.get('w')) {
resetWidgets();
} else if (nconf.get('s')) {
resetSettings();
} else if (nconf.get('a')) {
require('async').series([resetWidgets, resetThemes, resetPlugins, resetSettings], function(err) {
if (!err) {
winston.info('[reset] Reset complete.');
} else {
winston.error('[reset] Errors were encountered while resetting your forum settings: %s', err.message);
}
process.exit();
});
} else {
process.stdout.write('\nNodeBB Reset\n'.bold);
process.stdout.write('No arguments passed in, so nothing was reset.\n\n'.yellow);
process.stdout.write('Use ./nodebb reset ' + '{-t|-p|-w|-s|-a}\n'.red);
process.stdout.write(' -t\tthemes\n');
process.stdout.write(' -p\tplugins\n');
process.stdout.write(' -w\twidgets\n');
process.stdout.write(' -s\tsettings\n');
process.stdout.write(' -a\tall of the above\n');
process.stdout.write('\nPlugin reset flag (-p) can take a single argument\n');
process.stdout.write(' e.g. ./nodebb reset -p nodebb-plugin-mentions\n');
process.exit();
}
});
}
function resetSettings(callback) {
var meta = require('./src/meta');
meta.configs.set('allowLocalLogin', 1, function(err) {
winston.info('[reset] Settings reset to default');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function resetThemes(callback) {
var meta = require('./src/meta');
meta.themes.set({
type: 'local',
id: 'nodebb-theme-persona'
}, function(err) {
winston.info('[reset] Theme reset to Vanilla');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function resetPlugin(pluginId) {
var db = require('./src/database');
db.sortedSetRemove('plugins:active', pluginId, function(err) {
if (err) {
winston.error('[reset] Could not disable plugin: %s encountered error %s', pluginId, err.message);
} else {
winston.info('[reset] Plugin `%s` disabled', pluginId);
}
process.exit();
});
}
function resetPlugins(callback) {
var db = require('./src/database');
db.delete('plugins:active', function(err) {
winston.info('[reset] All Plugins De-activated');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function resetWidgets(callback) {
require('./src/widgets').reset(function(err) {
winston.info('[reset] All Widgets moved to Draft Zone');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function shutdown(code) {
winston.info('[app] Shutdown (SIGTERM/SIGINT) Initialised.');
require('./src/database').close();
winston.info('[app] Database connection closed.');
require('./src/webserver').server.close();
winston.info('[app] Web server closed to connections.');
winston.info('[app] Shutdown complete.');
process.exit(code || 0);
}
function restart() {
if (process.send) {
winston.info('[app] Restarting...');
process.send({
action: 'restart'
});
} else {
winston.error('[app] Could not restart server. Shutting down.');
shutdown(1);
}
}

34
bcrypt.js Normal file
View File

@@ -0,0 +1,34 @@
'use strict';
var bcrypt = require('bcryptjs'),
async = require('async');
process.on('message', function(msg) {
if (msg.type === 'hash') {
hashPassword(msg.password, msg.rounds);
} else if (msg.type === 'compare') {
bcrypt.compare(msg.password, msg.hash, done);
}
});
function hashPassword(password, rounds) {
async.waterfall([
function(next) {
bcrypt.genSalt(parseInt(rounds, 10), next);
},
function(salt, next) {
bcrypt.hash(password, salt, next);
}
], done);
}
function done(err, result) {
if (err) {
process.send({err: err.message});
return process.disconnect();
}
process.send({result: result});
process.disconnect();
}

4
build/.gitignore vendored
View File

@@ -1,4 +0,0 @@
*
*/
!export
!.gitignore

View File

@@ -1,3 +0,0 @@
.
!.gitignore
!README

View File

@@ -1,5 +0,0 @@
This directory contains archives of user uploads that are prepared on-demand
when a user wants to retrieve a copy of their uploaded content.
You can delete the files in here at will. They will just be regenerated if
requested again.

View File

@@ -1,26 +0,0 @@
'use strict';
module.exports = {
extends: ['@commitlint/config-angular'],
rules: {
'header-max-length': [1, 'always', 72],
'type-enum': [
2,
'always',
[
'breaking',
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test',
],
],
},
};

View File

@@ -1,24 +0,0 @@
version: '3.5'
services:
node:
build: .
restart: unless-stopped
depends_on:
- db
expose:
- 4567 # use a reverse proxy like Traefik
db:
image: mongo:bionic
restart: unless-stopped
expose:
- 27017
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: root
volumes:
- mongo:/data/db
volumes:
mongo:

View File

@@ -2,36 +2,32 @@
{
"name": "Announcements",
"description": "Announcements regarding our community",
"descriptionParsed": "<p>Announcements regarding our community</p>\n",
"bgColor": "#fda34b",
"color": "#ffffff",
"color": "#fff",
"icon" : "fa-bullhorn",
"order": 1
},
{
"name": "General Discussion",
"description": "A place to talk about whatever you want",
"descriptionParsed": "<p>A place to talk about whatever you want</p>\n",
"bgColor": "#59b3d0",
"color": "#ffffff",
"color": "#fff",
"icon" : "fa-comments-o",
"order": 2
},
{
"name": "Blogs",
"description": "Blog posts from individual members",
"descriptionParsed": "<p>Blog posts from individual members</p>\n",
"bgColor": "#86ba4b",
"color": "#ffffff",
"color": "#fff",
"icon" : "fa-newspaper-o",
"order": 4
},
{
"name": "Comments & Feedback",
"description": "Got a question? Ask away!",
"descriptionParsed": "<p>Got a question? Ask away!</p>\n",
"bgColor": "#e95c5a",
"color": "#ffffff",
"color": "#fff",
"icon" : "fa-question",
"order": 3
}

View File

@@ -1,173 +1,31 @@
{
"title": "NodeBB",
"showSiteTitle": 1,
"defaultLang": "en-GB",
"loginDays": 14,
"loginSeconds": 0,
"loginAttempts": 5,
"lockoutDuration": 60,
"adminReloginDuration": 60,
"postDelay": 10,
"initialPostDelay": 10,
"newbiePostDelay": 120,
"postEditDuration": 0,
"newbiePostEditDuration": 3600,
"postDeleteDuration": 0,
"enablePostHistory": 1,
"topicBacklinks": 1,
"postCacheSize": 10485760,
"disableChat": 0,
"chatEditDuration": 0,
"chatDeleteDuration": 0,
"chatMessageDelay": 200,
"notificationSendDelay": 60,
"newbiePostDelayThreshold": 3,
"postQueue": 0,
"postQueueReputationThreshold": 0,
"groupsExemptFromPostQueue": ["administrators", "Global Moderators"],
"minimumPostLength": 8,
"maximumPostLength": 32767,
"systemTags": "",
"minimumTagsPerTopic": 0,
"maximumTagsPerTopic": 5,
"minimumTagLength": 3,
"maximumTagLength": 15,
"undoTimeout": 10000,
"allowTopicsThumbnail": 1,
"allowGuestSearching": 0,
"allowTopicsThumbnail": 0,
"registrationType": "normal",
"registrationApprovalType": "normal",
"allowLocalLogin": 1,
"allowAccountDelete": 1,
"privateUploads": 0,
"allowedFileExtensions": "png,jpg,bmp,txt",
"uploadRateLimitThreshold": 10,
"uploadRateLimitCooldown": 60,
"allowUserHomePage": 1,
"allowMultipleBadges": 0,
"allowFileUploads": 0,
"maximumFileSize": 2048,
"stripEXIFData": 1,
"resizeImageWidthThreshold": 2000,
"resizeImageWidth": 760,
"rejectImageWidth": 5000,
"rejectImageHeight": 5000,
"resizeImageQuality": 80,
"topicThumbSize": 512,
"minimumTitleLength": 3,
"maximumTitleLength": 255,
"minimumUsernameLength": 2,
"maximumUsernameLength": 16,
"minimumPasswordLength": 6,
"minimumPasswordStrength": 1,
"maximumSignatureLength": 255,
"maximumAboutMeLength": 1000,
"maximumUsersInChatRoom": 0,
"maximumChatMessageLength": 1000,
"maximumProfileImageSize": 256,
"maximumCoverImageSize": 2048,
"profileImageDimension": 200,
"profile:convertProfileImageToPNG": 0,
"profile:keepAllUserImages": 0,
"gdpr_enabled": 1,
"allowProfileImageUploads": 1,
"teaserPost": "last-reply",
"showPostPreviewsOnHover": 1,
"allowPrivateGroups": 1,
"unreadCutoff": 2,
"bookmarkThreshold": 5,
"autoDetectLang": 1,
"reputation:disabled": 0,
"downvote:disabled": 0,
"disableSignatures": 0,
"downvotesPerDay": 10,
"downvotesPerUserPerDay": 3,
"min:rep:downvote": 0,
"min:rep:flag": 0,
"min:rep:profile-picture": 0,
"min:rep:cover-picture": 0,
"min:rep:website": 0,
"min:rep:aboutme": 0,
"min:rep:signature": 0,
"flags:limitPerTarget": 0,
"notificationType_upvote": "notification",
"notificationType_new-topic": "notification",
"notificationType_new-reply": "notification",
"notificationType_post-edit": "notification",
"notificationType_follow": "notification",
"notificationType_new-chat": "notification",
"notificationType_new-group-chat": "notification",
"notificationType_group-invite": "notification",
"notificationType_group-leave": "notification",
"notificationType_group-request-membership": "notification",
"notificationType_mention": "notification",
"notificationType_new-register": "notification",
"notificationType_post-queue": "notification",
"notificationType_new-post-flag": "notification",
"notificationType_new-user-flag": "notification",
"topicStaleDays": 60,
"maxTopicsPerPage": 20,
"maxPostsPerPage": 20,
"topicsPerPage": 20,
"postsPerPage": 20,
"categoriesPerPage": 50,
"userSearchResultsPerPage": 50,
"searchDefaultSortBy": "relevance",
"searchDefaultIn": "titlesposts",
"searchDefaultInQuick": "titles",
"maximumGroupNameLength": 255,
"maximumGroupTitleLength": 40,
"preventTopicDeleteAfterReplies": 0,
"feeds:disableSitemap": 0,
"feeds:disableRSS": 0,
"sitemapTopics": 500,
"maintenanceMode": 0,
"maintenanceModeStatus": 503,
"votesArePublic": 0,
"maximumInvites": 0,
"username:disableEdit": 0,
"email:disableEdit": 0,
"email:smtpTransport:pool": 0,
"hideFullname": 0,
"hideEmail": 0,
"showFullnameAsDisplayName": 0,
"allowGuestHandles": 0,
"guestsIncrementTopicViews": 1,
"allowGuestReplyNotifications": 1,
"incrementTopicViewsInterval": 60,
"recentMaxTopics": 200,
"disableRecentCategoryFilter": 0,
"maximumRelatedTopics": 0,
"disableEmailSubscriptions": 0,
"emailConfirmInterval": 10,
"removeEmailNotificationImages": 0,
"sendValidationEmail": 1,
"includeUnverifiedEmails": 0,
"emailPrompt": 1,
"sendEmailToBanned": 0,
"requireEmailAddress": 0,
"inviteExpiration": 7,
"dailyDigestFreq": "off",
"digestHour": 17,
"passwordExpiryDays": 0,
"cross-origin-embedder-policy": 0,
"cross-origin-resource-policy": "same-origin",
"hsts-maxage": 31536000,
"hsts-subdomains": 0,
"hsts-preload": 0,
"hsts-enabled": 0,
"eventLoopCheckEnabled": 1,
"eventLoopLagThreshold": 100,
"eventLoopInterval": 500,
"onlineCutoff": 30,
"timeagoCutoff": 30,
"necroThreshold": 7,
"categoryWatchState": "watching",
"submitPluginUsage": 1,
"showAverageApprovalTime": 1,
"autoApproveTime": 0,
"maxUserSessions": 10,
"useCompression": 0,
"updateUrlWithPostIndex": 1,
"composer:showHelpTab": 1,
"composer:allowPluginHelp": 1,
"maxReconnectionAttempts": 5,
"reconnectionDelay": 1500
}
"profileImageDimension": 128,
"requireEmailConfirmation": 0,
"profile:allowProfileImageUploads": 1,
"teaserPost": "last"
}

View File

@@ -2,7 +2,7 @@
{
"widget": "html",
"data" : {
"html": "<footer id=\"footer\" class=\"container footer\">\r\n\t<div>\r\n\t\tPowered by <a target=\"_blank\" href=\"https://nodebb.org\">NodeBB</a> | <a target=\"_blank\" href=\"//github.com/NodeBB/NodeBB/graphs/contributors\">Contributors</a>\r\n\t</div>\r\n</footer>",
"html": "<footer id=\"footer\" class=\"container footer\">\r\n\t<div class=\"copyright\">\r\n\t\tCopyright © 2014 <a target=\"_blank\" href=\"https://nodebb.org\">NodeBB Forums</a> | <a target=\"_blank\" href=\"//github.com/NodeBB/NodeBB/graphs/contributors\">Contributors</a>\r\n\t</div>\r\n</footer>",
"title":"",
"container":""
}

View File

@@ -1,69 +1,87 @@
[
{
"route": "/categories",
"title": "[[global:header.categories]]",
"title": "\\[\\[global:header.categories\\]\\]",
"enabled": true,
"iconClass": "fa-list",
"textClass": "visible-xs-inline",
"text": "[[global:header.categories]]"
"text": "\\[\\[global:header.categories\\]\\]"
},
{
"id": "unread-count",
"route": "/unread",
"title": "[[global:header.unread]]",
"title": "\\[\\[global:header.unread\\]\\]",
"enabled": true,
"iconClass": "fa-inbox",
"textClass": "visible-xs-inline",
"text": "[[global:header.unread]]",
"groups": ["registered-users"]
"text": "\\[\\[global:header.unread\\]\\]",
"properties": {
"loggedIn": true
}
},
{
"route": "/recent",
"title": "[[global:header.recent]]",
"title": "\\[\\[global:header.recent\\]\\]",
"enabled": true,
"iconClass": "fa-clock-o",
"textClass": "visible-xs-inline",
"text": "[[global:header.recent]]"
"text": "\\[\\[global:header.recent\\]\\]"
},
{
"route": "/tags",
"title": "[[global:header.tags]]",
"title": "\\[\\[global:header.tags\\]\\]",
"enabled": true,
"iconClass": "fa-tags",
"textClass": "visible-xs-inline",
"text": "[[global:header.tags]]"
"text": "\\[\\[global:header.tags\\]\\]"
},
{
"route": "/popular",
"title": "[[global:header.popular]]",
"title": "\\[\\[global:header.popular\\]\\]",
"enabled": true,
"iconClass": "fa-fire",
"textClass": "visible-xs-inline",
"text": "[[global:header.popular]]"
"text": "\\[\\[global:header.popular\\]\\]"
},
{
"route": "/users",
"title": "[[global:header.users]]",
"title": "\\[\\[global:header.users\\]\\]",
"enabled": true,
"iconClass": "fa-user",
"textClass": "visible-xs-inline",
"text": "[[global:header.users]]"
"text": "\\[\\[global:header.users\\]\\]"
},
{
"route": "/groups",
"title": "[[global:header.groups]]",
"title": "\\[\\[global:header.groups\\]\\]",
"enabled": true,
"iconClass": "fa-group",
"textClass": "visible-xs-inline",
"text": "[[global:header.groups]]"
"text": "\\[\\[global:header.groups\\]\\]"
},
{
"route": "/admin",
"title": "[[global:header.admin]]",
"title": "\\[\\[global:header.admin\\]\\]",
"enabled": true,
"iconClass": "fa-cogs",
"textClass": "visible-xs-inline",
"text": "[[global:header.admin]]",
"groups": ["administrators"]
"text": "\\[\\[global:header.admin\\]\\]",
"properties": {
"targetBlank": false,
"adminOnly": true
}
},
{
"route": "/search",
"title": "\\[\\[global:header.search\\]\\]",
"enabled": true,
"iconClass": "fa-search",
"textClass": "visible-xs-inline",
"text": "\\[\\[global:header.search\\]\\]",
"properties": {
"installed": {
"search": true
}
}
}
]

View File

@@ -1,10 +1,10 @@
# Welcome to your brand new NodeBB forum!
This is what a topic and post looks like. As an administrator, you can edit the post\'s title and content.
This is what a topic and post looks like. As an administator, you can edit the post\'s title and content.
To customise your forum, go to the [Administrator Control Panel](../../admin). You can modify all aspects of your forum there, including installation of third-party plugins.
## Additional Resources
* [NodeBB Documentation](https://docs.nodebb.org)
* [Community Support Forum](https://community.nodebb.org)
* [Project repository](https://github.com/nodebb/nodebb)
* [Project repository](https://github.com/nodebb/nodebb)

View File

@@ -1,87 +1,104 @@
'use strict';
"use strict";
const prompt = require('prompt');
const winston = require('winston');
var async = require('async'),
prompt = require('prompt'),
nconf = require('nconf'),
winston = require('winston'),
const questions = {
redis: require('../src/database/redis').questions,
mongo: require('../src/database/mongo').questions,
postgres: require('../src/database/postgres').questions,
};
questions = {};
module.exports = async function (config) {
winston.info(`\nNow configuring ${config.database} database:`);
const databaseConfig = await getDatabaseConfig(config);
return saveDatabaseConfig(config, databaseConfig);
};
async function getDatabaseConfig(config) {
function success(err, config, callback) {
if (!config) {
throw new Error('invalid config, aborted');
return callback(new Error('aborted'));
}
if (config.database === 'redis') {
var database = (config.redis || config.mongo) ? config.secondary_database : config.database;
function dbQuestionsSuccess(err, databaseConfig) {
if (!databaseConfig) {
return callback(new Error('aborted'));
}
// Translate redis properties into redis object
if(database === 'redis') {
config.redis = {
host: databaseConfig['redis:host'],
port: databaseConfig['redis:port'],
password: databaseConfig['redis:password'],
database: databaseConfig['redis:database']
};
if (config.redis.host.slice(0, 1) === '/') {
delete config.redis.port;
}
} else if (database === 'mongo') {
config.mongo = {
host: databaseConfig['mongo:host'],
port: databaseConfig['mongo:port'],
username: databaseConfig['mongo:username'],
password: databaseConfig['mongo:password'],
database: databaseConfig['mongo:database']
};
} else {
return callback(new Error('unknown database : ' + database));
}
var allQuestions = questions.redis.concat(questions.mongo);
for(var x=0;x<allQuestions.length;x++) {
delete config[allQuestions[x].name];
}
callback(err, config);
}
if(database === 'redis') {
if (config['redis:host'] && config['redis:port']) {
return config;
dbQuestionsSuccess(null, config);
} else {
prompt.get(questions.redis, dbQuestionsSuccess);
}
return await prompt.get(questions.redis);
} else if (config.database === 'mongo') {
if ((config['mongo:host'] && config['mongo:port']) || config['mongo:uri']) {
return config;
} else if(database === 'mongo') {
if (config['mongo:host'] && config['mongo:port']) {
dbQuestionsSuccess(null, config);
} else {
prompt.get(questions.mongo, dbQuestionsSuccess);
}
return await prompt.get(questions.mongo);
} else if (config.database === 'postgres') {
if (config['postgres:host'] && config['postgres:port']) {
return config;
}
return await prompt.get(questions.postgres);
}
throw new Error(`unknown database : ${config.database}`);
}
function saveDatabaseConfig(config, databaseConfig) {
if (!databaseConfig) {
throw new Error('invalid config, aborted');
}
// Translate redis properties into redis object
if (config.database === 'redis') {
config.redis = {
host: databaseConfig['redis:host'],
port: databaseConfig['redis:port'],
password: databaseConfig['redis:password'],
database: databaseConfig['redis:database'],
};
if (config.redis.host.slice(0, 1) === '/') {
delete config.redis.port;
}
} else if (config.database === 'mongo') {
config.mongo = {
host: databaseConfig['mongo:host'],
port: databaseConfig['mongo:port'],
username: databaseConfig['mongo:username'],
password: databaseConfig['mongo:password'],
database: databaseConfig['mongo:database'],
uri: databaseConfig['mongo:uri'],
};
} else if (config.database === 'postgres') {
config.postgres = {
host: databaseConfig['postgres:host'],
port: databaseConfig['postgres:port'],
username: databaseConfig['postgres:username'],
password: databaseConfig['postgres:password'],
database: databaseConfig['postgres:database'],
ssl: databaseConfig['postgres:ssl'],
};
} else {
throw new Error(`unknown database : ${config.database}`);
return callback(new Error('unknown database : ' + database));
}
const allQuestions = questions.redis.concat(questions.mongo).concat(questions.postgres);
for (let x = 0; x < allQuestions.length; x += 1) {
delete config[allQuestions[x].name];
}
return config;
}
function getSecondaryDatabaseModules(config, next) {
prompt.get({
"name": "secondary_db_modules",
"description": "Which database modules should " + config.secondary_database + " store?",
"default": nconf.get('secondary_db_modules') || "hash, list, sets, sorted"
}, function(err, db) {
config.secondary_db_modules = db.secondary_db_modules;
success(err, config, next);
});
}
module.exports = function(err, config, databases, callback) {
var allowedDBs = Object.keys(databases);
allowedDBs.forEach(function(db) {
questions[db] = require('./../src/database/' + db).questions;
});
async.waterfall([
function(next) {
process.stdout.write('\n');
winston.info('Now configuring ' + config.database + ' database:');
success(err, config, next);
},
function(config, next) {
if (config.secondary_database && allowedDBs.indexOf(config.secondary_database) !== -1) {
winston.info('Now configuring ' + config.secondary_database + ' database:');
getSecondaryDatabaseModules(config, next);
} else {
next(err, config);
}
}
], callback);
};

View File

@@ -1,189 +0,0 @@
{
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "2.0.0-beta.1",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",
"url": "https://github.com/NodeBB/NodeBB/"
},
"main": "app.js",
"scripts": {
"start": "node loader.js",
"lint": "eslint --cache ./nodebb .",
"test": "nyc --reporter=html --reporter=text-summary mocha",
"coverage": "nyc report --reporter=text-lcov > ./coverage/lcov.info",
"coveralls": "nyc report --reporter=text-lcov | coveralls && rm -r coverage"
},
"nyc": {
"exclude": [
"src/upgrades/*",
"test/*"
]
},
"lint-staged": {
"*.js": [
"eslint --fix"
]
},
"dependencies": {
"@adactive/bootstrap-tagsinput": "0.8.2",
"ace-builds": "1.4.14",
"archiver": "5.3.0",
"async": "3.2.3",
"autoprefixer": "10.4.2",
"bcryptjs": "2.4.3",
"benchpressjs": "2.4.3",
"body-parser": "1.19.1",
"bootbox": "5.5.2",
"bootstrap": "3.4.1",
"chalk": "4.1.2",
"chart.js": "2.9.4",
"cli-graph": "3.2.2",
"clipboard": "2.0.10",
"colors": "1.4.0",
"commander": "7.2.0",
"compare-versions": "4.1.3",
"compression": "1.7.4",
"connect-flash": "0.1.1",
"connect-mongo": "4.6.0",
"connect-multiparty": "2.2.0",
"connect-pg-simple": "7.0.0",
"connect-redis": "6.1.1",
"cookie-parser": "1.4.6",
"cron": "1.8.2",
"cropperjs": "1.5.12",
"csurf": "1.11.0",
"daemon": "1.1.0",
"diff": "5.0.0",
"express": "4.17.3",
"express-session": "1.17.2",
"express-useragent": "1.0.15",
"file-loader": "6.2.0",
"graceful-fs": "4.2.9",
"helmet": "5.0.2",
"html-to-text": "8.1.0",
"ipaddr.js": "2.0.1",
"jquery": "3.6.0",
"jquery-deserialize": "2.0.0",
"jquery-form": "4.3.0",
"jquery-serializeobject": "1.0.0",
"jquery-ui": "1.13.1",
"jsesc": "3.0.2",
"json2csv": "5.0.7",
"jsonwebtoken": "8.5.1",
"less": "3.13.1",
"lodash": "4.17.21",
"logrotate-stream": "0.2.8",
"lru-cache": "6.0.0",
"material-design-lite": "1.3.0",
"mime": "3.0.0",
"mkdirp": "1.0.4",
"mongodb": "4.4.0",
"morgan": "1.10.0",
"mousetrap": "1.6.5",
"multiparty": "4.2.3",
"@nodebb/bootswatch": "3.4.2",
"nconf": "0.11.3",
"nodebb-plugin-2factor": "https://github.com/NodeBB/nodebb-plugin-2factor#webpack5",
"nodebb-plugin-composer-default": "https://github.com/nodebb/nodebb-plugin-composer-default.git#webpack5",
"nodebb-plugin-dbsearch": "5.1.3",
"nodebb-plugin-emoji": "https://github.com/NodeBB/nodebb-plugin-emoji.git#webpack5",
"nodebb-plugin-emoji-android": "2.0.5",
"nodebb-plugin-markdown": "https://github.com/julianlam/nodebb-plugin-markdown.git#webpack5",
"nodebb-plugin-mentions": "3.0.7",
"nodebb-plugin-spam-be-gone": "https://github.com/akhoury/nodebb-plugin-spam-be-gone.git#webpack5",
"nodebb-rewards-essentials": "0.2.1",
"nodebb-theme-lavender": "https://github.com/nodebb/nodebb-theme-lavender.git#webpack5",
"nodebb-theme-persona": "https://github.com/nodebb/nodebb-theme-persona.git#webpack5",
"nodebb-theme-slick": "https://github.com/pichalite/nodebb-theme-slick.git#webpack5",
"nodebb-theme-vanilla": "12.1.17",
"nodebb-widget-essentials": "https://github.com/nodebb/nodebb-widget-essentials.git#webpack5",
"nodemailer": "6.7.2",
"nprogress": "0.2.0",
"passport": "0.5.2",
"passport-http-bearer": "1.0.1",
"passport-local": "1.0.0",
"pg": "8.7.3",
"pg-cursor": "2.7.3",
"postcss": "8.4.7",
"postcss-clean": "1.2.0",
"prompt": "1.2.2",
"ioredis": "4.28.5",
"request": "2.88.2",
"request-promise-native": "1.0.9",
"rimraf": "3.0.2",
"rss": "1.2.2",
"sanitize-html": "2.7.0",
"semver": "7.3.5",
"serve-favicon": "2.5.0",
"sharp": "0.30.1",
"sitemap": "7.1.1",
"slideout": "1.0.1",
"socket.io": "4.4.1",
"socket.io-adapter-cluster": "1.0.1",
"socket.io-client": "4.4.1",
"@socket.io/redis-adapter": "7.1.0",
"sortablejs": "1.14.0",
"spdx-license-list": "6.4.0",
"spider-detector": "2.0.0",
"textcomplete": "0.18.2",
"textcomplete.contenteditable": "0.1.1",
"timeago": "1.6.7",
"tinycon": "0.6.8",
"toobusy-js": "0.5.1",
"uglify-es": "3.3.9",
"validator": "13.7.0",
"visibilityjs": "2.0.2",
"webpack": "^5.68.0",
"webpack-merge": "5.8.0",
"winston": "3.6.0",
"xml": "1.0.1",
"xregexp": "5.1.0",
"yargs": "17.3.1",
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@apidevtools/swagger-parser": "10.0.3",
"@commitlint/cli": "16.2.1",
"@commitlint/config-angular": "16.2.1",
"coveralls": "3.1.1",
"eslint": "8.10.0",
"eslint-config-nodebb": "0.1.1",
"eslint-plugin-import": "2.25.4",
"grunt": "1.4.1",
"grunt-contrib-watch": "1.1.0",
"husky": "7.0.4",
"jsdom": "19.0.0",
"lint-staged": "12.3.4",
"mocha": "9.2.1",
"mocha-lcov-reporter": "1.3.0",
"mockdate": "3.0.5",
"nyc": "15.1.0",
"smtp-server": "3.10.0"
},
"bugs": {
"url": "https://github.com/NodeBB/NodeBB/issues"
},
"engines": {
"node": ">=12"
},
"maintainers": [
{
"name": "Andrew Rodrigues",
"email": "andrew@nodebb.org",
"url": "https://github.com/psychobunny"
},
{
"name": "Julian Lam",
"email": "julian@nodebb.org",
"url": "https://github.com/julianlam"
},
{
"name": "Barış Soner Uşaklı",
"email": "baris@nodebb.org",
"url": "https://github.com/barisusakli"
}
]
}

View File

@@ -1,100 +1,46 @@
'use strict';
"use strict";
const winston = require('winston');
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const childProcess = require('child_process');
const less = require('less');
var winston = require('winston'),
express = require('express'),
bodyParser = require('body-parser'),
fs = require('fs'),
path = require('path'),
less = require('less'),
async = require('async'),
uglify = require('uglify-js'),
nconf = require('nconf'),
app = express(),
server;
const webpack = require('webpack');
const nconf = require('nconf');
var web = {},
scripts = [
'public/vendor/xregexp/xregexp.js',
'public/vendor/xregexp/unicode/unicode-base.js',
'public/src/utils.js',
'public/src/installer/install.js'
];
const Benchpress = require('benchpressjs');
const mkdirp = require('mkdirp');
const { paths } = require('../src/constants');
const app = express();
let server;
const formats = [
winston.format.colorize(),
];
const timestampFormat = winston.format((info) => {
const dateString = `${new Date().toISOString()} [${global.process.pid}]`;
info.level = `${dateString} - ${info.level}`;
return info;
});
formats.push(timestampFormat());
formats.push(winston.format.splat());
formats.push(winston.format.simple());
winston.configure({
level: 'verbose',
format: winston.format.combine.apply(null, formats),
transports: [
new winston.transports.Console({
handleExceptions: true,
}),
new winston.transports.File({
filename: 'logs/webinstall.log',
handleExceptions: true,
}),
],
});
const web = module.exports;
let installing = false;
let success = false;
let error = false;
let launchUrl;
const viewsDir = path.join(paths.baseDir, 'build/public/templates');
web.install = async function (port) {
web.install = function(port) {
port = port || 4567;
winston.info(`Launching web installer on port ${port}`);
winston.info('Launching web installer on port', port);
app.use(express.static('public', {}));
app.use('/assets', express.static(path.join(__dirname, '../build/webpack'), {}));
app.engine('tpl', (filepath, options, callback) => {
filepath = filepath.replace(/\.tpl$/, '.js');
Benchpress.__express(filepath, options, callback);
});
app.engine('tpl', require('templates.js').__express);
app.set('view engine', 'tpl');
app.set('views', viewsDir);
app.set('views', path.join(__dirname, '../src/views'));
app.use(bodyParser.urlencoded({
extended: true,
}));
try {
await Promise.all([
compileTemplate(),
compileLess(),
runWebpack(),
copyCSS(),
loadDefaults(),
]);
extended: true
}));
async.parallel([compileLess, compileJS], function() {
setupRoutes();
launchExpress(port);
} catch (err) {
winston.error(err.stack);
}
});
};
async function runWebpack() {
const util = require('util');
const webpackCfg = require('../webpack.installer');
const compiler = webpack(webpackCfg);
const webpackRun = util.promisify(compiler.run).bind(compiler);
await webpackRun();
}
function launchExpress(port) {
server = app.listen(port, () => {
server = app.listen(port, function() {
winston.info('Web installer listening on http://%s:%s', '0.0.0.0', port);
});
}
@@ -103,180 +49,100 @@ function setupRoutes() {
app.get('/', welcome);
app.post('/', install);
app.post('/launch', launch);
app.get('/ping', ping);
app.get('/sping', ping);
}
function ping(req, res) {
res.status(200).send(req.path === '/sping' ? 'healthy' : '200');
}
function welcome(req, res) {
const dbs = ['mongo', 'redis', 'postgres'];
const databases = dbs.map((databaseName) => {
const questions = require(`../src/database/${databaseName}`).questions.filter(question => question && !question.hideOnWebInstall);
var dbs = ['redis', 'mongo'],
databases = [];
return {
name: databaseName,
questions: questions,
};
dbs.forEach(function(el) {
databases.push({
name: el,
questions: require('../src/database/' + el).questions
});
});
const defaults = require('./data/defaults.json');
res.render('install/index', {
url: nconf.get('url') || (`${req.protocol}://${req.get('host')}`),
launchUrl: launchUrl,
skipGeneralSetup: !!nconf.get('url'),
databases: databases,
skipDatabaseSetup: !!nconf.get('database'),
error: error,
success: success,
values: req.body,
minimumPasswordLength: defaults.minimumPasswordLength,
minimumPasswordStrength: defaults.minimumPasswordStrength,
installing: installing,
error: res.locals.error ? true : false,
success: res.locals.success ? true : false,
values: req.body
});
}
function install(req, res) {
if (installing) {
return welcome(req, res);
for (var i in req.body) {
if (req.body.hasOwnProperty(i) && !process.env.hasOwnProperty(i)) {
process.env[i.replace(':', '__')] = req.body[i];
}
}
req.setTimeout(0);
installing = true;
const database = nconf.get('database') || req.body.database || 'mongo';
const setupEnvVars = {
...process.env,
NODEBB_URL: nconf.get('url') || req.body.url || (`${req.protocol}://${req.get('host')}`),
NODEBB_PORT: nconf.get('port') || 4567,
NODEBB_ADMIN_USERNAME: nconf.get('admin:username') || req.body['admin:username'],
NODEBB_ADMIN_PASSWORD: nconf.get('admin:password') || req.body['admin:password'],
NODEBB_ADMIN_EMAIL: nconf.get('admin:email') || req.body['admin:email'],
NODEBB_DB: database,
NODEBB_DB_HOST: nconf.get(`${database}:host`) || req.body[`${database}:host`],
NODEBB_DB_PORT: nconf.get(`${database}:port`) || req.body[`${database}:port`],
NODEBB_DB_USER: nconf.get(`${database}:username`) || req.body[`${database}:username`],
NODEBB_DB_PASSWORD: nconf.get(`${database}:password`) || req.body[`${database}:password`],
NODEBB_DB_NAME: nconf.get(`${database}:database`) || req.body[`${database}:database`],
NODEBB_DB_SSL: nconf.get(`${database}:ssl`) || req.body[`${database}:ssl`],
defaultPlugins: JSON.stringify(nconf.get('defaultplugins') || nconf.get('defaultPlugins') || []),
};
winston.info('Starting setup process');
winston.info(JSON.stringify(setupEnvVars, null, 4));
launchUrl = setupEnvVars.NODEBB_URL;
const child = require('child_process').fork('app', ['--setup'], {
env: setupEnvVars,
var child = require('child_process').fork('app', ['--setup'], {
env: process.env
});
child.on('close', (data) => {
installing = false;
success = data === 0;
error = data !== 0;
child.on('close', function(data) {
if (data === 0) {
res.locals.success = true;
} else {
res.locals.error = true;
}
welcome(req, res);
});
}
async function launch(req, res) {
try {
res.json({});
server.close();
req.setTimeout(0);
let child;
function launch(req, res) {
var pidFilePath = __dirname + '../pidfile';
res.json({});
server.close();
if (!nconf.get('launchCmd')) {
child = childProcess.spawn('node', ['loader.js'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore'],
});
var child = require('child_process').spawn('node', ['loader.js'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore']
});
console.log('\nStarting NodeBB');
console.log(' "./nodebb stop" to stop the NodeBB server');
console.log(' "./nodebb log" to view server output');
console.log(' "./nodebb restart" to restart NodeBB');
} else {
// Use launchCmd instead, if specified
child = childProcess.exec(nconf.get('launchCmd'), {
detached: true,
stdio: ['ignore', 'ignore', 'ignore'],
});
}
process.stdout.write('\nStarting NodeBB\n');
process.stdout.write(' "./nodebb stop" to stop the NodeBB server\n');
process.stdout.write(' "./nodebb log" to view server output\n');
process.stdout.write(' "./nodebb restart" to restart NodeBB\n');
child.unref();
process.exit(0);
}
const filesToDelete = [
'installer.css',
'installer.min.js',
'bootstrap.min.css',
];
await Promise.all(
filesToDelete.map(
filename => fs.promises.unlink(path.join(__dirname, '../public', filename))
)
);
child.unref();
process.exit(0);
} catch (err) {
winston.error(err.stack);
throw err;
function compileLess(callback) {
if ((nconf.get('from-file') || '').indexOf('less') !== -1) {
winston.info('LESS compilation skipped');
return callback(false);
}
}
// this is necessary because otherwise the compiled templates won't be available on a clean install
async function compileTemplate() {
const sourceFile = path.join(__dirname, '../src/views/install/index.tpl');
const destTpl = path.join(viewsDir, 'install/index.tpl');
const destJs = path.join(viewsDir, 'install/index.js');
fs.readFile(path.join(__dirname, '../public/less/install.less'), function(err, style) {
less.render(style.toString(), function(err, css) {
if(err) {
return winston.error('Unable to compile LESS: ', err);
}
const source = await fs.promises.readFile(sourceFile, 'utf8');
const [compiled] = await Promise.all([
Benchpress.precompile(source, { filename: 'install/index.tpl' }),
mkdirp(path.dirname(destJs)),
]);
await Promise.all([
fs.promises.writeFile(destJs, compiled),
fs.promises.writeFile(destTpl, source),
]);
}
async function compileLess() {
try {
const installSrc = path.join(__dirname, '../public/less/install.less');
const style = await fs.promises.readFile(installSrc);
const css = await less.render(String(style), { filename: path.resolve(installSrc) });
await fs.promises.writeFile(path.join(__dirname, '../public/installer.css'), css.css);
} catch (err) {
winston.error(`Unable to compile LESS: \n${err.stack}`);
throw err;
}
}
async function copyCSS() {
const src = await fs.promises.readFile(
path.join(__dirname, '../node_modules/bootstrap/dist/css/bootstrap.min.css'), 'utf8'
);
await fs.promises.writeFile(path.join(__dirname, '../public/bootstrap.min.css'), src);
}
async function loadDefaults() {
const setupDefaultsPath = path.join(__dirname, '../setup.json');
try {
// eslint-disable-next-line no-bitwise
await fs.promises.access(setupDefaultsPath, fs.constants.F_OK | fs.constants.R_OK);
} catch (err) {
// setup.json not found or inaccessible, proceed with no defaults
if (err.code !== 'ENOENT') {
throw err;
}
return;
}
winston.info('[installer] Found setup.json, populating default values');
nconf.file({
file: setupDefaultsPath,
fs.writeFile(path.join(__dirname, '../public/stylesheet.css'), css.css, callback);
});
});
}
function compileJS(callback) {
if ((nconf.get('from-file') || '').indexOf('js') !== -1) {
winston.info('Client-side JS compilation skipped');
return callback(false);
}
var scriptPath = path.join(__dirname, '..'),
result = uglify.minify(scripts.map(function(script) {
return path.join(scriptPath, script);
}));
fs.writeFile(path.join(__dirname, '../public/nodebb.min.js'), result.code, callback);
}
module.exports = web;

298
loader.js
View File

@@ -1,78 +1,80 @@
'use strict';
const nconf = require('nconf');
const fs = require('fs');
const url = require('url');
const path = require('path');
const { fork } = require('child_process');
const logrotate = require('logrotate-stream');
const mkdirp = require('mkdirp');
var nconf = require('nconf'),
fs = require('fs'),
url = require('url'),
path = require('path'),
fork = require('child_process').fork,
const file = require('./src/file');
const pkg = require('./package.json');
async = require('async'),
logrotate = require('logrotate-stream'),
const pathToConfig = path.resolve(__dirname, process.env.CONFIG || 'config.json');
pkg = require('./package.json');
nconf.argv().env().file({
file: pathToConfig,
file: path.join(__dirname, '/config.json')
});
const pidFilePath = path.join(__dirname, 'pidfile');
var pidFilePath = __dirname + '/pidfile',
output = logrotate({ file: __dirname + '/logs/output.log', size: '1m', keep: 3, compress: true }),
silent = nconf.get('silent') === 'false' ? false : nconf.get('silent') !== false,
numProcs,
workers = [],
const outputLogFilePath = path.join(__dirname, nconf.get('logFile') || 'logs/output.log');
Loader = {
timesStarted: 0,
js: {
cache: undefined,
map: undefined
},
css: {
cache: undefined,
acpCache: undefined
}
};
const logDir = path.dirname(outputLogFilePath);
if (!fs.existsSync(logDir)) {
mkdirp.sync(path.dirname(outputLogFilePath));
}
const output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compress: true });
const silent = nconf.get('silent') === 'false' ? false : nconf.get('silent') !== false;
let numProcs;
const workers = [];
const Loader = {
timesStarted: 0,
};
const appPath = path.join(__dirname, 'app.js');
Loader.init = function () {
Loader.init = function(callback) {
if (silent) {
console.log = (...args) => {
output.write(`${args.join(' ')}\n`);
console.log = function(value) {
output.write(value + '\n');
};
}
process.on('SIGHUP', Loader.restart);
process.on('SIGUSR2', Loader.reload);
process.on('SIGTERM', Loader.stop);
callback();
};
Loader.displayStartupMessages = function () {
Loader.displayStartupMessages = function(callback) {
console.log('');
console.log(`NodeBB v${pkg.version} Copyright (C) 2013-${(new Date()).getFullYear()} NodeBB Inc.`);
console.log('NodeBB v' + pkg.version + ' Copyright (C) 2013-2014 NodeBB Inc.');
console.log('This program comes with ABSOLUTELY NO WARRANTY.');
console.log('This is free software, and you are welcome to redistribute it under certain conditions.');
console.log('For the full license, please visit: http://www.gnu.org/copyleft/gpl.html');
console.log('');
callback();
};
Loader.addWorkerEvents = function (worker) {
worker.on('exit', (code, signal) => {
Loader.addWorkerEvents = function(worker) {
worker.on('exit', function(code, signal) {
if (code !== 0) {
if (Loader.timesStarted < numProcs * 3) {
Loader.timesStarted += 1;
if (Loader.timesStarted < numProcs*3) {
Loader.timesStarted++;
if (Loader.crashTimer) {
clearTimeout(Loader.crashTimer);
}
Loader.crashTimer = setTimeout(() => {
Loader.crashTimer = setTimeout(function() {
Loader.timesStarted = 0;
}, 10000);
} else {
console.log(`${numProcs * 3} restarts in 10 seconds, most likely an error on startup. Halting.`);
console.log(numProcs*3 + ' restarts in 10 seconds, most likely an error on startup. Halting.');
process.exit();
}
}
console.log(`[cluster] Child Process (${worker.pid}) has exited (code: ${code}, signal: ${signal})`);
console.log('[cluster] Child Process (' + worker.pid + ') has exited (code: ' + code + ', signal: ' + signal +')');
if (!(worker.suicide || code === 0)) {
console.log('[cluster] Spinning up another process...');
@@ -80,54 +82,99 @@ Loader.addWorkerEvents = function (worker) {
}
});
worker.on('message', (message) => {
worker.on('message', function(message) {
if (message && typeof message === 'object' && message.action) {
switch (message.action) {
case 'ready':
if (Loader.js.cache && !worker.isPrimary) {
worker.send({
action: 'js-propagate',
cache: Loader.js.cache,
map: Loader.js.map,
hash: Loader.js.hash
});
}
if (Loader.css.cache && !worker.isPrimary) {
worker.send({
action: 'css-propagate',
cache: Loader.css.cache,
acpCache: Loader.css.acpCache,
hash: Loader.css.hash
});
}
break;
case 'restart':
console.log('[cluster] Restarting...');
Loader.restart();
break;
case 'pubsub':
workers.forEach((w) => {
w.send(message);
});
break;
case 'socket.io':
workers.forEach((w) => {
if (w !== worker) {
w.send(message);
}
});
break;
break;
case 'reload':
console.log('[cluster] Reloading...');
Loader.reload();
break;
case 'js-propagate':
Loader.js.cache = message.cache;
Loader.js.map = message.map;
Loader.js.hash = message.hash;
Loader.notifyWorkers({
action: 'js-propagate',
cache: message.cache,
map: message.map,
hash: message.hash
}, worker.pid);
break;
case 'css-propagate':
Loader.css.cache = message.cache;
Loader.css.acpCache = message.acpCache;
Loader.css.hash = message.hash;
Loader.notifyWorkers({
action: 'css-propagate',
cache: message.cache,
acpCache: message.acpCache,
hash: message.hash
}, worker.pid);
break;
case 'templates:compiled':
Loader.notifyWorkers({
action: 'templates:compiled',
}, worker.pid);
break;
}
}
});
};
Loader.start = function () {
Loader.start = function(callback) {
numProcs = getPorts().length;
console.log(`Clustering enabled: Spinning up ${numProcs} process(es).\n`);
console.log('Clustering enabled: Spinning up ' + numProcs + ' process(es).\n');
for (let x = 0; x < numProcs; x += 1) {
for (var x=0; x<numProcs; ++x) {
forkWorker(x, x === 0);
}
if (callback) {
callback();
}
};
function forkWorker(index, isPrimary) {
const ports = getPorts();
const args = [];
var ports = getPorts();
if (!ports[index]) {
return console.log(`[cluster] invalid port for worker : ${index} ports: ${ports.length}`);
if(!ports[index]) {
return console.log('[cluster] invalid port for worker : ' + index + ' ports: ' + ports.length);
}
process.env.isPrimary = isPrimary;
process.env.isCluster = nconf.get('isCluster') || ports.length > 1;
process.env.isCluster = true;
process.env.port = ports[index];
const worker = fork(appPath, args, {
var worker = fork('app.js', [], {
silent: silent,
env: process.env,
env: process.env
});
worker.index = index;
@@ -138,112 +185,99 @@ function forkWorker(index, isPrimary) {
Loader.addWorkerEvents(worker);
if (silent) {
const output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compress: true });
var output = logrotate({ file: __dirname + '/logs/output.log', size: '1m', keep: 3, compress: true });
worker.stdout.pipe(output);
worker.stderr.pipe(output);
}
}
function getPorts() {
const _url = nconf.get('url');
var _url = nconf.get('url');
if (!_url) {
console.log('[cluster] url is undefined, please check your config.json');
process.exit();
}
const urlObject = url.parse(_url);
let port = nconf.get('PORT') || nconf.get('port') || urlObject.port || 4567;
var urlObject = url.parse(_url);
var port = nconf.get('port') || nconf.get('PORT') || urlObject.port || 4567;
if (!Array.isArray(port)) {
port = [port];
}
return port;
}
Loader.restart = function () {
Loader.restart = function() {
killWorkers();
nconf.remove('file');
nconf.use('file', { file: pathToConfig });
Loader.start();
};
fs.readFile(pathToConfig, { encoding: 'utf-8' }, (err, configFile) => {
if (err) {
console.error('Error reading config');
throw err;
}
const conf = JSON.parse(configFile);
nconf.stores.env.readOnly = false;
nconf.set('url', conf.url);
nconf.stores.env.readOnly = true;
if (process.env.url !== conf.url) {
process.env.url = conf.url;
}
Loader.start();
Loader.reload = function() {
workers.forEach(function(worker) {
worker.send({
action: 'reload'
});
});
};
Loader.stop = function () {
Loader.stop = function() {
killWorkers();
// Clean up the pidfile
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
fs.unlinkSync(pidFilePath);
}
fs.unlinkSync(__dirname + '/pidfile');
};
function killWorkers() {
workers.forEach((worker) => {
workers.forEach(function(worker) {
worker.suicide = true;
worker.kill();
});
}
fs.open(pathToConfig, 'r', (err) => {
if (err) {
// No config detected, kickstart web installer
fork('app');
return;
}
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
if (file.existsSync(pidFilePath)) {
let pid = 0;
Loader.notifyWorkers = function(msg, worker_pid) {
worker_pid = parseInt(worker_pid, 10);
workers.forEach(function(worker) {
if (parseInt(worker.pid, 10) !== worker_pid) {
try {
pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
if (pid) {
process.kill(pid, 0);
console.info(`Process "${pid}" from pidfile already running, exiting`);
process.exit();
} else {
console.info(`Invalid pid "${pid}" from pidfile, deleting pidfile`);
fs.unlinkSync(pidFilePath);
}
} catch (err) {
if (err.code === 'ESRCH') {
console.info(`Process "${pid}" from pidfile not found, deleting pidfile`);
fs.unlinkSync(pidFilePath);
} else {
console.error(err.stack);
throw err;
}
worker.send(msg);
} catch (e) {
console.log('[cluster/notifyWorkers] Failed to reach pid ' + worker_pid);
}
}
});
};
require('daemon')({
stdout: process.stdout,
stderr: process.stderr,
cwd: process.cwd(),
fs.open(path.join(__dirname, 'config.json'), 'r', function(err) {
if (!err) {
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
if (fs.existsSync(pidFilePath)) {
try {
var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
process.kill(pid, 0);
process.exit();
} catch (e) {
fs.unlinkSync(pidFilePath);
}
}
require('daemon')({
stdout: process.stdout,
stderr: process.stderr
});
fs.writeFile(__dirname + '/pidfile', process.pid);
}
async.series([
Loader.init,
Loader.displayStartupMessages,
Loader.start
], function(err) {
if (err) {
console.log('[loader] Error during startup: ' + err.message);
}
});
fs.writeFileSync(pidFilePath, String(process.pid));
}
try {
Loader.init();
Loader.displayStartupMessages();
Loader.start();
} catch (err) {
console.error('[loader] Error during startup');
throw err;
} else {
// No config detected, kickstart web installer
var child = require('child_process').fork('app');
}
});

84
minifier.js Normal file
View File

@@ -0,0 +1,84 @@
"use strict";
var uglifyjs = require('uglify-js'),
less = require('less'),
async = require('async'),
fs = require('fs'),
crypto = require('crypto'),
utils = require('./public/src/utils'),
Minifier = {
js: {}
};
/* Javascript */
Minifier.js.minify = function (scripts, minify, callback) {
scripts = scripts.filter(function(file) {
return fs.existsSync(file) && file.endsWith('.js');
});
if (minify) {
minifyScripts(scripts, function() {
callback.apply(this, arguments);
});
} else {
concatenateScripts(scripts, callback);
}
};
process.on('message', function(payload) {
switch(payload.action) {
case 'js':
Minifier.js.minify(payload.scripts, payload.minify, function(minified/*, sourceMap*/) {
process.send({
type: 'end',
// sourceMap: sourceMap,
minified: minified
});
});
break;
}
});
function minifyScripts(scripts, callback) {
// The portions of code involving the source map are commented out as they're broken in UglifyJS2
// Follow along here: https://github.com/mishoo/UglifyJS2/issues/700
try {
var minified = uglifyjs.minify(scripts, {
// outSourceMap: "nodebb.min.js.map",
compress: false
}),
hasher = crypto.createHash('md5'),
hash;
// Calculate js hash
hasher.update(minified.code, 'utf-8');
hash = hasher.digest('hex');
process.send({
type: 'hash',
payload: hash.slice(0, 8)
});
callback(minified.code/*, minified.map*/);
} catch(err) {
process.send({
type: 'error',
payload: err.message
});
}
}
function concatenateScripts(scripts, callback) {
async.map(scripts, fs.readFile, function(err, scripts) {
if (err) {
process.send({
type: 'error',
payload: err
});
}
scripts = scripts.join(require('os').EOL + ';');
callback(scripts);
});
}

165
nodebb
View File

@@ -1,5 +1,166 @@
#!/usr/bin/env node
'use strict';
var colors = require('colors'),
cproc = require('child_process'),
argv = require('minimist')(process.argv.slice(2)),
fs = require('fs'),
async = require('async'),
touch = require('touch');
require('./src/cli');
var getRunningPid = function(callback) {
fs.readFile(__dirname + '/pidfile', {
encoding: 'utf-8'
}, function(err, pid) {
if (err) {
return callback(err);
}
try {
process.kill(parseInt(pid, 10), 0);
callback(null, parseInt(pid, 10));
} catch(e) {
callback(e);
}
});
};
switch(process.argv[2]) {
case 'status':
getRunningPid(function(err, pid) {
if (!err) {
process.stdout.write('\nNodeBB Running '.bold + '(pid '.cyan + pid.toString().cyan + ')\n'.cyan);
process.stdout.write('\t"' + './nodebb stop'.yellow + '" to stop the NodeBB server\n');
process.stdout.write('\t"' + './nodebb log'.yellow + '" to view server output\n');
process.stdout.write('\t"' + './nodebb restart'.yellow + '" to restart NodeBB\n\n');
} else {
process.stdout.write('\nNodeBB is not running\n'.bold);
process.stdout.write('\t"' + './nodebb start'.yellow + '" to launch the NodeBB server\n\n'.reset);
}
})
break;
case 'start':
process.stdout.write('\nStarting NodeBB\n'.bold);
process.stdout.write(' "' + './nodebb stop'.yellow + '" to stop the NodeBB server\n');
process.stdout.write(' "' + './nodebb log'.yellow + '" to view server output\n');
process.stdout.write(' "' + './nodebb restart'.yellow + '" to restart NodeBB\n\n'.reset);
// Spawn a new NodeBB process
cproc.fork(__dirname + '/loader.js', {
env: process.env
});
break;
case 'stop':
getRunningPid(function(err, pid) {
if (!err) {
process.kill(pid, 'SIGTERM');
process.stdout.write('Stopping NodeBB. Goodbye!\n')
} else {
process.stdout.write('NodeBB is already stopped.\n');
}
});
break;
case 'restart':
getRunningPid(function(err, pid) {
if (!err) {
process.kill(pid, 'SIGHUP');
} else {
process.stdout.write('NodeBB could not be restarted, as a running instance could not be found.');
}
});
break;
case 'reload':
getRunningPid(function(err, pid) {
if (!err) {
process.kill(pid, 'SIGUSR2');
} else {
process.stdout.write('NodeBB could not be reloaded, as a running instance could not be found.');
}
});
break;
case 'dev':
process.env.NODE_ENV = 'development';
cproc.fork(__dirname + '/loader.js', ['--no-daemon', '--no-silent'], {
env: process.env
});
break;
case 'log':
process.stdout.write('\nType '.red + 'Ctrl-C '.bold + 'to exit'.red);
process.stdout.write('\n\n'.reset);
cproc.spawn('tail', ['-F', './logs/output.log'], {
cwd: __dirname,
stdio: 'inherit'
});
break;
case 'setup':
cproc.fork('app.js', ['--setup'], {
cwd: __dirname,
silent: false
});
break;
case 'reset':
var args = process.argv.slice(0);
args.unshift('--reset');
cproc.fork('app.js', args, {
cwd: __dirname,
silent: false
});
break;
case 'upgrade':
async.series([
function(next) {
process.stdout.write('1. '.bold + 'Bringing base dependencies up to date... '.yellow);
require('child_process').execFile('/usr/bin/env', ['npm', 'i', '--production'], next);
},
function(next) {
process.stdout.write('OK\n'.green);
process.stdout.write('2. '.bold + 'Updating NodeBB data store schema.\n'.yellow);
var upgradeProc = cproc.fork('app.js', ['--upgrade'], {
cwd: __dirname,
silent: false
});
upgradeProc.on('close', next)
},
function(next) {
process.stdout.write('3. '.bold + 'Storing upgrade date in "package.json"... '.yellow);
touch(__dirname + '/package.json', {}, next);
}
], function(err) {
if (err) {
process.stdout.write('\nError'.red + ': ' + err.message + '\n');
} else {
var message = 'NodeBB Upgrade Complete!',
spaces = new Array(Math.floor(process.stdout.columns / 2) - (message.length / 2) + 1).join(' ');
process.stdout.write('OK\n'.green);
process.stdout.write('\n' + spaces + message.green.bold + '\n\n'.reset);
}
});
break;
default:
process.stdout.write('\nWelcome to NodeBB\n\n'.bold);
process.stdout.write('Usage: ./nodebb {start|stop|reload|restart|log|setup|reset|upgrade|dev}\n\n');
process.stdout.write('\t' + 'start'.yellow + '\tStart the NodeBB server\n');
process.stdout.write('\t' + 'stop'.yellow + '\tStops the NodeBB server\n');
process.stdout.write('\t' + 'reload'.yellow + '\tRestarts NodeBB\n');
process.stdout.write('\t' + 'restart'.yellow + '\tRestarts NodeBB\n');
process.stdout.write('\t' + 'log'.yellow + '\tOpens the logging interface (useful for debugging)\n');
process.stdout.write('\t' + 'setup'.yellow + '\tRuns the NodeBB setup script\n');
process.stdout.write('\t' + 'reset'.yellow + '\tDisables all plugins, restores the default theme.\n');
process.stdout.write('\t' + 'upgrade'.yellow + '\tRun NodeBB upgrade scripts, ensure packages are up-to-date\n');
process.stdout.write('\t' + 'dev'.yellow + '\tStart NodeBB in interactive development mode\n');
process.stdout.write('\t' + 'watch'.yellow + '\tStart NodeBB in development mode and watch for changes\n');
process.stdout.write('\n'.reset);
break;
}

View File

@@ -1 +1,122 @@
@echo off && cd %~dp0 && node ./nodebb %*
@echo off
rem %1 action
rem %2 subaction
setlocal enabledelayedexpansion
2>nul call :CASE_%1
if ERRORLEVEL 1 call :DEFAULT_CASE
exit /B
:CASE_start
echo Starting NodeBB
echo "nodebb.bat stop" to stop the NodeBB server
echo "nodebb.bat log" to view server output
rem Start the loader daemon
node loader %*
goto END_CASE
:CASE_stop
call :pidexists
if %_result%==0 (
echo NodeBB is already stopped.
) else (
echo Stopping NodeBB. Goodbye!
rem Doing this forcefully is probably not the best method
taskkill /PID !_pid! /f>nul
)
goto END_CASE
:CASE_restart
echo Unsupported
goto END_CASE
:CASE_reload
echo Unsupported
goto END_CASE
:CASE_status
call :pidexists
if %_result%==0 (
echo NodeBB is not running
echo "nodebb.bat start" to launch the NodeBB server
) else (
echo NodeBB Running ^(pid !_pid!^)
echo "nodebb.bat stop" to stop the NodeBB server
echo "nodebb.bat log" to view server output
echo "nodebb.bat restart" to restart NodeBB
)
goto END_CASE
:CASE_log
cls
type .\logs\output.log
goto END_CASE
:CASE_upgrade
call npm install
call npm i nodebb-theme-vanilla nodebb-theme-lavender nodebb-widget-essentials
node app --upgrade
copy /b package.json +,,>nul
goto END_CASE
:CASE_setup
node app --setup %*
goto END_CASE
:CASE_reset
node app --reset --%2
goto END_CASE
:CASE_dev
echo Launching NodeBB in "development" mode.
echo To run the production build of NodeBB, please use "forever".
echo More Information: https://docs.nodebb.org/en/latest/running/index.html
set NODE_ENV=development
node loader --no-daemon %*
goto END_CASE
:CASE_watch
echo Not supported
goto END_CASE
:DEFAULT_CASE
echo Welcome to NodeBB
echo Usage: nodebb.bat ^{start^|stop^|reload^|restart^|log^|setup^|reset^|upgrade^|dev^|watch^}
goto END_CASE
:END_CASE
endlocal
VER > NUL
goto :EOF
:pidexists
if exist %~dp0pidfile (
set /p _pid=<pidfile
for /f "usebackq" %%Z in (`tasklist /nh /fi "PID eq !_pid!"`) do (
if %%Z==INFO: (
del pidfile
set _result=0
) else (
set _result=1
)
)
) else (
set _result=0
)

3607
npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

107
package.json Normal file
View File

@@ -0,0 +1,107 @@
{
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "0.7.3",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",
"url": "https://github.com/NodeBB/NodeBB/"
},
"main": "app.js",
"scripts": {
"start": "node loader.js",
"test": "mocha ./tests -t 10000"
},
"dependencies": {
"async": "~0.9.0",
"bcryptjs": "~2.1.0",
"body-parser": "^1.9.0",
"colors": "^1.1.0",
"compression": "^1.1.0",
"connect-ensure-login": "^0.1.1",
"connect-flash": "^0.1.1",
"connect-multiparty": "^1.2.4",
"cookie-parser": "^1.3.3",
"cron": "^1.0.5",
"csurf": "^1.6.1",
"daemon": "~1.1.0",
"express": "^4.9.5",
"express-session": "^1.8.2",
"gravatar": "^1.1.0",
"heapdump": "^0.3.0",
"less": "^2.0.0",
"logrotate-stream": "^0.2.3",
"lru-cache": "^2.6.1",
"lwip": "0.0.7",
"mime": "^1.3.4",
"minimist": "^1.1.1",
"mkdirp": "~0.5.0",
"mmmagic": "^0.3.13",
"morgan": "^1.3.2",
"nconf": "~0.7.1",
"nodebb-plugin-composer-default": "1.0.9",
"nodebb-plugin-dbsearch": "0.2.15",
"nodebb-plugin-emoji-extended": "0.4.9",
"nodebb-plugin-markdown": "4.0.2",
"nodebb-plugin-mentions": "1.0.0",
"nodebb-plugin-soundpack-default": "0.1.2",
"nodebb-plugin-spam-be-gone": "0.4.1",
"nodebb-rewards-essentials": "0.0.3",
"nodebb-theme-lavender": "1.0.49",
"nodebb-theme-persona": "2.0.13",
"nodebb-theme-vanilla": "3.0.7",
"nodebb-widget-essentials": "1.0.4",
"npm": "^2.1.4",
"passport": "^0.2.1",
"passport-local": "1.0.0",
"prompt": "^0.2.14",
"request": "^2.44.0",
"rimraf": "~2.3.2",
"rss": "^1.0.0",
"semver": "^4.3.3",
"serve-favicon": "^2.1.5",
"sitemap": "^0.8.1",
"socket.io": "^1.2.1",
"socket.io-client": "^1.2.1",
"socket.io-redis": "^0.1.3",
"socketio-wildcard": "~0.1.1",
"string": "^3.0.0",
"templates.js": "0.2.8",
"touch": "0.0.3",
"uglify-js": "^2.4.23",
"underscore": "~1.8.3",
"underscore.deep": "^0.5.1",
"validator": "^3.30.0",
"winston": "^0.9.0",
"xregexp": "~2.0.0"
},
"devDependencies": {
"mocha": "~1.13.0",
"grunt": "~0.4.5",
"grunt-contrib-watch": "^0.6.1"
},
"bugs": {
"url": "https://github.com/NodeBB/NodeBB/issues"
},
"engines": {
"node": ">=0.10"
},
"maintainers": [
{
"name": "Andrew Rodrigues",
"email": "andrew@nodebb.org",
"url": "https://github.com/psychobunny"
},
{
"name": "Julian Lam",
"email": "julian@nodebb.org",
"url": "https://github.com/julianlam"
},
{
"name": "Barış Soner Uşaklı",
"email": "baris@nodebb.org",
"url": "https://github.com/barisusakli"
}
]
}

View File

@@ -1,3 +0,0 @@
{
"extends": "nodebb/public"
}

View File

@@ -1,177 +0,0 @@
<html>
<head>
<title>Excessive Load Warning</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
body {
background: #00A9EA;
color: white;
/* see public/less/admin/vars.less for documentation on system font family */
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
text-align: center;
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
}
h1 {
font-size: 250px;
color: #fff;
opacity: 0.5;
margin: 10px;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
}
p {
font-size: 20px;
}
p strong {
font-size: 28px;
}
@media (max-width: 640px) {
h1 {
font-size: 125px;
}
p {
font-size: 16px;
}
p strong {
font-size: 20px;
}
}
.center {
position: relative;
top: 50%;
-webkit-transform: translateY(50%);
-ms-transform: translateY(50%);
transform: translateY(50%);
}
@-webkit-keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
@keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
.bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-transform-origin: center bottom;
-ms-transform-origin: center bottom;
transform-origin: center bottom;
}
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated.infinite {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.hinge {
-webkit-animation-duration: 2s;
animation-duration: 2s;
}
.hide {
display: none;
}
</style>
<script type="text/javascript">
window.onload = function() {
var count = 0,
bounce = document.getElementById('click-me');
bounce.onclick = function() {
count++;
bounce.className = '';
setTimeout(function() {
bounce.className = 'animated bounce';
}, 50);
if (count > 5) {
document.getElementById('hide').className = '';
}
};
}
</script>
</head>
<body>
<div class="wrapper">
<div class="center">
<h1 id="click-me" class="animated bounce">503</h1>
<p>
<strong>This forum is temporarily unavailable due to excessive load.</strong>
</p>
<p>
We shouldn't be down for long. Please check back shortly. Sorry for the inconvenience!
</p>
<p>
&nbsp;<small id="hide" class="hide">Alright. You can stop clicking... it's not going to make the site come back sooner!</small>
</p>
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -1,16 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="321" height="75" viewBox="0 0 321 75">
<defs>
<radialGradient id="nodebb-a" cx="65.599%" cy="0%" r="169.036%" fx="65.599%" fy="0%" gradientTransform="matrix(-.5183 .5916 -.38031 -.80624 .996 -.388)">
<stop offset="0%" stop-color="#2A6CBE"/>
<stop offset="38.688%" stop-color="#2062BC"/>
<stop offset="49.182%" stop-color="#1F5FBC"/>
<stop offset="66.583%" stop-color="#1C5ABD"/>
<stop offset="85.354%" stop-color="#1956BC"/>
<stop offset="100%" stop-color="#1851BE"/>
</radialGradient>
</defs>
<g fill="none" transform="translate(1 1)">
<path fill="#000306" stroke="#000306" stroke-width=".275" d="M195.810017,20 C210.691849,20 221.256813,28.7272272 221.484017,45.845679 C221.490864,46.3615394 221.461776,47.1194739 221.396754,48.1194827 C221.328352,49.1714504 220.455158,49.9897119 219.400969,49.9897119 L179.110557,49.9897119 C179.110557,56.6336943 182.723978,64.2757202 196.378026,64.2757202 C204.088827,64.2757202 211.625406,61.928339 218.987763,57.2335766 L219.553745,56.8677968 C220.246813,56.4139645 221.17656,56.6079034 221.630392,57.3009716 C221.757029,57.4943637 221.837254,57.7144077 221.864806,57.943925 C222.131013,60.1615294 222.004084,61.5832211 221.484017,62.2089999 C220.452051,63.4507301 210.901843,73 196.378026,73 C180.58738,73 169,61.8765432 169,46.3909465 C169,31.3415638 180.019372,20 195.810017,20 Z M162.92733,0 C164.031899,-4.2495073e-16 164.92733,0.8954305 164.92733,2 L164.928075,44.1266361 C164.975781,44.8118124 165,45.5058194 165,46.2079256 C165,61.5334332 153.460581,73 138,73 C122.427386,73 111,61.5334332 111,46.2079256 C111,30.882418 122.427386,19.4158511 138,19.4158511 C144.981278,19.4158511 151.163029,21.7538895 155.843826,25.7295173 L155.843557,4.54188628 C155.843557,2.03347175 157.877029,4.60788274e-16 160.385444,0 L162.92733,0 Z M81,20 C96.4605809,20 108,31.3415638 108,46.5 C108,61.6584362 96.4605809,73 81,73 C65.4273859,73 54,61.6584362 54,46.5 C54,31.3415638 65.4273859,20 81,20 Z M27.324263,20 C41.6099773,20 50,29.1649049 50,41.7801268 L50,67.5 C50,69.4329966 48.4329966,71 46.5,71 L42.2494331,71 C41.1448636,71 40.2494331,70.1045695 40.2494331,69 L40.2494331,42.5348837 C40.2494331,34.3403805 35.8276644,28.6257928 26.3038549,28.6257928 C10.0816327,28.6257928 9.75718821,35.7795705 9.75069932,44.6831843 L9.75056689,71 L2,71 C0.8954305,71 1.3527075e-16,70.1045695 0,69 L0,35.2932975 L0,35.2932975 C0,24.3935718 19.1609977,20 27.324263,20 Z M81,28.7242798 C70.8049793,28.7242798 63.6348548,36.4670782 63.6348548,46.5 C63.6348548,56.3148148 70.8049793,64.2757202 81,64.2757202 C91.1950207,64.2757202 98.3651452,56.3148148 98.3651452,46.5 C98.3651452,36.4670782 91.1950207,28.7242798 81,28.7242798 Z M138,28.2362872 C127.804979,28.2362872 120.634855,36.0644241 120.634855,46.2079256 C120.634855,56.1309161 127.804979,64.179564 138,64.179564 C148.195021,64.179564 155.365145,56.1309161 155.365145,46.2079256 C155.365145,36.0644241 148.195021,28.2362872 138,28.2362872 Z M195.810017,28.7242798 C186.721876,28.7242798 180.473779,33.9588477 179.224159,41.8106996 L211.259858,41.8106996 C210.010238,33.9588477 204.443752,28.7242798 195.810017,28.7242798 Z"/>
<path fill="url(#nodebb-a)" d="M277,19 L276.999615,57.7303365 C276.99251,64.180962 276.88949,68.6242617 276.88949,73 L276.88949,73 L255.649055,73 C242.962262,73 236,67.06 236,57.7257143 C236,51.4771429 239.558488,46.9257143 245.1283,45.0742857 C240.564149,43.1457143 237.779243,38.8257143 237.779243,33.5028571 C237.779243,24.8628571 244.664149,19 256.732074,19 L256.732074,19 L277,19 Z M299.267926,19 C311.335851,19 318.220757,24.8628571 318.220757,33.5028571 C318.220757,38.8257143 315.435851,43.1457143 310.8717,45.0742857 C316.441512,46.9257143 320,51.4771429 320,57.7257143 C320,67.06 313.037738,73 300.350945,73 L279.11051,73 L279.11051,73 C279.11051,68.6242617 279.00749,64.180962 279.000385,57.7303365 L279,19 L299.267926,19 Z M266.092452,49.8571429 L256.499999,49.8571429 C250.156602,49.8571429 246.984904,52.2485714 246.984904,56.9542857 C246.984904,61.8914286 249.924527,64.36 255.726414,64.36 L255.726414,64.36 L260.754716,64.36 C264.777358,64.36 266.092452,62.4314286 266.092452,56.5685714 L266.092452,56.5685714 L266.092452,49.8571429 Z M299.500001,49.8571429 L289.907548,49.8571429 L289.907548,56.5685714 C289.907548,62.4314286 291.222642,64.36 295.245284,64.36 L300.273586,64.36 C306.075473,64.36 309.015096,61.8914286 309.015096,56.9542857 C309.015096,52.2485714 305.843398,49.8571429 299.500001,49.8571429 Z M266.169811,27.64 L257.350942,27.64 C251.703772,27.64 248.764149,30.1857143 248.764149,34.5057143 C248.764149,38.8257143 251.703772,41.3714286 257.350942,41.3714286 L257.350942,41.3714286 L266.169811,41.3714286 L266.169811,27.64 Z M298.649058,27.64 L289.830189,27.64 L289.830189,41.3714286 L298.649058,41.3714286 C304.296228,41.3714286 307.235851,38.8257143 307.235851,34.5057143 C307.235851,30.1857143 304.296228,27.64 298.649058,27.64 Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,14 +0,0 @@
# Important note about localising NodeBB
The files here are read-only and overwritten daily (if there are changes) by the
helper bot [Misty](https://github.com/nodebb-misty).
Our localisation efforts are handled via [our Transifex Project](https://www.transifex.com/nodebb/nodebb/),
and any pull requests made to this directory will be automatically closed because
localisations can go out-of-sync when edited directly.
If there are non-localised strings and you cannot find them in Transifex, please
[open a new issue on our bug tracker](https://github.com/NodeBB/NodeBB/issues/new)
so we can take a look.
Thank you for helping localise NodeBB!

3
public/language/TODO Normal file
View File

@@ -0,0 +1,3 @@
NPM INSTALL
For now, language packs will be stored here. Eventually, will be moved to server side to allow for npm install-ability.
When that happens, server code will generate compressed JSON language files in this folder.

View File

@@ -1,11 +0,0 @@
{
"alert.confirm-rebuild-and-restart": "هل أنت متأكد إنك تريد إعادة بناء وتشغيل الـ NodeBB؟",
"alert.confirm-restart": "هل تريد بالتأكيد إعادة تشغيل NodeBB؟",
"acp-title": "لوحة تحكم إدارة NodeBB | %1",
"settings-header-contents": "محتويات",
"changes-saved": "Changes Saved",
"changes-saved-message": "Your changes to the NodeBB configuration have been saved.",
"changes-not-saved": "Changes Not Saved",
"changes-not-saved-message": "NodeBB encountered a problem saving your changes. (%1)"
}

View File

@@ -1,6 +0,0 @@
{
"post-cache": "التخزين المؤقت للمشاركات",
"percent-full": "1% كاملة",
"post-cache-size": "حجم التخزين المؤقت للمشاركات",
"items-in-cache": "العناصر في التخزين المؤقت"
}

View File

@@ -1,52 +0,0 @@
{
"x-b": "%1 b",
"x-mb": "%1 mb",
"x-gb": "%1 gb",
"uptime-seconds": "Uptime in Seconds",
"uptime-days": "Uptime in Days",
"mongo": "Mongo",
"mongo.version": "MongoDB Version",
"mongo.storage-engine": "Storage Engine",
"mongo.collections": "Collections",
"mongo.objects": "Objects",
"mongo.avg-object-size": "Avg. Object Size",
"mongo.data-size": "Data Size",
"mongo.storage-size": "Storage Size",
"mongo.index-size": "Index Size",
"mongo.file-size": "File Size",
"mongo.resident-memory": "Resident Memory",
"mongo.virtual-memory": "الذاكرة الإفتراضية",
"mongo.mapped-memory": "Mapped Memory",
"mongo.bytes-in": "Bytes In",
"mongo.bytes-out": "Bytes Out",
"mongo.num-requests": "Number of Requests",
"mongo.raw-info": "MongoDB Raw Info",
"mongo.unauthorized": "NodeBB was unable to query the MongoDB database for relevant statistics. Please ensure that the user in use by NodeBB contains the &quot;clusterMonitor&quot; role for the &quot;admin&quot; database.",
"redis": "Redis",
"redis.version": "Redis Version",
"redis.keys": "Keys",
"redis.expires": "Expires",
"redis.avg-ttl": "Average TTL",
"redis.connected-clients": "Connected Clients",
"redis.connected-slaves": "Connected Slaves",
"redis.blocked-clients": "Blocked Clients",
"redis.used-memory": "الذاكرة المستخدمة",
"redis.memory-frag-ratio": "Memory Fragmentation Ratio",
"redis.total-connections-recieved": "إجمالي الاتصالات المستلمة",
"redis.total-commands-processed": "إجمالي الأوامر التي تمت معالجتها",
"redis.iops": "Instantaneous Ops. Per Second",
"redis.iinput": "Instantaneous Input Per Second",
"redis.ioutput": "Instantaneous Output Per Second",
"redis.total-input": "Total Input",
"redis.total-output": "Total Ouput",
"redis.keyspace-hits": "Keyspace Hits",
"redis.keyspace-misses": "Keyspace Misses",
"redis.raw-info": "Redis Raw Info",
"postgres": "Postgres",
"postgres.version": "PostgreSQL Version",
"postgres.raw-info": "Postgres Raw Info"
}

View File

@@ -1,14 +0,0 @@
{
"figure-x": "شكل %1",
"error-events-per-day": "<code>%1</code> حدث كل يوم ",
"error.404": "404 لم يتم العثور",
"error.503": "503 الخدمة غير متوفرة",
"manage-error-log": "إدارة سجل الأخطاء",
"export-error-log": "تصدير سجل الأخطاء (CSV)",
"clear-error-log": "محو سجل الأخطاء",
"route": "مسار",
"count": "عدد",
"no-routes-not-found": "لا توجد اخطاء 404!",
"clear404-confirm": "هل تريد بالتأكيد محو سجلات الخطأ 404؟",
"clear404-success": "أخطاء \"404 لم يتم العثور\" تم محوها بنجاح"
}

View File

@@ -1,13 +0,0 @@
{
"events": "أحداث",
"no-events": "لا توجد أحداث",
"control-panel": "لوحة تحكم الأحداث",
"delete-events": "حذف الاحداث",
"confirm-delete-all-events": "Are you sure you want to delete all logged events?",
"filters": "تصفية",
"filters-apply": "تطبيق التصفية",
"filter-type": "نوع الحدث",
"filter-start": "تاريخ البدء",
"filter-end": "تاريخ الانتهاء",
"filter-perPage": "لكل صفحة"
}

View File

@@ -1,7 +0,0 @@
{
"logs": "السجلات",
"control-panel": "لوحة تحكم السجلات",
"reload": "إعادة تحميل السجلات",
"clear": "محو السجلات",
"clear-success": "تم محو السجلات!"
}

View File

@@ -1,16 +0,0 @@
{
"custom-css": "Custom CSS/LESS",
"custom-css.description": "Enter your own CSS/LESS declarations here, which will be applied after all other styles.",
"custom-css.enable": "Enable Custom CSS/LESS",
"custom-js": "Javascript مخصصة",
"custom-js.description": "أدخل Javascript الخاص بك هنا. سيتم تنفيذها بعد تحميل الصفحة بالكامل.",
"custom-js.enable": "تفعيل Javascript المخصصة",
"custom-header": "ترويسة مخصصة",
"custom-header.description": "Enter custom HTML here (ex. Meta Tags, etc.), which will be appended to the <code>&lt;head&gt;</code> section of your forum's markup. Script tags are allowed, but are discouraged, as the <a href=\"#custom-js\" data-toggle=\"tab\">Custom Javascript</a> tab is available.",
"custom-header.enable": "تفعيل الترويسة المخصصة",
"custom-css.livereload": "تفعيل إعادة التحميل المباشرة",
"custom-css.livereload.description": "فعل هذا الخيار لإجبار جميع الجلسات في الأجهزة التي قمت بتسجيل الدخول فيها بحسابك على التحديث عند النقر على زر الحفظ"
}

View File

@@ -1,9 +0,0 @@
{
"loading": "جاري تحميل السمات...",
"homepage": "الصفحة الرئيسية",
"select-skin": "إختيار السمة",
"current-skin": "السمة الحالية",
"skin-updated": "تم تحديث السمة",
"applied-success": "تم تطبيق السمة %1 بنجاح",
"revert-success": "تم إستعادة الألوان الاساسية للسمة"
}

View File

@@ -1,11 +0,0 @@
{
"checking-for-installed": "جاري التحقق من القوالب المثبتة...",
"homepage": "الصفحة الرئيسية",
"select-theme": "إختيار القالب",
"current-theme": "القالب المستخدم حالياً",
"no-themes": "لم يتم العثور على قوالب مثبتة",
"revert-confirm": "هل أنت متأكد من أنك ترغب في استعادة قااب NodeBB الافتراضي؟",
"theme-changed": "تم تغيير القالب",
"revert-success": "لقد قمت بنجاح بإستعادة القالب الأساسي لـNodeBB",
"restart-to-activate": "Please rebuild and restart your NodeBB to fully activate this theme."
}

View File

@@ -1,90 +0,0 @@
{
"forum-traffic": "Forum Traffic",
"page-views": "مشاهدات الصفحات",
"unique-visitors": "زائرين فريدين",
"logins": "Logins",
"new-users": "New Users",
"posts": "مشاركات",
"topics": "مواضيع",
"page-views-seven": "آخر 7 ايام",
"page-views-thirty": "آخر 30 يوماً",
"page-views-last-day": "آخر 24 ساعة",
"page-views-custom": "مدة زمنية مخصصة",
"page-views-custom-start": "بداية المدة",
"page-views-custom-end": "نهاية المده",
"page-views-custom-help": "أدخل نطاقا زمنيا لمرات مشاهدة الصفحات التي ترغب في عرضها. إذا لم يظهر منتقي التاريخ، فإن التنسيق المقبول هو <code>YYYY-MM-DD</code>",
"page-views-custom-error": "الرجاء إدخال نطاق تاريخ صالح بالتنسيق <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": "كل الوقت",
"updates": "تحديثات",
"running-version": "المنتدى يعمل حاليا على <strong>NodeBB الإصدار<span id=\"version\">%1</span></strong>.",
"keep-updated": "تأكد دائما من أن NodeBB يعمل على احدث إصدار للحصول على أحدث التصحيحات الأمنية وإصلاحات الأخطاء.",
"up-to-date": "<p>المنتدى <strong>يعمل على أحدث إصدار</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>",
"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>هذه نسخة <strong>ماقبل الإصدار</strong> من NodeBB. قد تحدث أخطاء غير مقصودة. <i class=\"fa fa-exclamation-triangle\"></i></p>",
"fallback-emailer-not-found": "Fallback emailer not found!",
"running-in-development": "المنتدى قيد التشغيل في وضع \"المطورين\". وقد تكون هناك ثغرات أمنية مفتوحة؛ من فضلك تواصل مع مسؤول نظامك.",
"latest-lookup-failed": "<p>Failed to look up latest available version of NodeBB</p>",
"notices": "إشعارات",
"restart-not-required": "إعادة التشغيل غير مطلوب",
"restart-required": "إعادة التشغيل مطلوبة",
"search-plugin-installed": "إضافة البحث منصبة",
"search-plugin-not-installed": "إضافة البحث غير منصبة",
"search-plugin-tooltip": "نصب إضافة البحث من صفحة الإضافات البرمجية لتنشيط وظيفة البحث",
"control-panel": "التحكم بالنظام",
"rebuild-and-restart": "Rebuild &amp; Restart",
"restart": "Restart",
"restart-warning": "Rebuilding or Restarting your NodeBB will drop all existing connections for a few seconds.",
"restart-disabled": "Rebuilding and Restarting your NodeBB has been disabled as you do not seem to be running it via the appropriate daemon.",
"maintenance-mode": "وضع الصيانة",
"maintenance-mode-title": "انقر هنا لإعداد وضع الصيانة لـNodeBB",
"realtime-chart-updates": "التحديث الفوري للرسم البياني",
"active-users": "المستخدمين النشطين",
"active-users.users": "الأعضاء",
"active-users.guests": "الزوار",
"active-users.total": "المجموع",
"active-users.connections": "Connections",
"guest-registered-users": "Guest vs Registered Users",
"guest": "Guest",
"registered": "مسجل",
"user-presence": "تواجد المستخدمين",
"on-categories": "في قائمة الأقسام",
"reading-posts": "قراءة المشاركات",
"browsing-topics": "تصفح المواضيع",
"recent": "الأخيرة",
"unread": "غير مقروء",
"high-presence-topics": "مواضيع ذات حضور قوي",
"popular-searches": "Popular Searches",
"graphs.page-views": "مشاهدات الصفحة",
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
"graphs.unique-visitors": "زوار فريدين",
"graphs.registered-users": "مستخدمين مسجلين",
"graphs.guest-users": "Guest Users",
"last-restarted-by": "Last restarted by",
"no-users-browsing": "No users browsing",
"back-to-dashboard": "Back to Dashboard",
"details.no-users": "No users have joined within the selected timeframe",
"details.no-topics": "No topics have been posted within the selected timeframe",
"details.no-searches": "No searches have been made yet",
"details.no-logins": "No logins have been recorded within the selected timeframe",
"details.logins-static": "NodeBB only saves session data for %1 days, and so this table below will only show the most recently active sessions",
"details.logins-login-time": "Login Time"
}

View File

@@ -1,25 +0,0 @@
{
"you-are-on": "You are on <strong>%1:%2</strong>",
"ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!",
"host": "host",
"primary": "primary / run jobs",
"pid": "pid",
"nodejs": "nodejs",
"online": "online",
"git": "git",
"process-memory": "process memory",
"system-memory": "system memory",
"used-memory-process": "Used memory by process",
"used-memory-os": "Used system memory",
"total-memory-os": "Total system memory",
"load": "system load",
"cpu-usage": "cpu usage",
"uptime": "uptime",
"registered": "Registered",
"sockets": "Sockets",
"guests": "Guests",
"info": "Info"
}

View File

@@ -1,12 +0,0 @@
{
"logger-settings": "Logger Settings",
"description": "By enabling the check boxes, you will receive logs to your terminal. If you specify a path, logs will then be saved to a file instead. HTTP logging is useful for collecting statistics about who, when, and what people access on your forum. In addition to logging HTTP requests, we can also log socket.io events. Socket.io logging, in combination with redis-cli monitor, can be very helpful for learning NodeBB's internals.",
"explanation": "Simply check/uncheck the logging settings to enable or disable logging on the fly. No restart needed.",
"enable-http": "Enable HTTP logging",
"enable-socket": "Enable socket.io event logging",
"file-path": "Path to log file",
"file-path-placeholder": "/path/to/log/file.log ::: leave blank to log to your terminal",
"control-panel": "Logger Control Panel",
"update-settings": "Update Logger Settings"
}

View File

@@ -1,57 +0,0 @@
{
"trending": "Trending",
"installed": "منصبة",
"active": "مفعلة",
"inactive": "معطلة",
"out-of-date": "غير محدثة",
"none-found": "لم يتم العثور على إضافات",
"none-active": "لا توجد إضافات مفعلة",
"find-plugins": "العثور على الإضافات",
"plugin-search": "البحث عن الإضافات",
"plugin-search-placeholder": "جاري البحث عن الإضافات...",
"submit-anonymous-usage": "Submit anonymous plugin usage data.",
"reorder-plugins": "إعادة ترتيب الإضافات",
"order-active": "ترتيب الإضافات المفعلة",
"dev-interested": "هل انته مهتم ببرمجة إضافات لـNodeBB؟",
"docs-info": "دليل كامل حول برمجة الإضافات بالإمكان العثور عليه في <a target=\"_blank\" href=\"https://docs.nodebb.org/development/plugins/\"> NodeBB Docs Portal</a>.",
"order.description": "بعض الإضافات تعمل بشكل مثالي عندما يتم تفعيلها قبل أو بعد الإضافات الأخرى.",
"order.explanation": "يتم تحميل الإضافات حسب الترتيب المحدد هنا، من الأعلى إلى الأسفل",
"plugin-item.themes": "القوالب",
"plugin-item.deactivate": "تعطيل",
"plugin-item.activate": "تفعيل",
"plugin-item.install": "تنصيب",
"plugin-item.uninstall": "إلغاء التنصيب",
"plugin-item.settings": "الإعدادات",
"plugin-item.installed": "المنصبة",
"plugin-item.latest": "الأحدث",
"plugin-item.upgrade": "ترقية",
"plugin-item.more-info": "لمزيد من المعلومات:",
"plugin-item.unknown": "غير معروف",
"plugin-item.unknown-explanation": "تعذر تحديد حالة هذه الإضافة، ربما بسبب خطأ في الإعدادات.",
"plugin-item.compatible": "This plugin works on NodeBB %1",
"plugin-item.not-compatible": "This plugin has no compatibility data, make sure it works before installing on your production environment.",
"alert.enabled": "الإضافة مفعلة",
"alert.disabled": "الإضافة معطلة",
"alert.upgraded": "الإضافة مرقاة",
"alert.installed": "الإضافة منصبة",
"alert.uninstalled": "تم إلغاء تنصيب الإضافة",
"alert.activate-success": "Please rebuild and restart your NodeBB to fully activate this plugin",
"alert.deactivate-success": "تم تعطيل الإضافة بنجاح",
"alert.upgrade-success": "Please rebuild and restart your NodeBB to fully upgrade this plugin.",
"alert.install-success": "تم تثبيت الإضافة بنجاح، يرجى تفعيلها.",
"alert.uninstall-success": "تم تعطيل الإضافة وإلغاء تنصيبها بنجاح.",
"alert.suggest-error": "<p>NodeBB could not reach the package manager, proceed with installation of latest version?</p><div class=\"alert alert-danger\"><strong>Server returned (%1)</strong>: %2</div>",
"alert.package-manager-unreachable": "<p>NodeBB could not reach the package manager, an upgrade is not suggested at this time.</p>",
"alert.incompatible": "<p>Your version of NodeBB (v%1) is only cleared to upgrade to v%2 of this plugin. Please update your NodeBB if you wish to install a newer version of this plugin.</p>",
"alert.possibly-incompatible": "<div class=\"alert alert-warning\"><p><strong>No Compatibility Information Found</strong></p><p>This plugin did not specify a specific version for installation given your NodeBB version. Full compatibility cannot be guaranteed, and may cause your NodeBB to no longer start properly.</p></div><p>In the event that NodeBB cannot boot properly:</p><pre><code>$ ./nodebb reset plugin=\"%1\"</code></pre><p>Continue installation of latest version of this plugin?</p>",
"alert.reorder": "Plugins Re-ordered",
"alert.reorder-success": "Please rebuild and restart your NodeBB to fully complete the process.",
"license.title": "معلومات ترخيص الإضافة",
"license.intro": "The plugin <strong>%1</strong> is licensed under the %2. Please read and understand the license terms prior to activating this plugin.",
"license.cta": "هل ترغب بالاستمرار في تفعيل هذه الإضافة؟"
}

View File

@@ -1,15 +0,0 @@
{
"rewards": "Rewards",
"condition-if-users": "If User's",
"condition-is": "Is:",
"condition-then": "Then:",
"max-claims": "Amount of times reward is claimable",
"zero-infinite": "Enter 0 for infinite",
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
"alert.save-success": "Successfully saved rewards"
}

View File

@@ -1,30 +0,0 @@
{
"available": "Available Widgets",
"explanation": "Select a widget from the dropdown menu and then drag and drop it into a template's widget area on the left.",
"none-installed": "No widgets found! Activate the widget essentials plugin in the <a href=\"%1\">plugins</a> control panel.",
"clone-from": "Clone widgets from",
"containers.available": "Available Containers",
"containers.explanation": "Drag and drop on top of any active widget",
"containers.none": "None",
"container.well": "Well",
"container.jumbotron": "Jumbotron",
"container.panel": "Panel",
"container.panel-header": "Panel Header",
"container.panel-body": "Panel Body",
"container.alert": "Alert",
"alert.confirm-delete": "Are you sure you wish to delete this widget?",
"alert.updated": "Widgets Updated",
"alert.update-success": "Successfully updated widgets",
"alert.clone-success": "Successfully cloned widgets",
"error.select-clone": "Please select a page to clone from",
"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"
}

View File

@@ -1,10 +0,0 @@
{
"administrators": "Administrators",
"global-moderators": "Global Moderators",
"no-global-moderators": "No Global Moderators",
"moderators-of-category": "%1 Moderators",
"no-moderators": "No Moderators",
"add-administrator": "Add Administrator",
"add-global-moderator": "Add Global Moderator",
"add-moderator": "Add Moderator"
}

View File

@@ -1,92 +0,0 @@
{
"settings": "اعدادات القسم",
"privileges": "الصلاحيات",
"name": "Category Name",
"description": "Category Description",
"bg-color": "Background Colour",
"text-color": "Text Colour",
"bg-image-size": "Background Image Size",
"custom-class": "Custom Class",
"num-recent-replies": "# of Recent Replies",
"ext-link": "External Link",
"subcategories-per-page": "Subcategories per page",
"is-section": "Treat this category as a section",
"post-queue": "Post queue",
"tag-whitelist": "Tag Whitelist",
"upload-image": "Upload Image",
"delete-image": "Remove",
"category-image": "Category Image",
"parent-category": "Parent Category",
"optional-parent-category": "(Optional) Parent Category",
"top-level": "Top Level",
"parent-category-none": "(None)",
"copy-parent": "Copy Parent",
"copy-settings": "Copy Settings From",
"optional-clone-settings": "(Optional) Clone Settings From Category",
"clone-children": "Clone Children Categories And Settings",
"purge": "Purge Category",
"enable": "Enable",
"disable": "Disable",
"edit": "Edit",
"analytics": "Analytics",
"view-category": "View category",
"set-order": "Set order",
"set-order-help": "Setting the order of the category will move this category to that order and update the order of other categories as necessary. Minimum order is 1 which puts the category at the top.",
"select-category": "Select Category",
"set-parent-category": "Set Parent Category",
"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.category-selector": "Configuring privileges for ",
"privileges.warning": "<strong>Note</strong>: Privilege settings take effect immediately. It is not necessary to save the category after adjusting these settings.",
"privileges.section-viewing": "Viewing Privileges",
"privileges.section-posting": "Posting Privileges",
"privileges.section-moderation": "Moderation Privileges",
"privileges.section-other": "Other",
"privileges.section-user": "User",
"privileges.search-user": "Add User",
"privileges.no-users": "No user-specific privileges in this category.",
"privileges.section-group": "Group",
"privileges.group-private": "This group is private",
"privileges.inheritance-exception": "This group does not inherit privileges from registered-users group",
"privileges.banned-user-inheritance": "Banned users inherit privileges from banned-users group",
"privileges.search-group": "Add Group",
"privileges.copy-to-children": "Copy to Children",
"privileges.copy-from-category": "Copy from Category",
"privileges.copy-privileges-to-all-categories": "Copy to All Categories",
"privileges.copy-group-privileges-to-children": "Copy this group's privileges to the children of this category.",
"privileges.copy-group-privileges-to-all-categories": "Copy this group's privileges to all categories.",
"privileges.copy-group-privileges-from": "Copy this group's privileges from another category.",
"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.copy-success": "Privileges copied!",
"analytics.back": "Back to Categories List",
"analytics.title": "Analytics for \"%1\" category",
"analytics.pageviews-hourly": "<strong>Figure 1</strong> &ndash; Hourly page views for this category</small>",
"analytics.pageviews-daily": "<strong>Figure 2</strong> &ndash; Daily page views for this category</small>",
"analytics.topics-daily": "<strong>Figure 3</strong> &ndash; Daily topics created in this category</small>",
"analytics.posts-daily": "<strong>Figure 4</strong> &ndash; Daily posts made in this category</small>",
"alert.created": "Created",
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
"alert.confirm-purge": "<p class=\"lead\">Do you really want to purge this category \"%1\"?</p><h5><strong class=\"text-danger\">Warning!</strong> All topics and posts in this category will be purged!</h5> <p class=\"help-block\">Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category <em>temporarily</em>, you'll want to \"disable\" the category instead.</p>",
"alert.purge-success": "Category purged!",
"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.upload-image": "Upload category image",
"alert.find-user": "Find a User",
"alert.user-search": "Search for a user here...",
"alert.find-group": "Find a Group",
"alert.group-search": "Search for a group here...",
"alert.not-enough-whitelisted-tags": "Whitelisted tags are less than minimum tags, you need to create more whitelisted tags!",
"collapse-all": "Collapse All",
"expand-all": "Expand All",
"disable-on-create": "Disable on create",
"no-matches": "No matches"
}

View File

@@ -1,22 +0,0 @@
{
"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>.",
"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: &quot;<strong>%1</strong>&quot;",
"resend": "Resend Digest",
"resend-all-confirm": "Are you sure you wish to manually execute this digest run?",
"resent-single": "Manual digest resend completed",
"resent-day": "Daily digest resent",
"resent-week": "Weekly digest resent",
"resent-biweek": "Bi-Weekly digest resent",
"resent-month": "Monthly digest resent",
"null": "<em>Never</em>",
"manual-run": "Manual digest run:",
"no-delivery-data": "No delivery data found"
}

View File

@@ -1,44 +0,0 @@
{
"name": "اسم المجموعة",
"badge": "Badge",
"properties": "Properties",
"description": "Group Description",
"member-count": "Member Count",
"system": "System",
"hidden": "Hidden",
"private": "Private",
"edit": "Edit",
"delete": "Delete",
"privileges": "Privileges",
"download-csv": "CSV",
"search-placeholder": "Search",
"create": "Create Group",
"description-placeholder": "A short description about your group",
"create-button": "Create",
"alerts.create-failure": "<strong>Uh-Oh</strong><p>There was a problem creating your group. Please try again later!</p>",
"alerts.confirm-delete": "Are you sure you wish to delete this group?",
"edit.name": "Name",
"edit.description": "Description",
"edit.user-title": "Title of Members",
"edit.icon": "Group Icon",
"edit.label-color": "Group Label Color",
"edit.text-color": "Group Text Color",
"edit.show-badge": "Show Badge",
"edit.private-details": "If enabled, joining of groups requires approval from a group owner.",
"edit.private-override": "Warning: Private groups is disabled at system level, which overrides this option.",
"edit.disable-join": "Disable join requests",
"edit.disable-leave": "Disallow users from leaving the group",
"edit.hidden": "Hidden",
"edit.hidden-details": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
"edit.add-user": "Add User to Group",
"edit.add-user-search": "Search Users",
"edit.members": "Member List",
"control-panel": "Groups Control Panel",
"revert": "Revert",
"edit.no-users-found": "No Users Found",
"edit.confirm-remove-user": "Are you sure you want to remove this user?",
"edit.save-success": "Changes saved!"
}

View File

@@ -1,63 +0,0 @@
{
"global": "Global",
"admin": "Admin",
"group-privileges": "Group Privileges",
"user-privileges": "User Privileges",
"edit-privileges": "Edit Privileges",
"select-clear-all": "Select/Clear All",
"chat": "Chat",
"upload-images": "Upload Images",
"upload-files": "Upload Files",
"signature": "Signature",
"ban": "Ban",
"invite": "Invite",
"search-content": "Search Content",
"search-users": "Search Users",
"search-tags": "Search Tags",
"view-users": "View Users",
"view-tags": "View Tags",
"view-groups": "View Groups",
"allow-local-login": "Local Login",
"allow-group-creation": "Group Create",
"view-users-info": "View Users Info",
"find-category": "Find Category",
"access-category": "Access Category",
"access-topics": "Access Topics",
"create-topics": "Create Topics",
"reply-to-topics": "Reply to Topics",
"schedule-topics": "Schedule Topics",
"tag-topics": "Tag Topics",
"edit-posts": "Edit Posts",
"view-edit-history": "View Edit History",
"delete-posts": "Delete Posts",
"view_deleted": "View Deleted Posts",
"upvote-posts": "Upvote Posts",
"downvote-posts": "Downvote Posts",
"delete-topics": "Delete Topics",
"purge": "Purge",
"moderate": "Moderate",
"admin-dashboard": "Dashboard",
"admin-categories": "Categories",
"admin-privileges": "Privileges",
"admin-users": "Users",
"admin-admins-mods": "Admins &amp; Mods",
"admin-groups": "Groups",
"admin-tags": "Tags",
"admin-settings": "Settings",
"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-admins-mods": "<strong>Are you sure you wish to grant the &quot;Admins &amp; Mods&quot; privilege to this user/group?</strong> Users with this privilege are able to promote and demote other users into privileged positions, <em>including super administrator</em>",
"alert.confirm-save": "Please confirm your intention to save these privileges",
"alert.saved": "Privilege changes saved and applied",
"alert.confirm-discard": "Are you sure you wish to discard your privilege changes?",
"alert.discarded": "Privilege changes discarded",
"alert.confirm-copyToAll": "Are you sure you wish to apply this set of <strong>%1</strong> to <strong>all categories</strong>?",
"alert.confirm-copyToAllGroup": "Are you sure you wish to apply this group's set of <strong>%1</strong> to <strong>all categories</strong>?",
"alert.confirm-copyToChildren": "Are you sure you wish to apply this set of <strong>%1</strong> to <strong>all descendant (child) categories</strong>?",
"alert.confirm-copyToChildrenGroup": "Are you sure you wish to apply this group's set of <strong>%1</strong> to <strong>all descendant (child) categories</strong>?",
"alert.no-undo": "<em>This action cannot be undone.</em>",
"alert.admin-warning": "Administrators implicitly get all privileges",
"alert.copyPrivilegesFrom-title": "Select a category to copy from",
"alert.copyPrivilegesFrom-warning": "This will copy <strong>%1</strong> from the selected category.",
"alert.copyPrivilegesFromGroup-warning": "This will copy this group's set of <strong>%1</strong> from the selected category."
}

View File

@@ -1,20 +0,0 @@
{
"queue": "Queue",
"description": "There are no users in the registration queue. <br> To enable this feature, go to <a href=\"%1\">Settings &rarr; User &rarr; User Registration</a> and set <strong>Registration Type</strong> to \"Admin Approval\".",
"list.name": "Name",
"list.email": "Email",
"list.ip": "IP",
"list.time": "Time",
"list.username-spam": "Frequency: %1 Appears: %2 Confidence: %3",
"list.email-spam": "Frequency: %1 Appears: %2",
"list.ip-spam": "Frequency: %1 Appears: %2",
"invitations": "Invitations",
"invitations.description": "Below is a complete list of invitations sent. Use ctrl-f to search through the list by email or username. <br><br>The username will be displayed to the right of the emails for users who have redeemed their invitations.",
"invitations.inviter-username": "Inviter Username",
"invitations.invitee-email": "Invitee Email",
"invitations.invitee-username": "Invitee Username (if registered)",
"invitations.confirm-delete": "Are you sure you wish to delete this invitation?"
}

View File

@@ -1,18 +0,0 @@
{
"none": "Your forum does not have any topics with tags yet.",
"bg-color": "Background Colour",
"text-color": "Text Colour",
"description": "Select tags by clicking or dragging, use <code>CTRL</code> to select multiple tags.",
"create": "Create Tag",
"modify": "Modify Tags",
"rename": "Rename Tags",
"delete": "Delete Selected Tags",
"search": "Search for tags...",
"settings": "Tags Settings",
"name": "Tag Name",
"alerts.editing": "Editing tag(s)",
"alerts.confirm-delete": "Do you want to delete the selected tags?",
"alerts.update-success": "Tag Updated!",
"reset-colors": "Reset colors"
}

View File

@@ -1,11 +0,0 @@
{
"upload-file": "Upload File",
"filename": "Filename",
"usage": "Post Usage",
"orphaned": "Orphaned",
"size/filecount": "Size / Filecount",
"confirm-delete": "Do you really want to delete this file?",
"filecount": "%1 files",
"new-folder": "New Folder",
"name-new-folder": "Enter a name for new the folder"
}

View File

@@ -1,110 +0,0 @@
{
"users": "المستخدمين",
"edit": "Actions",
"make-admin": "Make Admin",
"remove-admin": "Remove Admin",
"validate-email": "Validate Email",
"send-validation-email": "Send Validation Email",
"password-reset-email": "Send Password Reset Email",
"force-password-reset": "Force Password Reset & Log User Out",
"ban": "Ban User(s)",
"temp-ban": "Ban User(s) Temporarily",
"unban": "Unban User(s)",
"reset-lockout": "Reset Lockout",
"reset-flags": "Reset Flags",
"delete": "Delete <strong>User(s)</strong>",
"delete-content": "Delete User(s) <strong>Content</strong>",
"purge": "Delete <strong>User(s)</strong> and <strong>Content</strong>",
"download-csv": "Download CSV",
"manage-groups": "Manage Groups",
"add-group": "Add Group",
"invite": "Invite",
"new": "New User",
"filter-by": "Filter by",
"pills.unvalidated": "Not Validated",
"pills.validated": "Validated",
"pills.banned": "Banned",
"50-per-page": "50 per page",
"100-per-page": "100 per page",
"250-per-page": "250 per page",
"500-per-page": "500 per page",
"search.uid": "By User ID",
"search.uid-placeholder": "Enter a user ID to search",
"search.username": "By User Name",
"search.username-placeholder": "Enter a username to search",
"search.email": "By Email",
"search.email-placeholder": "Enter a email to search",
"search.ip": "By IP Address",
"search.ip-placeholder": "Enter an IP Address to search",
"search.not-found": "User not found!",
"inactive.3-months": "3 months",
"inactive.6-months": "6 months",
"inactive.12-months": "12 months",
"users.uid": "uid",
"users.username": "username",
"users.email": "email",
"users.no-email": "(no email)",
"users.ip": "IP",
"users.postcount": "postcount",
"users.reputation": "reputation",
"users.flags": "flags",
"users.joined": "joined",
"users.last-online": "last online",
"users.banned": "banned",
"create.username": "User Name",
"create.email": "Email",
"create.email-placeholder": "Email of this user",
"create.password": "Password",
"create.password-confirm": "Confirm Password",
"temp-ban.length": "Ban Length",
"temp-ban.reason": "Reason <span class=\"text-muted\">(Optional)</span>",
"temp-ban.hours": "Hours",
"temp-ban.days": "Days",
"temp-ban.explanation": "Enter the length of time for the ban. Note that a time of 0 will be a considered a permanent ban.",
"alerts.confirm-ban": "Do you really want to ban this user <strong>permanently</strong>?",
"alerts.confirm-ban-multi": "Do you really want to ban these users <strong>permanently</strong>?",
"alerts.ban-success": "User(s) banned!",
"alerts.button-ban-x": "Ban %1 user(s)",
"alerts.unban-success": "User(s) unbanned!",
"alerts.lockout-reset-success": "Lockout(s) reset!",
"alerts.flag-reset-success": "Flags(s) reset!",
"alerts.no-remove-yourself-admin": "You can't remove yourself as Administrator!",
"alerts.make-admin-success": "User is now administrator.",
"alerts.confirm-remove-admin": "Do you really want to remove this administrator?",
"alerts.remove-admin-success": "User is no longer administrator.",
"alerts.make-global-mod-success": "User is now global moderator.",
"alerts.confirm-remove-global-mod": "Do you really want to remove this global moderator?",
"alerts.remove-global-mod-success": "User is no longer global moderator.",
"alerts.make-moderator-success": "User is now moderator.",
"alerts.confirm-remove-moderator": "Do you really want to remove this moderator?",
"alerts.remove-moderator-success": "User is no longer moderator.",
"alerts.confirm-validate-email": "Do you want to validate email(s) of these user(s)?",
"alerts.confirm-force-password-reset": "Are you sure you want to force the password reset and log out these user(s)?",
"alerts.validate-email-success": "Emails validated",
"alerts.validate-force-password-reset-success": "User(s) passwords have been reset and their existing sessions have been revoked.",
"alerts.password-reset-confirm": "Do you want to send password reset email(s) to these user(s)?",
"alerts.confirm-delete": "<strong>Warning!</strong><p>Do you really want to delete <strong>user(s)</strong>?</p><p>This action is not reversible! Only the user account will be deleted, their posts and topics will remain.</p>",
"alerts.delete-success": "User(s) Deleted!",
"alerts.confirm-delete-content": "<strong>Warning!</strong><p>Do you really want to delete these user(s) <strong>content</strong>?</p><p>This action is not reversible! The users' accounts will remain, but their posts and topics will be deleted.</p>",
"alerts.delete-content-success": "User(s) Content Deleted!",
"alerts.confirm-purge": "<strong>Warning!</strong><p>Do you really want to delete <strong>user(s) and their content</strong>?</p><p>This action is not reversible! All user data and content will be erased!</p>",
"alerts.create": "Create User",
"alerts.button-create": "Create",
"alerts.button-cancel": "Cancel",
"alerts.error-passwords-different": "Passwords must match!",
"alerts.error-x": "<strong>Error</strong><p>%1</p>",
"alerts.create-success": "User created!",
"alerts.prompt-email": "Emails: ",
"alerts.email-sent-to": "An invitation email has been sent to %1",
"alerts.x-users-found": "%1 user(s) found, (%2 seconds)",
"export-users-started": "Exporting users as csv, this might take a while. You will receive a notification when it is complete.",
"export-users-completed": "Users exported as csv, click here to download."
}

View File

@@ -1,89 +0,0 @@
{
"section-dashboard": "Dashboards",
"dashboard/overview": "Overview",
"dashboard/logins": "Logins",
"dashboard/users": "Users",
"dashboard/topics": "Topics",
"dashboard/searches": "Searches",
"section-general": "عام",
"section-manage": "إدارة",
"manage/categories": "الأقسام",
"manage/privileges": "Privileges",
"manage/tags": "الكلمات المفتاحية",
"manage/users": "الأعضاء",
"manage/admins-mods": "Admins & Mods",
"manage/registration": "قائمة انتظار التسجيل",
"manage/post-queue": "قائمة انتظار المشاركة",
"manage/groups": "المجموعات",
"manage/ip-blacklist": "قائمة حظر عناوين IP",
"manage/uploads": "Uploads",
"manage/digest": "Digests",
"section-settings": "إعدادات",
"settings/general": "عامة",
"settings/homepage": "Home Page",
"settings/navigation": "Navigation",
"settings/reputation": "Reputation & Flags",
"settings/email": "البريد الإلكتروني",
"settings/user": "Users",
"settings/group": "Groups",
"settings/guest": "الزوار",
"settings/uploads": "الرفع",
"settings/languages": "Languages",
"settings/post": "Posts",
"settings/chat": "Chats",
"settings/pagination": "ترقيم الصفحات",
"settings/tags": "الكلمات المفتاحية",
"settings/notifications": "التنبيهات",
"settings/api": "API Access",
"settings/sounds": "Sounds",
"settings/social": "Social",
"settings/cookies": "Cookies",
"settings/web-crawler": "Web Crawler",
"settings/sockets": "Sockets",
"settings/advanced": "Advanced",
"settings.page-title": "%1 Settings",
"section-appearance": "Appearance",
"appearance/themes": "Themes",
"appearance/skins": "Skins",
"appearance/customise": "Custom Content (HTML/JS/CSS)",
"section-extend": "Extend",
"extend/plugins": "Plugins",
"extend/widgets": "Widgets",
"extend/rewards": "Rewards",
"section-social-auth": "Social Authentication",
"section-plugins": "Plugins",
"extend/plugins.install": "Install Plugins",
"section-advanced": "Advanced",
"advanced/database": "Database",
"advanced/events": "Events",
"advanced/hooks": "Hooks",
"advanced/logs": "Logs",
"advanced/errors": "Errors",
"advanced/cache": "Cache",
"development/logger": "Logger",
"development/info": "Info",
"rebuild-and-restart-forum": "Rebuild & Restart Forum",
"restart-forum": "Restart Forum",
"logout": "Log out",
"view-forum": "View Forum",
"search.placeholder": "Press &quot;/&quot; to search for settings",
"search.no-results": "No results...",
"search.search-forum": "Search the forum for <strong></strong>",
"search.keep-typing": "Type more to see results...",
"search.start-typing": "Start typing to see results...",
"connection-lost": "Connection to %1 has been lost, attempting to reconnect...",
"alerts.version": "Running <strong>NodeBB v%1</strong>",
"alerts.upgrade": "Upgrade to v%1"
}

View File

@@ -1,46 +0,0 @@
{
"maintenance-mode": "Maintenance Mode",
"maintenance-mode.help": "When the forum is in maintenance mode, all requests will be redirected to a static holding page. Administrators are exempt from this redirection, and are able to access the site normally.",
"maintenance-mode.status": "Maintenance Mode Status Code",
"maintenance-mode.message": "Maintenance Message",
"headers": "Headers",
"headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame",
"headers.csp-frame-ancestors": "Set Content-Security-Policy frame-ancestors header to Place NodeBB in an iFrame",
"headers.csp-frame-ancestors-help": "'none', 'self'(default) or list of URIs to allow.",
"headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB",
"headers.acao": "Access-Control-Allow-Origin",
"headers.acao-regex": "Access-Control-Allow-Origin Regular Expression",
"headers.acao-help": "To deny access to all sites, leave empty",
"headers.acao-regex-help": "Enter regular expressions here to match dynamic origins. To deny access to all sites, leave empty",
"headers.acac": "Access-Control-Allow-Credentials",
"headers.acam": "Access-Control-Allow-Methods",
"headers.acah": "Access-Control-Allow-Headers",
"headers.coep": "Cross-Origin-Embedder-Policy",
"headers.coep-help": "When enabled (default), will set the header to <code>require-corp</code>",
"headers.corp": "Cross-Origin-Resource-Policy",
"hsts": "Strict Transport Security",
"hsts.enabled": "Enabled HSTS (recommended)",
"hsts.maxAge": "HSTS Max Age",
"hsts.subdomains": "Include subdomains in HSTS header",
"hsts.preload": "Allow preloading of HSTS header",
"hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. <a href=\"%1\">More information <i class=\"fa fa-external-link\"></i></a>",
"traffic-management": "Traffic Management",
"traffic.help": "NodeBB uses a module that automatically denies requests in high-traffic situations. You can tune these settings here, although the defaults are a good starting point.",
"traffic.enable": "Enable Traffic Management",
"traffic.event-lag": "Event Loop Lag Threshold (in milliseconds)",
"traffic.event-lag-help": "Lowering this value decreases wait times for page loads, but will also show the \"excessive load\" message to more users. (Restart required)",
"traffic.lag-check-interval": "Check Interval (in milliseconds)",
"traffic.lag-check-interval-help": "Lowering this value causes NodeBB to become more sensitive to spikes in load, but may also cause the check to become too sensitive. (Restart required)",
"sockets.settings": "WebSocket Settings",
"sockets.max-attempts": "Max Reconnection Attempts",
"sockets.default-placeholder": "Default: %1",
"sockets.delay": "Reconnection Delay",
"analytics.settings": "Analytics Settings",
"analytics.max-cache": "Analytics Cache Max Value",
"analytics.max-cache-help": "On high-traffic installs, the cache could be exhausted continuously if there are more concurrent active users than the Max Cache value. (Restart required)",
"compression.settings": "Compression Settings",
"compression.enable": "Enable Compression",
"compression.help": "This setting enables gzip compression. For a high-traffic website in production, the best way to put compression in place is to implement it at a reverse proxy level. You can enable it here for testing purposes."
}

View File

@@ -1,16 +0,0 @@
{
"tokens": "Tokens",
"settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
"require-https": "Require API usage via HTTPS only",
"require-https-caveat": "<strong>Note</strong>: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is <code>0</code>, it will be considered a <em>master</em> token, which can assume the identity of other users based on the <code>_uid</code> parameter",
"description": "Description",
"no-description": "No description specified.",
"token-on-save": "Token will be generated once form is saved"
}

View File

@@ -1,12 +0,0 @@
{
"chat-settings": "Chat Settings",
"disable": "Disable chat",
"disable-editing": "Disable chat message editing/deletion",
"disable-editing-help": "Administrators and global moderators are exempt from this restriction",
"max-length": "Maximum length of chat messages",
"max-room-size": "Maximum number of users in chat rooms",
"delay": "Time between chat messages in milliseconds",
"notification-delay": "Notification delay for chat messages. (0 for no delay)",
"restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable. (0 disabled)",
"restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable. (0 disabled)"
}

View File

@@ -1,13 +0,0 @@
{
"eu-consent": "EU Consent",
"consent.enabled": "تفعيل",
"consent.message": "Notification message",
"consent.acceptance": "Acceptance message",
"consent.link-text": "Policy Link Text",
"consent.link-url": "Policy Link URL",
"consent.blank-localised-default": "Leave blank to use NodeBB localised defaults",
"settings": "Settings",
"cookie-domain": "Session cookie domain",
"max-user-sessions": "Max active sessions per user",
"blank-default": "Leave blank for default"
}

View File

@@ -1,48 +0,0 @@
{
"email-settings": "Email Settings",
"address": "Email Address",
"address-help": "The following email address refers to the email that the recipient will see in the \"From\" and \"Reply To\" fields.",
"from": "From Name",
"from-help": "The from name to display in the email.",
"smtp-transport": "SMTP Transport",
"smtp-transport.enabled": "Enable SMTP Transport",
"smtp-transport-help": "You can select from a list of well-known services or enter a custom one.",
"smtp-transport.service": "Select a service",
"smtp-transport.service-custom": "Custom Service",
"smtp-transport.service-help": "Select a service name above in order to use the known information about it. Alternatively, select 'Custom Service' and enter the details below.",
"smtp-transport.gmail-warning1": "There have been reports of the Gmail service not working on accounts with heightened security. In those scenarios, you will have to <a href=\"https://www.google.com/settings/security/lesssecureapps\">configure your GMail account to allow less secure apps</a>.",
"smtp-transport.gmail-warning2": "For more information about this workaround, <a href=\"https://nodemailer.com/usage/using-gmail/\">please consult this NodeMailer article on the issue.</a> An alternative would be to utilise a third-party emailer plugin such as SendGrid, Mailgun, etc. <a href=\"../extend/plugins\">Browse available plugins here</a>.",
"smtp-transport.host": "SMTP Host",
"smtp-transport.port": "SMTP Port",
"smtp-transport.security": "Connection security",
"smtp-transport.security-encrypted": "Encrypted",
"smtp-transport.security-starttls": "StartTLS",
"smtp-transport.security-none": "None",
"smtp-transport.username": "Username",
"smtp-transport.username-help": "<b>For the Gmail service,</b> enter the full email address here, especially if you are using a Google Apps managed domain.",
"smtp-transport.password": "Password",
"smtp-transport.pool": "Enable pooled connections",
"smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.",
"template": "Edit Email Template",
"template.select": "Select Email Template",
"template.revert": "Revert to Original",
"testing": "Email Testing",
"testing.select": "Select Email Template",
"testing.send": "Send Test Email",
"testing.send-help": "The test email will be sent to the currently logged in user's email address.",
"subscriptions": "Email Digests",
"subscriptions.disable": "Disable email digests",
"subscriptions.hour": "Digest Hour",
"subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. <code>0</code> for midnight, <code>17</code> for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.<br /> The approximate server time is: <span id=\"serverTime\"></span><br /> The next daily digest is scheduled to be sent <span id=\"nextDigestTime\"></span>",
"notifications.remove-images": "Remove images from email notifications",
"require-email-address": "Require new users to specify an email address",
"require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. <strong>It does not ensure user will enter a real email address, nor even an address they own.</strong>",
"send-validation-email": "Send validation emails when an email is added or changed",
"include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails",
"include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). <strong>Enable this setting at your own risk</strong> &ndash; sending emails to unverified addresses may be a violation of regional anti-spam laws.",
"prompt": "Prompt users to enter or confirm their emails",
"prompt-help": "If a user does not have an email set, or their email is not confirmed, a warning will be shown on screen.",
"sendEmailToBanned": "Send emails to users even if they have been banned"
}

View File

@@ -1,50 +0,0 @@
{
"site-settings": "اعدادات الموقع",
"title": "عنوان الموقع",
"title.short": "عنوان قصير",
"title.short-placeholder": "ان لم تقم بكتابة عنوان مختصر, سيتم استخدام عنوان الموقع الكلي",
"title.url": "Title Link URL",
"title.url-placeholder": "The URL of the site title",
"title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index. <br> Note: This is not the external URL used in emails, etc. That is set by the <code>url</code> property in config.json",
"title.name": "اسم المنتدي",
"title.show-in-header": "Show Site Title in Header",
"browser-title": "عنوان المتصفح",
"browser-title-help": "If no browser title is specified, the site title will be used",
"title-layout": "Title Layout",
"title-layout-help": "Define how the browser title will be structured ie. &#123;pageTitle&#125; | &#123;browserTitle&#125;",
"description.placeholder": "A short description about your community",
"description": "وصف الموقع",
"keywords": "الكلمات الدليله للموقع",
"keywords-placeholder": "Keywords describing your community, comma-separated",
"logo": "شعار الموقع",
"logo.image": "صورة",
"logo.image-placeholder": "Path to a logo to display on forum header",
"logo.upload": "رفع",
"logo.url": "Logo Link URL",
"logo.url-placeholder": "The URL of the site logo",
"logo.url-help": "When the logo is clicked, send users to this address. If left blank, user will be sent to the forum index. <br> Note: This is not the external URL used in emails, etc. That is set by the <code>url</code> property in config.json",
"logo.alt-text": "نص بديل",
"log.alt-text-placeholder": "Alternative text for accessibility",
"favicon": "صورة المفضله",
"favicon.upload": "رفع",
"pwa": "Progressive Web App",
"touch-icon": "Touch Icon",
"touch-icon.upload": "رفع",
"touch-icon.help": "Recommended size and format: 512x512, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.",
"maskable-icon": "Maskable (Homescreen) Icon",
"maskable-icon.help": "Recommended size and format: 512x512, PNG format only. If no maskable icon is specified, NodeBB will fall back to the Touch Icon.",
"outgoing-links": "Outgoing Links",
"outgoing-links.warning-page": "Use Outgoing Links Warning Page",
"search": "Search",
"search-default-in": "Search In",
"search-default-in-quick": "Quick Search In",
"search-default-sort-by": "Sort by",
"outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page",
"site-colors": "Site Color Metadata",
"theme-color": "لون الثيم",
"background-color": "لون الخلفية",
"background-color-help": "Color used for splash screen background when website is installed as a PWA",
"undo-timeout": "Undo Timeout",
"undo-timeout-help": "Some operations such as moving topics will allow for the moderator to undo their action within a certain timeframe. Set to 0 to disable undo completely.",
"topic-tools": "Topic Tools"
}

View File

@@ -1,13 +0,0 @@
{
"general": "General",
"private-groups": "Private Groups",
"private-groups.help": "If enabled, joining of groups requires the approval of the group owner <em>(Default: enabled)</em>",
"private-groups.warning": "<strong>Beware!</strong> If this option is disabled and you have private groups, they automatically become public.",
"allow-multiple-badges": "Allow Multiple Badges",
"allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.",
"max-name-length": "Maximum Group Name Length",
"max-title-length": "Maximum Group Title Length",
"cover-image": "Group Cover Image",
"default-cover": "Default Cover Images",
"default-cover-help": "Add comma-separated default cover images for groups that don't have an uploaded cover image"
}

View File

@@ -1,7 +0,0 @@
{
"settings": "Settings",
"handles.enabled": "Allow guest handles",
"handles.enabled-help": "This option exposes a new field that allows guests to pick a name to associate with each post they make. If disabled, they will simply be called \"Guest\"",
"topic-views.enabled": "Allow guests to increase topic view counts",
"reply-notifications.enabled": "Allow guests to generate reply notifications"
}

Some files were not shown because too many files have changed in this diff Show More