Compare commits

..

72 Commits

Author SHA1 Message Date
NodeBB Misty
fb2f857af2 Incremented version number - v1.4.6 2017-04-19 19:06:05 +00:00
NodeBB Misty
7181bb4e1a Merge commit 'de81a340ec374d7103287b446ce3081bb3a020cc' into v1.x.x 2017-04-19 19:06:05 +00:00
Misty (Bot)
d19e59f1e1 Incremented version number 2017-03-16 18:57:33 +00:00
Misty (Bot)
f5e8a40d79 Merge commit '449352d8996b4c98be67052fbb5b68fdc65cd0f2' into v1.x.x 2017-03-16 18:57:33 +00:00
Julian Lam
4713e0d81a Merge pull request #5514 from manolino/v1.x.x
apple-touch-icon for Web App on IOS
2017-03-09 14:22:40 -05:00
manolino
562b088423 apple-touch-icon for Web App on IOS
route apple-touch-icon not serving touchicon-orig.png. I made needed
correction to file path.
Now works with new /assets and strategy.
2017-03-08 16:32:50 -06:00
Misty (Bot)
92691782e6 Incremented version number 2017-02-24 15:45:36 +00:00
Misty (Bot)
fe582d8a7b Merge commit '048eb8a320a217c5e692c598c86a7661bd5eb2c5' into v1.x.x 2017-02-24 15:45:36 +00:00
Julian Lam
98f2fd62a5 Up markdown 2017-02-02 15:19:31 -05:00
Misty (Bot)
922620fe0a Incremented version number 2017-02-02 17:14:59 +00:00
Misty (Bot)
94b9d77118 Merge commit 'adb66c71b8aff297706056f69b1cf17b972c42af' into v1.x.x 2017-02-02 17:14:58 +00:00
Barış Soner Uşaklı
b1b9174479 backport #5333 2017-01-03 14:11:04 +02:00
Misty (Bot)
d5fb6dac38 Incremented version number 2016-12-22 16:58:30 +00:00
Misty (Bot)
deb9a9189d Merge commit '66d8c29d251231585986f72d30cd9b8d59ed92b3' into v1.x.x 2016-12-22 16:58:29 +00:00
Misty (Bot)
3309f2910d Incremented version number 2016-12-19 19:17:56 +00:00
Misty (Bot)
b600b9305e Merge commit '70f71888f1aaf7d88f58466d29265e43746e2bb0' into v1.x.x 2016-12-19 19:17:56 +00:00
Misty (Bot)
d120ee86cf Incremented version number 2016-12-01 21:28:53 +00:00
Misty (Bot)
f7ceace48f Merge commit 'bcb3903446e9b40be899d9606d26ca490afcc69e' into v1.x.x 2016-12-01 21:28:53 +00:00
psychobunny
fbbd0deaea Revert "testing"
This reverts commit aabe1c3a16.
2016-12-01 16:13:40 -05:00
psychobunny
aabe1c3a16 testing 2016-12-01 16:13:02 -05:00
Barış Soner Uşaklı
94fb6b9823 Merge pull request #5246 from AliasT/v1.x.x
wrong socket.io.js path
2016-12-01 12:12:32 +02:00
xiaobing
d6af2ae13b wrong socket.io.js path 2016-12-01 18:02:34 +08:00
NodeBB Misty
4db89407c4 Incremented version number 2016-11-11 15:17:14 -05:00
NodeBB Misty
ef14511e24 Merge commit 'ee75c69817e21974a93e3981ebd32f745db94e19' into v1.x.x 2016-11-11 15:17:13 -05:00
Julian Lam
37cdf9ce25 fixes #5144 2016-10-21 14:44:38 -04:00
Julian Lam
7454902678 remove conflicts 2016-10-21 13:43:54 -04:00
Julian Lam
d6a485a6cc exposing pid, username, and userslug to flag modal 2016-10-21 13:43:08 -04:00
barisusakli
301e62c3ba up composer 2016-10-19 12:55:22 -04:00
barisusakli
d6c2fc96f8 up composer 2016-10-19 12:55:16 -04:00
Barış Soner Uşaklı
a9d69c4779 backport #5080 2016-10-13 13:46:06 +03:00
Barış Soner Uşaklı
1cf07a6fc5 backport #5103 2016-10-11 23:24:53 +03:00
NodeBB Misty
dfc8ceef9e Incremented version number 2016-10-06 07:59:33 -04:00
NodeBB Misty
b666261602 Merge commit '9bd4ab9682191c71482c5778b450d532e9c40655' into v1.x.x 2016-10-06 07:59:33 -04:00
NodeBB Misty
3c5d7f36b2 Incremented version number 2016-09-30 14:18:18 -04:00
NodeBB Misty
fda10ea194 Merge commit '6aec61d7227b73b49f30ea912dcab5f299f240f9' into v1.x.x 2016-09-30 14:18:18 -04:00
Barış Soner Uşaklı
9cc13aaf16 fix /unread 2016-09-26 18:43:00 +03:00
Barış Soner Uşaklı
2dec354131 backport xss fix 2016-09-26 17:10:39 +03:00
Barış Soner Uşaklı
1fefc8d427 backport xss fix 2016-09-26 17:05:30 +03:00
Barış Soner Uşaklı
c3b6974779 backport 42a8346d2d 2016-09-26 17:02:17 +03:00
Barış Soner Uşaklı
fdd8514058 backport https://github.com/NodeBB/NodeBB/issues/4997 2016-08-30 13:18:40 +03:00
Barış Soner Uşaklı
550fa4717d backport missing return fix
fixes double topic creation if guest handles are allowed
2016-08-25 14:01:33 +03:00
NodeBB Misty
c0a4bf91f3 Incremented version number 2016-08-10 00:15:35 -04:00
NodeBB Misty
2cbdb886b1 Merge commit 'fd8f5f9415d17c646dfa7d0806050f4f38785c93' into v1.x.x 2016-08-10 00:15:34 -04:00
NodeBB Misty
ff70f36e1a Incremented version number 2016-08-08 07:47:04 -04:00
NodeBB Misty
60721ce75d Merge commit 'aac0313f2eb87123347c5925a134adcd57fb869e' into v1.x.x 2016-08-08 07:47:03 -04:00
NodeBB Misty
296dc77c7b Incremented version number 2016-07-12 12:27:22 -04:00
NodeBB Misty
79cd9b1bcb Merge commit '03208807e444e8583051ed8fd04dedcc491ba5c3' into v1.x.x 2016-07-12 12:27:21 -04:00
Barış Soner Uşaklı
a196c834d9 backport user delete fix 2016-05-10 11:42:03 +03:00
Barış Soner Uşaklı
427500fc04 backport clone category fix 2016-05-07 20:40:19 +03:00
Barış Soner Uşaklı
1a5b6d4839 backport crash fix 2016-05-05 21:01:15 +03:00
NodeBB Misty
61404be94b Incremented version number 2016-04-13 13:20:13 -04:00
NodeBB Misty
c40c64a9c5 Merge commit '3390b7d7f6e9b4f6bd5f0bc6a5d554c21eb2b9ff' into v1.x.x 2016-04-13 13:20:13 -04:00
NodeBB Misty
b81e395a26 Incremented version number 2016-03-21 09:06:33 -04:00
NodeBB Misty
c12963593d Merge commit '294171b07266706ac78850be8b85b3d27e332424' into v1.x.x 2016-03-21 09:06:33 -04:00
Julian Lam
1736b1c015 removing additional socket methods added via #4304
@barisusakli added these methods in 8d9f7d8e19
2016-03-18 15:35:38 -04:00
NodeBB Misty
3d815fff72 bumped up version 2016-03-18 13:46:58 -04:00
NodeBB Misty
f88b695d4c Merge commit 'f7e43877482f6efc276ea71e61d7c7524a8f5a44' into v1.x.x 2016-03-18 13:40:33 -04:00
NodeBB Misty
72940f7bdf 1.0.1-auto.0 2016-03-12 18:39:12 -05:00
NodeBB Misty
ffc9da97e3 Merge commit '263b09f11a9496b7ad498a5d698186b9d774ae23' into v1.x.x 2016-03-12 18:39:10 -05:00
Julian Lam
2be3054e94 removed shrinkwrap file from release branch 2016-03-12 13:12:28 -05:00
Barış Soner Uşaklı
fdf32a6ed4 backport cover fix 2016-03-12 19:55:22 +02:00
Barış Soner Uşaklı
19b3069ad0 backport global mod fix 2016-03-09 22:45:59 +02:00
Barış Soner Uşaklı
5765313b63 backport image.normalise fix 2016-03-09 14:24:12 +02:00
Barış Soner Uşaklı
cbbd254d33 Merge pull request #4304 from AccaliaDeElementia/v1.x.x
Add API endpoint analogs to websocket interface
2016-03-08 10:33:20 +02:00
Barış Soner Uşaklı
8f8fcc8416 backport upgradescript fix for global moderators 2016-03-05 11:46:11 +02:00
Julian Lam
55b43dca23 updated shrinkwrap file 2016-03-03 16:45:20 -05:00
accalia
c13aab39e7 fix formatting error 2016-03-03 16:42:10 -05:00
accalia
072fa38927 Add API endpoint analogs to websocket
includes post.getPost, topic.getTopic, category.getCategory, user.getUserBy*
2016-03-03 08:34:08 -05:00
psychobunny
0b35c81586 @barisusakli fixing minfile caching for multiple processes 2016-03-02 23:58:45 -05:00
psychobunny
cc6d4f5d43 https://github.com/NodeBB/NodeBB/issues/4287 2016-03-02 13:15:22 -05:00
psychobunny
d9474d5de2 closes #4296 2016-03-02 13:07:42 -05:00
Julian Lam
f6c60517db added shrinkwrap file 2016-02-29 11:43:04 -05:00
5420 changed files with 139526 additions and 251310 deletions

View File

@@ -1,30 +1,8 @@
# Save as .codeclimate.yml (note leading .) in project root directory # Save as .codeclimate.yml (note leading .) in project root directory
version: "2"
languages: languages:
Ruby: false Ruby: true
JavaScript: true JavaScript: true
PHP: false PHP: true
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: exclude_paths:
- "public/vendor/*" - "public/vendor/*"
- "test/*" - "test/*"

View File

@@ -1,10 +0,0 @@
.*
logs
test
node_modules
commitlint.config.js
nodebb.bat
renovate.json
*.yml
*.md
Dockerfile

View File

@@ -7,6 +7,7 @@ node_modules/
logs/ logs/
/public/templates /public/templates
/public/uploads /public/uploads
/public/sounds
/public/vendor /public/vendor
/public/src/modules/string.js /public/src/modules/string.js
.idea/ .idea/
@@ -16,6 +17,3 @@ logs/
/coverage /coverage
/build /build
.eslintrc .eslintrc
test/files
*.min.js
install/docker/

127
.eslintrc
View File

@@ -1,3 +1,128 @@
{ {
"extends": "nodebb" "extends": "airbnb-base",
"parserOptions": {
"sourceType": "script"
},
"rules": {
// Customized
"handle-callback-err": [ "error","^(e$|(e|(.*(_e|E)))rr)" ],
"comma-dangle": ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "never"
}],
"no-empty": ["error", { "allowEmptyCatch": true }],
"no-underscore-dangle": "off",
"newline-per-chained-call": "off",
"no-console": "off",
"no-mixed-operators": ["error", { "allowSamePrecedence": true }],
"strict": ["error", "global"],
"consistent-return": "off",
"func-names": "off",
"no-tabs": "off",
"indent": ["error", "tab"],
"no-eq-null": "off",
"camelcase": "off",
"no-new": "off",
"no-shadow": "off",
"no-use-before-define": ["error", "nofunc"],
"no-prototype-builtins": "off",
"new-cap": "off",
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
"import/no-unresolved": "error",
// ES6
"prefer-rest-params": "off",
"prefer-spread": "off",
"prefer-arrow-callback": "off",
"prefer-template": "off",
"no-var": "off",
"object-shorthand": "off",
"vars-on-top": "off",
// TODO
"import/no-extraneous-dependencies": "off",
"import/no-dynamic-require": "off",
"import/newline-after-import": "off",
"no-bitwise": "off",
"global-require": "off",
"max-len": "off",
"no-param-reassign": "off",
"no-restricted-syntax": "off",
"no-script-url": "off",
"default-case": "off",
// "no-multi-assign": "off",
// "linebreak-style": "off",
// "one-var": "off",
// "no-undef": "off",
// "max-nested-callbacks": "off",
// "no-mixed-requires": "off",
// "brace-style": "off",
// "max-statements-per-line": "off",
// "no-unused-vars": "off",
// "no-mixed-spaces-and-tabs": "off",
// "no-useless-concat": "off",
// "require-jsdoc": "off",
// "eqeqeq": "off",
// "no-negated-condition": "off",
// "one-var-declaration-per-line": "off",
// "no-lonely-if": "off",
// "radix": "off",
// "no-else-return": "off",
// "no-useless-escape": "off",
// "block-scoped-var": "off",
// "operator-assignment": "off",
// "yoda": "off",
// "no-loop-func": "off",
// "no-void": "off",
// "valid-jsdoc": "off",
// "no-cond-assign": "off",
// "no-redeclare": "off",
// "no-unreachable": "off",
// "no-nested-ternary": "off",
// "operator-linebreak": "off",
// "guard-for-in": "off",
// "no-unneeded-ternary": "off",
// "no-sequences": "off",
// "no-extend-native": "off",
// "no-shadow-restricted-names": "off",
// "no-extra-boolean-cast": "off",
// "no-path-concat": "off",
// "no-unused-expressions": "off",
// "no-return-assign": "off",
// "no-restricted-modules": "off",
// "object-curly-spacing": "off",
// "indent": "off",
// "padded-blocks": "off",
// "eol-last": "off",
// "lines-around-directive": "off",
// "strict": "off",
// "comma-dangle": "off",
// "no-multi-spaces": "off",
// "quotes": "off",
// "keyword-spacing": "off",
// "no-mixed-operators": "off",
// "comma-spacing": "off",
// "no-trailing-spaces": "off",
// "key-spacing": "off",
// "no-multiple-empty-lines": "off",
// "spaced-comment": "off",
// "space-in-parens": "off",
// "block-spacing": "off",
// "quote-props": "off",
// "space-unary-ops": "off",
// "no-empty": "off",
// "dot-notation": "off",
// "func-call-spacing": "off",
// "array-bracket-spacing": "off",
// "object-property-newline": "off",
// "no-continue": "off",
// "no-extra-semi": "off",
// "no-spaced-func": "off",
// "no-useless-return": "off"
}
} }

View File

@@ -15,7 +15,7 @@ If you are writing contributions as part of employment from another company / in
# Having problems installing NodeBB? # 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. 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? # Found a Security Vulnerability?

13
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,13 @@
Please include the following information when submitting a bug report/issue:
* NodeBB version and git hash (to find your git hash, execute `git rev-parse HEAD` from the main NodeBB directory)
* Database (mongo or redis) and it's version.
* Exact steps to cause this issue
1. First I did this...
2. Then, I clicked on this item...
* 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!

View File

@@ -1,81 +0,0 @@
name: Bug Report
description: File a bug report
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
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 ←
- type: input
attributes:
label: NodeBB version
placeholder: e.g. v2.2.0
- type: input
attributes:
label: NodeBB git hash
description: to find your git hash, execute `git rev-parse HEAD` from the main NodeBB directory
placeholder: e.g. 783250ee6f8c51cdc243ce3b8d9f1a080517247e
- type: input
attributes:
label: NodeJS version
placeholder: e.g. v16.15.1
- type: textarea
attributes:
label: Installed NodeBB plugins
description: to find installed plugins run `./nodebb plugins`
placeholder: |
e.g.
* nodebb-plugin-2factor@5.0.1 (installed, disabled)
* nodebb-plugin-composer-default@8.0.0 (installed, enabled)
* nodebb-plugin-dbsearch@5.1.5 (installed, disabled)
* nodebb-plugin-emoji@4.0.4 (installed, enabled)
* nodebb-plugin-emoji-android@3.0.0 (installed, enabled)
* nodebb-plugin-markdown@10.0.0 (installed, enabled)
* nodebb-plugin-mentions@3.0.11 (installed, enabled)
* nodebb-plugin-spam-be-gone@1.0.0 (installed, disabled)
* nodebb-rewards-essentials@0.2.1 (installed, enabled)
* nodebb-theme-lavender@6.0.0 (installed, disabled)
* nodebb-theme-persona@12.0.11 (installed, enabled)
* nodebb-theme-slick@2.0.2 (installed, disabled)
* nodebb-theme-vanilla@12.1.18 (installed, disabled)
* nodebb-widget-essentials@6.0.0 (installed, enabled)
- type: dropdown
attributes:
label: Database type
multiple: true
options:
- MongoDB
- Redis
- PostgreSQL
- type: input
attributes:
label: Database version
description: "`mongod --version`, `redis-server --version`, or `postgres --version`"
placeholder: e.g. v5.0.9
- type: textarea
attributes:
label: Exact steps to cause this issue
placeholder: |
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.
- type: textarea
attributes:
label: What you expected
placeholder: e.g. I expected *abc* to *xyz*
- type: textarea
attributes:
label: What happened instead
placeholder: e.g. Instead, I got *zyx* and NodeBB set fire to my house
- type: textarea
attributes:
label: Anything else?
description: Any additional context about the issue you're encountering
- type: markdown
attributes:
value: "**Thank you!**"

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Community Forum
url: https://community.nodebb.org
about: Github Issues are for bug reports and feature requests only, please use community forum for other support

View File

@@ -1,24 +0,0 @@
name: Feature Request
description: Suggest a new feature
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
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 ←
- type: textarea
attributes:
label: Description
validations:
required: true
- type: input
attributes:
label: Community forum reference
description: If this feature was already discussed on the Community Forum link it here
placeholder: https://community.nodebb.org/topic/0/example-feature-request
- type: markdown
attributes:
value: "**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,61 +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
permissions:
contents: read
packages: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}.x
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch,enable=${{ github.event.repository.default_branch != github.ref }}
- name: Build and push Docker images
uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -1,212 +0,0 @@
name: Lint and test
on:
push:
branches:
- master
- develop
pull_request:
branches:
- master
- develop
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
test:
permissions:
checks: write # for coverallsapp/github-action to create new checks
contents: read # for actions/checkout to fetch code
name: Lint and test
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
node: [18, 20]
database: [mongo-dev, mongo, redis, postgres]
include:
# only run coverage once
- os: ubuntu-latest
node: 18
coverage: true
# test under development once
- database: mongo-dev
test_env: development
# only run eslint once
- os: ubuntu-latest
node: 18
database: mongo-dev
lint: true
runs-on: ${{ matrix.os }}
env:
TEST_ENV: ${{ matrix.test_env || 'production' }}
services:
postgres:
image: 'postgres:16-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:7.2.3'
# 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:7.0'
ports:
# Maps port 27017 on service container to the host
- 27017:27017
steps:
- uses: actions/checkout@v4
- run: cp install/package.json package.json
- name: Install Node
uses: actions/setup-node@v4
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@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949 # v2.2.3
if: matrix.coverage
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: ${{ matrix.os }}-node-${{ matrix.node }}-db-${{ matrix.database }}
parallel: true
finish:
permissions:
checks: write # for coverallsapp/github-action to create new checks
needs: test
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949 # v2.2.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true

18
.gitignore vendored
View File

@@ -1,5 +1,3 @@
dist/
yarn.lock
npm-debug.log npm-debug.log
node_modules/ node_modules/
sftp-config.json sftp-config.json
@@ -19,7 +17,6 @@ provision.sh
.DS_Store .DS_Store
feeds/recent.rss feeds/recent.rss
.eslintcache .eslintcache
.svn
logs/ logs/
@@ -28,8 +25,7 @@ pidfile
# templates # templates
/public/templates /public/templates
/public/uploads /public/sounds
/test/uploads
# compiled files # compiled files
/public/stylesheet.css /public/stylesheet.css
@@ -40,7 +36,6 @@ pidfile
/public/acp.min.js.map /public/acp.min.js.map
/public/installer.css /public/installer.css
/public/installer.min.js /public/installer.min.js
/public/bootstrap.min.css
/public/logo.png /public/logo.png
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
@@ -60,15 +55,6 @@ tx.exe
##Coverage output ##Coverage output
coverage coverage
.nyc_output
build
*.log *.log
test/files/normalise.jpg.png
test/files/normalise-resized.jpg
package-lock.json
/package.json
*.mongodb
link-plugins.sh
test.sh
.docker/

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
}

89
.jshintrc Normal file
View File

@@ -0,0 +1,89 @@
{
// 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" : true, // true: Require all defined variables be used
"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
"mocha": true,
// 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" : {
"Promise": true
} // additional predefined global variables
}

View File

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

30
.travis.yml Normal file
View File

@@ -0,0 +1,30 @@
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"
before_script:
- sh -c "if [ '$DB' = 'mongodb' ]; then 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}\"; fi"
- sh -c "if [ '$DB' = 'redis' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"redis\\\",\\\"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\\\":6379,\\\"database\\\":0}\"; fi"
- "until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done"
after_success:
- "npm run coveralls"
language: node_js
env:
- CXX=g++-4.8 DB=mongodb
- CXX=g++-4.8 DB=redis
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
node_js:
- "6"
- "4"
branches:
only:
- master
- develop

7044
.tx/config

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +1,15 @@
FROM --platform=$BUILDPLATFORM node:lts as npm # The base image is the latest 4.x node (LTS) on jessie (debian)
# -onbuild will install the node dependencies found in the project package.json
# and copy its content in /usr/src/app, its WORKDIR
FROM node:4-onbuild
RUN mkdir -p /usr/src/build && \ ENV NODE_ENV=production \
chown -R node:node /usr/src/build
WORKDIR /usr/src/build
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
COPY --chown=node:node install/package.json /usr/src/build/package.json
USER node
RUN npm install --omit=dev
FROM node:lts as rebuild
ARG BUILDPLATFORM
ARG TARGETPLATFORM
RUN mkdir -p /usr/src/build && \
chown -R node:node /usr/src/build
COPY --from=npm /usr/src/build /usr/src/build
RUN if [ $BUILDPLATFORM != $TARGETPLATFORM ]; then \
npm rebuild && \
npm cache clean --force; fi
FROM node:lts-slim as run
ARG NODE_ENV
ENV NODE_ENV=$NODE_ENV \
daemon=false \ daemon=false \
silent=false silent=false
RUN mkdir -p /usr/src/app && \ # nodebb setup will ask you for connection information to a redis (default), mongodb then run the forum
chown -R node:node /usr/src/app # nodebb upgrade is not included and might be desired
CMD node app --setup && npm start
COPY --chown=node:node --from=rebuild /usr/src/build /usr/src/app
WORKDIR /usr/src/app
USER node
COPY --chown=node:node . /usr/src/app
# the default port for NodeBB is exposed outside the container
EXPOSE 4567 EXPOSE 4567
VOLUME ["/usr/src/app/node_modules", "/usr/src/app/build", "/usr/src/app/public/uploads", "/opt/config"]
ENTRYPOINT ["./install/docker/entrypoint.sh"]

View File

@@ -1,201 +1,158 @@
'use strict'; 'use strict';
const path = require('path'); var fork = require('child_process').fork;
const nconf = require('nconf'); var env = process.env;
var worker;
nconf.argv().env({ var updateWorker;
separator: '__', var initWorker;
}); var incomplete = [];
const winston = require('winston'); var running = 0;
const { fork } = require('child_process');
const { env } = process;
let worker;
env.NODE_ENV = env.NODE_ENV || 'development';
const configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json');
const prestart = require('./src/prestart');
prestart.loadConfig(configFile);
const db = require('./src/database');
const plugins = require('./src/plugins');
module.exports = function (grunt) { module.exports = function (grunt) {
const args = []; var args = [];
var initArgs = ['--build'];
if (!grunt.option('verbose')) { if (!grunt.option('verbose')) {
args.push('--log-level=info'); args.push('--log-level=info');
nconf.set('log-level', 'info'); initArgs.push('--log-level=info');
} }
prestart.setupWinston();
grunt.initConfig({ function update(action, filepath, target) {
watch: {}, var updateArgs = args.slice();
}); var compiling = '';
var time = Date.now();
grunt.loadNpmTasks('grunt-contrib-watch'); if (target === 'lessUpdated_Client') {
compiling = 'clientCSS';
grunt.registerTask('default', ['watch']); } else if (target === 'lessUpdated_Admin') {
compiling = 'acpCSS';
grunt.registerTask('init', async function () { } else if (target === 'clientUpdated') {
const done = this.async(); compiling = 'js';
let pluginList = []; } else if (target === 'templatesUpdated') {
if (!process.argv.includes('--core')) { compiling = 'tpl';
await db.init(); } else if (target === 'langUpdated') {
pluginList = await plugins.getActive(); compiling = 'lang';
addBaseThemes(pluginList); } else if (target === 'serverUpdated') {
if (!pluginList.includes('nodebb-plugin-composer-default')) { // Do nothing, just restart
pluginList.push('nodebb-plugin-composer-default');
}
if (!pluginList.includes('nodebb-theme-harmony')) {
pluginList.push('nodebb-theme-harmony');
}
} }
const styleUpdated_Client = pluginList.map(p => `node_modules/${p}/*.scss`) if (incomplete.indexOf(compiling) === -1) {
.concat(pluginList.map(p => `node_modules/${p}/*.css`)) incomplete.push(compiling);
.concat(pluginList.map(p => `node_modules/${p}/+(public|static|scss)/**/*.scss`)) }
.concat(pluginList.map(p => `node_modules/${p}/+(public|static)/**/*.css`));
const clientUpdated = pluginList.map(p => `node_modules/${p}/+(public|static)/**/*.js`); updateArgs.push('--build');
const serverUpdated = pluginList.map(p => `node_modules/${p}/*.js`) updateArgs.push(incomplete.join(','));
.concat(pluginList.map(p => `node_modules/${p}/+(lib|src)/**/*.js`));
const templatesUpdated = pluginList.map(p => `node_modules/${p}/+(public|static|templates)/**/*.tpl`); worker.kill();
const langUpdated = pluginList.map(p => `node_modules/${p}/+(public|static|languages)/**/*.json`); if (updateWorker) {
const interval = 100; updateWorker.kill('SIGKILL');
grunt.config(['watch'], { }
styleUpdated: { updateWorker = fork('app.js', updateArgs, { env: env });
running += 1;
updateWorker.on('exit', function () {
running -= 1;
if (running === 0) {
worker = fork('app.js', args, {
env: env,
});
worker.on('message', function () {
if (incomplete.length) {
incomplete = [];
if (grunt.option('verbose')) {
grunt.log.writeln('NodeBB restarted in ' + (Date.now() - time) + ' ms');
}
}
});
}
});
}
grunt.initConfig({
watch: {
lessUpdated_Client: {
files: [ files: [
'public/scss/**/*.scss', 'public/*.less',
...styleUpdated_Client, 'node_modules/nodebb-*/*.less', 'node_modules/nodebb-*/**/*.less',
'!node_modules/nodebb-*/node_modules/**',
'!node_modules/nodebb-*/.git/**',
], ],
options: { options: {
interval: interval, interval: 1000,
},
},
lessUpdated_Admin: {
files: ['public/**/*.less'],
options: {
interval: 1000,
}, },
}, },
clientUpdated: { clientUpdated: {
files: [ files: [
'public/src/**/*.js', 'public/src/**/*.js',
'public/vendor/**/*.js', 'node_modules/nodebb-*/*.js', 'node_modules/nodebb-*/**/*.js',
...clientUpdated, '!node_modules/nodebb-*/node_modules/**',
'node_modules/benchpressjs/build/benchpress.js', 'node_modules/templates.js/lib/templates.js',
'!node_modules/nodebb-*/.git/**',
], ],
options: { options: {
interval: interval, interval: 1000,
}, },
}, },
serverUpdated: { serverUpdated: {
files: [ files: ['*.js', 'install/*.js', 'src/**/*.js'],
'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: { options: {
interval: interval, interval: 1000,
}, },
}, },
templatesUpdated: { templatesUpdated: {
files: [ files: [
'src/views/**/*.tpl', 'src/views/**/*.tpl',
...templatesUpdated, 'node_modules/nodebb-*/*.tpl', 'node_modules/nodebb-*/**/*.tpl',
'!node_modules/nodebb-*/node_modules/**',
'!node_modules/nodebb-*/.git/**',
], ],
options: { options: {
interval: interval, interval: 1000,
}, },
}, },
langUpdated: { langUpdated: {
files: [ files: [
'public/language/en-GB/*.json', 'public/language/en-GB/*.json',
'public/language/en-GB/**/*.json', 'public/language/en-GB/**/*.json',
...langUpdated, 'node_modules/nodebb-*/**/*.json',
'!node_modules/nodebb-*/node_modules/**',
'!node_modules/nodebb-*/.git/**',
'!node_modules/nodebb-*/plugin.json',
'!node_modules/nodebb-*/package.json',
'!node_modules/nodebb-*/theme.json',
], ],
options: { options: {
interval: interval, interval: 1000,
}, },
}, },
}); },
const build = require('./src/meta/build');
if (!grunt.option('skip')) {
await build.build(true, { watch: true });
}
run();
done();
}); });
function run() { grunt.loadNpmTasks('grunt-contrib-watch');
if (worker) {
worker.kill();
}
const execArgv = []; grunt.registerTask('default', ['watch']);
const inspect = process.argv.find(a => a.startsWith('--inspect')); env.NODE_ENV = 'development';
if (inspect) {
execArgv.push(inspect);
}
if (grunt.option('skip')) {
worker = fork('app.js', args, { worker = fork('app.js', args, {
env, env: env,
execArgv, });
} else {
initWorker = fork('app.js', initArgs, {
env: env,
});
initWorker.on('exit', function () {
worker = fork('app.js', args, {
env: env,
});
}); });
} }
grunt.task.run('init'); grunt.event.on('watch', update);
grunt.event.removeAllListeners('watch');
grunt.event.on('watch', (action, filepath, target) => {
let compiling;
if (target === 'styleUpdated') {
compiling = ['clientCSS', '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(pluginList) {
let themeId = pluginList.find(p => p.includes('nodebb-theme-'));
if (!themeId) {
return pluginList;
}
let baseTheme;
do {
try {
baseTheme = require(`${themeId}/theme`).baseTheme;
} catch (err) {
console.log(err);
}
if (baseTheme) {
pluginList.push(baseTheme);
themeId = baseTheme;
}
} while (baseTheme);
return pluginList;
}

View File

@@ -1,15 +1,23 @@
# ![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) [![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)
[![Coverage Status](https://coveralls.io/repos/github/NodeBB/NodeBB/badge.svg?branch=master)](https://coveralls.io/github/NodeBB/NodeBB?branch=master) [![Coverage Status](https://coveralls.io/repos/github/NodeBB/NodeBB/badge.svg?branch=master)](https://coveralls.io/github/NodeBB/NodeBB?branch=master)
[![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) [![Code Climate](https://codeclimate.com/github/NodeBB/NodeBB/badges/gpa.svg)](https://codeclimate.com/github/NodeBB/NodeBB)
[![](https://dcbadge.vercel.app/api/server/p6YKPXu7er?style=flat)](https://discord.gg/p6YKPXu7er) [![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**](https://nodebb.org) 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 has many modern features out of the box such as social network integration and streaming discussions, while still making sure to be compatible with older browsers.
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) * [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)
* [Premium Hosting for NodeBB](http://www.nodebb.org/ "NodeBB")
* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
## Screenshots ## Screenshots
@@ -24,30 +32,31 @@ 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/LmHtPhob.png)](http://i.imgur.com/LmHtPho.png)
[![](http://i.imgur.com/paiJPJkb.jpg)](http://i.imgur.com/paiJPJk.jpg) [![](http://i.imgur.com/paiJPJkb.jpg)](http://i.imgur.com/paiJPJk.jpg)
Our minimalist "Harmony" theme gets you going right away, no coding experience required. 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)
![Rendering of a NodeBB install on desktop and mobile devices](https://user-images.githubusercontent.com/923011/228570420-2a4db745-b20d-474a-a571-1b59259508ef.png)
## How can I follow along/contribute? ## How can I follow along/contribute?
* 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 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 SCSS or CSS. NodeBB's base theme utilizes [Bootstrap 5](http://getbootstrap.com/) as a frontend toolkit. * 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://explore.transifex.com/nodebb/nodebb/) for internationalization. * 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. * 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.
## Requirements ## Requirements
NodeBB requires the following software to be installed: NodeBB requires the following software to be installed:
* A version of Node.js at least 16 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions)) * A version of Node.js at least 4 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions))
* MongoDB, version 3.6 or greater **or** Redis, version 2.8.9 or greater * Redis, version 2.8.9 or greater **or** MongoDB, version 2.6 or greater
* If you are using [clustering](https://docs.nodebb.org/configuring/scaling/) you need Redis installed and configured.
* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB) * nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
## Installation ## 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)
If installing via the cloud (or using Docker), [please see cloud-based installation documentation](https://docs.nodebb.org/installing/cloud/).
## Securing NodeBB ## Securing NodeBB
@@ -60,25 +69,12 @@ It is important to ensure that your NodeBB and database servers are secured. Bea
2. Use `iptables` to secure your server from unintended open ports. In Ubuntu, `ufw` provides a friendlier interface to working with `iptables`. 2. Use `iptables` to secure your server from unintended open ports. In Ubuntu, `ufw` provides a friendlier interface to working with `iptables`.
* e.g. If your NodeBB is proxied, no ports should be open except 80 (and possibly 22, for SSH access) * e.g. If your NodeBB is proxied, no ports should be open except 80 (and possibly 22, for SSH access)
## Upgrading NodeBB ## 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 ## License
NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html). 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. 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](https://docs.nodebb.org)
* [Help translate NodeBB](https://explore.transifex.com/nodebb/nodebb/)
* [NodeBB Blog](https://nodebb.org/blog)
* [Premium Hosting for NodeBB](https://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")

261
app.js
View File

@@ -1,7 +1,7 @@
/* /*
NodeBB - A better forum platform for the modern web NodeBB - A better forum platform for the modern web
https://github.com/NodeBB/NodeBB/ https://github.com/NodeBB/NodeBB/
Copyright (C) 2013-2021 NodeBB Inc. Copyright (C) 2013-2017 NodeBB Inc.
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -19,64 +19,257 @@
'use strict'; 'use strict';
require('./require-main'); if (require.main !== module) {
require.main.require = function (path) {
return require(path);
};
}
const nconf = require('nconf'); var nconf = require('nconf');
nconf.argv().env('__');
nconf.argv().env({ var url = require('url');
separator: '__', var async = require('async');
}); var winston = require('winston');
var path = require('path');
var pkg = require('./package.json');
var file = require('./src/file');
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'; global.env = process.env.NODE_ENV || 'production';
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {
colorize: true,
timestamp: function () {
var date = new Date();
return nconf.get('json-logging') ? date.toJSON() : date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0, 8) + ' [' + global.process.pid + ']';
},
level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose'),
json: (!!nconf.get('json-logging')),
stringify: (!!nconf.get('json-logging')),
});
// Alternate configuration file support // Alternate configuration file support
const configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json'); var configFile = path.join(__dirname, '/config.json');
const configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database')); if (nconf.get('config')) {
configFile = path.resolve(__dirname, nconf.get('config'));
}
const prestart = require('./src/prestart'); var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
prestart.loadConfig(configFile); loadConfig();
prestart.setupWinston(); versionCheck();
prestart.versionCheck();
winston.verbose('* using configuration stored in: %s', configFile);
if (!process.send) { if (!process.send) {
// If run using `node app`, log GNU copyright info along with server info // 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('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 program comes with ABSOLUTELY NO WARRANTY.');
winston.info('This is free software, and you are welcome to redistribute it under certain conditions.'); winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
winston.info(''); winston.info('');
} }
if (nconf.get('setup') || nconf.get('install')) { if (nconf.get('setup') || nconf.get('install')) {
require('./src/cli/setup').setup(); setup();
} else if (!configExists) { } else if (!configExists) {
require('./install/web').install(nconf.get('port')); require('./install/web').install(nconf.get('port'));
} else if (nconf.get('upgrade')) { } else if (nconf.get('upgrade')) {
require('./src/cli/upgrade').upgrade(true); upgrade();
} else if (nconf.get('reset')) { } else if (nconf.get('reset')) {
require('./src/cli/reset').reset({ async.waterfall([
theme: nconf.get('t'), async.apply(require('./src/reset').reset),
plugin: nconf.get('p'), async.apply(require('./src/meta/build').buildAll),
widgets: nconf.get('w'), ], function (err) {
settings: nconf.get('s'), process.exit(err ? 1 : 0);
all: nconf.get('a'),
}); });
} else if (nconf.get('activate')) { } else if (nconf.get('activate')) {
require('./src/cli/manage').activate(nconf.get('activate')); activate();
} else if (nconf.get('plugins') && typeof nconf.get('plugins') !== 'object') { } else if (nconf.get('plugins')) {
require('./src/cli/manage').listPlugins(); listPlugins();
} else if (nconf.get('build')) { } else if (nconf.get('build')) {
require('./src/cli/manage').build(nconf.get('build')); require('./src/meta/build').build(nconf.get('build'));
} else if (nconf.get('events')) {
require('./src/cli/manage').listEvents();
} else { } else {
require('./src/start').start(); require('./src/start').start();
} }
function loadConfig(callback) {
winston.verbose('* using configuration stored in: %s', configFile);
nconf.file({
file: configFile,
});
nconf.defaults({
base_dir: __dirname,
themes_path: path.join(__dirname, 'node_modules'),
upload_path: 'public/uploads',
views_dir: path.join(__dirname, 'build/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-persona/templates'));
nconf.set('upload_path', path.resolve(nconf.get('base_dir'), nconf.get('upload_path')));
if (nconf.get('url')) {
nconf.set('url_parsed', url.parse(nconf.get('url')));
}
// Explicitly cast 'jobsDisabled' as Bool
var castAsBool = ['jobsDisabled'];
nconf.stores.env.readOnly = false;
castAsBool.forEach(function (prop) {
var value = nconf.get(prop);
if (value) {
nconf.set(prop, typeof value === 'boolean' ? value : String(value).toLowerCase() === 'true');
}
});
nconf.stores.env.readOnly = true;
if (typeof callback === 'function') {
callback();
}
}
function setup() {
winston.info('NodeBB Setup Triggered via Command Line');
var install = require('./src/install');
var build = require('./src/meta/build');
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');
async.series([
async.apply(install.setup),
async.apply(loadConfig),
async.apply(build.buildAll),
], function (err, data) {
// Disregard build step data
data = data[0];
var separator = ' ';
if (process.stdout.columns > 10) {
for (var x = 0, cols = process.stdout.columns - 10; x < cols; x += 1) {
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() {
var db = require('./src/database');
var meta = require('./src/meta');
var upgrade = require('./src/upgrade');
var build = require('./src/meta/build');
async.series([
async.apply(db.init),
async.apply(meta.configs.init),
async.apply(upgrade.upgrade),
async.apply(build.buildAll),
], function (err) {
if (err) {
winston.error(err.stack);
process.exit(1);
} else {
process.exit(0);
}
});
}
function activate() {
var db = require('./src/database');
var plugins = require('./src/plugins');
var plugin = nconf.get('activate');
async.waterfall([
function (next) {
db.init(next);
},
function (next) {
if (plugin.indexOf('nodebb-') !== 0) {
// Allow omission of `nodebb-plugin-`
plugin = 'nodebb-plugin-' + plugin;
}
plugins.isInstalled(plugin, next);
},
function (isInstalled, next) {
if (!isInstalled) {
return next(new Error('plugin not installed'));
}
winston.info('Activating plugin `%s`', plugin);
db.sortedSetAdd('plugins:active', 0, plugin, next);
},
], function (err) {
if (err) {
winston.error(err.message);
}
process.exit(err ? 1 : 0);
});
}
function listPlugins() {
require('./src/database').init(function (err) {
if (err) {
winston.error(err.stack);
process.exit(1);
}
var db = require('./src/database');
db.getSortedSetRange('plugins:active', 0, -1, function (err, plugins) {
if (err) {
winston.error(err.stack);
process.exit(1);
}
winston.info('Active plugins: \n\t - ' + plugins.join('\n\t - '));
process.exit();
});
});
}
function versionCheck() {
var version = process.version.slice(1);
var range = pkg.engines.node;
var semver = require('semver');
var compatible = semver.satisfies(version, range);
if (!compatible) {
winston.warn('Your version of Node.js is too outdated for NodeBB. Please update your version of Node.js.');
winston.warn('Recommended ' + range.green + ', '.reset + version.yellow + ' provided\n'.reset);
}
}

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,55 +0,0 @@
version: '3.8'
services:
nodebb:
build: .
restart: unless-stopped
ports:
- "4567:4567/tcp" # comment this out if you don't want to expose NodeBB to the host, or change the first number to any port you want
# uncomment if you want to use another container as a reverse proxy
# expose:
# - 4567
volumes:
- ./.docker/build:/usr/src/app/build
- ./.docker/public/uploads:/usr/src/app/public/uploads
- ./.docker:/opt/config
- ./install/docker/setup.json:/usr/src/app/setup.json
mongo:
image: "mongo:7-jammy"
restart: unless-stopped
expose:
- "27017"
environment:
MONGO_INITDB_ROOT_USERNAME: nodebb
MONGO_INITDB_ROOT_PASSWORD: nodebb
MONGO_INITDB_DATABASE: nodebb
volumes:
- ./.docker/database/mongo/config:/etc/mongo
- ./.docker/database/mongo/data:/data/db
- ./install/docker/mongodb-user-init.js:/docker-entrypoint-initdb.d/user-init.js
profiles:
- mongo
postgres:
image: postgres:16.1-alpine
restart: unless-stopped
expose:
- "5432"
environment:
POSTGRES_USER: nodebb
POSTGRES_PASSWORD: nodebb
POSTGRES_DB: nodebb
volumes:
- ./.docker/database/postgresql/data:/var/lib/postgresql/data
profiles:
- postgres
redis:
image: redis:7.2.3-alpine
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes", "--loglevel", "warning"]
# command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF
expose:
- "6379"
volumes:
- ./.docker/database/redis:/data
profiles:
- redis

View File

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

View File

@@ -1,191 +1,39 @@
{ {
"title": "NodeBB", "title": "NodeBB",
"showSiteTitle": 1, "showSiteTitle": 1,
"defaultLang": "en-GB",
"loginDays": 14,
"loginSeconds": 0,
"sessionDuration": 0,
"loginAttempts": 5,
"lockoutDuration": 60,
"adminReloginDuration": 60,
"postDelay": 10, "postDelay": 10,
"initialPostDelay": 10, "initialPostDelay": 10,
"newbiePostDelay": 120, "newbiePostDelay": 120,
"postEditDuration": 0, "newbiePostDelayThreshold": 3,
"newbiePostEditDuration": 3600,
"postDeleteDuration": 0,
"enablePostHistory": 1,
"topicBacklinks": 1,
"postCacheSize": 20971520,
"disableChat": 0,
"chatEditDuration": 0,
"chatDeleteDuration": 0,
"chatMessageDelay": 2000,
"newbieChatMessageDelay": 120000,
"notificationSendDelay": 60,
"newbieReputationThreshold": 3,
"postQueue": 0,
"postQueueReputationThreshold": 0,
"groupsExemptFromPostQueue": ["administrators", "Global Moderators"],
"groupsExemptFromNewUserRestrictions": ["administrators", "Global Moderators"],
"groupsExemptFromMaintenanceMode": ["administrators", "Global Moderators"],
"minimumPostLength": 8, "minimumPostLength": 8,
"maximumPostLength": 32767, "maximumPostLength": 32767,
"systemTags": "",
"minimumTagsPerTopic": 0, "minimumTagsPerTopic": 0,
"maximumTagsPerTopic": 5, "maximumTagsPerTopic": 5,
"minimumTagLength": 3, "minimumTagLength": 3,
"maximumTagLength": 15, "maximumTagLength": 15,
"undoTimeout": 10000, "allowGuestSearching": 0,
"allowTopicsThumbnail": 1, "allowTopicsThumbnail": 0,
"registrationType": "normal", "registrationType": "normal",
"registrationApprovalType": "normal", "allowLocalLogin": 1,
"allowAccountDelete": 1, "allowAccountDelete": 1,
"privateUploads": 0, "allowFileUploads": 0,
"allowedFileExtensions": "png,jpg,bmp,txt",
"uploadRateLimitThreshold": 10,
"uploadRateLimitCooldown": 60,
"allowUserHomePage": 1, "allowUserHomePage": 1,
"allowMultipleBadges": 0,
"maximumFileSize": 2048, "maximumFileSize": 2048,
"stripEXIFData": 1,
"orphanExpiryDays": 0,
"resizeImageWidthThreshold": 2000,
"resizeImageWidth": 760,
"rejectImageWidth": 5000,
"rejectImageHeight": 5000,
"resizeImageQuality": 80,
"topicThumbSize": 512,
"minimumTitleLength": 3, "minimumTitleLength": 3,
"maximumTitleLength": 255, "maximumTitleLength": 255,
"minimumUsernameLength": 2, "minimumUsernameLength": 2,
"maximumUsernameLength": 16, "maximumUsernameLength": 16,
"minimumPasswordLength": 6, "minimumPasswordLength": 6,
"minimumPasswordStrength": 1,
"maximumSignatureLength": 255, "maximumSignatureLength": 255,
"maximumAboutMeLength": 1000, "maximumAboutMeLength": 1000,
"maximumUsersInChatRoom": 0,
"maximumChatMessageLength": 1000,
"maximumChatRoomNameLength": 50,
"maximumProfileImageSize": 256, "maximumProfileImageSize": 256,
"maximumCoverImageSize": 2048, "maximumCoverImageSize": 2048,
"profileImageDimension": 200, "profileImageDimension": 128,
"profile:convertProfileImageToPNG": 0, "requireEmailConfirmation": 0,
"profile:keepAllUserImages": 0,
"gdpr_enabled": 1,
"allowProfileImageUploads": 1, "allowProfileImageUploads": 1,
"teaserPost": "last-reply", "teaserPost": "last-reply",
"showPostPreviewsOnHover": 1,
"allowPrivateGroups": 1, "allowPrivateGroups": 1,
"unreadCutoff": 2, "unreadCutoff": 2,
"bookmarkThreshold": 5, "bookmarkThreshold": 5,
"autoDetectLang": 1, "topicsPerList": 20
"reputation:disabled": 0, }
"downvote:disabled": 0,
"disableSignatures": 0,
"signatures:hideDuplicates": 0,
"upvotesPerDay": 20,
"upvotesPerUserPerDay": 6,
"downvotesPerDay": 10,
"downvotesPerUserPerDay": 3,
"min:rep:chat": 0,
"min:rep:downvote": 0,
"min:rep:upvote": 0,
"min:rep:post-links": 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,
"flags:autoFlagOnDownvoteThreshold": 0,
"flags:actionOnResolve": "rescind",
"flags:actionOnReject": "rescind",
"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,
"emailConfirmExpiry": 24,
"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-opener-policy": "same-origin",
"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,
"disableCustomUserSkins": 0
}

View File

@@ -2,7 +2,7 @@
{ {
"widget": "html", "widget": "html",
"data" : { "data" : {
"html": "<footer id=\"footer\" class=\"container footer d-flex flex-column align-items-center gap-1 mb-2\">\n\t<span>Powered by <a class=\"link-secondary text-decoration-underline\" target=\"_blank\" href=\"https://nodebb.org\">NodeBB</a></span>\n\t<span><a class=\"link-secondary\" target=\"_blank\" href=\"//github.com/NodeBB/NodeBB/graphs/contributors\"><i class=\"fa fa-users\"></i> <span class=\"text-decoration-underline\">Contributors<span></a></span>\n</footer>", "html": "<footer id=\"footer\" class=\"container footer\">\r\n\t<div class=\"copyright\">\r\n\t\tCopyright © 2016 <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":"", "title":"",
"container":"" "container":""
} }

View File

@@ -4,7 +4,7 @@
"title": "[[global:header.categories]]", "title": "[[global:header.categories]]",
"enabled": true, "enabled": true,
"iconClass": "fa-list", "iconClass": "fa-list",
"textClass": "d-lg-none", "textClass": "visible-xs-inline",
"text": "[[global:header.categories]]" "text": "[[global:header.categories]]"
}, },
{ {
@@ -13,16 +13,18 @@
"title": "[[global:header.unread]]", "title": "[[global:header.unread]]",
"enabled": true, "enabled": true,
"iconClass": "fa-inbox", "iconClass": "fa-inbox",
"textClass": "d-lg-none", "textClass": "visible-xs-inline",
"text": "[[global:header.unread]]", "text": "[[global:header.unread]]",
"groups": ["registered-users"] "properties": {
"loggedIn": true
}
}, },
{ {
"route": "/recent", "route": "/recent",
"title": "[[global:header.recent]]", "title": "[[global:header.recent]]",
"enabled": true, "enabled": true,
"iconClass": "fa-clock-o", "iconClass": "fa-clock-o",
"textClass": "d-lg-none", "textClass": "visible-xs-inline",
"text": "[[global:header.recent]]" "text": "[[global:header.recent]]"
}, },
{ {
@@ -30,7 +32,7 @@
"title": "[[global:header.tags]]", "title": "[[global:header.tags]]",
"enabled": true, "enabled": true,
"iconClass": "fa-tags", "iconClass": "fa-tags",
"textClass": "d-lg-none", "textClass": "visible-xs-inline",
"text": "[[global:header.tags]]" "text": "[[global:header.tags]]"
}, },
{ {
@@ -38,7 +40,7 @@
"title": "[[global:header.popular]]", "title": "[[global:header.popular]]",
"enabled": true, "enabled": true,
"iconClass": "fa-fire", "iconClass": "fa-fire",
"textClass": "d-lg-none", "textClass": "visible-xs-inline",
"text": "[[global:header.popular]]" "text": "[[global:header.popular]]"
}, },
{ {
@@ -46,7 +48,7 @@
"title": "[[global:header.users]]", "title": "[[global:header.users]]",
"enabled": true, "enabled": true,
"iconClass": "fa-user", "iconClass": "fa-user",
"textClass": "d-lg-none", "textClass": "visible-xs-inline",
"text": "[[global:header.users]]" "text": "[[global:header.users]]"
}, },
{ {
@@ -54,7 +56,7 @@
"title": "[[global:header.groups]]", "title": "[[global:header.groups]]",
"enabled": true, "enabled": true,
"iconClass": "fa-group", "iconClass": "fa-group",
"textClass": "d-lg-none", "textClass": "visible-xs-inline",
"text": "[[global:header.groups]]" "text": "[[global:header.groups]]"
}, },
{ {
@@ -62,8 +64,22 @@
"title": "[[global:header.admin]]", "title": "[[global:header.admin]]",
"enabled": true, "enabled": true,
"iconClass": "fa-cogs", "iconClass": "fa-cogs",
"textClass": "d-lg-none", "textClass": "visible-xs-inline",
"text": "[[global:header.admin]]", "text": "[[global:header.admin]]",
"groups": ["administrators"] "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": {
"searchInstalled": true
}
} }
] ]

View File

@@ -1,9 +1,9 @@
### Welcome to your brand new NodeBB forum! # 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 administrator, 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. 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 ## Additional Resources
* [NodeBB Documentation](https://docs.nodebb.org) * [NodeBB Documentation](https://docs.nodebb.org)
* [Community Support Forum](https://community.nodebb.org) * [Community Support Forum](https://community.nodebb.org)

View File

@@ -1,47 +1,52 @@
'use strict'; 'use strict';
const prompt = require('prompt'); var async = require('async');
const winston = require('winston'); var prompt = require('prompt');
var winston = require('winston');
const questions = { var questions = {
redis: require('../src/database/redis').questions, redis: require('../src/database/redis').questions,
mongo: require('../src/database/mongo').questions, mongo: require('../src/database/mongo').questions,
postgres: require('../src/database/postgres').questions,
}; };
module.exports = async function (config) { module.exports = function (config, callback) {
winston.info(`\nNow configuring ${config.database} database:`); async.waterfall([
const databaseConfig = await getDatabaseConfig(config); function (next) {
return saveDatabaseConfig(config, databaseConfig); process.stdout.write('\n');
winston.info('Now configuring ' + config.database + ' database:');
getDatabaseConfig(config, next);
},
function (databaseConfig, next) {
saveDatabaseConfig(config, databaseConfig, next);
},
], callback);
}; };
async function getDatabaseConfig(config) { function getDatabaseConfig(config, callback) {
if (!config) { if (!config) {
throw new Error('invalid config, aborted'); return callback(new Error('aborted'));
} }
if (config.database === 'redis') { if (config.database === 'redis') {
if (config['redis:host'] && config['redis:port']) { if (config['redis:host'] && config['redis:port']) {
return config; callback(null, config);
} else {
prompt.get(questions.redis, callback);
} }
return await prompt.get(questions.redis);
} else if (config.database === 'mongo') { } else if (config.database === 'mongo') {
if ((config['mongo:host'] && config['mongo:port']) || config['mongo:uri']) { if (config['mongo:host'] && config['mongo:port']) {
return config; callback(null, config);
} else {
prompt.get(questions.mongo, callback);
} }
return await prompt.get(questions.mongo); } else {
} else if (config.database === 'postgres') { return callback(new Error('unknown database : ' + config.database));
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) { function saveDatabaseConfig(config, databaseConfig, callback) {
if (!databaseConfig) { if (!databaseConfig) {
throw new Error('invalid config, aborted'); return callback(new Error('aborted'));
} }
// Translate redis properties into redis object // Translate redis properties into redis object
@@ -63,25 +68,15 @@ function saveDatabaseConfig(config, databaseConfig) {
username: databaseConfig['mongo:username'], username: databaseConfig['mongo:username'],
password: databaseConfig['mongo:password'], password: databaseConfig['mongo:password'],
database: databaseConfig['mongo:database'], 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 { } else {
throw new Error(`unknown database : ${config.database}`); return callback(new Error('unknown database : ' + config.database));
} }
const allQuestions = questions.redis.concat(questions.mongo).concat(questions.postgres); var allQuestions = questions.redis.concat(questions.mongo);
for (let x = 0; x < allQuestions.length; x += 1) { for (var x = 0; x < allQuestions.length; x += 1) {
delete config[allQuestions[x].name]; delete config[allQuestions[x].name];
} }
return config; callback(null, config);
} }

View File

@@ -1,46 +0,0 @@
#!/bin/bash
export CONFIG_DIR="${CONFIG_DIR:-/opt/config}"
export CONFIG=$CONFIG_DIR/config.json
export FORCE_BUILD_BEFORE_START="${FORCE_BUILD_BEFORE_START:-false}"
# Supported verbs: install (web install), setup (interactive CLI session). Default: web install
# TODO: constraint it using a hash set (or hash table)
export NODEBB_INIT_VERB="${NODEBB_INIT_VERB:-install}"
# Setup variable for backward compatibility, default: <empty>
export SETUP="${SETUP:-}"
mkdir -p $CONFIG_DIR
# if the folder is mounted as a volume this can fail, the check below is to ensure there is still write access
chmod -fR 760 $CONFIG_DIR 2> /dev/null
if [[ ! -w $CONFIG_DIR ]]; then
echo "panic: no write permission for $CONFIG_DIR"
exit 1
fi
[[ -f $CONFIG_DIR/package.json ]] || cp install/package.json $CONFIG_DIR/package.json
[[ -f $CONFIG_DIR/package-lock.json ]] || touch $CONFIG_DIR/package-lock.json
ln -fs $CONFIG_DIR/package.json package.json
ln -fs $CONFIG_DIR/package-lock.json package-lock.json
npm install --omit=dev
if [[ -n $SETUP ]]; then
echo "Setup environmental variable detected"
echo "Starting setup session"
./nodebb setup --config=$CONFIG
elif [ -f $CONFIG ]; then
echo "Config file exist at $CONFIG, assuming it is a valid config"
echo "Starting forum"
if [ "$FORCE_BUILD_BEFORE_START" = true ]; then
./nodebb build --config=$CONFIG
fi
./nodebb start --config=$CONFIG
else
echo "Config file not found at $CONFIG"
echo "Starting installation session"
./nodebb "${NODEBB_INIT_VERB}" --config=$CONFIG
fi

View File

@@ -1 +0,0 @@
db.createUser( { user: 'nodebb', pwd: 'nodebb', roles: [ { role: 'readWrite', db: 'nodebb' }, { role: 'clusterMonitor', db: 'admin' } ] } )

View File

@@ -1,21 +0,0 @@
{
"mongo": {
"host": "mongo",
"port": 27017,
"database": "nodebb",
"username": "nodebb",
"password": "nodebb"
},
"redis": {
"host": "redis",
"port": 6379,
"database": 0
},
"postgres": {
"host": "postgres",
"port": 5432,
"database": "nodebb",
"username": "nodebb",
"password": "nodebb"
}
}

View File

@@ -1,198 +0,0 @@
{
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "3.5.1",
"homepage": "https://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",
"@fontsource/inter": "5.0.15",
"@fontsource/poppins": "5.0.8",
"@fortawesome/fontawesome-free": "6.4.2",
"@isaacs/ttlcache": "1.4.1",
"@popperjs/core": "2.11.8",
"ace-builds": "1.31.2",
"archiver": "6.0.1",
"async": "3.2.5",
"autoprefixer": "10.4.16",
"bcryptjs": "2.4.3",
"benchpressjs": "2.5.1",
"body-parser": "1.20.2",
"bootbox": "6.0.0",
"bootstrap": "5.3.2",
"bootswatch": "5.3.2",
"chalk": "4.1.2",
"chart.js": "4.4.0",
"cli-graph": "3.2.2",
"clipboard": "2.0.11",
"colors": "1.4.0",
"commander": "11.1.0",
"compare-versions": "6.1.0",
"compression": "1.7.4",
"connect-flash": "0.1.1",
"connect-mongo": "5.1.0",
"connect-multiparty": "2.2.0",
"connect-pg-simple": "9.0.1",
"connect-redis": "7.1.0",
"cookie-parser": "1.4.6",
"cron": "3.1.6",
"cropperjs": "1.6.1",
"csrf-sync": "4.0.1",
"daemon": "1.1.0",
"diff": "5.1.0",
"esbuild": "0.19.5",
"express": "4.18.2",
"express-session": "1.17.3",
"express-useragent": "1.0.15",
"file-loader": "6.2.0",
"fs-extra": "11.1.1",
"graceful-fs": "4.2.11",
"helmet": "7.1.0",
"html-to-text": "9.0.5",
"imagesloaded": "5.0.0",
"ipaddr.js": "2.1.0",
"jquery": "3.7.1",
"jquery-deserialize": "2.0.0",
"jquery-form": "4.3.0",
"jquery-serializeobject": "1.0.0",
"jquery-ui": "1.13.2",
"jsesc": "3.0.2",
"json2csv": "5.0.7",
"jsonwebtoken": "9.0.2",
"lodash": "4.17.21",
"logrotate-stream": "0.2.9",
"lru-cache": "10.0.2",
"mime": "3.0.0",
"mkdirp": "3.0.1",
"mongodb": "6.3.0",
"morgan": "1.10.0",
"mousetrap": "1.6.5",
"multiparty": "4.2.3",
"nconf": "0.12.1",
"nodebb-plugin-2factor": "7.4.0",
"nodebb-plugin-composer-default": "10.2.27",
"nodebb-plugin-dbsearch": "6.2.3",
"nodebb-plugin-emoji": "5.1.13",
"nodebb-plugin-emoji-android": "4.0.0",
"nodebb-plugin-markdown": "12.2.5",
"nodebb-plugin-mentions": "4.3.7",
"nodebb-plugin-ntfy": "1.7.3",
"nodebb-plugin-spam-be-gone": "2.2.0",
"nodebb-rewards-essentials": "1.0.0",
"nodebb-theme-harmony": "1.1.99",
"nodebb-theme-lavender": "7.1.5",
"nodebb-theme-peace": "2.1.25",
"nodebb-theme-persona": "13.2.47",
"nodebb-widget-essentials": "7.0.14",
"nodemailer": "6.9.7",
"nprogress": "0.2.0",
"passport": "0.6.0",
"passport-http-bearer": "1.0.1",
"passport-local": "1.0.0",
"pg": "8.11.3",
"pg-cursor": "2.10.3",
"postcss": "8.4.31",
"postcss-clean": "1.2.0",
"progress-webpack-plugin": "1.0.16",
"prompt": "1.3.0",
"ioredis": "5.3.2",
"request": "2.88.2",
"request-promise-native": "1.0.9",
"rimraf": "5.0.5",
"rss": "1.2.2",
"rtlcss": "4.1.1",
"sanitize-html": "2.11.0",
"sass": "1.69.5",
"semver": "7.5.4",
"serve-favicon": "2.5.0",
"sharp": "0.32.6",
"sitemap": "7.1.1",
"socket.io": "4.7.2",
"socket.io-client": "4.7.2",
"@socket.io/redis-adapter": "8.2.1",
"sortablejs": "1.15.0",
"spdx-license-list": "6.8.0",
"spider-detector": "2.0.1",
"terser-webpack-plugin": "5.3.9",
"textcomplete": "0.18.2",
"textcomplete.contenteditable": "0.1.1",
"timeago": "1.6.7",
"tinycon": "0.6.8",
"toobusy-js": "0.5.1",
"validator": "13.11.0",
"webpack": "5.89.0",
"webpack-merge": "5.10.0",
"winston": "3.11.0",
"workerpool": "8.0.0",
"xml": "1.0.1",
"xregexp": "5.1.1",
"yargs": "17.7.2",
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@apidevtools/swagger-parser": "10.1.0",
"@commitlint/cli": "18.4.2",
"@commitlint/config-angular": "18.4.2",
"coveralls": "3.1.1",
"eslint": "8.54.0",
"eslint-config-nodebb": "0.2.1",
"eslint-plugin-import": "2.29.0",
"grunt": "1.6.1",
"grunt-contrib-watch": "1.1.0",
"husky": "8.0.3",
"jsdom": "22.1.0",
"lint-staged": "15.1.0",
"mocha": "10.2.0",
"mocha-lcov-reporter": "1.3.0",
"mockdate": "3.0.5",
"nyc": "15.1.0",
"smtp-server": "3.13.0"
},
"optionalDependencies": {
"sass-embedded": "1.69.5"
},
"resolutions": {
"*/jquery": "3.7.1"
},
"bugs": {
"url": "https://github.com/NodeBB/NodeBB/issues"
},
"engines": {
"node": ">=16"
},
"maintainers": [
{
"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,103 +1,56 @@
'use strict'; 'use strict';
const winston = require('winston'); var winston = require('winston');
const express = require('express'); var express = require('express');
const bodyParser = require('body-parser'); var bodyParser = require('body-parser');
const fs = require('fs'); var fs = require('fs');
const path = require('path'); var path = require('path');
const childProcess = require('child_process'); var less = require('less');
var async = require('async');
var uglify = require('uglify-js');
var nconf = require('nconf');
var app = express();
var server;
const webpack = require('webpack'); winston.add(winston.transports.File, {
const nconf = require('nconf'); filename: 'logs/webinstall.log',
colorize: true,
timestamp: function () {
var date = new Date();
return date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0, 5) + ' [' + global.process.pid + ']';
},
level: 'verbose',
});
const Benchpress = require('benchpressjs'); var web = {};
const { mkdirp } = require('mkdirp'); var scripts = [
const { paths } = require('../src/constants'); 'public/vendor/xregexp/xregexp.js',
const sass = require('../src/utils').getSass(); 'public/vendor/xregexp/unicode/unicode-base.js',
'public/src/utils.js',
const app = express(); 'public/src/installer/install.js',
let server;
const formats = [
winston.format.colorize(),
]; ];
const timestampFormat = winston.format((info) => { web.install = function (port) {
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;
let timeStart = 0;
const totalTime = 1000 * 60 * 3;
const viewsDir = path.join(paths.baseDir, 'build/public/templates');
web.install = async function (port) {
port = port || 4567; 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(express.static('public', {}));
app.use('/assets', express.static(path.join(__dirname, '../build/public'), {})); app.engine('tpl', require('templates.js').__express);
app.engine('tpl', (filepath, options, callback) => {
filepath = filepath.replace(/\.tpl$/, '.js');
Benchpress.__express(filepath, options, callback);
});
app.set('view engine', 'tpl'); app.set('view engine', 'tpl');
app.set('views', viewsDir); app.set('views', path.join(__dirname, '../src/views'));
app.use(bodyParser.urlencoded({ app.use(bodyParser.urlencoded({
extended: true, extended: true,
})); }));
try {
await Promise.all([ async.parallel([compileLess, compileJS], function () {
compileTemplate(),
compileSass(),
runWebpack(),
copyCSS(),
loadDefaults(),
]);
setupRoutes(); setupRoutes();
launchExpress(port); 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) { 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); winston.info('Web installer listening on http://%s:%s', '0.0.0.0', port);
}); });
} }
@@ -105,215 +58,103 @@ function launchExpress(port) {
function setupRoutes() { function setupRoutes() {
app.get('/', welcome); app.get('/', welcome);
app.post('/', install); app.post('/', install);
app.get('/testdb', testDatabase); app.post('/launch', launch);
app.get('/ping', ping);
app.get('/sping', ping);
}
async function testDatabase(req, res) {
let db;
try {
const keys = Object.keys(req.query);
const dbName = keys[0].split(':')[0];
db = require(`../src/database/${dbName}`);
const opts = {};
keys.forEach((key) => {
opts[key.replace(`${dbName}:`, '')] = req.query[key];
});
await db.init(opts);
const global = await db.getObject('global');
await db.close();
res.json({ success: 1, dbfull: !!global });
} catch (err) {
res.json({ error: err.stack });
}
}
function ping(req, res) {
res.status(200).send(req.path === '/sping' ? 'healthy' : '200');
} }
function welcome(req, res) { function welcome(req, res) {
const dbs = ['mongo', 'redis', 'postgres']; var dbs = ['redis', 'mongo'];
const databases = dbs.map((databaseName) => { var databases = dbs.map(function (el) {
const questions = require(`../src/database/${databaseName}`).questions.filter(question => question && !question.hideOnWebInstall);
return { return {
name: databaseName, name: el,
questions: questions, questions: require('../src/database/' + el).questions,
}; };
}); });
const defaults = require('./data/defaults.json'); var defaults = require('./data/defaults');
res.render('install/index', { res.render('install/index', {
url: nconf.get('url') || (`${req.protocol}://${req.get('host')}`),
launchUrl: launchUrl,
skipGeneralSetup: !!nconf.get('url'),
databases: databases, databases: databases,
skipDatabaseSetup: !!nconf.get('database'), skipDatabaseSetup: !!nconf.get('database'),
error: error, error: !!res.locals.error,
success: success, success: !!res.locals.success,
values: req.body, values: req.body,
minimumPasswordLength: defaults.minimumPasswordLength, minimumPasswordLength: defaults.minimumPasswordLength,
minimumPasswordStrength: defaults.minimumPasswordStrength,
installing: installing,
percentInstalled: installing ? ((Date.now() - timeStart) / totalTime * 100).toFixed(2) : 0,
}); });
} }
function install(req, res) { function install(req, res) {
if (installing) { for (var i in req.body) {
return welcome(req, res); if (req.body.hasOwnProperty(i) && !process.env.hasOwnProperty(i)) {
process.env[i.replace(':', '__')] = req.body[i];
}
} }
timeStart = Date.now();
req.setTimeout(0);
installing = true;
const database = nconf.get('database') || req.body.database || 'mongo'; var child = require('child_process').fork('app', ['--setup'], {
const setupEnvVars = { env: process.env,
...process.env,
CONFIG: nconf.get('config'),
NODEBB_CONFIG: nconf.get('config'),
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');
launchUrl = setupEnvVars.NODEBB_URL;
const child = require('child_process').fork('app', ['--setup'], {
env: setupEnvVars,
}); });
child.on('error', (err) => {
error = true;
success = false;
winston.error(err.stack);
});
child.on('close', (data) => {
success = data === 0;
error = data !== 0;
launch();
});
welcome(req, res);
}
async function launch() { child.on('close', function (data) {
try { if (data === 0) {
server.close(); res.locals.success = true;
let child;
if (!nconf.get('launchCmd')) {
child = childProcess.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 { } else {
// Use launchCmd instead, if specified res.locals.error = true;
child = childProcess.exec(nconf.get('launchCmd'), {
detached: true,
stdio: ['ignore', 'ignore', 'ignore'],
});
} }
const filesToDelete = [ welcome(req, res);
path.join(__dirname, '../public', 'installer.css'), });
path.join(__dirname, '../public', 'bootstrap.min.css'), }
path.join(__dirname, '../build/public', 'installer.min.js'),
]; function launch(req, res) {
try { res.json({});
await Promise.all( server.close();
filesToDelete.map(
filename => fs.promises.unlink(filename) var child = require('child_process').spawn('node', ['loader.js'], {
) detached: true,
); stdio: ['ignore', 'ignore', 'ignore'],
} catch (err) { });
console.log(err.stack);
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');
async.parallel([
async.apply(fs.unlink(path.join(__dirname, '../public/installer.css'))),
async.apply(fs.unlink(path.join(__dirname, '../public/installer.min.js'))),
], function (err) {
if (err) {
winston.warn('Unable to remove installer files');
} }
child.unref(); child.unref();
process.exit(0); process.exit(0);
} catch (err) {
winston.error(err.stack);
throw err;
}
}
// 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');
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 compileSass() {
try {
const installSrc = path.join(__dirname, '../public/scss/install.scss');
const style = await fs.promises.readFile(installSrc);
const scssOutput = sass.compileString(String(style), {
loadPaths: [
path.join(__dirname, '../public/scss'),
],
});
await fs.promises.writeFile(path.join(__dirname, '../public/installer.css'), scssOutput.css.toString());
} catch (err) {
winston.error(`Unable to compile SASS: \n${err.stack}`);
throw err;
}
}
async function copyCSS() {
await fs.promises.copyFile(
path.join(__dirname, '../node_modules/bootstrap/dist/css/bootstrap.min.css'),
path.join(__dirname, '../public/bootstrap.min.css'),
);
}
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,
}); });
} }
function compileLess(callback) {
fs.readFile(path.join(__dirname, '../public/less/install.less'), function (err, style) {
if (err) {
return winston.error('Unable to read LESS install file: ', err);
}
less.render(style.toString(), function (err, css) {
if (err) {
return winston.error('Unable to compile LESS: ', err);
}
fs.writeFile(path.join(__dirname, '../public/installer.css'), css.css, callback);
});
});
}
function compileJS(callback) {
var scriptPath = path.join(__dirname, '..');
var result = uglify.minify(scripts.map(function (script) {
return path.join(scriptPath, script);
}));
fs.writeFile(path.join(__dirname, '../public/installer.min.js'), result.code, callback);
}
module.exports = web;

242
loader.js
View File

@@ -1,61 +1,73 @@
'use strict'; 'use strict';
const nconf = require('nconf'); var nconf = require('nconf');
const fs = require('fs'); var fs = require('fs');
const url = require('url'); var url = require('url');
const path = require('path'); var path = require('path');
const { fork } = require('child_process'); var fork = require('child_process').fork;
const logrotate = require('logrotate-stream'); var async = require('async');
const { mkdirp } = require('mkdirp'); var logrotate = require('logrotate-stream');
const file = require('./src/file'); var file = require('./src/file');
const pkg = require('./package.json'); var pkg = require('./package.json');
const pathToConfig = path.resolve(__dirname, process.env.CONFIG || 'config.json');
nconf.argv().env().file({ nconf.argv().env().file({
file: pathToConfig, file: path.join(__dirname, 'config.json'),
}); });
const pidFilePath = path.join(__dirname, 'pidfile'); var pidFilePath = path.join(__dirname, 'pidfile');
var outputLogFilePath = path.join(__dirname, 'logs/output.log');
var output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compress: true });
var silent = nconf.get('silent') === 'false' ? false : nconf.get('silent') !== false;
var numProcs;
var workers = [];
var Loader = {
timesStarted: 0,
};
var appPath = path.join(__dirname, 'app.js');
const outputLogFilePath = path.join(__dirname, nconf.get('logFile') || 'logs/output.log'); Loader.init = function (callback) {
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 = {};
const appPath = path.join(__dirname, 'app.js');
Loader.init = function () {
if (silent) { if (silent) {
console.log = (...args) => { console.log = function () {
output.write(`${args.join(' ')}\n`); var args = Array.prototype.slice.call(arguments);
output.write(args.join(' ') + '\n');
}; };
} }
process.on('SIGHUP', Loader.restart); process.on('SIGHUP', Loader.restart);
process.on('SIGUSR2', Loader.reload);
process.on('SIGTERM', Loader.stop); process.on('SIGTERM', Loader.stop);
callback();
}; };
Loader.displayStartupMessages = function () { Loader.displayStartupMessages = function (callback) {
console.log(''); 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 program comes with ABSOLUTELY NO WARRANTY.');
console.log('This is free software, and you are welcome to redistribute it under certain conditions.'); 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('For the full license, please visit: http://www.gnu.org/copyleft/gpl.html');
console.log(''); console.log('');
callback();
}; };
Loader.addWorkerEvents = function (worker) { Loader.addWorkerEvents = function (worker) {
worker.on('exit', (code, signal) => { worker.on('exit', function (code, signal) {
console.log(`[cluster] Child Process (${worker.pid}) has exited (code: ${code}, signal: ${signal})`); if (code !== 0) {
if (Loader.timesStarted < numProcs * 3) {
Loader.timesStarted += 1;
if (Loader.crashTimer) {
clearTimeout(Loader.crashTimer);
}
Loader.crashTimer = setTimeout(function () {
Loader.timesStarted = 0;
}, 10000);
} else {
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 + ')');
if (!(worker.suicide || code === 0)) { if (!(worker.suicide || code === 0)) {
console.log('[cluster] Spinning up another process...'); console.log('[cluster] Spinning up another process...');
@@ -63,52 +75,48 @@ Loader.addWorkerEvents = function (worker) {
} }
}); });
worker.on('message', (message) => { worker.on('message', function (message) {
if (message && typeof message === 'object' && message.action) { if (message && typeof message === 'object' && message.action) {
switch (message.action) { switch (message.action) {
case 'restart': case 'restart':
console.log('[cluster] Restarting...'); console.log('[cluster] Restarting...');
Loader.restart(); Loader.restart();
break; break;
case 'pubsub': case 'reload':
workers.forEach((w) => { console.log('[cluster] Reloading...');
w.send(message); Loader.reload();
}); break;
break;
case 'socket.io':
workers.forEach((w) => {
if (w !== worker) {
w.send(message);
}
});
break;
} }
} }
}); });
}; };
Loader.start = function () { Loader.start = function (callback) {
numProcs = getPorts().length; 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 += 1) {
forkWorker(x, x === 0); forkWorker(x, x === 0);
} }
if (callback) {
callback();
}
}; };
function forkWorker(index, isPrimary) { function forkWorker(index, isPrimary) {
const ports = getPorts(); var ports = getPorts();
const args = []; var args = [];
if (!ports[index]) { if (!ports[index]) {
return console.log(`[cluster] invalid port for worker : ${index} ports: ${ports.length}`); return console.log('[cluster] invalid port for worker : ' + index + ' ports: ' + ports.length);
} }
process.env.isPrimary = isPrimary; process.env.isPrimary = isPrimary;
process.env.isCluster = nconf.get('isCluster') || ports.length > 1; process.env.isCluster = ports.length > 1;
process.env.port = ports[index]; process.env.port = ports[index];
const worker = fork(appPath, args, { var worker = fork(appPath, args, {
silent: silent, silent: silent,
env: process.env, env: process.env,
}); });
@@ -121,20 +129,20 @@ function forkWorker(index, isPrimary) {
Loader.addWorkerEvents(worker); Loader.addWorkerEvents(worker);
if (silent) { if (silent) {
const output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compress: true }); var output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compress: true });
worker.stdout.pipe(output); worker.stdout.pipe(output);
worker.stderr.pipe(output); worker.stderr.pipe(output);
} }
} }
function getPorts() { function getPorts() {
const _url = nconf.get('url'); var _url = nconf.get('url');
if (!_url) { if (!_url) {
console.log('[cluster] url is undefined, please check your config.json'); console.log('[cluster] url is undefined, please check your config.json');
process.exit(); process.exit();
} }
const urlObject = url.parse(_url); var urlObject = url.parse(_url);
let port = nconf.get('PORT') || nconf.get('port') || urlObject.port || 4567; var port = nconf.get('port') || nconf.get('PORT') || urlObject.port || 4567;
if (!Array.isArray(port)) { if (!Array.isArray(port)) {
port = [port]; port = [port];
} }
@@ -144,89 +152,93 @@ function getPorts() {
Loader.restart = function () { Loader.restart = function () {
killWorkers(); killWorkers();
var pathToConfig = path.join(__dirname, '/config.json');
nconf.remove('file'); nconf.remove('file');
nconf.use('file', { file: pathToConfig }); nconf.use('file', { file: pathToConfig });
fs.readFile(pathToConfig, { encoding: 'utf-8' }, (err, configFile) => { fs.readFile(pathToConfig, { encoding: 'utf-8' }, function (err, configFile) {
if (err) { if (err) {
console.error('Error reading config'); console.log('Error reading config : ' + err.message);
throw err; process.exit();
} }
const conf = JSON.parse(configFile); var conf = JSON.parse(configFile);
nconf.stores.env.readOnly = false; nconf.stores.env.readOnly = false;
nconf.set('url', conf.url); nconf.set('url', conf.url);
nconf.stores.env.readOnly = true; nconf.stores.env.readOnly = true;
if (process.env.url !== conf.url) {
process.env.url = conf.url;
}
Loader.start(); Loader.start();
}); });
}; };
Loader.reload = function () {
workers.forEach(function (worker) {
worker.send({
action: 'reload',
});
});
};
Loader.stop = function () { Loader.stop = function () {
killWorkers(); killWorkers();
// Clean up the pidfile // Clean up the pidfile
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) { fs.unlinkSync(pidFilePath);
fs.unlinkSync(pidFilePath);
}
}; };
function killWorkers() { function killWorkers() {
workers.forEach((worker) => { workers.forEach(function (worker) {
worker.suicide = true; worker.suicide = true;
worker.kill(); worker.kill();
}); });
} }
fs.open(pathToConfig, 'r', (err) => { Loader.notifyWorkers = function (msg, worker_pid) {
if (err) { worker_pid = parseInt(worker_pid, 10);
// No config detected, kickstart web installer workers.forEach(function (worker) {
fork('app'); if (parseInt(worker.pid, 10) !== worker_pid) {
return;
}
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
if (file.existsSync(pidFilePath)) {
let pid = 0;
try { try {
pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' }); worker.send(msg);
if (pid) { } catch (e) {
process.kill(pid, 0); console.log('[cluster/notifyWorkers] Failed to reach pid ' + worker_pid);
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;
}
} }
} }
});
};
require('daemon')({ fs.open(path.join(__dirname, 'config.json'), 'r', function (err) {
stdout: process.stdout, if (!err) {
stderr: process.stderr, if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
cwd: process.cwd(), if (file.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.writeFileSync(pidFilePath, process.pid);
}
async.series([
Loader.init,
Loader.displayStartupMessages,
Loader.start,
], function (err) {
if (err) {
console.log('[loader] Error during startup: ' + err.message);
}
}); });
} else {
fs.writeFileSync(pidFilePath, String(process.pid)); // No config detected, kickstart web installer
} require('child_process').fork('app');
try {
Loader.init();
Loader.displayStartupMessages();
Loader.start();
} catch (err) {
console.error('[loader] Error during startup');
throw err;
} }
}); });

501
nodebb
View File

@@ -2,4 +2,503 @@
'use strict'; 'use strict';
require('./src/cli'); var cproc;
var args;
var fs;
var path;
var request;
var semver;
var prompt;
var async;
try {
require('colors');
cproc = require('child_process');
args = require('minimist')(process.argv.slice(2));
fs = require('fs');
path = require('path');
request = require('request');
semver = require('semver');
prompt = require('prompt');
async = require('async');
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
process.stdout.write('NodeBB could not be started because it\'s dependencies have not been installed.\n');
process.stdout.write('Please ensure that you have executed "npm install --production" prior to running NodeBB.\n\n');
process.stdout.write('For more information, please see: https://docs.nodebb.org/en/latest/installing/os.html\n\n');
process.stdout.write('Could not start: ' + e.code + '\n');
process.exit(1);
}
}
var loaderPath = path.join(__dirname, 'loader.js');
var appPath = path.join(__dirname, 'app.js');
if (args.dev) {
process.env.NODE_ENV = 'development';
}
function getRunningPid(callback) {
fs.readFile(path.join(__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);
}
});
}
function getCurrentVersion(callback) {
fs.readFile(path.join(__dirname, 'package.json'), { encoding: 'utf-8' }, function (err, pkg) {
if (err) {
return callback(err);
}
try {
pkg = JSON.parse(pkg);
return callback(null, pkg.version);
} catch (err) {
return callback(err);
}
});
}
function fork(args) {
return cproc.fork(appPath, args, {
cwd: __dirname,
silent: false,
});
}
function getInstalledPlugins(callback) {
async.parallel({
files: async.apply(fs.readdir, path.join(__dirname, 'node_modules')),
deps: async.apply(fs.readFile, path.join(__dirname, 'package.json'), { encoding: 'utf-8' }),
}, function (err, payload) {
if (err) {
return callback(err);
}
var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w-]+$/;
var moduleName;
var isGitRepo;
payload.files = payload.files.filter(function (file) {
return isNbbModule.test(file);
});
try {
payload.deps = JSON.parse(payload.deps).dependencies;
payload.bundled = [];
payload.installed = [];
} catch (err) {
return callback(err);
}
for (moduleName in payload.deps) {
if (isNbbModule.test(moduleName)) {
payload.bundled.push(moduleName);
}
}
// Whittle down deps to send back only extraneously installed plugins/themes/etc
payload.files.forEach(function (moduleName) {
try {
fs.accessSync(path.join(__dirname, 'node_modules/' + moduleName, '.git'));
isGitRepo = true;
} catch (e) {
isGitRepo = false;
}
if (
payload.files.indexOf(moduleName) !== -1 && // found in `node_modules/`
payload.bundled.indexOf(moduleName) === -1 && // not found in `package.json`
!fs.lstatSync(path.join(__dirname, 'node_modules/' + moduleName)).isSymbolicLink() && // is not a symlink
!isGitRepo // .git/ does not exist, so it is not a git repository
) {
payload.installed.push(moduleName);
}
});
getModuleVersions(payload.installed, callback);
});
}
function getModuleVersions(modules, callback) {
var versionHash = {};
async.eachLimit(modules, 50, function (module, next) {
fs.readFile(path.join(__dirname, 'node_modules/' + module + '/package.json'), { encoding: 'utf-8' }, function (err, pkg) {
if (err) {
return next(err);
}
try {
pkg = JSON.parse(pkg);
versionHash[module] = pkg.version;
next();
} catch (err) {
next(err);
}
});
}, function (err) {
callback(err, versionHash);
});
}
function checkPlugins(standalone, callback) {
if (standalone) {
process.stdout.write('Checking installed plugins and themes for updates... ');
}
async.waterfall([
async.apply(async.parallel, {
plugins: async.apply(getInstalledPlugins),
version: async.apply(getCurrentVersion),
}),
function (payload, next) {
var toCheck = Object.keys(payload.plugins);
if (!toCheck.length) {
process.stdout.write('OK'.green + '\n'.reset);
return next(null, []); // no extraneous plugins installed
}
request({
method: 'GET',
url: 'https://packages.nodebb.org/api/v1/suggest?version=' + payload.version + '&package[]=' + toCheck.join('&package[]='),
json: true,
}, function (err, res, body) {
if (err) {
process.stdout.write('error'.red + '\n'.reset);
return next(err);
}
process.stdout.write('OK'.green + '\n'.reset);
if (!Array.isArray(body) && toCheck.length === 1) {
body = [body];
}
var current;
var suggested;
var upgradable = body.map(function (suggestObj) {
current = payload.plugins[suggestObj.package];
suggested = suggestObj.version;
if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) {
return {
name: suggestObj.package,
current: current,
suggested: suggested,
};
}
return null;
}).filter(Boolean);
next(null, upgradable);
});
},
], callback);
}
function upgradePlugins(callback) {
var standalone = false;
if (typeof callback !== 'function') {
callback = function () {};
standalone = true;
}
checkPlugins(standalone, function (err, found) {
if (err) {
process.stdout.write('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability\n'.reset);
return callback(err);
}
if (found && found.length) {
process.stdout.write('\nA total of ' + String(found.length).bold + ' package(s) can be upgraded:\n');
found.forEach(function (suggestObj) {
process.stdout.write(' * '.yellow + suggestObj.name.reset + ' (' + suggestObj.current.yellow + ' -> '.reset + suggestObj.suggested.green + ')\n'.reset);
});
process.stdout.write('\n');
} else {
if (standalone) {
process.stdout.write('\nAll packages up-to-date!'.green + '\n'.reset);
}
return callback();
}
prompt.message = '';
prompt.delimiter = '';
prompt.start();
prompt.get({
name: 'upgrade',
description: 'Proceed with upgrade (y|n)?'.reset,
type: 'string',
}, function (err, result) {
if (err) {
return callback(err);
}
if (['y', 'Y', 'yes', 'YES'].indexOf(result.upgrade) !== -1) {
process.stdout.write('\nUpgrading packages...');
var args = ['i'];
found.forEach(function (suggestObj) {
args.push(suggestObj.name + '@' + suggestObj.suggested);
});
cproc.execFile((process.platform === 'win32') ? 'npm.cmd' : 'npm', args, { stdio: 'ignore' }, function (err) {
if (!err) {
process.stdout.write(' OK\n'.green);
}
callback(err);
});
} else {
process.stdout.write('\nPackage upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade-plugins'.green + '".\n'.reset);
callback();
}
});
});
}
var commands = {
status: {
description: 'View the status of the NodeBB server',
usage: 'Usage: ' + './nodebb status'.yellow,
handler: function () {
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);
}
});
},
},
start: {
description: 'Start the NodeBB server',
usage: 'Usage: ' + './nodebb start'.yellow,
handler: function () {
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(loaderPath, {
env: process.env,
});
},
},
stop: {
description: 'Stop the NodeBB server',
usage: 'Usage: ' + './nodebb stop'.yellow,
handler: function () {
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');
}
});
},
},
restart: {
description: 'Restart the NodeBB server',
usage: 'Usage: ' + './nodebb restart'.yellow,
handler: function () {
getRunningPid(function (err, pid) {
if (!err) {
process.kill(pid, 'SIGHUP');
process.stdout.write('\nRestarting NodeBB\n'.bold);
} else {
process.stdout.write('NodeBB could not be restarted, as a running instance could not be found.\n');
}
});
},
},
log: {
description: 'Open the output log (useful for debugging)',
usage: 'Usage: ' + './nodebb log'.yellow,
handler: function () {
process.stdout.write('\nHit '.red + 'Ctrl-C '.bold + 'to exit'.red);
process.stdout.write('\n\n'.reset);
cproc.spawn('tail', ['-F', './logs/output.log'], {
cwd: __dirname,
stdio: 'inherit',
});
},
},
slog: {
description: 'Start the NodeBB server and view the live output log',
usage: 'Usage: ' + './nodebb slog'.yellow,
handler: function () {
process.stdout.write('\nStarting NodeBB with logging output\n'.bold);
process.stdout.write('\nHit '.red + 'Ctrl-C '.bold + 'to exit'.red);
process.stdout.write('\n\n'.reset);
// Spawn a new NodeBB process
cproc.fork(loaderPath, {
env: process.env,
});
cproc.spawn('tail', ['-F', './logs/output.log'], {
cwd: __dirname,
stdio: 'inherit',
});
},
},
dev: {
description: 'Start NodeBB in verbose development mode',
usage: 'Usage: ' + './nodebb dev'.yellow,
handler: function () {
process.env.NODE_ENV = 'development';
cproc.fork(loaderPath, ['--no-daemon', '--no-silent'], {
env: process.env,
});
},
},
build: {
description: 'Compile static assets (CSS, Javascript, etc)',
usage: 'Usage: ' + './nodebb build'.yellow + ' [js,clientCSS,acpCSS,tpl,lang]'.red + '\n' +
' e.g. ' + './nodebb build js,tpl'.yellow + '\tbuilds JS and templates\n' +
' ' + './nodebb build'.yellow + '\t\tbuilds all targets\n',
handler: function () {
var arr = ['--build'].concat(process.argv.slice(3));
fork(arr);
},
},
setup: {
description: 'Run the NodeBB setup script',
usage: 'Usage: ' + './nodebb setup'.yellow,
handler: function () {
var arr = ['--setup'].concat(process.argv.slice(3));
fork(arr);
},
},
reset: {
description: 'Disable plugins and restore the default theme',
usage: 'Usage: ' + './nodebb reset '.yellow + '{-t|-p|-w|-s|-a}'.red + '\n' +
' -t <theme>\tuse specified theme\n' +
' -p <plugin>\tdisable specified plugin\n' +
'\n' +
' -t\t\tuse default theme\n' +
' -p\t\tdisable all but core plugins\n' +
' -w\t\twidgets\n' +
' -s\t\tsettings\n' +
' -a\t\tall of the above\n',
handler: function () {
var arr = ['--reset'].concat(process.argv.slice(3));
fork(arr);
},
},
activate: {
description: 'Activate a plugin for the next startup of NodeBB',
usage: 'Usage: ' + './nodebb activate <plugin>'.yellow,
handler: function () {
var name = args._[1];
if (!name) {
process.stdout.write(commands.activate.usage + '\n');
process.exit();
}
if (name.startsWith('nodebb-theme')) {
fork(['--reset', '-t', name]);
return;
}
var arr = ['--activate=' + name].concat(process.argv.slice(4));
fork(arr);
},
},
plugins: {
description: 'List all installed plugins',
usage: 'Usage: ' + './nodebb plugins'.yellow,
handler: function () {
var arr = ['--plugins'].concat(process.argv.slice(3));
fork(arr);
},
},
upgrade: {
description: 'Run NodeBB upgrade scripts, ensure packages are up-to-date',
usage: 'Usage: ' + './nodebb upgrade'.yellow,
handler: function () {
async.series([
function (next) {
process.stdout.write('1. '.bold + 'Bringing base dependencies up to date... '.yellow);
cproc.exec('npm i --production', { cwd: __dirname, stdio: 'ignore' }, next);
},
function (next) {
process.stdout.write('OK\n'.green);
process.stdout.write('2. '.bold + 'Checking installed plugins for updates... '.yellow);
upgradePlugins(next);
},
function (next) {
process.stdout.write('3. '.bold + 'Updating NodeBB data store schema...\n'.yellow);
var arr = ['--upgrade'].concat(process.argv.slice(3));
var upgradeProc = fork(arr);
upgradeProc.on('close', next);
},
], function (err) {
if (err) {
process.stdout.write('\nError'.red + ': ' + err.message + '\n');
} else {
var message = 'NodeBB Upgrade Complete!';
// some consoles will return undefined/zero columns, so just use 2 spaces in upgrade script if we can't get our column count
var columns = process.stdout.columns;
var spaces = columns ? new Array(Math.floor(columns / 2) - (message.length / 2) + 1).join(' ') : ' ';
process.stdout.write('OK\n'.green);
process.stdout.write('\n' + spaces + message.green.bold + '\n\n'.reset);
}
});
},
},
upgradePlugins: {
hidden: true,
description: '',
handler: function () {
upgradePlugins();
},
},
help: {
description: 'Display the help message for a given command',
usage: 'Usage: ' + './nodebb help <command>'.yellow,
handler: function () {
var command = commands[args._[1]];
if (command) {
process.stdout.write(command.description + '\n'.reset);
process.stdout.write(command.usage + '\n'.reset);
return;
}
var keys = Object.keys(commands).filter(function (key) {
return !commands[key].hidden;
});
process.stdout.write('\nWelcome to NodeBB\n\n'.bold);
process.stdout.write('Usage: ./nodebb {' + keys.join('|') + '}\n\n');
var usage = keys.map(function (key) {
var line = '\t' + key.yellow + (key.length < 8 ? '\t\t' : '\t');
return line + commands[key].description;
}).join('\n');
process.stdout.write(usage + '\n'.reset);
},
},
};
commands['upgrade-plugins'] = commands.upgradePlugins;
if (!commands[args._[0]]) {
commands.help.handler();
} else {
commands[args._[0]].handler();
}

View File

@@ -1 +1 @@
@echo off && cd %~dp0 && node ./nodebb %* node ./nodebb %*

139
package.json Normal file
View File

@@ -0,0 +1,139 @@
{
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "1.4.6",
"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 .",
"pretest": "npm run lint",
"test": "istanbul cover node_modules/mocha/bin/_mocha -- -R dot",
"coveralls": "istanbul cover _mocha --report lcovonly -- -R dot && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
},
"dependencies": {
"ace-builds": "^1.2.6",
"async": "~1.5.0",
"autoprefixer": "^6.2.3",
"bcryptjs": "~2.3.0",
"body-parser": "^1.9.0",
"bootstrap": "^3.3.7",
"chart.js": "^2.4.0",
"colors": "^1.1.0",
"compression": "^1.1.0",
"connect-ensure-login": "^0.1.1",
"connect-flash": "^0.1.1",
"connect-mongo": "1.3.2",
"connect-multiparty": "^2.0.0",
"connect-redis": "~3.1.0",
"cookie-parser": "^1.3.3",
"cron": "^1.0.5",
"cropperjs": "^0.8.1",
"csurf": "^1.6.1",
"daemon": "~1.1.0",
"express": "^4.14.0",
"express-session": "^1.8.2",
"express-useragent": "1.0.7",
"html-to-text": "2.1.3",
"ip": "1.1.3",
"jimp": "0.2.27",
"jquery": "^3.1.0",
"json-2-csv": "^2.0.22",
"less": "^2.0.0",
"logrotate-stream": "^0.2.3",
"lru-cache": "4.0.1",
"mime": "^1.3.4",
"minimist": "^1.1.1",
"mkdirp": "~0.5.0",
"mongodb": "2.2.25",
"morgan": "^1.3.2",
"mousetrap": "^1.5.3",
"nconf": "~0.8.2",
"nodebb-plugin-composer-default": "4.4.6",
"nodebb-plugin-dbsearch": "1.0.5",
"nodebb-plugin-emoji-extended": "1.1.1",
"nodebb-plugin-emoji-one": "1.1.5",
"nodebb-plugin-markdown": "7.1.1",
"nodebb-plugin-mentions": "1.1.3",
"nodebb-plugin-soundpack-default": "1.0.0",
"nodebb-plugin-spam-be-gone": "0.4.13",
"nodebb-rewards-essentials": "0.0.9",
"nodebb-theme-lavender": "4.0.0",
"nodebb-theme-persona": "4.2.10",
"nodebb-theme-vanilla": "5.2.1",
"nodebb-widget-essentials": "2.0.13",
"nodemailer": "2.6.4",
"nodemailer-sendmail-transport": "1.0.0",
"nodemailer-smtp-transport": "^2.4.1",
"passport": "^0.3.0",
"passport-local": "1.0.0",
"postcss": "^5.0.13",
"postcss-clean": "^1.0.0",
"promise-polyfill": "^6.0.2",
"prompt": "^1.0.0",
"redis": "~2.6.2",
"request": "^2.44.0",
"rimraf": "~2.5.0",
"rss": "^1.0.0",
"sanitize-html": "^1.13.0",
"semver": "^5.1.0",
"serve-favicon": "^2.1.5",
"sitemap": "^1.4.0",
"socket.io": "1.7.2",
"socket.io-client": "1.7.2",
"socket.io-redis": "3.1.0",
"socketio-wildcard": "~0.3.0",
"string": "^3.0.0",
"templates.js": "0.3.10",
"toobusy-js": "^0.5.1",
"uglify-js": "^2.6.0",
"underscore": "^1.8.3",
"underscore.deep": "^0.5.1",
"validator": "^6.1.0",
"winston": "^2.1.0",
"xml": "^1.0.1",
"xregexp": "~3.1.0",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"coveralls": "^2.11.14",
"eslint": "^3.12.0",
"eslint-config-airbnb-base": "^11.1.0",
"eslint-plugin-import": "^2.2.0",
"grunt": "~1.0.0",
"grunt-contrib-watch": "^1.0.0",
"istanbul": "^0.4.2",
"mocha": "~3.2.0",
"mocha-lcov-reporter": "^1.2.0",
"xmlhttprequest": "1.8.0",
"xmlhttprequest-ssl": "1.5.3"
},
"bugs": {
"url": "https://github.com/NodeBB/NodeBB/issues"
},
"engines": {
"node": ">=4"
},
"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 +1,58 @@
{ {
"extends": "nodebb/public" "globals": {
"app": true,
"io": true,
"socket": true,
"ajaxify": true,
"config": true,
"RELATIVE_PATH": true,
"utils": true,
"overrides": true,
"componentHandler": true,
"bootbox": true,
"templates": true,
"Visibility": true,
"Tinycon": true,
"Promise": true
},
"env": {
"jquery": true,
"amd": true,
"browser": true,
"es6": false
},
"rules": {
"no-dupe-class-members": "off",
"no-var": "off",
"object-shorthand": "off",
"prefer-arrow-callback": "off",
"prefer-spread": "off",
"prefer-reflect": "off",
"prefer-template": "off"
},
"parserOptions": {
"ecmaVersion": 5,
"ecmaFeatures": {
"arrowFunctions": false,
"classes": false,
"defaultParams": false,
"destructuring": false,
"experimentalObjectRestSpread": false,
"blockBindings": false,
"forOf": false,
"generators": false,
"globalReturn": false,
"jsx": false,
"modules": false,
"objectLiteralComputedProperties": false,
"objectLiteralDuplicateProperties": false,
"objectLiteralShorthandMethods": false,
"objectLiteralShorthandProperties": false,
"impliedStrict": false,
"restParams": false,
"spread": false,
"superInFunctions": false,
"templateStrings": false
}
}
} }

84
public/.jshintrc Normal file
View File

@@ -0,0 +1,84 @@
{
"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" : true, // true: Require all defined variables be used
"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
"globals": {
"app": true,
"io": true,
"socket": true,
"ajaxify": true,
"config": true,
"RELATIVE_PATH": true,
"utils": true,
"overrides": true,
"componentHandler": true,
"bootbox": true,
"templates": true,
"Visibility": true,
"Tinycon": true,
"require": true,
"define": true,
"ace": true,
"Sortable": true,
"Slideout": true,
"NProgress": true
},
"jquery": true,
"browser": true
}

View File

@@ -1,12 +1,13 @@
<html> <html>
<head> <head>
<title>Excessive Load Warning</title> <title>Excessive Load Warning</title>
<link href='https://fonts.googleapis.com/css?family=Ubuntu:400,500,700' rel='stylesheet' type='text/css'>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css"> <style type="text/css">
body { body {
background: #00A9EA; background: #00A9EA;
color: white; color: white;
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"; font-family: 'Ubuntu', sans-serif;
text-align: center; text-align: center;
-webkit-transform-style: preserve-3d; -webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d; -moz-transform-style: preserve-3d;

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

7
public/js-enabled.css Normal file
View File

@@ -0,0 +1,7 @@
/*
The following stylesheet is only included on pages that can execute javascript
*/
.page-topic [component="post/content"] img:not(.not-responsive):not([data-state]) {
display: none !important;
}

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://explore.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,3 +0,0 @@
# The files here are not meant to be edited directly
Please see the &rarr; [Internalization README](../README.md).

View File

@@ -1,18 +1,7 @@
{ {
"alert.confirm-rebuild-and-restart": "هل أنت متأكد إنك تريد إعادة بناء وتشغيل الـ NodeBB؟", "alert.confirm-reload": "Are you sure you wish to reload NodeBB?",
"alert.confirm-restart": "هل تريد بالتأكيد إعادة تشغيل NodeBB؟", "alert.confirm-restart": "Are you sure you wish to restart NodeBB?",
"acp-title": "لوحة تحكم إدارة NodeBB | %1", "acp-title": "%1 | NodeBB Admin Control Panel",
"settings-header-contents": "محتويات", "settings-header-contents": "Contents"
"changes-saved": "Changes Saved",
"changes-saved-message": "Your changes to the NodeBB configuration have been saved.",
"changes-not-saved": "لم يتم حفظ التغييرات",
"changes-not-saved-message": "حدثت مشكلة أثناء حفظ التغييرات في NodeBB. (%1)",
"save-changes": "Save changes",
"min": "Min:",
"max": "Max:",
"view": "View",
"edit": "Edit",
"add": "Add",
"select-icon": "Select Icon"
} }

View File

@@ -1,10 +1,11 @@
{ {
"cache": "Cache", "post-cache": "Post Cache",
"post-cache": "التخزين المؤقت للمشاركات", "posts-in-cache": "Posts in Cache",
"group-cache": "التخزين المؤقت للمجموعات", "average-post-size": "Average Post Size",
"local-cache": "تخزين مؤقت محلي", "length-to-max": "Length / Max",
"object-cache": "تخزين مؤقت للأشياء", "percent-full": "%1% Full",
"percent-full": "1% كاملة", "post-cache-size": "Post Cache Size",
"post-cache-size": "حجم التخزين المؤقت للمشاركات", "items-in-cache": "Items in Cache",
"items-in-cache": "العناصر في التخزين المؤقت" "control-panel": "Control Panel",
"update-settings": "Update Cache Settings"
} }

View File

@@ -1,52 +1,35 @@
{ {
"x-b": "٪1 بايت", "x-b": "%1 b",
"x-mb": "٪1 ميقا بايت", "x-mb": "%1 mb",
"x-gb": "٪1 قيقا بايت", "uptime-seconds": "Uptime in Seconds",
"uptime-seconds": "مدة التشغيل بالثواني", "uptime-days": "Uptime in Days",
"uptime-days": "مدة التشغيل بالأيام",
"mongo": "MongoDB", "mongo": "Mongo",
"mongo.version": "اصدار MongoDB", "mongo.version": "MongoDB Version",
"mongo.storage-engine": "محرك التخزين", "mongo.storage-engine": "Storage Engine",
"mongo.collections": "التجميعات", "mongo.collections": "Collections",
"mongo.objects": "العناصر", "mongo.objects": "Objects",
"mongo.avg-object-size": "معدل حجم العناصر", "mongo.avg-object-size": "Avg. Object Size",
"mongo.data-size": "حجم البيانات", "mongo.data-size": "Data Size",
"mongo.storage-size": "حجم التخرين", "mongo.storage-size": "Storage Size",
"mongo.index-size": "حجم الفهرس", "mongo.index-size": "Index Size",
"mongo.file-size": "حجم الملف", "mongo.file-size": "File Size",
"mongo.resident-memory": "الذاكرة الساكنة", "mongo.resident-memory": "Resident Memory",
"mongo.virtual-memory": "الذاكرة الإفتراضية", "mongo.virtual-memory": "Virtual Memory",
"mongo.mapped-memory": "Mapped Memory", "mongo.mapped-memory": "Mapped Memory",
"mongo.bytes-in": "البايتات الواردة", "mongo.raw-info": "MongoDB Raw Info",
"mongo.bytes-out": "البايتات الصادرة",
"mongo.num-requests": "عدد الطلبات",
"mongo.raw-info": "معلومات MongoDB الأولية",
"mongo.unauthorized": "لم يستطع NodeBB من الاستعلام عن احصاءات قواعد البيانات ل MongoDB. الرجاء التأكد من أن المستخدم في NodeBB يحتوي على دور &quot;clusterMonitor&quot; ال &quot;admin&quot; لقواعد البيانات.",
"redis": "Redis", "redis": "Redis",
"redis.version": "إصدار Redis", "redis.version": "Redis Version",
"redis.keys": "المفاتيح", "redis.connected-clients": "Connected Clients",
"redis.expires": "ينتهي ", "redis.connected-slaves": "Connected Slaves",
"redis.avg-ttl": "متوسط وقت الانعاش", "redis.blocked-clients": "Blocked Clients",
"redis.connected-clients": "العميل المتصل", "redis.used-memory": "Used Memory",
"redis.connected-slaves": "البدلاء المتصلين", "redis.memory-frag-ratio": "Memory Fragmentation Ratio",
"redis.blocked-clients": "العملاء المحظورون", "redis.total-connections-recieved": "Total Connections Received",
"redis.used-memory": "الذاكرة المستخدمة", "redis.total-commands-processed": "Total Commands Processed",
"redis.memory-frag-ratio": "نسبة تجزئة الذاكرة", "redis.iops": "Instantaneous Ops. Per Second",
"redis.total-connections-recieved": "إجمالي الاتصالات المستلمة", "redis.keyspace-hits": "Keyspace Hits",
"redis.total-commands-processed": "إجمالي الأوامر التي تمت معالجتها", "redis.keyspace-misses": "Keyspace Misses",
"redis.iops": "العمليات اللحظية في الثانية", "redis.raw-info": "Redis Raw Info"
"redis.iinput": "الإدخال الفوري في الثانية", }
"redis.ioutput": "المخرجات الفورية في الثانية",
"redis.total-input": "مجموع المدخلات",
"redis.total-output": "مجموع المخرجات",
"redis.keyspace-hits": "المفاتيح المضغوطة",
"redis.keyspace-misses": "المفاتيح المخفقة",
"redis.raw-info": "معلومات Redis الأولية",
"postgres": "Postgres",
"postgres.version": "إصدار PostgreSQL",
"postgres.raw-info": "معلومات Postgres الأولية"
}

View File

@@ -1,15 +1,14 @@
{ {
"errors": "Errors", "figure-x": "Figure %1",
"figure-x": "شكل %1", "error-events-per-day": "<code>%1</code> events per day",
"error-events-per-day": "<code>%1</code> حدث كل يوم ", "error.404": "404 Not Found",
"error.404": "404 لم يتم العثور", "error.503": "503 Service Unavailable",
"error.503": "503 الخدمة غير متوفرة", "manage-error-log": "Manage Error Log",
"manage-error-log": "إدارة سجل الأخطاء", "export-error-log": "Export Error Log (CSV)",
"export-error-log": "تصدير سجل الأخطاء (CSV)", "clear-error-log": "Clear Error Log",
"clear-error-log": "محو سجل الأخطاء", "route": "Route",
"route": "مسار", "count": "Count",
"count": "عدد", "no-routes-not-found": "Hooray! No 404 errors!",
"no-routes-not-found": "لا توجد اخطاء 404!", "clear404-confirm": "Are you sure you wish to clear the 404 error logs?",
"clear404-confirm": "هل تريد بالتأكيد محو سجلات الخطأ 404؟", "clear404-success": "\"404 Not Found\" errors cleared"
"clear404-success": "أخطاء \"404 لم يتم العثور\" تم محوها بنجاح"
} }

View File

@@ -1,13 +1,6 @@
{ {
"events": "أحداث", "events": "Events",
"no-events": "لا توجد أحداث", "no-events": "There are no events",
"control-panel": "لوحة تحكم الأحداث", "control-panel": "Events Control Panel",
"delete-events": "حذف الاحداث", "delete-events": "Delete Events"
"confirm-delete-all-events": "هل أنت متاكد أنك تريد حذف كل سجل اﻻحداث؟",
"filters": "تصفية",
"filters-apply": "تطبيق التصفية",
"filter-type": "نوع الحدث",
"filter-start": "تاريخ البدء",
"filter-end": "تاريخ الانتهاء",
"filter-per-page": "لكل صفحة"
} }

View File

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

View File

@@ -1,20 +1,9 @@
{ {
"customise": "Customise", "custom-css": "Custom CSS",
"custom-css": "Custom CSS/SASS", "custom-css.description": "Enter your own CSS declarations here, which will be applied after all other styles.",
"custom-css.description": "Enter your own CSS/SASS declarations here, which will be applied after all other styles.", "custom-css.enable": "Enable Custom CSS",
"custom-css.enable": "Enable Custom CSS/SASS",
"custom-js": "Javascript مخصصة", "custom-header": "Custom Header",
"custom-js.description": "أدخل Javascript الخاص بك هنا. سيتم تنفيذها بعد تحميل الصفحة بالكامل.", "custom-header.description": "Enter custom HTML here (ex. JavaScript, Meta Tags, etc.), which will be appended to the <code>&lt;head&gt;</code> section of your forum's markup.",
"custom-js.enable": "تفعيل Javascript المخصصة", "custom-header.enable": "Enable Custom Header"
"custom-header": "ترويسة مخصصة",
"custom-header.description": "أدخل وسوم HTML المخصصة هنا (مثل: Meta Tags, وغيرها)، والتي سيتم تضمينها لجزئية <code>&lt;head&gt;</code> من ترميز المنتدى. يُسمح بعلامات البرمجة النصية، ولكن لا يُنصح بها ، نظرًا لأن علامة التبويب <a href=\"#custom-js\" data-toggle=\"tab\"> Custom Javascript </a> متاحة.",
"custom-header.enable": "تفعيل الترويسة المخصصة",
"custom-css.livereload": "تفعيل إعادة التحميل المباشرة",
"custom-css.livereload.description": "فعل هذا الخيار لإجبار جميع الجلسات في الأجهزة التي قمت بتسجيل الدخول فيها بحسابك على التحديث عند النقر على زر الحفظ",
"bsvariables": "_variables.scss",
"bsvariables.description": "Override bootstrap variables here. You can also use a tool like <a href=\"https://bootstrap.build/app\">bootstrap.build</a> and paste the output here.<br/>Changes require a rebuild & restart.",
"bsvariables.enable": "Enable _variables.scss"
} }

View File

@@ -1,18 +1,9 @@
{ {
"skins": "Skins", "loading": "Loading Skins...",
"bootswatch-skins": "Bootswatch Skins", "homepage": "Homepage",
"custom-skins": "Custom Skins", "select-skin": "Select Skin",
"add-skin": "Add Skin", "current-skin": "Current Skin",
"save-custom-skins": "Save Custom Skins", "skin-updated": "Skin Updated",
"save-custom-skins-success": "Custom skins saved successfully", "applied-success": "%1 skin was succesfully applied",
"custom-skin-name": "Custom Skin Name", "revert-success": "Skin reverted to base colours"
"custom-skin-variables": "Custom Skin Variables",
"loading": "جاري تحميل السمات...",
"homepage": "الصفحة الرئيسية",
"select-skin": "إختيار السمة",
"revert-skin": "Revert Skin",
"current-skin": "السمة الحالية",
"skin-updated": "تم تحديث السمة",
"applied-success": "تم تطبيق السمة %1 بنجاح",
"revert-success": "تم إستعادة الألوان الاساسية للسمة"
} }

View File

@@ -1,13 +1,11 @@
{ {
"themes": "Themes", "checking-for-installed": "Checking for installed themes...",
"checking-for-installed": "جاري التحقق من القوالب المثبتة...", "homepage": "Homepage",
"homepage": "الصفحة الرئيسية", "select-theme": "Select Theme",
"select-theme": "إختيار القالب", "current-theme": "Current Theme",
"revert-theme": "Revert Theme", "no-themes": "No installed themes found",
"current-theme": "القالب المستخدم حالياً", "revert-confirm": "Are you sure you wish to restore the default NodeBB theme?",
"no-themes": "لم يتم العثور على قوالب مثبتة", "theme-changed": "Theme Changed",
"revert-confirm": "هل أنت متأكد من أنك ترغب في استعادة قااب NodeBB الافتراضي؟", "revert-success": "You have successfully reverted your NodeBB back to it's default theme.",
"theme-changed": "تم تغيير القالب", "restart-to-activate": "Please restart your NodeBB to fully activate this theme"
"revert-success": "لقد قمت بنجاح بإستعادة القالب الأساسي لـNodeBB",
"restart-to-activate": "يرجى إعادة بناء وإعادة تشغيل NodeBB لتنشيط هذا الثيم."
} }

View File

@@ -1,98 +0,0 @@
{
"forum-traffic": "حركة المنتدى",
"page-views": "مشاهدات الصفحات",
"unique-visitors": "زائرين فريدين",
"logins": "عمليات تسجيل الدخول",
"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": "الأمس",
"stats.today": "اليوم",
"stats.last-week": "الاسبوع الماضي",
"stats.this-week": "هذا الاسبوع",
"stats.last-month": "الشهر الماضي",
"stats.this-month": "هذا الشهر",
"stats.all": "كل الوقت",
"updates": "تحديثات",
"running-version": "المنتدى يعمل حاليا على <strong>NodeBB الإصدار<span id=\"version\">%1</span></strong>.",
"keep-updated": "تأكد دائما من أن NodeBB يعمل على احدث إصدار للحصول على أحدث التصحيحات الأمنية وإصلاحات الأخطاء.",
"up-to-date": "You are <strong>up-to-date</strong> <i class=\"fa fa-check\"></i>",
"upgrade-available": "A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/configuring/upgrade/\" target=\"_blank\">upgrading your NodeBB</a>.",
"prerelease-upgrade-available": "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>.",
"prerelease-warning": "This is a <strong>pre-release</strong> version of NodeBB. Unintended bugs may occur. <i class=\"fa fa-exclamation-triangle\"></i>",
"fallback-emailer-not-found": "مرسل البريد الإلكتروني الاحتياطي غير موجود!",
"running-in-development": "Forum is running in development mode. The forum may be open to potential vulnerabilities; please contact your system administrator",
"latest-lookup-failed": "Failed to look up latest available version of NodeBB",
"notices": "إشعارات",
"restart-not-required": "إعادة التشغيل غير مطلوب",
"restart-required": "إعادة التشغيل مطلوبة",
"search-plugin-installed": "إضافة البحث منصبة",
"search-plugin-not-installed": "إضافة البحث غير منصبة",
"search-plugin-tooltip": "نصب إضافة البحث من صفحة الإضافات البرمجية لتنشيط وظيفة البحث",
"control-panel": "التحكم بالنظام",
"rebuild-and-restart": "أعد البناء &amp; وأعد التشغيل",
"restart": "أعد التشغيل",
"restart-warning": "ستؤدي إعادة بناء أو إعادة تشغيل NodeBB إلى إنقطاع الاتصال بالمنتدى لبضع ثوان.",
"restart-disabled": "تم تعطيل إعادة بناء NodeBB وإعادة تشغيله حيث لا يبدو أنك تقوم بتشغيله عبر البرنامج الخفي المناسب.",
"maintenance-mode": "وضع الصيانة",
"maintenance-mode-title": "انقر هنا لإعداد وضع الصيانة لـNodeBB",
"dark-mode": "Dark Mode",
"realtime-chart-updates": "التحديث الفوري للرسم البياني",
"active-users": "المستخدمين النشطين",
"active-users.users": "الأعضاء",
"active-users.guests": "الزوار",
"active-users.total": "المجموع",
"active-users.connections": "روابط الاتصال",
"guest-registered-users": "الزائر مقابل المستخدمين المسجلين",
"guest": "الزائر",
"registered": "مسجل",
"user-presence": "تواجد المستخدمين",
"on-categories": "في قائمة الأقسام",
"reading-posts": "قراءة المشاركات",
"browsing-topics": "تصفح المواضيع",
"recent": "الأخيرة",
"unread": "غير مقروء",
"high-presence-topics": "مواضيع ذات حضور قوي",
"popular-searches": "عمليات البحث الشائعة",
"graphs.page-views": "مشاهدات الصفحة",
"graphs.page-views-registered": "زيارات الصفحات المسجلة",
"graphs.page-views-guest": "زيارات الصفحات للزوار",
"graphs.page-views-bot": "زيارات الصفحات الآلية",
"graphs.unique-visitors": "زوار فريدين",
"graphs.registered-users": "مستخدمين مسجلين",
"graphs.guest-users": "المستخدمين الزوار",
"last-restarted-by": "آخر إعادة تشغيل بواسطة",
"no-users-browsing": "لا يوجد مستخدمين يتصفحون",
"back-to-dashboard": "العودة للوحة التحكم",
"details.no-users": "لم ينضم أي مستخدم خلال الإطار الزمني المحدد",
"details.no-topics": "لم يتم نشر أي مواضيع خلال الإطار الزمني المحدد",
"details.no-searches": "لم يتم إجراء أي بحث خلال الإطار الزمني المحدد",
"details.no-logins": "لم يوثق أي تسجيل دخول خلال الإطار الزمني المحدد",
"details.logins-static": "يقوم NodeBB بحفظ بيانات الجلسة لمدة ٪1 يوم/أيام فقط، ولذا فإن هذا الجدول أدناه سيعرض فقط أحدث الجلسات النشطة",
"details.logins-login-time": "وقت تسجيل الدخول",
"start": "بدء",
"end": "إنهاء",
"filter": "تصفية",
"view-as-json": "View as JSON",
"expand-analytics": "Expand analytics",
"clear-search-history": "Clear Search History",
"clear-search-history-confirm": "Are you sure you want to clear entire search history?"
}

View File

@@ -1,20 +1,11 @@
{ {
"you-are-on": "أنت في <strong> %1:%2 </strong>", "you-are-on": "Info - You are on <strong>%1:%2</strong>",
"ip": "رقم الآي بي <strong> %1 </strong>", "host": "host",
"nodes-responded": "عدد %1 نقطة/نقاط استجابوا خلال %2 جزء من الثانية.",
"host": "المضيف",
"primary": "primary / run jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
"git": "git", "git": "git",
"process-memory": "process memory", "load": "load",
"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", "uptime": "uptime",
"registered": "Registered", "registered": "Registered",

View File

@@ -1,13 +1,12 @@
{ {
"logger": "Logger", "logger-settings": "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.",
"description": "من خلال تمكين مربعات الاختيار ، ستتلقى سجلات إلى جهازك الطرفي. إذا حددت مسارًا ، فسيتم بعد ذلك حفظ السجلات في ملف بدلاً من ذلك. يعد تسجيل HTTP مفيدًا لجمع الإحصائيات حول من ومتى وماذا يصل الأشخاص في المنتدى. بالإضافة إلى تسجيل طلبات HTTP ، يمكننا أيضًا تسجيل أحداث socket.io. يمكن أن يكون تسجيل Socket.io ، جنبًا إلى جنب مع شاشة redis-cli ، مفيدًا جدًا في تعلم العناصر الداخلية لـ NodeBB.", "explanation": "Simply check/uncheck the logging settings to enable or disable logging on the fly. No restart needed.",
"explanation": "ما عليك سوى تحديدأو/ إلغاء تحديد إعدادات التسجيل لتمكين أو تعطيل التسجيل أثناء التنقل. لا حاجة لإعادة التشغيل.", "enable-http": "Enable HTTP logging",
"enable-http": "تمكين سجلات HTTP", "enable-socket": "Enable socket.io event logging",
"enable-socket": "تفعيل تسجيل أحداث socket.io", "file-path": "Path to log file",
"file-path": "مسار ملف السجل", "file-path-placeholder": "/path/to/log/file.log ::: leave blank to log to your terminal",
"file-path-placeholder": "/path/to/log/file.log ::: اتركه فارغا لاظهار السجلات لنافذة الطرفية",
"control-panel": "لوحة تحكم المسجل", "control-panel": "Logger Control Panel",
"update-settings": "تحديث إعدادات المسجل" "update-settings": "Update Logger Settings"
} }

View File

@@ -1,58 +1,47 @@
{ {
"plugins": "Plugins", "installed": "Installed",
"trending": "Trending", "active": "Active",
"installed": "منصبة", "inactive": "Inactive",
"active": "مفعلة", "out-of-date": "Out of Date",
"inactive": "معطلة", "none-found": "No plugins found.",
"out-of-date": "غير محدثة", "none-active": "No Active Plugins",
"none-found": "لم يتم العثور على إضافات", "find-plugins": "Find Plugins",
"none-active": "لا توجد إضافات مفعلة",
"find-plugins": "العثور على الإضافات",
"plugin-search": "البحث عن الإضافات", "plugin-search": "Plugin Search",
"plugin-search-placeholder": "جاري البحث عن الإضافات...", "plugin-search-placeholder": "Search for plugin...",
"submit-anonymous-usage": "Submit anonymous plugin usage data.", "reorder-plugins": "Re-order Plugins",
"reorder-plugins": "إعادة ترتيب الإضافات", "order-active": "Order Active Plugins",
"order-active": "ترتيب الإضافات المفعلة", "dev-interested": "Interested in writing plugins for NodeBB?",
"dev-interested": "هل انته مهتم ببرمجة إضافات لـNodeBB؟", "docs-info": "Full documentation regarding plugin authoring can be found in the <a target=\"_blank\" href=\"https://docs.nodebb.org/en/latest/plugins/create.html\">NodeBB Docs Portal</a>.",
"docs-info": "دليل كامل حول برمجة الإضافات بالإمكان العثور عليه في <a target=\"_blank\" href=\"https://docs.nodebb.org/development/plugins/\"> NodeBB Docs Portal</a>.",
"order.description": "بعض الإضافات تعمل بشكل مثالي عندما يتم تفعيلها قبل أو بعد الإضافات الأخرى.", "order.description": "Certain plugins work ideally when they are initialised before/after other plugins.",
"order.explanation": "يتم تحميل الإضافات حسب الترتيب المحدد هنا، من الأعلى إلى الأسفل", "order.explanation": "Plugins load in the order specified here, from top to bottom",
"plugin-item.themes": "Themes",
"plugin-item.deactivate": "Deactivate",
"plugin-item.activate": "Activate",
"plugin-item.install": "Install",
"plugin-item.uninstall": "Uninstall",
"plugin-item.settings": "Settings",
"plugin-item.installed": "Installed",
"plugin-item.latest": "Latest",
"plugin-item.upgrade": "Upgrade",
"plugin-item.more-info": "For more information:",
"plugin-item.unknown": "Unknown",
"plugin-item.unknown-explanation": "The state of this plugin could not be determined, possibly due to a misconfiguration error.",
"plugin-item.themes": "القوالب", "alert.enabled": "Plugin Enabled",
"plugin-item.deactivate": "تعطيل", "alert.disabled": "Plugin Disabled",
"plugin-item.activate": "تفعيل", "alert.upgraded": "Plugin Upgraded",
"plugin-item.install": "تنصيب", "alert.installed": "Plugin Installed",
"plugin-item.uninstall": "إلغاء التنصيب", "alert.uninstalled": "Plugin Uninstalled",
"plugin-item.settings": "الإعدادات", "alert.activate-success": "Please restart your NodeBB to fully activate this plugin",
"plugin-item.installed": "المنصبة", "alert.deactivate-success": "Plugin successfully deactivated",
"plugin-item.latest": "الأحدث", "alert.upgrade-success": "Please reload your NodeBB to fully upgrade this plugin",
"plugin-item.upgrade": "ترقية", "alert.install-success": "Plugin successfully installed, please activate the plugin.",
"plugin-item.more-info": "لمزيد من المعلومات:", "alert.uninstall-success": "The plugin has been successfully deactivated and uninstalled.",
"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.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.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.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.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,17 +1,17 @@
{ {
"rewards": "المكافآت", "rewards": "Rewards",
"add-reward": "Add reward", "condition-if-users": "If User's",
"condition-if-users": "إذا كان للمستخدم", "condition-is": "Is:",
"condition-is": "يكون: ", "condition-then": "Then:",
"condition-then": "عندئذ:", "max-claims": "Amount of times reward is claimable",
"max-claims": "عدد المرات التي يمكن فيها المطالبة بالمكافأة", "zero-infinite": "Enter 0 for infinite",
"zero-infinite": "أدخل 0 للقيمة اللانهائية", "delete": "Delete",
"select-reward": "Select reward", "enable": "Enable",
"delete": "احذف", "disable": "Disable",
"enable": "مكّن", "control-panel": "Rewards Control",
"disable": "عطّل", "new-reward": "New Reward",
"alert.delete-success": "المكافآت المحذوفة بنجاح", "alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "مكافأة غير قانونية - لم يتم العثور على مدخلات!", "alert.no-inputs-found": "Illegal reward - no inputs found!",
"alert.save-success": "المكافآت المحفوظة بنجاح" "alert.save-success": "Successfully saved rewards"
} }

View File

@@ -1,35 +1,19 @@
{ {
"widgets": "Widgets",
"available": "Available Widgets", "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.", "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.", "none-installed": "No widgets found! Activate the essential widgets plugin in the <a href=\"%1\">plugins</a> control panel.",
"clone-from": "Clone widgets from",
"containers.available": "Available Containers", "containers.available": "Available Containers",
"containers.explanation": "Drag and drop on top of any widget", "containers.explanation": "Drag and drop on top of any active widget",
"containers.none": "None", "containers.none": "None",
"container.well": "Well", "container.well": "Well",
"container.jumbotron": "Jumbotron", "container.jumbotron": "Jumbotron",
"container.card": "Card", "container.panel": "Panel",
"container.card-header": "Card Header", "container.panel-header": "Panel Header",
"container.card-body": "Card Body", "container.panel-body": "Panel Body",
"container.alert": "Alert", "container.alert": "Alert",
"alert.confirm-delete": "Are you sure you wish to delete this widget?", "alert.confirm-delete": "Are you sure you wish to delete this widget?",
"alert.updated": "Widgets Updated", "alert.updated": "Widgets Updated",
"alert.update-success": "Successfully updated widgets", "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",
"start-date": "Start date",
"end-date": "End date",
"hide-on-mobile": "Hide on mobile",
"hide-drafts": "Hide drafts",
"show-drafts": "Show drafts"
} }

View File

@@ -0,0 +1,64 @@
{
"forum-traffic": "Forum Traffic",
"page-views": "Page Views",
"unique-visitors": "Unique Visitors",
"users": "Users",
"posts": "Posts",
"topics": "Topics",
"page-views-last-month": "Page views Last Month",
"page-views-this-month": "Page views This Month",
"page-views-last-day": "Page views in last 24 hours",
"stats.day": "Day",
"stats.week": "Week",
"stats.month": "Month",
"stats.all": "All Time",
"updates": "Updates",
"running-version": "You are running <strong>NodeBB v<span id=\"version\">%1</span></strong>.",
"keep-updated": "Always make sure that your NodeBB is up to date for the latest security patches and bug fixes.",
"up-to-date": "<p>You are <strong>up-to-date</strong> <i class=\"fa fa-check\"></i></p>",
"upgrade-available": "<p>A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/en/latest/upgrading/index.html\">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/en/latest/upgrading/index.html\">upgrading your NodeBB</a>.</p>",
"prerelease-warning": "<p>This is a <strong>pre-release</strong> version of NodeBB. Unintended bugs may occur. <i class=\"fa fa-exclamation-triangle\"></i></p>",
"running-in-development": "<span>Forum is running in development mode. The forum may be open to potential vulnerabilities; please contact your system administrator.</span>",
"notices": "Notices",
"restart-not-required": "Restart not required",
"restart-required": "Restart required",
"search-plugin-installed": "Search Plugin installed",
"search-plugin-not-installed": "Search Plugin not installed",
"search-plugin-tooltip": "Install a search plugin from the plugin page in order to activate search functionality",
"control-panel": "System Control",
"reload": "Reload",
"restart": "Restart",
"restart-warning": "Reloading or Restarting your NodeBB will drop all existing connections for a few seconds.",
"maintenance-mode": "Maintenance Mode",
"maintenance-mode-title": "Click here to set up maintenance mode for NodeBB",
"realtime-chart-updates": "Realtime Chart Updates",
"active-users": "Active Users",
"active-users.users": "Users",
"active-users.guests": "Guests",
"active-users.total": "Total",
"active-users.connections": "Connections",
"anonymous-registered-users": "Anonymous vs Registered Users",
"anonymous": "Anonymous",
"registered": "Registered",
"user-presence": "User Presence",
"on-categories": "On categories list",
"reading-posts": "Reading posts",
"browsing-topics": "Browsing topics",
"recent": "Recent",
"unread": "Unread",
"high-presence-topics": "High Presence Topics",
"graphs.page-views": "Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.anonymous-users": "Anonymous Users"
}

View File

@@ -0,0 +1,7 @@
{
"home-page": "Home Page",
"description": "Choose what page is shown when users navigate to the root URL of your forum.",
"home-page-route": "Home Page Route",
"custom-route": "Custom Route",
"allow-user-home-pages": "Allow User Home Pages"
}

View File

@@ -0,0 +1,5 @@
{
"language-settings": "Language Settings",
"description": "The default language determines the language settings for all users who are visiting your forum. <br />Individual users can override the default language on their account settings page.",
"default-language": "Default Language"
}

View File

@@ -0,0 +1,27 @@
{
"icon": "Icon:",
"change-icon": "change",
"route": "Route:",
"tooltip": "Tooltip:",
"text": "Text:",
"text-class": "Text Class: <small>optional</small>",
"id": "ID: <small>optional</small>",
"properties": "Properties:",
"only-admins": "Only display to Admins",
"only-global-mods-and-admins": "Only display to Global Moderators and Admins",
"only-logged-in": "Only display to logged in users",
"open-new-window": "Open in a new window",
"installed-plugins-required": "Installed Plugins Required:",
"search-plugin": "Search plugin",
"btn.delete": "Delete",
"btn.disable": "Disable",
"btn.enable": "Enable",
"available-menu-items": "Available Menu Items",
"custom-route": "Custom Route",
"core": "core",
"plugin": "plugin"
}

View File

@@ -0,0 +1,5 @@
{
"post-sharing": "Post Sharing",
"info-plugins-additional": "Plugins can add additional networks for sharing posts.",
"save-success": "Successfully saved Post Sharing Networks!"
}

View File

@@ -1,13 +0,0 @@
{
"manage-admins-and-mods": "Manage Admins & Mods",
"administrators": "Administrators",
"global-moderators": "Global Moderators",
"moderators": "Moderators",
"no-global-moderators": "No Global Moderators",
"no-sub-categories": "No subcategories",
"view-children": "View children (%1)",
"no-moderators": "No Moderators",
"add-administrator": "Add Administrator",
"add-global-moderator": "Add Global Moderator",
"add-moderator": "Add Moderator"
}

View File

@@ -1,11 +1,7 @@
{ {
"manage-categories": "Manage Categories", "settings": "Category Settings",
"add-category": "Add category", "privileges": "Privileges",
"jump-to": "Jump to...",
"settings": "اعدادات القسم",
"edit-category": "Edit Category",
"privileges": "الصلاحيات",
"back-to-categories": "Back to categories",
"name": "Category Name", "name": "Category Name",
"description": "Category Description", "description": "Category Description",
"bg-color": "Background Colour", "bg-color": "Background Colour",
@@ -14,60 +10,37 @@
"custom-class": "Custom Class", "custom-class": "Custom Class",
"num-recent-replies": "# of Recent Replies", "num-recent-replies": "# of Recent Replies",
"ext-link": "External Link", "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", "upload-image": "Upload Image",
"upload": "Upload",
"delete-image": "Remove", "delete-image": "Remove",
"category-image": "Category Image", "category-image": "Category Image",
"image-and-icon": "Image & Icon",
"parent-category": "Parent Category", "parent-category": "Parent Category",
"optional-parent-category": "(Optional) Parent Category", "optional-parent-category": "(Optional) Parent Category",
"top-level": "Top Level",
"parent-category-none": "(None)", "parent-category-none": "(None)",
"copy-parent": "Copy Parent",
"copy-settings": "Copy Settings From", "copy-settings": "Copy Settings From",
"optional-clone-settings": "(Optional) Clone Settings From Category", "optional-clone-settings": "(Optional) Clone Settings From Category",
"clone-children": "Clone Children Categories And Settings",
"purge": "Purge Category", "purge": "Purge Category",
"enable": "Enable", "enable": "Enable",
"disable": "Disable", "disable": "Disable",
"edit": "Edit", "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", "select-category": "Select Category",
"set-parent-category": "Set Parent 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.description": "You can configure the access control privileges for this category in this section. Privileges can be granted on a per-user or a per-group basis. You can add a new user to this table by searching for them in the form 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.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-viewing": "Viewing Privileges",
"privileges.section-posting": "Posting Privileges", "privileges.section-posting": "Posting Privileges",
"privileges.section-moderation": "Moderation Privileges", "privileges.section-moderation": "Moderation Privileges",
"privileges.section-other": "Other",
"privileges.section-user": "User", "privileges.section-user": "User",
"privileges.search-user": "Add User", "privileges.search-user": "Add User",
"privileges.no-users": "No user-specific privileges in this category.", "privileges.no-users": "No user-specific privileges in this category.",
"privileges.section-group": "Group", "privileges.section-group": "Group",
"privileges.group-private": "This group is private", "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.search-group": "Add Group",
"privileges.copy-to-children": "Copy to Children", "privileges.copy-to-children": "Copy to Children",
"privileges.copy-from-category": "Copy from Category", "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.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.back": "Back to Categories List",
"analytics.title": "Analytics for \"%1\" category", "analytics.title": "Analytics for \"%1\" category",
@@ -80,6 +53,7 @@
"alert.create-success": "Category successfully created!", "alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.", "alert.none-active": "You have no active categories.",
"alert.create": "Create a Category", "alert.create": "Create a Category",
"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-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.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.purge-success": "Category purged!",
"alert.copy-success": "Settings Copied!", "alert.copy-success": "Settings Copied!",
@@ -90,10 +64,5 @@
"alert.find-user": "Find a User", "alert.find-user": "Find a User",
"alert.user-search": "Search for a user here...", "alert.user-search": "Search for a user here...",
"alert.find-group": "Find a Group", "alert.find-group": "Find a Group",
"alert.group-search": "Search for a group here...", "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

@@ -0,0 +1,19 @@
{
"daily": "Daily flags",
"by-user": "Flags by user",
"by-user-search": "Search flagged posts by username",
"category": "Category",
"sort-by": "Sort By",
"sort-by.most-flags": "Most Flags",
"sort-by.most-recent": "Most Recent",
"search": "Search",
"dismiss-all": "Dismiss All",
"none-flagged": "No flagged posts!",
"posted-in": "Posted in %1",
"read-more": "Read More",
"flagged-x-times": "This post has been flagged %1 time(s):",
"dismiss": "Dismiss this Flag",
"delete-post": "Delete the Post",
"alerts.confirm-delete-post": "Do you really want to delete this post?"
}

View File

@@ -1,22 +1,8 @@
{ {
"manage-groups": "Manage Groups", "name": "Group Name",
"add-group": "Add group",
"edit-group": "Edit Group",
"back-to-groups": "Back to groups",
"view-group": "View group",
"icon-and-title": "Icon & Title",
"name": "اسم المجموعة",
"badge": "Badge",
"properties": "Properties",
"description": "Group Description", "description": "Group Description",
"member-count": "Member Count", "system": "System Group",
"system": "System",
"hidden": "Hidden",
"private": "Private",
"edit": "Edit", "edit": "Edit",
"delete": "Delete",
"privileges": "Privileges",
"members-csv": "Members (CSV)",
"search-placeholder": "Search", "search-placeholder": "Search",
"create": "Create Group", "create": "Create Group",
"description-placeholder": "A short description about your group", "description-placeholder": "A short description about your group",
@@ -30,12 +16,10 @@
"edit.user-title": "Title of Members", "edit.user-title": "Title of Members",
"edit.icon": "Group Icon", "edit.icon": "Group Icon",
"edit.label-color": "Group Label Color", "edit.label-color": "Group Label Color",
"edit.text-color": "Group Text Color",
"edit.show-badge": "Show Badge", "edit.show-badge": "Show Badge",
"edit.private-details": "If enabled, joining of groups requires approval from a group owner.", "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.private-override": "Warning: Private groups is disabled at system level, which overrides this option.",
"edit.disable-join": "Disable join requests", "edit.disable-requests": "Disable join requests",
"edit.disable-leave": "Disallow users from leaving the group",
"edit.hidden": "Hidden", "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.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": "Add User to Group",
@@ -45,5 +29,6 @@
"revert": "Revert", "revert": "Revert",
"edit.no-users-found": "No Users Found", "edit.no-users-found": "No Users Found",
"edit.confirm-remove-user": "Are you sure you want to remove this user?" "edit.confirm-remove-user": "Are you sure you want to remove this user?",
"edit.save-success": "Changes saved!"
} }

View File

@@ -0,0 +1,15 @@
{
"lead": "Configure your IP blacklist here.",
"description": "Occasionally, a user account ban is not enough of a deterrant. Other times, restricting access to the forum to a specific IP or a range of IPs is the best way to protect a forum. In these scenarios, you can add troublesome IP addresses or entire CIDR blocks to this blacklist, and they will be prevented from logging in to or registering a new account.",
"active-rules": "Active Rules",
"validate": "Validate Blacklist",
"apply": "Apply Blacklist",
"hints": "Syntax Hints",
"hint-1": "Define a single IP addresses per line. You can add IP blocks as long as they follow the CIDR format (e.g. <code>192.168.100.0/22</code>).",
"hint-2": "You can add in comments by starting lines with the <code>#</code> symbol.",
"validate.x-valid": "<strong>%1</strong> out of <strong>%2</strong> rule(s) valid.",
"validate.x-invalid": "The following <strong>%1</strong> rules are invalid:",
"alerts.applied-success": "Blacklist Applied"
}

View File

@@ -1,66 +0,0 @@
{
"manage-privileges": "Manage Privileges",
"discard-changes": "Discard changes",
"global": "Global",
"admin": "Admin",
"group-privileges": "Group Privileges",
"user-privileges": "User Privileges",
"edit-privileges": "Edit Privileges",
"select-clear-all": "Select/Clear All",
"chat": "Chat",
"chat-with-privileged": "Chat with Privileged",
"upload-images": "Upload Images",
"upload-files": "Upload Files",
"signature": "Signature",
"ban": "Ban",
"mute": "Mute",
"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.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."
}

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