diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fb2bd3ccce..e74e58c07b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -80,7 +80,7 @@ jobs: touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: digests-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* @@ -96,7 +96,7 @@ jobs: echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV echo "CURRENT_DATE_NST=$(date +'%Y%m%d-%H%M%S' -d '-3 hours -30 minutes')" >> $GITHUB_ENV - name: Download digests - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: path: ${{ runner.temp }}/digests pattern: digests-* diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a94157f446..b1ee43512d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -48,7 +48,7 @@ jobs: services: postgres: - image: 'postgres:17-alpine' + image: 'postgres:18-alpine' env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -63,7 +63,7 @@ jobs: - 5432:5432 redis: - image: 'redis:8.2.1' + image: 'redis:8.4.0' # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" @@ -75,7 +75,7 @@ jobs: - 6379:6379 mongo: - image: 'mongo:8.0' + image: 'mongo:8.2' ports: # Maps port 27017 on service container to the host - 27017:27017 @@ -86,7 +86,7 @@ jobs: - run: cp install/package.json package.json - name: Install Node - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} diff --git a/CHANGELOG.md b/CHANGELOG.md index fec4e9ed8e..47192ee9f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +#### v4.6.3 (2025-11-20) + +##### Chores + +* incrementing version number - v4.6.2 (f98747db) +* update changelog for v4.6.2 (8da3819c) +* incrementing version number - v4.6.1 (f47aa678) +* incrementing version number - v4.6.0 (ee395bc5) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### Bug Fixes + +* update validator dep. to get fix for CVE-2025-56200 (af477d0c) +* missing logic in mocks.notes.private that precluded the use of emoji (76a07d59) +* tiny fix for IS when page is empty (12dab849) + #### v4.6.2 (2025-11-19) ##### Chores diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml index 3e32023939..4ff8db6428 100644 --- a/docker-compose-pgsql.yml +++ b/docker-compose-pgsql.yml @@ -14,7 +14,7 @@ services: - ./install/docker/setup.json:/usr/src/app/setup.json postgres: - image: postgres:17.6-alpine + image: postgres:18.1-alpine restart: unless-stopped environment: POSTGRES_USER: nodebb @@ -24,7 +24,7 @@ services: - postgres-data:/var/lib/postgresql/data redis: - image: redis:8.2.1-alpine + image: redis:8.4.0-alpine restart: unless-stopped command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning'] # command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF diff --git a/docker-compose-redis.yml b/docker-compose-redis.yml index 98bceb6721..6334267ff7 100644 --- a/docker-compose-redis.yml +++ b/docker-compose-redis.yml @@ -14,7 +14,7 @@ services: - ./install/docker/setup.json:/usr/src/app/setup.json redis: - image: redis:8.2.1-alpine + image: redis:8.4.0-alpine restart: unless-stopped command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning'] # command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF diff --git a/docker-compose.yml b/docker-compose.yml index ec096eb060..54e178d336 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: - mongo-data:/data/db - ./install/docker/mongodb-user-init.js:/docker-entrypoint-initdb.d/user-init.js redis: - image: redis:8.2.1-alpine + image: redis:8.4.0-alpine restart: unless-stopped command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning'] # command: ['redis-server', '--save', '60', '1', '--loglevel', 'warning'] # uncomment if you want to use snapshotting instead of AOF @@ -34,7 +34,7 @@ services: - redis postgres: - image: postgres:17.6-alpine + image: postgres:18.1-alpine restart: unless-stopped environment: POSTGRES_USER: nodebb diff --git a/install/data/defaults.json b/install/data/defaults.json index 574a8bfe01..67557efabe 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -38,6 +38,7 @@ "maximumTagLength": 15, "undoTimeout": 0, "allowTopicsThumbnail": 1, + "showPostUploadsAsThumbnails": 1, "registrationType": "normal", "registrationApprovalType": "normal", "allowAccountDelete": 1, diff --git a/install/package.json b/install/package.json index 64b015b273..36c08a9e2e 100644 --- a/install/package.json +++ b/install/package.json @@ -33,27 +33,27 @@ "@fontsource/inter": "5.2.8", "@fontsource/poppins": "5.2.7", "@fortawesome/fontawesome-free": "6.7.2", - "@isaacs/ttlcache": "1.4.1", + "@isaacs/ttlcache": "2.1.2", "@nodebb/spider-detector": "2.0.3", "@popperjs/core": "2.11.8", "@textcomplete/contenteditable": "0.1.13", "@textcomplete/core": "0.1.13", "@textcomplete/textarea": "0.1.13", - "ace-builds": "1.43.3", + "ace-builds": "1.43.4", "archiver": "7.0.1", "async": "3.2.6", - "autoprefixer": "10.4.21", - "bcryptjs": "3.0.2", + "autoprefixer": "10.4.22", + "bcryptjs": "3.0.3", "benchpressjs": "2.5.5", "body-parser": "2.2.0", "bootbox": "6.0.4", "bootstrap": "5.3.8", "bootswatch": "5.3.8", "chalk": "4.1.2", - "chart.js": "4.5.0", + "chart.js": "4.5.1", "cli-graph": "3.2.2", "clipboard": "2.0.11", - "commander": "14.0.1", + "commander": "14.0.2", "compare-versions": "6.1.1", "compression": "1.8.1", "connect-flash": "0.1.1", @@ -61,15 +61,15 @@ "connect-pg-simple": "10.0.0", "connect-redis": "9.0.0", "cookie-parser": "1.4.7", - "cron": "4.3.3", + "cron": "4.3.4", "cropperjs": "1.6.2", "csrf-sync": "4.2.1", "daemon": "1.1.0", "diff": "8.0.2", - "esbuild": "0.25.10", + "esbuild": "0.27.0", "express": "4.21.2", "express-session": "1.18.2", - "express-useragent": "1.0.15", + "express-useragent": "2.0.2", "fetch-cookie": "3.1.0", "file-loader": "6.2.0", "fs-extra": "11.3.2", @@ -91,27 +91,28 @@ "lru-cache": "11.2.2", "mime": "3.0.0", "mkdirp": "3.0.1", - "mongodb": "6.20.0", + "mongodb": "6.21.0", "morgan": "1.10.1", "mousetrap": "1.6.5", "multer": "2.0.2", "nconf": "0.13.0", "nodebb-plugin-2factor": "7.6.0", "nodebb-plugin-composer-default": "10.3.1", - "nodebb-plugin-dbsearch": "6.3.2", + "nodebb-plugin-dbsearch": "6.3.4", "nodebb-plugin-emoji": "6.0.5", "nodebb-plugin-emoji-android": "4.1.1", - "nodebb-plugin-markdown": "13.2.1", - "nodebb-plugin-mentions": "4.7.6", + "nodebb-plugin-link-preview": "2.1.5", + "nodebb-plugin-markdown": "13.2.2", + "nodebb-plugin-mentions": "4.8.3", "nodebb-plugin-spam-be-gone": "2.3.2", - "nodebb-plugin-web-push": "0.7.5", + "nodebb-plugin-web-push": "0.7.6", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.21", + "nodebb-theme-harmony": "2.1.25", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.49", - "nodebb-theme-persona": "14.1.15", + "nodebb-theme-persona": "14.1.18", "nodebb-widget-essentials": "7.0.40", - "nodemailer": "7.0.6", + "nodemailer": "7.0.10", "nprogress": "0.2.0", "passport": "0.7.0", "passport-http-bearer": "1.0.1", @@ -123,18 +124,18 @@ "pretty": "^2.0.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "redis": "5.8.2", - "rimraf": "6.0.1", + "redis": "5.9.0", + "rimraf": "6.1.2", "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.17.0", - "sass": "1.93.2", + "sass": "1.94.1", "satori": "0.18.3", "sbd": "^1.0.19", - "semver": "7.7.2", + "semver": "7.7.3", "serve-favicon": "2.5.1", - "sharp": "0.34.4", - "sitemap": "8.0.0", + "sharp": "0.34.5", + "sitemap": "9.0.0", "socket.io": "4.8.1", "socket.io-client": "4.8.1", "@socket.io/redis-adapter": "8.3.0", @@ -149,10 +150,10 @@ "tough-cookie": "6.0.0", "undici": "^7.10.0", "validator": "13.15.23", - "webpack": "5.102.0", + "webpack": "5.103.0", "webpack-merge": "6.0.1", - "winston": "3.17.0", - "workerpool": "9.3.4", + "winston": "3.18.3", + "workerpool": "10.0.1", "xml": "1.0.1", "xregexp": "5.1.2", "yargs": "17.7.2", @@ -160,26 +161,26 @@ }, "devDependencies": { "@apidevtools/swagger-parser": "10.1.0", - "@commitlint/cli": "20.0.0", + "@commitlint/cli": "20.1.0", "@commitlint/config-angular": "20.0.0", "coveralls": "3.1.1", - "@eslint/js": "9.36.0", - "@stylistic/eslint-plugin": "5.4.0", + "@eslint/js": "9.39.1", + "@stylistic/eslint-plugin": "5.6.1", "eslint-config-nodebb": "1.1.11", "eslint-plugin-import": "2.32.0", "grunt": "1.6.1", "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", - "jsdom": "27.0.0", - "lint-staged": "16.2.3", - "mocha": "11.7.2", + "jsdom": "27.2.0", + "lint-staged": "16.2.6", + "mocha": "11.7.5", "mocha-lcov-reporter": "1.3.0", "mockdate": "3.0.5", "nyc": "17.1.0", - "smtp-server": "3.14.0" + "smtp-server": "3.16.1" }, "optionalDependencies": { - "sass-embedded": "1.93.2" + "sass-embedded": "1.93.3" }, "resolutions": { "*/jquery": "3.7.1" @@ -202,4 +203,4 @@ "url": "https://github.com/barisusakli" } ] -} \ No newline at end of file +} diff --git a/public/language/ar/admin/manage/categories.json b/public/language/ar/admin/manage/categories.json index 42282e425d..64beb3ce28 100644 --- a/public/language/ar/admin/manage/categories.json +++ b/public/language/ar/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/ar/admin/settings/uploads.json b/public/language/ar/admin/settings/uploads.json index b8d85be443..af3efb16f7 100644 --- a/public/language/ar/admin/settings/uploads.json +++ b/public/language/ar/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "السماح للاعضاء برفع الصور المصغرة للموضوع", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "حجم الصورة المصغرة للموضوع", "allowed-file-extensions": "إمتدادات الملفات المسموح بها", "allowed-file-extensions-help": "أدخل قائمة بامتدادات الملفات مفصولة بفواصل (مثال: pdf,xls,doc). القائمة الفارغة تعني أن كل الامتدادات مسموح بها.", diff --git a/public/language/az/admin/manage/categories.json b/public/language/az/admin/manage/categories.json index 1fc1544231..ed6b0c63d5 100644 --- a/public/language/az/admin/manage/categories.json +++ b/public/language/az/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Kateqoriya dəstəyi", "handle.help": "Kateqoriya dəstəyiniz istifadəçi adına bənzər digər şəbəkələrdə bu kateqoriyanın təmsili kimi istifadə olunur. Kateqoriya sapı mövcud istifadəçi adı və ya istifadəçi qrupuna uyğun olmamalıdır.", "description": "Kateqoriya təsviri", - "federatedDescription": "Federasiya təsviri", - "federatedDescription.help": "Bu mətn digər vebsaytlar/tətbiqlər tərəfindən sorğulandıqda kateqoriya təsvirinə əlavə olunacaq.", - "federatedDescription.default": "Bu, aktual müzakirələrdən ibarət forum kateqoriyasıdır. Bu kateqoriyanı qeyd etməklə yeni müzakirələrə başlaya bilərsiniz.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Arxa fon rəngi", diff --git a/public/language/az/admin/settings/uploads.json b/public/language/az/admin/settings/uploads.json index ceed4ae396..a403a54e34 100644 --- a/public/language/az/admin/settings/uploads.json +++ b/public/language/az/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maksimum şəklin hündürlüyü (piksellə)", "reject-image-height-help": "Bu dəyərdən yüksək olan şəkillər rədd ediləcək.", "allow-topic-thumbnails": "İstifadəçilərə mövzu miniatürlərini yükləməyə icazə ver", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Mövzu thumb ölçüsü", "allowed-file-extensions": "İcazə verilən fayl uzantıları", "allowed-file-extensions-help": "Fayl uzantılarının vergüllə ayrılmış siyahısını buraya daxil edin (məsələn, pdf, xls, doc). Boş siyahı bütün genişləndirmələrə icazə verildiyini bildirir.", diff --git a/public/language/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json index ee60e90e0c..755a7a8caa 100644 --- a/public/language/bg/admin/manage/categories.json +++ b/public/language/bg/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Идентификатор на категорията", "handle.help": "Идентификаторът на категорията се ползва за представяне на тази категория в други мрежи, подобно на потребителското име. Този идентификатор не трябва да съвпада със съществуващо потребителско име или потребителска група.", "description": "Описание на категорията", - "federatedDescription": "Федерирано описание", - "federatedDescription.help": "Този текст ще бъде добавен към описанието на категорията, когато други уеб сайтове и приложения изискват информация за нея.", - "federatedDescription.default": "Това е категория във форума, съдържаща тематични дискусии. Може да започнете нова дискусия, като споменете този форум.", "topic-template": "Шаблон за темите", "topic-template.help": "Създайте шаблон за новите теми в тази категория.", "bg-color": "Цвят на фона", diff --git a/public/language/bg/admin/settings/uploads.json b/public/language/bg/admin/settings/uploads.json index 4820730824..0cb47ec123 100644 --- a/public/language/bg/admin/settings/uploads.json +++ b/public/language/bg/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Максимална височина на изображенията (в пиксели)", "reject-image-height-help": "Изображенията, чиято височина е по-голяма от тази стойност, ще бъдат отхвърляни.", "allow-topic-thumbnails": "Позволяване на потребителите да качват миниатюрни изображения за темите", + "show-post-uploads-as-thumbnails": "Показване на качените файлове в публикациите като миниатюрни изображения", "topic-thumb-size": "Размер на миниатюрите за темите", "allowed-file-extensions": "Разрешени файлови разширения", "allowed-file-extensions-help": "Въведете файловите разширения, разделени със запетаи (пример: pdf,xls,doc). Ако списъкът е празен, всички файлови разширения ще бъдат разрешени.", diff --git a/public/language/bn/admin/manage/categories.json b/public/language/bn/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/bn/admin/manage/categories.json +++ b/public/language/bn/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/bn/admin/settings/uploads.json b/public/language/bn/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/bn/admin/settings/uploads.json +++ b/public/language/bn/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/cs/admin/manage/categories.json b/public/language/cs/admin/manage/categories.json index 1623ea6e66..0a8a39620d 100644 --- a/public/language/cs/admin/manage/categories.json +++ b/public/language/cs/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Popis kategorie", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Barva pozadí", diff --git a/public/language/cs/admin/settings/uploads.json b/public/language/cs/admin/settings/uploads.json index dea6aa44df..90d14e88d8 100644 --- a/public/language/cs/admin/settings/uploads.json +++ b/public/language/cs/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximální výška obrázku (v pixelech)", "reject-image-height-help": "Vyšší obrázek než tato hodnota bude zamítnut.", "allow-topic-thumbnails": "Povolit uživatelům nahrát miniatury témat", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Velikost miniatury tématu", "allowed-file-extensions": "Povolené přípony souborů", "allowed-file-extensions-help": "Zadejte seznam přípon souborů oddělených čárkou (např.: pdf, xls, doc). Prázdný seznam znamená, že všechny přípony jsou povoleny.", diff --git a/public/language/da/admin/manage/categories.json b/public/language/da/admin/manage/categories.json index 8985fc7d64..7ba0e7739a 100644 --- a/public/language/da/admin/manage/categories.json +++ b/public/language/da/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/da/admin/settings/uploads.json b/public/language/da/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/da/admin/settings/uploads.json +++ b/public/language/da/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/de/admin/manage/categories.json b/public/language/de/admin/manage/categories.json index 968badebce..c5c971a259 100644 --- a/public/language/de/admin/manage/categories.json +++ b/public/language/de/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategorie-Beschreibung", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Hintergrundfarbe", diff --git a/public/language/de/admin/settings/uploads.json b/public/language/de/admin/settings/uploads.json index 269bfb8178..75a5223588 100644 --- a/public/language/de/admin/settings/uploads.json +++ b/public/language/de/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximale Bildhöhe (in Pixeln)", "reject-image-height-help": "Höhere Bilder werden abgelehnt.", "allow-topic-thumbnails": "Nutzern erlauben Themen Thumbnails hochzuladen", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Thema Thumbnailgröße", "allowed-file-extensions": "Erlaubte Dateiendungen", "allowed-file-extensions-help": "Komma-getrennte Liste der Dateiendungen hier einfügen (z.B. pdf,xls,doc). Eine leere Liste bedeutet, dass alle Dateiendungen erlaubt sind.", diff --git a/public/language/el/admin/manage/categories.json b/public/language/el/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/el/admin/manage/categories.json +++ b/public/language/el/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/el/admin/settings/uploads.json b/public/language/el/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/el/admin/settings/uploads.json +++ b/public/language/el/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/en-GB/admin/settings/uploads.json b/public/language/en-GB/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/en-GB/admin/settings/uploads.json +++ b/public/language/en-GB/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/en-US/admin/manage/categories.json b/public/language/en-US/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/en-US/admin/manage/categories.json +++ b/public/language/en-US/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/en-US/admin/settings/uploads.json b/public/language/en-US/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/en-US/admin/settings/uploads.json +++ b/public/language/en-US/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/en-x-pirate/admin/manage/categories.json b/public/language/en-x-pirate/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/en-x-pirate/admin/manage/categories.json +++ b/public/language/en-x-pirate/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/en-x-pirate/admin/settings/uploads.json b/public/language/en-x-pirate/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/en-x-pirate/admin/settings/uploads.json +++ b/public/language/en-x-pirate/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/es/admin/manage/categories.json b/public/language/es/admin/manage/categories.json index 5cd865c742..2980f59ab2 100644 --- a/public/language/es/admin/manage/categories.json +++ b/public/language/es/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Identificador de categoría ", "handle.help": "Tu identificador de categoría está siendo utilizado como representación de esta categoría a través de otras redes, similar al nombre de usuario. El identificador de la categoría no puede ser igual a un nombre de usuario o usuario de grupo existente.", "description": "Descripción de Categoría", - "federatedDescription": "Descripción federada", - "federatedDescription.help": "Este texto será agregado a la descripción de la categoría cuando sea buscado por otros sitios y aplicaciones.", - "federatedDescription.default": "Esta es una categoría de foro que contiene discusiones pasadas. Puedes iniciar nuevas discusiones mencionando esta categoría.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Color de Fondo", diff --git a/public/language/es/admin/settings/uploads.json b/public/language/es/admin/settings/uploads.json index 60273aa8ac..d92fe07210 100644 --- a/public/language/es/admin/settings/uploads.json +++ b/public/language/es/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Altura máxima de la imágen (en píxeles)", "reject-image-height-help": "Las imágenes más altas que este valor serán rechazadas.", "allow-topic-thumbnails": "Permitir a los usuarios subir imágenes en miniatura para los temas", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Tamaño de la Imagen en Miniatura para el Tema", "allowed-file-extensions": "Permitir Extensiones de Archivo", "allowed-file-extensions-help": "Introduzca una lista de extensiones de archivos, separadas por comas, aquí (por ejemplo: pdf,xls,doc). Una lista vacía significa que se permiten todas las extensiones.", diff --git a/public/language/et/admin/manage/categories.json b/public/language/et/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/et/admin/manage/categories.json +++ b/public/language/et/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/et/admin/settings/uploads.json b/public/language/et/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/et/admin/settings/uploads.json +++ b/public/language/et/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/fa-IR/admin/manage/categories.json b/public/language/fa-IR/admin/manage/categories.json index 2f2a16a5ec..923d4defe7 100644 --- a/public/language/fa-IR/admin/manage/categories.json +++ b/public/language/fa-IR/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "توضیحات دسته‌بندی", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/fa-IR/admin/settings/uploads.json b/public/language/fa-IR/admin/settings/uploads.json index baf90ba0e4..507f64c898 100644 --- a/public/language/fa-IR/admin/settings/uploads.json +++ b/public/language/fa-IR/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/fi/admin/manage/categories.json b/public/language/fi/admin/manage/categories.json index 42aea3b6bb..c15a783323 100644 --- a/public/language/fi/admin/manage/categories.json +++ b/public/language/fi/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategorian kuvaus", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Taustaväri", diff --git a/public/language/fi/admin/settings/uploads.json b/public/language/fi/admin/settings/uploads.json index a1412b0ed2..792ccd9d3d 100644 --- a/public/language/fi/admin/settings/uploads.json +++ b/public/language/fi/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Kuvan suurin sallittu korkeus (kuvapisteinä)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/fr/admin/manage/categories.json b/public/language/fr/admin/manage/categories.json index fc33293483..c84cbc67d2 100644 --- a/public/language/fr/admin/manage/categories.json +++ b/public/language/fr/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Description de la catégorie", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Couleur d'arrière plan", diff --git a/public/language/fr/admin/settings/uploads.json b/public/language/fr/admin/settings/uploads.json index 453b6be283..6662c0d074 100644 --- a/public/language/fr/admin/settings/uploads.json +++ b/public/language/fr/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Hauteur maximale des images (en pixels)", "reject-image-height-help": "Les images plus grandes que cette valeur seront rejetées.", "allow-topic-thumbnails": "Autoriser les utilisateurs à téléverser des miniatures de sujet", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Miniature du sujet", "allowed-file-extensions": "Extensions de fichiers autorisées", "allowed-file-extensions-help": "Entrer une liste d’extensions de fichier séparées par une virgule (ex : pdf,xls,doc). Une liste vide signifie que toutes les extensions sont autorisées.", diff --git a/public/language/gl/admin/manage/categories.json b/public/language/gl/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/gl/admin/manage/categories.json +++ b/public/language/gl/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/gl/admin/settings/uploads.json b/public/language/gl/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/gl/admin/settings/uploads.json +++ b/public/language/gl/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/he/admin/manage/categories.json b/public/language/he/admin/manage/categories.json index f52d852820..ace84ca716 100644 --- a/public/language/he/admin/manage/categories.json +++ b/public/language/he/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "מקשר קטגוריה", "handle.help": "המקשר לקטגוריה שלך משמשת כייצוג של קטגוריה זו ברשתות אחרות, בדומה לשם משתמש. נקודת אחיזה בקטגוריה אינה יכולה להתאים לשם משתמש או קבוצת משתמשים קיימים.", "description": "תיאור קטגוריה", - "federatedDescription": "תיאור פדרציה", - "federatedDescription.help": "טקסט זה יצורף לתיאור הקטגוריה כאשר הוא יתבקש על ידי אתרים או אפליקציות אחרות.", - "federatedDescription.default": "זוהי קטגוריית פורום המכילה דיון אקטואלי. תוכלו להתחיל דיונים חדשים על ידי אזכור קטגוריה זו.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "צבע רקע", diff --git a/public/language/he/admin/settings/uploads.json b/public/language/he/admin/settings/uploads.json index 55286bd76d..3b4c72a82b 100644 --- a/public/language/he/admin/settings/uploads.json +++ b/public/language/he/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "גובה תמונה מקסימלי (בפיקסלים)", "reject-image-height-help": "תמונות גבוהות יותר מערך זה יידחו", "allow-topic-thumbnails": "אפשרו למשתמשים להעלות תמונה ממוזערת לנושא", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "גודל תמונה ממוזערת לנושא", "allowed-file-extensions": "סיומות קבצים מאושרים", "allowed-file-extensions-help": "הכניסו כאן רשימת פורמטי קבצים מאושרים (לדוגמא. pdf,xls,doc). השארת השורה ללא תוכן פירושו שכל הקבצים יהיו מאושרים.", diff --git a/public/language/hr/admin/manage/categories.json b/public/language/hr/admin/manage/categories.json index e35b386134..5ae77e77e5 100644 --- a/public/language/hr/admin/manage/categories.json +++ b/public/language/hr/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Opis kategorije", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Pozadniska boja", diff --git a/public/language/hr/admin/settings/uploads.json b/public/language/hr/admin/settings/uploads.json index 1efba861e4..e206adf803 100644 --- a/public/language/hr/admin/settings/uploads.json +++ b/public/language/hr/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Dozvoli korisnicima da učitaju sliku teme", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Veličina slike teme", "allowed-file-extensions": "Dozvoljene ekstenzije datoteka", "allowed-file-extensions-help": "Unesite popis dozvoljenih ekstenzija datoteka sa zarezima između (npr. pdf,xls,doc ).Prazan popis znači da su sve ekstenzije dozvoljene.", diff --git a/public/language/hu/admin/manage/categories.json b/public/language/hu/admin/manage/categories.json index 92b0b26507..e9b029a849 100644 --- a/public/language/hu/admin/manage/categories.json +++ b/public/language/hu/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategória leírása", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Háttérszín", diff --git a/public/language/hu/admin/settings/uploads.json b/public/language/hu/admin/settings/uploads.json index fb103aea58..8a575d432f 100644 --- a/public/language/hu/admin/settings/uploads.json +++ b/public/language/hu/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Képek maximális magassága (pixelben)", "reject-image-height-help": "Azon képek, amik magasabbak ennél az értéknél visszautasításra kerülnek.", "allow-topic-thumbnails": "Kis képek feltöltésének engedélyezése témakörhöz a felhasználók számára", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Témakörkép mérete", "allowed-file-extensions": "Megengedett fájlkiterjesztések", "allowed-file-extensions-help": "Itt adj meg fájlkiterjesztési listát, vesszővel elválasztva (pl. pdf,xls,doc). Az üres lista azt jelenti, hogy minden kiterjesztés megengedett.", diff --git a/public/language/hy/admin/manage/categories.json b/public/language/hy/admin/manage/categories.json index 8a69bcad22..a890b90dd9 100644 --- a/public/language/hy/admin/manage/categories.json +++ b/public/language/hy/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Կատեգորիայի նկարագրություն", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Ֆոնի գույնը", diff --git a/public/language/hy/admin/settings/uploads.json b/public/language/hy/admin/settings/uploads.json index 6fa6f789fa..388ddc3c09 100644 --- a/public/language/hy/admin/settings/uploads.json +++ b/public/language/hy/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Նկարի առավելագույն բարձրությունը (պիքսելներով)", "reject-image-height-help": "Այս արժեքից բարձր նկարները կմերժվեն:", "allow-topic-thumbnails": "Թույլ տվեք օգտատերերին վերբեռնել թեմայի մանրապատկերները", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Թեմայի Thumb չափ", "allowed-file-extensions": "Թույլատրված ֆայլերի ընդարձակումներ", "allowed-file-extensions-help": "Մուտքագրեք ստորակետերով բաժանված ֆայլերի ընդարձակման ցանկն այստեղ (օրինակ՝ pdf, xls, doc): Դատարկ ցուցակը նշանակում է, որ բոլոր ընդլայնումները թույլատրված են:", diff --git a/public/language/id/admin/manage/categories.json b/public/language/id/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/id/admin/manage/categories.json +++ b/public/language/id/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/id/admin/settings/uploads.json b/public/language/id/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/id/admin/settings/uploads.json +++ b/public/language/id/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json index d88533b81a..5b081acb6a 100644 --- a/public/language/it/admin/manage/categories.json +++ b/public/language/it/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Pseudonimo categoria", "handle.help": "Lo pseudonimo della categoria è utilizzato come rappresentazione di questa categoria in altre reti, in modo simile a un nome utente. Lo pseudonimo di una categoria non deve corrispondere a un nome utente o a un gruppo di utenti esistenti.", "description": "Descrizione categoria", - "federatedDescription": "Descrizione federazione", - "federatedDescription.help": "Questo testo sarà aggiunto alla descrizione della categoria quando interrogato da altri siti web/app.", - "federatedDescription.default": "Questa è una categoria del forum che contiene discussioni di attualità. Puoi iniziare nuove discussioni menzionando questa categoria.", "topic-template": "Modello discussione", "topic-template.help": "Definisci un modello per le nuove discussioni create in questa categoria.", "bg-color": "Colore sfondo", diff --git a/public/language/it/admin/settings/uploads.json b/public/language/it/admin/settings/uploads.json index a2ba8602e9..d40563b47f 100644 --- a/public/language/it/admin/settings/uploads.json +++ b/public/language/it/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Lunghezza Massima Immagine (in pixel)", "reject-image-height-help": "Le immagini più alte di questo valore saranno rifiutate.", "allow-topic-thumbnails": "Consenti agli utenti di caricare le miniature degli argomenti", + "show-post-uploads-as-thumbnails": "Mostra i post caricati come miniature", "topic-thumb-size": "Dimensione miniatura Argomento", "allowed-file-extensions": "Abilita Estensioni File", "allowed-file-extensions-help": "Inserisci una lista di estensioni separati da virgola quì (es. pdf,xls,doc). Una lista vuota indica che tutte le estensioni sono abilitate.", diff --git a/public/language/ja/admin/manage/categories.json b/public/language/ja/admin/manage/categories.json index 9e2b43ca2a..560eb4b239 100644 --- a/public/language/ja/admin/manage/categories.json +++ b/public/language/ja/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "カテゴリの説明", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "背景色", diff --git a/public/language/ja/admin/settings/uploads.json b/public/language/ja/admin/settings/uploads.json index 84b216ba93..1e7ff65241 100644 --- a/public/language/ja/admin/settings/uploads.json +++ b/public/language/ja/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "ユーザーがスレッドのサムネイルをアップロードできるようにする", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "スレッドのサムネイルの大きさ", "allowed-file-extensions": "ファイル拡張子が有効になりました。", "allowed-file-extensions-help": "ここにファイル拡張子のカンマ区切りリストを入力します(例: pdf,xls,doc )。空のリストは、すべての拡張が許可されていることを意味します。", diff --git a/public/language/ko/admin/manage/categories.json b/public/language/ko/admin/manage/categories.json index 5d15d42604..8eb6c15d16 100644 --- a/public/language/ko/admin/manage/categories.json +++ b/public/language/ko/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "카테고리 설명", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "배경 색상", diff --git a/public/language/ko/admin/settings/uploads.json b/public/language/ko/admin/settings/uploads.json index f222e5fe80..c6ae4c5a87 100644 --- a/public/language/ko/admin/settings/uploads.json +++ b/public/language/ko/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "최대 이미지 높이(픽셀 단위)", "reject-image-height-help": "이 값보다 큰 이미지는 등록할 수 없습니다.", "allow-topic-thumbnails": "사용자가 토픽 썸네일 업로드 허용", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "토픽 썸네일 크기", "allowed-file-extensions": "허용된 파일 확장자", "allowed-file-extensions-help": "허용된 파일 확장자를 쉼표로 구분하여 입력하세요 (예: pdf,xls,doc). 비어 있는 목록은 모든 확장자가 허용됨을 의미합니다.", diff --git a/public/language/lt/admin/manage/categories.json b/public/language/lt/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/lt/admin/manage/categories.json +++ b/public/language/lt/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/lt/admin/settings/uploads.json b/public/language/lt/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/lt/admin/settings/uploads.json +++ b/public/language/lt/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/lv/admin/manage/categories.json b/public/language/lv/admin/manage/categories.json index 5bd9db9d4f..20933eec31 100644 --- a/public/language/lv/admin/manage/categories.json +++ b/public/language/lv/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategorijas apraksts", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Fona krāsa", diff --git a/public/language/lv/admin/settings/uploads.json b/public/language/lv/admin/settings/uploads.json index 0b79f6bb9a..94082ee915 100644 --- a/public/language/lv/admin/settings/uploads.json +++ b/public/language/lv/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maksimālais bildes augstums (pikseļos)", "reject-image-height-help": "Bildes, kas ir augstākas par šo vērtību, tiks noraidītas.", "allow-topic-thumbnails": "Atļaut lietotājiem augšupielādēt tematu sīktēlus", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Tematu sīktēlu lielums", "allowed-file-extensions": "Atļautie failu paplašinājumi", "allowed-file-extensions-help": "Ievadīt ar komatu atdalītu failu paplašinājumu sarakstu (piemērām pdf,xls,doc). Tukšais saraksts nozīmē, ka visi failu paplašinājumi ir atļauti.", diff --git a/public/language/ms/admin/manage/categories.json b/public/language/ms/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/ms/admin/manage/categories.json +++ b/public/language/ms/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/ms/admin/settings/uploads.json b/public/language/ms/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/ms/admin/settings/uploads.json +++ b/public/language/ms/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/nb/admin/manage/categories.json b/public/language/nb/admin/manage/categories.json index fb07c084b9..2031f16e36 100644 --- a/public/language/nb/admin/manage/categories.json +++ b/public/language/nb/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Kategoristi", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategoribeskrivelse", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Bakgrunnsfarge", diff --git a/public/language/nb/admin/settings/uploads.json b/public/language/nb/admin/settings/uploads.json index ff5386fedb..8209e108e4 100644 --- a/public/language/nb/admin/settings/uploads.json +++ b/public/language/nb/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maksimal bildehøyde (i piksler)", "reject-image-height-help": "Bilder høyere enn denne verdien vil bli avvist.", "allow-topic-thumbnails": "Tillat brukere å laste opp emneminiatyrbilder", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Størrelse på emneminiatyrbilder", "allowed-file-extensions": "Tillatte filtyper", "allowed-file-extensions-help": "Skriv inn kommaseparerte filtyper her (f.eks. pdf,xls,doc). En tom liste betyr at alle filtyper er tillatt.", diff --git a/public/language/nl/admin/manage/categories.json b/public/language/nl/admin/manage/categories.json index 96f61da1a3..470ed0209d 100644 --- a/public/language/nl/admin/manage/categories.json +++ b/public/language/nl/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/nl/admin/settings/uploads.json b/public/language/nl/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/nl/admin/settings/uploads.json +++ b/public/language/nl/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/nn-NO/admin/manage/categories.json b/public/language/nn-NO/admin/manage/categories.json index 7d175abcf3..ef3cfa44c2 100644 --- a/public/language/nn-NO/admin/manage/categories.json +++ b/public/language/nn-NO/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Kategori-sti", "handle.help": " Kategori-stien din blir brukt som ein representasjon av denne kategorien på andre nettverk, som eit brukarnamn. Ein kategori-sti må ikkje samsvare med eit eksisterande brukarnamn eller ei brukargruppe.", "description": "Skildring", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Bakgrunnsfarge", diff --git a/public/language/nn-NO/admin/settings/uploads.json b/public/language/nn-NO/admin/settings/uploads.json index c729541aff..c7730e9264 100644 --- a/public/language/nn-NO/admin/settings/uploads.json +++ b/public/language/nn-NO/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Avvis bilete høgde", "reject-image-height-help": "Angi maksimal høgde for bilete som vert avvist ved opplasting.", "allow-topic-thumbnails": "Tillat emne-miniatyrbilete", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Storleik på emne-miniatyr", "allowed-file-extensions": "Tillatne filtypar", "allowed-file-extensions-help": "Angi kva filtypar som er tillatne ved opplasting.", diff --git a/public/language/pl/admin/manage/categories.json b/public/language/pl/admin/manage/categories.json index ba3cc5a058..7ff4422d86 100644 --- a/public/language/pl/admin/manage/categories.json +++ b/public/language/pl/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Przydział kategorii", "handle.help": "Obsługa kategorii robi za znak rozpoznawczy w innych sieciach na wzór nazwy użytkownika. Z tej racji jej nazwa nie może się pokrywać z nazwą użytkownika lub grupą użytkowników.", "description": "Opis kategorii", - "federatedDescription": "Opis federacji", - "federatedDescription.help": "Ten tekst zostanie użyty dla opisu sekcji widocznej z poziomu innych stron/aplikacji.", - "federatedDescription.default": "Ta sekcja forum zawiera dyskusje tematyczne. Możesz rozpocząć nową dyskusję wzmiankując tę kategorię.", "topic-template": "Szablon wątku", "topic-template.help": "Stwórz szablon dla nowych wątków dodawanych w tej kategorii.", "bg-color": "Kolor tła", diff --git a/public/language/pl/admin/settings/uploads.json b/public/language/pl/admin/settings/uploads.json index 10d0606239..87d598de8b 100644 --- a/public/language/pl/admin/settings/uploads.json +++ b/public/language/pl/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maksymalna wysokość obrazu (w pikselach)", "reject-image-height-help": "Obrazy o wysokości przekraczającej tę wartość zostaną odrzucone.", "allow-topic-thumbnails": "Zezwalaj użytkownikom na ustawianie miniaturek tematów", + "show-post-uploads-as-thumbnails": "Pokaż załączniki do wpisów w formie miniaturek", "topic-thumb-size": "Rozmiar miniatury tematu", "allowed-file-extensions": "Dozwolone typy plików", "allowed-file-extensions-help": "Wprowadź rozdzielone przecinkami rozszerzenia plików (np. pdf,xls,doc). Pusta lista oznacza, że wszystkie rozszerzenia są dozwolone.", diff --git a/public/language/pt-BR/admin/manage/categories.json b/public/language/pt-BR/admin/manage/categories.json index 8f8d3bc66e..808455c32a 100644 --- a/public/language/pt-BR/admin/manage/categories.json +++ b/public/language/pt-BR/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Descrição da Categoria", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Cor de Fundo", diff --git a/public/language/pt-BR/admin/settings/uploads.json b/public/language/pt-BR/admin/settings/uploads.json index c203d2cd23..5f0b8362c9 100644 --- a/public/language/pt-BR/admin/settings/uploads.json +++ b/public/language/pt-BR/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Altura Máxima das Imagens (em pixels)", "reject-image-height-help": "Imagens com uma altura maior do que este valor serão rejeitadas.", "allow-topic-thumbnails": "Permitir usuários de enviar miniaturas de tópico", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Tamanho da Miniatura de Tópico", "allowed-file-extensions": "Extensões de Arquivo Permitidas", "allowed-file-extensions-help": "Digite uma lista, separada por vírgulas, de extensões de arquivos aqui (por exemplo: pdf,xls,doc). Uma lista vazia significa que todas as extensões são permitidas.", diff --git a/public/language/pt-PT/admin/manage/categories.json b/public/language/pt-PT/admin/manage/categories.json index 7ec4c606d0..b309193c8f 100644 --- a/public/language/pt-PT/admin/manage/categories.json +++ b/public/language/pt-PT/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Descrição da Categoria", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Cor de Fundo", diff --git a/public/language/pt-PT/admin/settings/uploads.json b/public/language/pt-PT/admin/settings/uploads.json index 91c89d7a46..593df275f8 100644 --- a/public/language/pt-PT/admin/settings/uploads.json +++ b/public/language/pt-PT/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Altura Máxima da Imagem (em píxeis)", "reject-image-height-help": "Imagens mais altas que este valor vão ser rejeitadas.", "allow-topic-thumbnails": "Permitir aos utilizadores enviar miniaturas de tópicos", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Tamanho da Miniatura do Tópico", "allowed-file-extensions": "Extensões de Ficheiro Permitidas", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/ro/admin/admin.json b/public/language/ro/admin/admin.json index 96c58b1733..7be102ffdc 100644 --- a/public/language/ro/admin/admin.json +++ b/public/language/ro/admin/admin.json @@ -1,18 +1,18 @@ { - "alert.confirm-rebuild-and-restart": "Are you sure you wish to rebuild and restart NodeBB?", - "alert.confirm-restart": "Are you sure you wish to restart NodeBB?", + "alert.confirm-rebuild-and-restart": "Sigur dorești să reconstruiești și să repornești NodeBB?", + "alert.confirm-restart": "Sigur dorești să repornești NodeBB?", - "acp-title": "%1 | NodeBB Admin Control Panel", - "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", - "changes-not-saved-message": "NodeBB encountered a problem saving your changes. (%1)", - "save-changes": "Save changes", + "acp-title": "%1 | Panou de control NodeBB", + "settings-header-contents": "Conținut", + "changes-saved": "Modificări Salvate", + "changes-saved-message": "Modificările aduse configurației NodeBB au fost salvate.", + "changes-not-saved": "Modificări Nesalvate", + "changes-not-saved-message": "NodeBB a întâmpinat o problemă la salvarea modificărilor. (%1)", + "save-changes": "Salvați modificările", "min": "Min:", "max": "Max:", - "view": "View", - "edit": "Edit", - "add": "Add", - "select-icon": "Select Icon" + "view": "Vizualizare", + "edit": "Modifică", + "add": "Adaugă", + "select-icon": "Selectați Icon" } \ No newline at end of file diff --git a/public/language/ro/admin/advanced/database.json b/public/language/ro/admin/advanced/database.json index 55eea6c023..f67e83859a 100644 --- a/public/language/ro/admin/advanced/database.json +++ b/public/language/ro/admin/advanced/database.json @@ -17,7 +17,7 @@ "mongo.file-size": "File Size", "mongo.resident-memory": "Resident Memory", "mongo.virtual-memory": "Virtual Memory", - "mongo.mapped-memory": "Mapped Memory", + "mongo.mapped-memory": "Memorie mapată", "mongo.bytes-in": "Bytes In", "mongo.bytes-out": "Bytes Out", "mongo.num-requests": "Number of Requests", diff --git a/public/language/ro/admin/dashboard.json b/public/language/ro/admin/dashboard.json index 0be6d5866c..9e4b880154 100644 --- a/public/language/ro/admin/dashboard.json +++ b/public/language/ro/admin/dashboard.json @@ -75,7 +75,7 @@ "graphs.page-views-registered": "Page Views Registered", "graphs.page-views-guest": "Page Views Guest", "graphs.page-views-bot": "Page Views Bot", - "graphs.page-views-ap": "ActivityPub Page Views", + "graphs.page-views-ap": "Vizualizări Pagină ActivityPub", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.guest-users": "Guest Users", @@ -96,7 +96,7 @@ "expand-analytics": "Expand analytics", "clear-search-history": "Clear Search History", "clear-search-history-confirm": "Are you sure you want to clear entire search history?", - "search-term": "Term", + "search-term": "Termen", "search-count": "Count", - "view-all": "View all" + "view-all": "Arată Tot" } diff --git a/public/language/ro/admin/development/info.json b/public/language/ro/admin/development/info.json index 9834719daf..782b9133e0 100644 --- a/public/language/ro/admin/development/info.json +++ b/public/language/ro/admin/development/info.json @@ -3,7 +3,7 @@ "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", - "primary": "primary / jobs", + "primary": "principal / job-uri", "pid": "pid", "nodejs": "nodejs", "online": "online", @@ -19,7 +19,7 @@ "registered": "Registered", "sockets": "Sockets", - "connection-count": "Connection Count", + "connection-count": "Număr Conexiuni", "guests": "Guests", "info": "Info" diff --git a/public/language/ro/admin/manage/categories.json b/public/language/ro/admin/manage/categories.json index 38037f7206..a179d5eb4d 100644 --- a/public/language/ro/admin/manage/categories.json +++ b/public/language/ro/admin/manage/categories.json @@ -1,25 +1,22 @@ { "manage-categories": "Manage Categories", "add-category": "Add category", - "add-local-category": "Add Local category", - "add-remote-category": "Add Remote category", - "remove": "Remove", - "rename": "Rename", + "add-local-category": "Adăugați Categorie Locală", + "add-remote-category": "Adăugă Categorie de la Distanță", + "remove": "Elimină", + "rename": "Redenumește", "jump-to": "Jump to...", "settings": "Category Settings", "edit-category": "Edit Category", "privileges": "Privileges", "back-to-categories": "Back to categories", - "id": "Category ID", + "id": "ID Categorie", "name": "Category Name", - "handle": "Category Handle", - "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", + "handle": "Identificator Categorie", + "handle.help": "Identificatorul categoriei este folosit ca o reprezentare a acestei categorii în alte rețele, similar unui nume de utilizator. Un identificator de categorie nu trebuie să corespundă unui nume de utilizator sau unui grup de utilizatori existent.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", - "topic-template": "Topic Template", - "topic-template.help": "Define a template for new topics created in this category.", + "topic-template": "Șablon Subiect", + "topic-template.help": "Definiți un șablon pentru subiectele noi create în această categorie.", "bg-color": "Background Colour", "text-color": "Text Colour", "bg-image-size": "Background Image Size", @@ -49,7 +46,7 @@ "disable": "Disable", "edit": "Edit", "analytics": "Analytics", - "federation": "Federation", + "federation": "Federație", "view-category": "View category", "set-order": "Set order", @@ -89,32 +86,32 @@ "analytics.topics-daily": "Figure 3 – Daily topics created in this category", "analytics.posts-daily": "Figure 4 – Daily posts made in this category", - "federation.title": "Federation settings for \"%1\" category", - "federation.disabled": "Federation is disabled site-wide, so category federation settings are currently unavailable.", - "federation.disabled-cta": "Federation Settings →", - "federation.syncing-header": "Synchronization", - "federation.syncing-intro": "A category can follow a \"Group Actor\" via the ActivityPub protocol. If content is received from one of the actors listed below, it will be automatically added to this category.", - "federation.syncing-caveat": "N.B. Setting up syncing here establishes a one-way synchronization. NodeBB attempts to subscribe/follow the actor, but the reverse cannot be assumed.", - "federation.syncing-none": "This category is not currently following anybody.", - "federation.syncing-add": "Synchronize with...", + "federation.title": "Setări Federație pentru categoria \"%1\"", + "federation.disabled": "Federația este dezactivată la nivel de site, așadar setările de federare a categoriilor nu sunt disponibile în prezent.", + "federation.disabled-cta": "Setări Federație →", + "federation.syncing-header": "Sincronizare", + "federation.syncing-intro": "O categorie poate urma un „Actor de grup” prin protocolul ActivityPub. Dacă se primește conținut de la unul dintre actorii enumerați mai jos, acesta va fi adăugat automat în această categorie.", + "federation.syncing-caveat": "Notă: Configurarea sincronizării aici stabilește o sincronizare unidirecțională. NodeBB încearcă să se aboneze/să urmărească actorul, dar nu se poate presupune inversul.", + "federation.syncing-none": "Această categorie nu urmărește pe nimeni în prezent.", + "federation.syncing-add": "Sincronizează cu...", "federation.syncing-actorUri": "Actor", - "federation.syncing-follow": "Follow", - "federation.syncing-unfollow": "Unfollow", - "federation.followers": "Remote users following this category", - "federation.followers-handle": "Handle", + "federation.syncing-follow": "Urmărește", + "federation.syncing-unfollow": "Nu urmări", + "federation.followers": "Utilizatori la distanță care urmăresc această categorie", + "federation.followers-handle": "Identificator", "federation.followers-id": "ID", - "federation.followers-none": "No followers.", - "federation.followers-autofill": "Autofill", + "federation.followers-none": "Niciun urmăritor.", + "federation.followers-autofill": "Completare automată", "alert.created": "Created", "alert.create-success": "Category successfully created!", "alert.none-active": "You have no active categories.", "alert.create": "Create a Category", - "alert.add": "Add a Category", - "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.

Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.", - "alert.rename": "Rename a Remote Category", - "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", - "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", + "alert.add": "Adăugă Categorie", + "alert.add-help": "Categoriile la distanță pot fi adăugate la lista de categorii specificând identificatorul/identificatorul acestora.

Notă — Categoria la distanță poate să nu reflecte toate subiectele publicate, cu excepția cazului în care cel puțin un utilizator local o urmărește/o urmărește.", + "alert.rename": "Redenumiți o categorie de la distanță", + "alert.rename-help": "Vă rugăm să introduceți un nume nou pentru această categorie. Lăsați câmpul necompletat pentru a restaura numele original.", + "alert.confirm-remove": "Sigur doriți să eliminați această categorie? O puteți adăuga din nou oricând.", "alert.confirm-purge": "

Do you really want to purge this category \"%1\"?

Warning! All topics and posts in this category will be purged!

Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.

", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/ro/admin/manage/privileges.json b/public/language/ro/admin/manage/privileges.json index 240cff6aa5..1b0a547853 100644 --- a/public/language/ro/admin/manage/privileges.json +++ b/public/language/ro/admin/manage/privileges.json @@ -8,7 +8,7 @@ "edit-privileges": "Edit Privileges", "select-clear-all": "Select/Clear All", "chat": "Chat", - "chat-with-privileged": "Chat with Privileged", + "chat-with-privileged": "Conversează cu cineva cu drepturi", "upload-images": "Upload Images", "upload-files": "Upload Files", "signature": "Signature", diff --git a/public/language/ro/admin/manage/user-custom-fields.json b/public/language/ro/admin/manage/user-custom-fields.json index dab10670d2..a2c2da240d 100644 --- a/public/language/ro/admin/manage/user-custom-fields.json +++ b/public/language/ro/admin/manage/user-custom-fields.json @@ -1,28 +1,28 @@ { - "title": "Manage Custom User Fields", - "create-field": "Create Field", - "edit-field": "Edit Field", - "manage-custom-fields": "Manage Custom Fields", - "type-of-input": "Type of input", - "key": "Key", - "name": "Name", - "icon": "Icon", - "type": "Type", - "min-rep": "Minimum Reputation", - "input-type-text": "Input (Text)", - "input-type-link": "Input (Link)", - "input-type-number": "Input (Number)", - "input-type-date": "Input (Date)", - "input-type-select": "Select", - "input-type-select-multi": "Select Multiple", - "select-options": "Options", - "select-options-help": "Add one option per line for the select element", - "minimum-reputation": "Minimum reputation", - "minimum-reputation-help": "If a user has less than this value they won't be able to use this field", - "delete-field-confirm-x": "Do you really want to delete custom field \"%1\"?", - "custom-fields-saved": "Custom fields saved", - "visibility": "Visibility", - "visibility-all": "Everyone can see the field", - "visibility-loggedin": "Only logged in users can see the field", - "visibility-privileged": "Only privileged users like admins & moderators can see the field" + "title": "Gestionarea câmpurilor personalizate ale utilizatorilor", + "create-field": "Creare Câmp", + "edit-field": "Modificare Câmp", + "manage-custom-fields": "Administrare Câmpuri Personalizate", + "type-of-input": "Tipul editorului", + "key": "Cheie", + "name": "Nume", + "icon": "Iconîță", + "type": "Tip", + "min-rep": "Reputație Minimă", + "input-type-text": "Editor (Text)", + "input-type-link": "Editor (Link)", + "input-type-number": "Editor (Număr)", + "input-type-date": "Editor (Dată)", + "input-type-select": "Selecție", + "input-type-select-multi": "Selecție Multiplă", + "select-options": "Opțiuni", + "select-options-help": "Adăugați pe linie câte o opțiune pentru elementul select", + "minimum-reputation": "Reputație Minimă", + "minimum-reputation-help": "Dacă un utilizator are o valoare mai mică decât această, nu va putea folosi acest câmp.", + "delete-field-confirm-x": "Sigur doriți să ștergeți câmpul personalizat „%1”?", + "custom-fields-saved": "Câmpuri personalizate salvate", + "visibility": "Vizibilitate", + "visibility-all": "Oricine poate vedea câmpul", + "visibility-loggedin": "Doar utilizatorii autentificați pot vedea câmpul", + "visibility-privileged": "Doar utilizatorii privilegiați ca administratori sau moderatori pot vedea câmpul" } \ No newline at end of file diff --git a/public/language/ro/admin/menu.json b/public/language/ro/admin/menu.json index 913c74f475..8114756e80 100644 --- a/public/language/ro/admin/menu.json +++ b/public/language/ro/admin/menu.json @@ -38,7 +38,7 @@ "settings/tags": "Tags", "settings/notifications": "Notifications", "settings/api": "API Access", - "settings/activitypub": "Federation (ActivityPub)", + "settings/activitypub": "Federație (ActivityPub)", "settings/sounds": "Sounds", "settings/social": "Social", "settings/cookies": "Cookies", diff --git a/public/language/ro/admin/settings/activitypub.json b/public/language/ro/admin/settings/activitypub.json index 5ab4fa43e8..e94dfe6b6d 100644 --- a/public/language/ro/admin/settings/activitypub.json +++ b/public/language/ro/admin/settings/activitypub.json @@ -1,48 +1,48 @@ { - "intro-lead": "What is Federation?", - "intro-body": "NodeBB is able to communicate with other NodeBB instances that support it. This is achieved through a protocol called ActivityPub. If enabled, NodeBB will also be able to communicate with other apps and websites that use ActivityPub (e.g. Mastodon, Peertube, etc.)", + "intro-lead": "Ce este Federația?", + "intro-body": "NodeBB poate comunica cu alte instanțe NodeBB care îl suportă. Acest lucru se realizează printr-un protocol numit ActivityPub. Dacă este activat, NodeBB va putea comunica și cu alte aplicații și site-uri web care utilizează ActivityPub (de exemplu, Mastodon, Peertube etc.).", "general": "General", - "pruning": "Content Pruning", - "content-pruning": "Days to keep remote content", - "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)", - "user-pruning": "Days to cache remote user accounts", - "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)", - "enabled": "Enable Federation", - "enabled-help": "If enabled, will allow this NodeBB will be able to communicate with all Activitypub-enabled clients on the wider fediverse.", - "allowLoopback": "Allow loopback processing", - "allowLoopback-help": "Useful for debugging purposes only. You should probably leave this disabled.", + "pruning": "Eliminarea Conținutului", + "content-pruning": "Zile pentru păstrarea conținutului de la distanță", + "content-pruning-help": "Rețineți că va fi păstrat conținutul de la distanță cu care s-a interacționat (un răspuns sau un vot pozitiv/negativ). (0 pentru dezactivat)", + "user-pruning": "Zile pentru a păstra în memoria cache conturile de utilizatori de la distanță", + "user-pruning-help": "Conturile de utilizatori de la distanță vor fi eliminate doar dacă nu au postări. În caz contrar, vor fi recuperate. (0 pentru dezactivat)", + "enabled": "Activează Federația", + "enabled-help": "Dacă este activat, acest lucru va permite ca NodeBB să poată comunica cu toți clienții compatibili cu Activitypub de pe fediverse.", + "allowLoopback": "Permite procesarea loopback", + "allowLoopback-help": "Util doar pentru depanare. Probabil ar trebui să lași această opțiune dezactivată.", - "probe": "Open in App", - "probe-enabled": "Try to open ActivityPub-enabled resources in NodeBB", - "probe-enabled-help": "If enabled, NodeBB will check every external link for an ActivityPub equivalent, and load it in NodeBB instead.", - "probe-timeout": "Lookup Timeout (milliseconds)", - "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "probe": "Deschide în Aplicație", + "probe-enabled": "Încercă să deschidă resurse compatibile cu ActivityPub în NodeBB", + "probe-enabled-help": "Dacă este activat, NodeBB va verifica fiecare link extern pentru un echivalent ActivityPub și îl va încărca în NodeBB.", + "probe-timeout": "Timp de așteptare (milisecunde)", + "probe-timeout-help": "(Implicit: 2000) Dacă interogarea de căutare nu primește un răspuns în intervalul de timp setat, utilizatorul va fi direcționat direct către link. Ajustați acest număr mai mare dacă site-urile răspund lent și doriți să acordați timp suplimentar.", - "rules": "Categorization", - "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", - "rules.modal.title": "How it works", - "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.

N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.", - "rules.add": "Add New Rule", - "rules.help-hashtag": "Topics containing this case-insensitive hashtag will match. Do not enter the # symbol", - "rules.help-user": "Topics created by the entered user will match. Enter a handle or full ID (e.g. bob@example.org or https://example.org/users/bob.", - "rules.type": "Type", - "rules.value": "Value", - "rules.cid": "Category", + "rules": "Clasificare", + "rules-intro": "Conținutul descoperit prin ActivityPub poate fi clasificat automat pe baza anumitor reguli (de exemplu, hashtag)", + "rules.modal.title": "Cum funcționează", + "rules.modal.instructions": "Orice conținut primit este verificat în funcție de aceste reguli de clasificare, iar conținutul corespunzător este mutat automat în categoria aleasă.

N.B. Conținutul care este deja clasificat (adică într-o categorie de la distanță) nu va trece prin aceste reguli.", + "rules.add": "Adăugă Regulă Nouă", + "rules.help-hashtag": "Subiectele care conțin acest hashtag fără a ține cont de majuscule/minuscule se vor potrivi. Nu introduceți simbolul #", + "rules.help-user": "Subiectele create de utilizatorul introdus se vor potrivi. Introduceți un nume de utilizator sau un ID complet (e.g. bob@example.org sau https://example.org/users/bob.", + "rules.type": "Tip", + "rules.value": "Valoare", + "rules.cid": "Categorie", - "relays": "Relays", - "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.", - "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.", - "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.", - "relays.add": "Add New Relay", - "relays.relay": "Relay", - "relays.state": "State", - "relays.state-0": "Pending", - "relays.state-1": "Receiving only", - "relays.state-2": "Active", + "relays": "Retransmițători", + "relays.intro": "O funcție de retransmitere îmbunătățește descoperirea conținutului către și de la NodeBB-ul dvs. Abonarea la o funcție de retransmitere înseamnă că respectivul conținut primit de către retransmitere este redirecționat aici, iar conținutul postat aici este sindicalizat către exterior de către retransmitere.", + "relays.warning": "Notă: Releele pot trimite volume mari de trafic și pot crește costurile de stocare și procesare.", + "relays.litepub": "NodeBB respectă standardul de retransmisie în stil LitePub. URL-ul pe care îl introduceți aici ar trebui să se termine cu /actor.", + "relays.add": "Adaugă Retransmițător Nou", + "relays.relay": "Retransmițător", + "relays.state": "Stare", + "relays.state-0": "În Așteptare", + "relays.state-1": "Doar Primește", + "relays.state-2": "Activ", - "server-filtering": "Filtering", - "count": "This NodeBB is currently aware of %1 server(s)", - "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", - "server.filter-help-hostname": "Enter just the instance hostname below (e.g. example.org), separated by line breaks.", - "server.filter-allow-list": "Use this as an Allow List instead" + "server-filtering": "Filtrează", + "count": "NodeBB cunoaște acum %1 server(e)", + "server.filter-help": "Specificați serverele interzise a se conecta cu NodeBB-ul dvs. Alternativ, puteți opta să permiteți selectiv conectarea cu anumite servere. Ambele opțiuni sunt acceptate, deși se exclud reciproc.", + "server.filter-help-hostname": "Introduceți mai jos doar numele instanței (de exemplu, example.org), câte unul pe rând.", + "server.filter-allow-list": "Folosește ca Poziții Permise" } \ No newline at end of file diff --git a/public/language/ro/admin/settings/chat.json b/public/language/ro/admin/settings/chat.json index 6d6cad284b..0ffa932af3 100644 --- a/public/language/ro/admin/settings/chat.json +++ b/public/language/ro/admin/settings/chat.json @@ -5,8 +5,8 @@ "disable-editing": "Disable chat message editing/deletion", "disable-editing-help": "Administrators and global moderators are exempt from this restriction", "max-length": "Maximum length of chat messages", - "max-length-remote": "Maximum length of remote chat messages", - "max-length-remote-help": "This value is usually set higher than the chat message maximum for local users as remote messages tend to be longer (with @ mentions, etc.)", + "max-length-remote": "Lungimea maximă a mesajelor de chat la distanță", + "max-length-remote-help": "Această valoare este de obicei setată la o valoare mai mare decât numărul maxim de mesaje de chat pentru utilizatorii locali, deoarece mesajele la distanță tind să fie mai lungi (cu mențiuni @ etc.).", "max-chat-room-name-length": "Maximum length of chat room names", "max-room-size": "Maximum number of users in chat rooms", "delay": "Time between chat messages (ms)", diff --git a/public/language/ro/admin/settings/email.json b/public/language/ro/admin/settings/email.json index 0310939cb3..ac3d5eb0f9 100644 --- a/public/language/ro/admin/settings/email.json +++ b/public/language/ro/admin/settings/email.json @@ -28,8 +28,8 @@ "smtp-transport.password": "Password", "smtp-transport.pool": "Enable pooled connections", "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", - "smtp-transport.allow-self-signed": "Allow self-signed certificates", - "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.", + "smtp-transport.allow-self-signed": "Permite certificate self-signed", + "smtp-transport.allow-self-signed-help": "Activarea acestei setări vă va permite să utilizați certificate TLS self-signed sau invalide.", "template": "Edit Email Template", "template.select": "Select Email Template", diff --git a/public/language/ro/admin/settings/general.json b/public/language/ro/admin/settings/general.json index d56c819745..ebebb10fa5 100644 --- a/public/language/ro/admin/settings/general.json +++ b/public/language/ro/admin/settings/general.json @@ -15,7 +15,7 @@ "title-layout": "Title Layout", "title-layout-help": "Define how the browser title will be structured ie. {pageTitle} | {browserTitle}", "description.placeholder": "A short description about your community", - "description": "Site Description", + "description": "Descrierea site-ului", "keywords": "Site Keywords", "keywords-placeholder": "Keywords describing your community, comma-separated", "logo-and-icons": "Site Logo & Icons", @@ -51,7 +51,7 @@ "topic-tools": "Topic Tools", "home-page": "Home Page", "home-page-route": "Home Page Route", - "home-page-description": "Choose what page is shown when users navigate to the root URL of your forum.", + "home-page-description": "Alegeți ce pagină este afișată când utilizatorii navighează la adresa URL rădăcină a forumului dvs.", "custom-route": "Custom Route", "allow-user-home-pages": "Allow User Home Pages", "home-page-title": "Title of the home page (default \"Home\")", diff --git a/public/language/ro/admin/settings/navigation.json b/public/language/ro/admin/settings/navigation.json index 3a71061ecf..130a14c17a 100644 --- a/public/language/ro/admin/settings/navigation.json +++ b/public/language/ro/admin/settings/navigation.json @@ -10,7 +10,7 @@ "id": "ID: optional", "properties": "Properties:", - "show-to-groups": "Show to Groups:", + "show-to-groups": "Afișare în Grupurile:", "open-new-window": "Open in a new window", "dropdown": "Dropdown", "dropdown-placeholder": "Place your dropdown menu items below, ie:
<li><a class="dropdown-item" href="https://myforum.com">Link 1</a></li>", diff --git a/public/language/ro/admin/settings/post.json b/public/language/ro/admin/settings/post.json index e000f6b10b..5c11545151 100644 --- a/public/language/ro/admin/settings/post.json +++ b/public/language/ro/admin/settings/post.json @@ -4,11 +4,11 @@ "sorting.post-default": "Default Post Sorting", "sorting.oldest-to-newest": "Oldest to Newest", "sorting.newest-to-oldest": "Newest to Oldest", - "sorting.recently-replied": "Recently Replied", - "sorting.recently-created": "Recently Created", + "sorting.recently-replied": "Răspunse Recent", + "sorting.recently-created": "Create Recent", "sorting.most-votes": "Most Votes", "sorting.most-posts": "Most Posts", - "sorting.most-views": "Most Views", + "sorting.most-views": "Cele Mai Văzute", "sorting.topic-default": "Default Topic Sorting", "length": "Post Length", "post-queue": "Post Queue", diff --git a/public/language/ro/admin/settings/uploads.json b/public/language/ro/admin/settings/uploads.json index 22046915d9..da12502884 100644 --- a/public/language/ro/admin/settings/uploads.json +++ b/public/language/ro/admin/settings/uploads.json @@ -9,10 +9,10 @@ "private-extensions": "File extensions to make private", "private-uploads-extensions-help": "Enter comma-separated list of file extensions to make private here (e.g. pdf,xls,doc). An empty list means all files are private.", "resize-image-width-threshold": "Resize images if they are wider than specified width", - "resize-image-width-threshold-help": "(in pixels, default: 2000 pixels, set to 0 to disable)", + "resize-image-width-threshold-help": "(în pixeli, implicit: 2000 pixeli, setați la 0 pentru dezactivare)", "resize-image-width": "Resize images down to specified width", "resize-image-width-help": "(in pixels, default: 760 pixels, set to 0 to disable)", - "resize-image-keep-original": "Keep original image after resize", + "resize-image-keep-original": "Păstrează imaginea originală după redimensionare", "resize-image-quality": "Quality to use when resizing images", "resize-image-quality-help": "Use a lower quality setting to reduce the file size of resized images.", "max-file-size": "Maximum File Size (in KiB)", @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Afișează încărcările de postări ca miniaturi", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/ro/admin/settings/user.json b/public/language/ro/admin/settings/user.json index c8cc3c9c34..7f87942d2a 100644 --- a/public/language/ro/admin/settings/user.json +++ b/public/language/ro/admin/settings/user.json @@ -64,7 +64,7 @@ "show-email": "Show email", "show-fullname": "Show fullname", "restrict-chat": "Only allow chat messages from users I follow", - "disable-incoming-chats": "Disable incoming chat messages", + "disable-incoming-chats": "Dezactivați primirea de mesaje", "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "update-url-with-post-index": "Update url with post index while browsing topics", diff --git a/public/language/ro/pages.json b/public/language/ro/pages.json index 87ab32ddb6..41f0901058 100644 --- a/public/language/ro/pages.json +++ b/public/language/ro/pages.json @@ -36,7 +36,7 @@ "chat": "Chatting with %1", "flags": "Flags", "flag-details": "Flag %1 Details", - "world": "World", + "world": "Lumea", "account/edit": "Editing \"%1\"", "account/edit/password": "Editing password of \"%1\"", "account/edit/username": "Editing username of \"%1\"", @@ -55,7 +55,7 @@ "account/settings-of": "Changing settings of %1", "account/watched": "Topics watched by %1", "account/ignored": "Topics ignored by %1", - "account/read": "Topics read by %1", + "account/read": "Subiecte citite de %1", "account/upvoted": "Posts upvoted by %1", "account/downvoted": "Posts downvoted by %1", "account/best": "Best posts made by %1", @@ -63,7 +63,7 @@ "account/blocks": "Blocked users for %1", "account/uploads": "Uploads by %1", "account/sessions": "Login Sessions", - "account/shares": "Topics shared by %1", + "account/shares": "Subiecte partajate de %1", "confirm": "Email Confirmed", "maintenance.text": "%1 is currently undergoing maintenance.
Please come back another time.", "maintenance.messageIntro": "Additionally, the administrator has left this message:", diff --git a/public/language/ro/post-queue.json b/public/language/ro/post-queue.json index 24b33da2e6..c9d806c61b 100644 --- a/public/language/ro/post-queue.json +++ b/public/language/ro/post-queue.json @@ -3,10 +3,10 @@ "post-queue": "Post Queue", "no-queued-posts": "There are no posts in the post queue.", "no-single-post": "The topic or post you are looking for is no longer in the queue. It has likely been approved or deleted already.", - "enabling-help": "The post queue is currently disabled. To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", + "enabling-help": "Coada de publicare este dezactivată . Pentru a o activa, mergeți la Setări → Post → Post Queue și activați Post Queue.", "back-to-list": "Back to Post Queue", - "public-intro": "If you have any queued posts, they will be shown here.", - "public-description": "This forum is configured to automatically queue posts from new accounts, pending moderator approval.
If you have queued posts awaiting approval, you will be able to see them here.", + "public-intro": "Dacă aveți postări în coadă, acestea vor fi afișate aici.", + "public-description": "Acest forum este configurat să adauge automat în coadă postările de la conturile noi, în așteptarea aprobării moderatorului.
Dacă aveți postări în coadă care așteaptă aprobarea, le veți putea vedea aici.", "user": "User", "when": "When", "category": "Category", @@ -39,5 +39,5 @@ "remove-selected-confirm": "Do you want to remove %1 selected posts?", "bulk-accept-success": "%1 posts accepted", "bulk-reject-success": "%1 posts rejected", - "links-in-this-post": "Links in this post" + "links-in-this-post": "Linkuri în această postare" } \ No newline at end of file diff --git a/public/language/ro/recent.json b/public/language/ro/recent.json index 825ef74692..9e878b96c7 100644 --- a/public/language/ro/recent.json +++ b/public/language/ro/recent.json @@ -8,6 +8,6 @@ "no-recent-topics": "Nu există subiecte recente.", "no-popular-topics": "Nu sunt subiecte populare.", "load-new-posts": "Load new posts", - "uncategorized.title": "All known topics", - "uncategorized.intro": "This page shows a chronological listing of every topic that this forum has received.
The views and opinions expressed in the topics below are not moderated and may not represent the views and opinions of this website." + "uncategorized.title": "Toate subiectele cunoscute", + "uncategorized.intro": "Această pagină prezintă o listă cronologică a fiecărui subiect primit de acest forum. Părerile și opiniile exprimate în subiectele de mai jos nu sunt moderate și este posibil să nu reprezinte opiniile și opiniile acestui site web." } \ No newline at end of file diff --git a/public/language/ro/search.json b/public/language/ro/search.json index f994b924cb..0d544d3545 100644 --- a/public/language/ro/search.json +++ b/public/language/ro/search.json @@ -7,7 +7,7 @@ "in-titles": "In titles", "in-titles-posts": "In titles and posts", "in-posts": "In posts", - "in-bookmarks": "In bookmarks", + "in-bookmarks": "În marcaje", "in-categories": "In categories", "in-users": "In users", "in-tags": "In tags", diff --git a/public/language/ro/social.json b/public/language/ro/social.json index 5b8dd99a46..40dc8ec5ec 100644 --- a/public/language/ro/social.json +++ b/public/language/ro/social.json @@ -7,8 +7,8 @@ "sign-up-with-google": "Sign up with Google", "log-in-with-facebook": "Log in with Facebook", "continue-with-facebook": "Continue with Facebook", - "sign-in-with-linkedin": "Sign in with LinkedIn", - "sign-up-with-linkedin": "Sign up with LinkedIn", - "sign-in-with-wordpress": "Sign in with WordPress", - "sign-up-with-wordpress": "Sign up with WordPress" + "sign-in-with-linkedin": "Conectează-te cu LinkedIn", + "sign-up-with-linkedin": "Înregistrează-te cu LinkedIn", + "sign-in-with-wordpress": "Conectează-te cu WordPress", + "sign-up-with-wordpress": "Înregistrează-te cu WordPress" } \ No newline at end of file diff --git a/public/language/ro/tags.json b/public/language/ro/tags.json index b412a8c85d..cdf91bf1e0 100644 --- a/public/language/ro/tags.json +++ b/public/language/ro/tags.json @@ -3,7 +3,7 @@ "no-tag-topics": "Nu există nici un subiect cu acest tag.", "no-tags-found": "No tags found", "tags": "Taguri", - "enter-tags-here": "Enter tags, %1 - %2 characters.", + "enter-tags-here": "Introduceți etichete, %1 - %2 caractere.", "enter-tags-here-short": "Introdu taguri...", "no-tags": "În acest moment nu există nici un tag.", "select-tags": "Select Tags", diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json index 5d44fe17d7..30691b771b 100644 --- a/public/language/ro/topic.json +++ b/public/language/ro/topic.json @@ -15,7 +15,7 @@ "replies-to-this-post": "%1 Replies", "one-reply-to-this-post": "1 Reply", "last-reply-time": "Last reply", - "reply-options": "Reply options", + "reply-options": "Opțiuni răspuns", "reply-as-topic": "Răspunde ca subiect", "guest-login-reply": "Login pentru a răspunde", "login-to-view": "🔒 Log in to view", @@ -27,7 +27,7 @@ "restore": "Restaurează", "move": "Mută", "change-owner": "Change Owner", - "manage-editors": "Manage Editors", + "manage-editors": "Gestionați Editorii", "fork": "Bifurcă", "link": "Link", "share": "Distribuie", @@ -36,7 +36,7 @@ "pinned": "Pinned", "pinned-with-expiry": "Pinned until %1", "scheduled": "Scheduled", - "deleted": "Deleted", + "deleted": "Șters", "moved": "Moved", "moved-from": "Moved from %1", "copy-code": "Copy Code", @@ -61,8 +61,8 @@ "user-restored-topic-on": "%1 restored this topic on %2", "user-moved-topic-from-ago": "%1 moved this topic from %2 %3", "user-moved-topic-from-on": "%1 moved this topic from %2 on %3", - "user-shared-topic-ago": "%1 shared this topic %2", - "user-shared-topic-on": "%1 shared this topic on %2", + "user-shared-topic-ago": "%1 a distribuit acest subiect %2", + "user-shared-topic-on": "%1 a distribuit acest subiect pe %2", "user-queued-post-ago": "%1 queued post for approval %3", "user-queued-post-on": "%1 queued post for approval on %3", "user-referenced-topic-ago": "%1 referenced this topic %3", @@ -106,7 +106,7 @@ "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Mută-le pe toate", "thread-tools.change-owner": "Change Owner", - "thread-tools.manage-editors": "Manage Editors", + "thread-tools.manage-editors": "Gestionați Editorii", "thread-tools.select-category": "Select Category", "thread-tools.fork": "Bifurcă Subiect", "thread-tools.tag": "Tag Topic", @@ -137,7 +137,7 @@ "bookmarks": "Bookmarks", "bookmarks.has-no-bookmarks": "You haven't bookmarked any posts yet.", "copy-permalink": "Copy Permalink", - "go-to-original": "View Original Post", + "go-to-original": "Vizualizați Postarea Originală", "loading-more-posts": "Se încarcă mai multe mesaje", "move-topic": "Mută Subiect", "move-topics": "Mută Subiecte", @@ -162,7 +162,7 @@ "move-posts-instruction": "Click the posts you want to move then enter a topic ID or go to the target topic", "move-topic-instruction": "Select the target category and then click move", "change-owner-instruction": "Click the posts you want to assign to another user", - "manage-editors-instruction": "Manage the users who can edit this post below.", + "manage-editors-instruction": "Gestionați mai jos utilizatorii care pot edita această postare.", "composer.title-placeholder": "Introdu numele subiectului aici ...", "composer.handle-placeholder": "Enter your name/handle here", "composer.hide": "Hide", @@ -188,8 +188,8 @@ "sort-by": "Sortează de la", "oldest-to-newest": "Vechi la Noi", "newest-to-oldest": "Noi la Vechi", - "recently-replied": "Recently Replied", - "recently-created": "Recently Created", + "recently-replied": "Răspunse Recent", + "recently-created": "Create Recent", "most-votes": "Most Votes", "most-posts": "Most Posts", "most-views": "Most Views", @@ -214,15 +214,15 @@ "last-post": "Last post", "go-to-my-next-post": "Go to my next post", "no-more-next-post": "You don't have more posts in this topic", - "open-composer": "Open composer", + "open-composer": "Deschide composer-ul", "post-quick-reply": "Quick reply", "navigator.index": "Post %1 of %2", "navigator.unread": "%1 unread", - "upvote-post": "Upvote post", - "downvote-post": "Downvote post", - "post-tools": "Post tools", - "unread-posts-link": "Unread posts link", - "thumb-image": "Topic thumbnail image", - "announcers": "Shares", - "announcers-x": "Shares (%1)" + "upvote-post": "Votează pentru postare", + "downvote-post": "Votează împotriva postării", + "post-tools": "Unelte de postare", + "unread-posts-link": "Link pentru postări necitite", + "thumb-image": "Imagine miniatură subiect", + "announcers": "Partajări", + "announcers-x": "Partajări (%1)" } \ No newline at end of file diff --git a/public/language/ro/unread.json b/public/language/ro/unread.json index bccada87c3..f80d56c432 100644 --- a/public/language/ro/unread.json +++ b/public/language/ro/unread.json @@ -3,7 +3,7 @@ "no-unread-topics": "Nu există nici un subiect necitit.", "load-more": "Încarcă mai multe", "mark-as-read": "Marchează ca citit", - "mark-as-unread": "Mark as Unread", + "mark-as-unread": "Marchează ca Necitit", "selected": "Selectate", "all": "Toate", "all-categories": "Toate categoriile", diff --git a/public/language/ro/users.json b/public/language/ro/users.json index fd6c6a6c58..ba124c9d31 100644 --- a/public/language/ro/users.json +++ b/public/language/ro/users.json @@ -1,6 +1,6 @@ { "all-users": "All Users", - "followed-users": "Followed Users", + "followed-users": "Utilizatori Urmăriți", "latest-users": "Ultimii Utilizatori", "top-posters": "Top Utilizatori", "most-reputation": "Cei mai apreciați utilizatori", diff --git a/public/language/ro/world.json b/public/language/ro/world.json index 7fdb1569f2..33a94dd925 100644 --- a/public/language/ro/world.json +++ b/public/language/ro/world.json @@ -1,21 +1,21 @@ { - "name": "World", - "popular": "Popular topics", - "recent": "All topics", - "help": "Help", + "name": "Lumea", + "popular": "Subiecte Populare", + "recent": "Toate subiectele", + "help": "Ajutor", - "help.title": "What is this page?", - "help.intro": "Welcome to your corner of the fediverse.", - "help.fediverse": "The \"fediverse\" is a network of interconnected applications and websites that all talk to one another and whose users can see each other. This forum is federated, and can interact with that social web (or \"fediverse\"). This page is your corner of the fediverse. It consists solely of topics created by — and shared from — users you follow.", - "help.build": "There might not be a lot of topics here to start; that's normal. You will start to see more content here over time when you start following other users.", - "help.federating": "Likewise, if users from outside of this forum start following you, then your posts will start appearing on those apps and websites as well.", - "help.next-generation": "This is the next generation of social media, start contributing today!", + "help.title": "Ce este în pagina curentă", + "help.intro": "Bine ai venit în colțul tău din universul fediverse", + "help.fediverse": "„Fediversul” este o rețea de aplicații și site-uri web interconectate care comunică între ele și ai căror utilizatori se pot vedea reciproc. Acest forum este federat și poate interacționa cu acea rețea socială (sau „fediversul”). Această pagină este colțul tău din fedivers. Constă exclusiv din subiecte create de — și partajate de — utilizatori pe care îi urmărești.", + "help.build": "S-ar putea să nu fie multe subiecte de început aici; este normal. Vei începe să vezi mai mult conținut aici în timp, când vei începe să urmărești alți utilizatori.", + "help.federating": "De asemenea, dacă utilizatori din afara acestui forum încep să te urmărească, atunci postările tale vor începe să apară și pe acele aplicații și site-uri web.", + "help.next-generation": "Aceasta este următoarea generație de social media, începe să contribui chiar azi!", - "onboard.title": "Your window to the fediverse...", - "onboard.what": "This is your personalized category made up of only content found outside of this forum. Whether something shows up in this page depends on whether you follow them, or whether that post was shared by someone you follow.", - "onboard.why": "There's a lot that goes on outside of this forum, and not all of it is relevant to your interests. That's why following people is the best way to signal that you want to see more from someone.", - "onboard.how": "In the meantime, you can click on the shortcut buttons at the top to see what else this forum knows about, and start discovering some new content!", + "onboard.title": "Fereastra ta către fedivers...", + "onboard.what": "Aceasta este categoria ta personalizată, formată doar din conținut găsit în afara acestui forum. Afișarea unui element pe această pagină depinde de dacă îl urmărești sau dacă postarea respectivă a fost distribuită de cineva pe care îl urmărești.", + "onboard.why": "Se întâmplă multe lucruri în afara acestui forum și nu toate sunt relevante pentru interesele tale. De aceea, urmărirea oamenilor este cea mai bună modalitate de a semnala că vrei să vezi mai multe de la cineva.", + "onboard.how": "Între timp, puteți da clic pe butoanele de comandă rapidă din partea de sus pentru a vedea ce mai știe acest forum și pentru a începe să descoperiți conținut nou!", - "show-categories": "Show categories", - "hide-categories": "Hide categories" + "show-categories": "Afișează categoriile", + "hide-categories": "Ascunde categoriile" } \ No newline at end of file diff --git a/public/language/ru/admin/manage/categories.json b/public/language/ru/admin/manage/categories.json index 145aaebd0d..9e7839f40b 100644 --- a/public/language/ru/admin/manage/categories.json +++ b/public/language/ru/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Описание категории", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Цвет фона", diff --git a/public/language/ru/admin/settings/uploads.json b/public/language/ru/admin/settings/uploads.json index 8725c70db8..c26cbe27da 100644 --- a/public/language/ru/admin/settings/uploads.json +++ b/public/language/ru/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Макс. высота изображения (в пикселях)", "reject-image-height-help": "Загрузка изображений выше указанного значения будет отклонена.", "allow-topic-thumbnails": "Разрешить пользователям загружать миниатюры для тем", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Размер миниатюр", "allowed-file-extensions": "Допустимые расширения файлов", "allowed-file-extensions-help": "Укажите через запятую список расширений файлов, например pdf,xls,doc. Оставьте поле пустым, чтобы разрешить любые загрузки.", diff --git a/public/language/rw/admin/manage/categories.json b/public/language/rw/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/rw/admin/manage/categories.json +++ b/public/language/rw/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/rw/admin/settings/uploads.json b/public/language/rw/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/rw/admin/settings/uploads.json +++ b/public/language/rw/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/sc/admin/manage/categories.json b/public/language/sc/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/sc/admin/manage/categories.json +++ b/public/language/sc/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/sc/admin/settings/uploads.json b/public/language/sc/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/sc/admin/settings/uploads.json +++ b/public/language/sc/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/sk/admin/manage/categories.json b/public/language/sk/admin/manage/categories.json index ead53a4cce..e3ecddda6c 100644 --- a/public/language/sk/admin/manage/categories.json +++ b/public/language/sk/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Popis kategórie", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Farba pozadia", diff --git a/public/language/sk/admin/settings/uploads.json b/public/language/sk/admin/settings/uploads.json index f9a18dcb4c..fdabe75093 100644 --- a/public/language/sk/admin/settings/uploads.json +++ b/public/language/sk/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Povoliť používateľom nahrať miniatúry tém", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Veľkosť miniatúry témy", "allowed-file-extensions": "Predvolené prípony súborov", "allowed-file-extensions-help": "Zadajte zoznam prípon súborov oddelených čiarkou (napr.: pdf, xls, doc). Prázdny zoznam znamená, že všetky prípony sú povolené.", diff --git a/public/language/sl/admin/manage/categories.json b/public/language/sl/admin/manage/categories.json index d32877156b..e74837dc71 100644 --- a/public/language/sl/admin/manage/categories.json +++ b/public/language/sl/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Opis kategorije", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Barva ozadja", diff --git a/public/language/sl/admin/settings/uploads.json b/public/language/sl/admin/settings/uploads.json index fc43ca3793..8d185fe531 100644 --- a/public/language/sl/admin/settings/uploads.json +++ b/public/language/sl/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Dovoljene pripone datoteke", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/sq-AL/admin/manage/categories.json b/public/language/sq-AL/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/sq-AL/admin/manage/categories.json +++ b/public/language/sq-AL/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/sq-AL/admin/settings/uploads.json b/public/language/sq-AL/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/sq-AL/admin/settings/uploads.json +++ b/public/language/sq-AL/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/sr/admin/manage/categories.json b/public/language/sr/admin/manage/categories.json index 38037f7206..cdb3e1f356 100644 --- a/public/language/sr/admin/manage/categories.json +++ b/public/language/sr/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/sr/admin/settings/uploads.json b/public/language/sr/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/sr/admin/settings/uploads.json +++ b/public/language/sr/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/sv/admin/manage/categories.json b/public/language/sv/admin/manage/categories.json index 0064d996e3..b48ce5b455 100644 --- a/public/language/sv/admin/manage/categories.json +++ b/public/language/sv/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Category Description", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Background Colour", diff --git a/public/language/sv/admin/settings/uploads.json b/public/language/sv/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/sv/admin/settings/uploads.json +++ b/public/language/sv/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/th/admin/manage/categories.json b/public/language/th/admin/manage/categories.json index 81e8ac0f4a..45f2da252d 100644 --- a/public/language/th/admin/manage/categories.json +++ b/public/language/th/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "คำอธิบายหมวดหมู่", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "สีพื้น", diff --git a/public/language/th/admin/settings/uploads.json b/public/language/th/admin/settings/uploads.json index 22046915d9..e91a7bee36 100644 --- a/public/language/th/admin/settings/uploads.json +++ b/public/language/th/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", "allowed-file-extensions": "Allowed File Extensions", "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", diff --git a/public/language/tr/admin/manage/categories.json b/public/language/tr/admin/manage/categories.json index cc0566b075..9af38c0945 100644 --- a/public/language/tr/admin/manage/categories.json +++ b/public/language/tr/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Kategori Açıklaması", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Arkaplan Rengi", diff --git a/public/language/tr/admin/settings/uploads.json b/public/language/tr/admin/settings/uploads.json index f9b369f66a..b9d2884b62 100644 --- a/public/language/tr/admin/settings/uploads.json +++ b/public/language/tr/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maksimum Görsel Yüksekliği (piksel)", "reject-image-height-help": "Bu değerden daha uzun olan görseller reddedilecektir.", "allow-topic-thumbnails": "Kullanıcıların konulara küçük resim yüklemesine izin ver", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Konu Küçük Resim Boyutu", "allowed-file-extensions": "İzin Verilen Dosya Uzantıları", "allowed-file-extensions-help": "Virgül ile ayrılmış dosya uzantıları listesini buraya girin (ör. pdf, xls, doc). Boş bir liste, tüm uzantılara izin verildiği anlamına gelir.", diff --git a/public/language/uk/admin/manage/categories.json b/public/language/uk/admin/manage/categories.json index 2903206195..2b713f196a 100644 --- a/public/language/uk/admin/manage/categories.json +++ b/public/language/uk/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "Опис категорії", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "Колір фону", diff --git a/public/language/uk/admin/settings/uploads.json b/public/language/uk/admin/settings/uploads.json index f54640cb29..18dda7a1ca 100644 --- a/public/language/uk/admin/settings/uploads.json +++ b/public/language/uk/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", "allow-topic-thumbnails": "Дозволити користувачам завантажувати мініатюри тем", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Розмір мініатюри теми", "allowed-file-extensions": "Допустимі розширення файлів", "allowed-file-extensions-help": "Вкажіть розширеня файлів розділені комою (наприклад, pdf,xls,doc). Пустий список дає дозвіл на будь-які розширення.", diff --git a/public/language/ur/admin/manage/categories.json b/public/language/ur/admin/manage/categories.json index e183820111..006428fca4 100644 --- a/public/language/ur/admin/manage/categories.json +++ b/public/language/ur/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "زمرہ کا شناخت کنندہ", "handle.help": "زمرہ کا شناخت کنندہ اس زمرہ کو دیگر نیٹ ورکس میں پیش کرنے کے لیے استعمال ہوتا ہے، جیسے کہ صارف نام۔ اس شناخت کنندہ کا موجودہ صارف نام یا صارف گروپ سے مماثل نہیں ہونا چاہیے۔", "description": "زمرہ کی تفصیل", - "federatedDescription": "فیڈریٹڈ تفصیل", - "federatedDescription.help": "یہ متن زمرہ کی تفصیل میں شامل کیا جائے گا جب دیگر ویب سائٹس اور ایپلی کیشنز اس کے بارے میں معلومات طلب کریں گی۔", - "federatedDescription.default": "یہ فورم میں ایک زمرہ ہے جس میں موضوعاتی بحثیں شامل ہیں۔ آپ اس فورم کا ذکر کرکے ایک نئی بحث شروع کر سکتے ہیں۔", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "پس منظر کا رنگ", diff --git a/public/language/ur/admin/settings/uploads.json b/public/language/ur/admin/settings/uploads.json index f44da0e354..289d295862 100644 --- a/public/language/ur/admin/settings/uploads.json +++ b/public/language/ur/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "تصاویر کی زیادہ سے زیادہ اونچائی (پکسلز میں)", "reject-image-height-help": "جن تصاویر کی اونچائی اس قیمت سے زیادہ ہوگی وہ مسترد کر دی جائیں گی۔", "allow-topic-thumbnails": "صارفین کو موضوعات کے لیے تھمب نیلز اپ لوڈ کرنے کی اجازت دیں", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "موضوعات کے تھمب نیلز کا سائز", "allowed-file-extensions": "اجازت شدہ فائل ایکسٹینشنز", "allowed-file-extensions-help": "فائل ایکسٹینشنز کوموں سے الگ کرکے درج کریں (مثال: pdf,xls,doc)۔ اگر فہرست خالی ہو تو تمام فائل ایکسٹینشنز کی اجازت ہوگی۔", diff --git a/public/language/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json index d5775e1fe0..adb8895367 100644 --- a/public/language/vi/admin/manage/categories.json +++ b/public/language/vi/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Xử Lý Danh Mục", "handle.help": "Xử lý danh mục của bạn được sử dụng làm đại diện cho danh mục này trên các mạng khác, tương tự như tên đăng nhập. Thẻ điều khiển danh mục không được khớp với tên người dùng hoặc nhóm người dùng hiện có.", "description": "Mô Tả Chuyên Mục", - "federatedDescription": "Mô Tả Liên Kết", - "federatedDescription.help": "Văn bản này sẽ được thêm vào mô tả danh mục khi được truy vấn bởi các trang web/ứng dụng khác.", - "federatedDescription.default": "Đây là một danh mục diễn đàn có chứa thảo luận tại chỗ. Bạn có thể bắt đầu các cuộc thảo luận mới bằng cách đề cập đến thể loại này.", "topic-template": "Mẫu Chủ Đề", "topic-template.help": "Xác định mẫu cho các chủ đề mới được tạo trong danh mục này.", "bg-color": "Màu Nền", diff --git a/public/language/vi/admin/settings/uploads.json b/public/language/vi/admin/settings/uploads.json index f89c13a955..055f3dcf47 100644 --- a/public/language/vi/admin/settings/uploads.json +++ b/public/language/vi/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "Chiều Cao Ảnh Tối Đa (pixel)", "reject-image-height-help": "Hình ảnh cao hơn giá trị này sẽ bị từ chối.", "allow-topic-thumbnails": "Cho phép người dùng tải lên ảnh thumbnails chủ đề", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Kích Cỡ Ảnh Thumbnails Chủ Đề", "allowed-file-extensions": "Cho Phép Phần Mở Rộng Tệp", "allowed-file-extensions-help": "Nhập danh sách phần mở rộng tệp phân tách bằng dấu phẩy ở đây (VD: pdf,xls,doc). Để trống là cho phép tất cả.", diff --git a/public/language/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index 07c53a18aa..cbeb2689ba 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -4,7 +4,7 @@ "add-local-category": "添加本地版块", "add-remote-category": "添加远程版块", "remove": "移除", - "rename": "Rename", + "rename": "重命名", "jump-to": "跳转…", "settings": "版块设置", "edit-category": "编辑版块", @@ -15,9 +15,6 @@ "handle": "版块句柄", "handle.help": "您的版块句柄在其他网络中用作该版块的代表,类似于用户名。版块句柄不得与现有的用户名或用户组相匹配。", "description": "版块描述", - "federatedDescription": "“联邦化”说明", - "federatedDescription.help": "当其他网站/应用程序查询时,该文本将附加到版块描述中。", - "federatedDescription.default": "这是一个包含专题讨论的论坛版块。您可以通过提及该版块开始新的讨论。", "topic-template": "主题模板", "topic-template.help": "为本版块下新建的主题定义模板。", "bg-color": "背景颜色", @@ -112,9 +109,9 @@ "alert.create": "创建一个版块", "alert.add": "添加一个版块", "alert.add-help": "可以通过指定其句柄将远程版块添加到版块列表中。

注: — 远程版块可能无法反映所有已发布的主题,除非至少有一名本地用户关注或订阅该版块。", - "alert.rename": "Rename a Remote Category", - "alert.rename-help": "Please enter a new name for this category. Leave blank to restore original name.", - "alert.confirm-remove": "Do you really want to remove this category? You can add it back at any time.", + "alert.rename": "重命名远程版块", + "alert.rename-help": "请为该版块输入新名称。留空则恢复原名。", + "alert.confirm-remove": "您确定要删除此版块吗?您可以随时将其重新添加回来。", "alert.confirm-purge": "

您确定要清除此版块“%1”吗?

警告! 版块将被清除!

清除版块将删除所有主题和帖子,并从数据库中删除版块。 如果您想暂时移除版块,请使用停用版块。

", "alert.purge-success": "版块已删除!", "alert.copy-success": "设置已复制!", diff --git a/public/language/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 5a0ef40eca..4effab4baf 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -30,9 +30,9 @@ "rules.cid": "版块", "relays": "中继服务", - "relays.intro": "中继服务能提升您NodeBB论坛内容的发现效率。订阅中继服务意味着:中继服务接收的内容将转发至此处,而本论坛发布的内容则由中继服务向外同步传播。", - "relays.warning": "注:中继服务可能接收大量流量,并可能增加存储和处理成本。", - "relays.litepub": "NodeBB 遵循 LitePub 风格的中继标准。您在此处输入的URL应以 /actor 结尾。", + "relays.intro": "中继服务能提升您 NodeBB 论坛内容的发现效率。订阅中继服务意味着:中继服务接收的内容将转发至此处,而本论坛发布的内容则由中继服务向外同步传播。", + "relays.warning": "注意:中继服务可能接收大量传入流量,并可能增加存储和处理成本。", + "relays.litepub": "NodeBB 遵循 LitePub 风格的中继标准。您在此处输入的 URL 应以 /actor 结尾。", "relays.add": "添加新中继服务", "relays.relay": "中继服务", "relays.state": "状态", diff --git a/public/language/zh-CN/admin/settings/chat.json b/public/language/zh-CN/admin/settings/chat.json index d7c8f595a3..ac92fca1e4 100644 --- a/public/language/zh-CN/admin/settings/chat.json +++ b/public/language/zh-CN/admin/settings/chat.json @@ -5,13 +5,13 @@ "disable-editing": "禁止编辑/删除聊天消息", "disable-editing-help": "管理员和超级管理员不受此限制", "max-length": "聊天信息的最大长度", - "max-length-remote": "远程聊天信息的最长长度", - "max-length-remote-help": "对于本地用户,该值通常设置为高于聊天消息最大值,因为远程消息往往较长(包含 @ 提及等)。", + "max-length-remote": "远程聊天消息的最大长度", + "max-length-remote-help": "该值通常设置得高于本地用户的聊天消息上限,因为远程消息往往更长(包含@提及等内容)", "max-chat-room-name-length": "聊天室名称最大长度", "max-room-size": "聊天室的最多用户数", "delay": "发言频率(毫秒)", - "notification-delay": "聊天信息的通知延迟", - "notification-delay-help": "服务器可能无法承受即时通讯所带来的资源占用,因此在这段时间内发送的其他信息将被整理,并在每个延迟期通知用户一次。设置为 0 则禁用延迟。", - "restrictions.seconds-edit-after": "聊天信息保持可编辑状态的秒数。", - "restrictions.seconds-delete-after": "聊天信息保持可撤回状态的秒数。" + "notification-delay": "聊天消息通知延迟", + "notification-delay-help": "在此期间发送的额外消息将被汇总,并在每个延迟周期内向用户发送一次通知。设置为0可禁用延迟功能。", + "restrictions.seconds-edit-after": "聊天消息保持可编辑状态的秒数。", + "restrictions.seconds-delete-after": "聊天消息保持可撤回状态的秒数。" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/settings/uploads.json b/public/language/zh-CN/admin/settings/uploads.json index 382195d94a..c2cc7c587e 100644 --- a/public/language/zh-CN/admin/settings/uploads.json +++ b/public/language/zh-CN/admin/settings/uploads.json @@ -5,14 +5,14 @@ "strip-exif-data": "去除 EXIF 数据", "preserve-orphaned-uploads": "当一个帖子被清除后保留上传的文件", "orphanExpiryDays": "保存未使用文件的天数", - "orphanExpiryDays-help": "超过此天数后,无主的上传文件将从文件系统中删除。
设置为 0 或留空表示禁用。", + "orphanExpiryDays-help": "超过此天数后,未使用的文件将从文件系统中删除。
设置为 0 或留空表示禁用。", "private-extensions": "自定义文件扩展名", "private-uploads-extensions-help": "在此处输入以逗号分隔的文件扩展名列表 (例如 pdf,xls,doc )并将其用于自定义。为空则表示允许所有扩展名。", "resize-image-width-threshold": "如果图像宽度超过指定大小,则对图像进行缩放", - "resize-image-width-threshold-help": "(单位:像素,默认值:2000 px,设为 0 则禁用)", + "resize-image-width-threshold-help": "(单位:像素,默认值:2000 px,设置为 0 则禁用)", "resize-image-width": "缩小图片到指定宽度", "resize-image-width-help": "(像素单位,默认 760 px,设置为0以禁用)", - "resize-image-keep-original": "调整大小后保留原始图片", + "resize-image-keep-original": "缩放后保留原始图片", "resize-image-quality": "调整图像大小时使用的质量", "resize-image-quality-help": "使用较低质量的设置来减小调整过大小的图像的文件大小", "max-file-size": "最大文件尺寸(单位 KiB)", @@ -22,6 +22,7 @@ "reject-image-height": "图片最大高度值(单位:像素)", "reject-image-height-help": "高于此数值大小的图片将会被拒绝", "allow-topic-thumbnails": "允许用户上传主题缩略图", + "show-post-uploads-as-thumbnails": "将帖子上传内容显示为缩略图", "topic-thumb-size": "主题缩略图大小", "allowed-file-extensions": "允许的文件扩展名", "allowed-file-extensions-help": "在此处输入以逗号分隔的文件扩展名列表 (例如 pdf,xls,doc )。 为空则表示允许所有扩展名。", diff --git a/public/language/zh-CN/aria.json b/public/language/zh-CN/aria.json index 9df98227c1..2467cf769b 100644 --- a/public/language/zh-CN/aria.json +++ b/public/language/zh-CN/aria.json @@ -1,9 +1,9 @@ { - "post-sort-option": "帖子分类选项,%1", - "topic-sort-option": "主题分类选项,%1", - "user-avatar-for": "用户头像%1", - "profile-page-for": "用户 %1 的资料页面", - "user-watched-tags": "用户关注标签", + "post-sort-option": "帖子排序选项,%1", + "topic-sort-option": "主题排序选项,%1", + "user-avatar-for": "%1 的用户头像", + "profile-page-for": "用户 %1 的个人资料页面", + "user-watched-tags": "用户关注的标签", "delete-upload-button": "删除上传按钮", - "group-page-link-for": "群组页面链接%1" + "group-page-link-for": "%1 的群组页面链接" } \ No newline at end of file diff --git a/public/language/zh-CN/category.json b/public/language/zh-CN/category.json index 27a3f239b0..a03dcf42c6 100644 --- a/public/language/zh-CN/category.json +++ b/public/language/zh-CN/category.json @@ -2,12 +2,12 @@ "category": "版块", "subcategories": "子版块", "uncategorized": "未分类的", - "uncategorized.description": "不完全符合任何现有类别的主题", - "handle.description": "您可以通过 %1 标签在开放的社交网络上关注这一版块。", + "uncategorized.description": "不严格符合任何现有版块的主题", + "handle.description": "此版块可通过社交网络公开平台使用用户名 %1 进行关注", "new-topic-button": "发表主题", "guest-login-post": "登录以发布", - "no-topics": "此版块还没有任何内容。
赶紧来发帖吧!", - "no-followers": "本网站无人跟踪或关注此版块。跟踪或关注此版块,以便开始接收更新。", + "no-topics": "此版块下尚无主题。
何不尝试发布一个?", + "no-followers": "本网站上没有用户正在关注或订阅此版块。请关注或订阅此版块,以便开始接收更新。", "browsing": "正在浏览", "no-replies": "尚无回复", "no-new-posts": "没有新主题", @@ -17,14 +17,14 @@ "tracking": "跟踪", "not-watching": "未关注", "ignoring": "已忽略", - "watching.description": "有新主题时通知我。
在未读/最近主题中显示。", + "watching.description": "有新主题时通知我。
在未读/最近主题中显示", "tracking.description": "显示未读和最近的主题", "not-watching.description": "不显示未读主题,显示最近主题", "ignoring.description": "不在未读和最近主题显示", - "watching.message": "您关注了此版块和全部子版块的动态。", - "tracking.message": "您关注了此版块和全部子版块的动态。", - "notwatching.message": "您未关注了此版块和全部子版块的动态。", - "ignoring.message": "您未关注此版块和全部子版块的动态。", + "watching.message": "您正在关注此版块及其所有子版块的更新", + "tracking.message": "您正在订阅此版块及其所有子版块的更新", + "notwatching.message": "您未订阅此版块及其所有子版块的更新", + "ignoring.message": "您正在忽略此版块及其所有子版块的更新", "watched-categories": "已关注的版块", "x-more-categories": "还有 %1 个版块" } \ No newline at end of file diff --git a/public/language/zh-CN/email.json b/public/language/zh-CN/email.json index da067df94d..137ea80a8e 100644 --- a/public/language/zh-CN/email.json +++ b/public/language/zh-CN/email.json @@ -6,26 +6,26 @@ "greeting-no-name": "您好", "greeting-with-name": "%1,您好", "email.verify-your-email.subject": "请验证你的电子邮箱", - "email.verify.text1": "您已要求我们更改或确认您的邮件地址", - "email.verify.text2": "为了安全起见,我们只会在通过邮件验证邮件地址所有权以后才会更改存档的邮件地址。假如您没有提出过此请求,您不用进行任何操作。", - "email.verify.text3": "一旦您验证了此电子邮箱地址,我们将会把您当前的电子邮箱地址替换为此电子邮箱地址(%1)。", + "email.verify.text1": "您已要求我们更改或确认您的电子邮件地址", + "email.verify.text2": "出于安全考虑,我们仅在通过邮件确认邮箱所有权后,才会修改或确认系统中登记的邮箱地址。若您未主动提出此请求,则无需采取任何行动。", + "email.verify.text3": "一旦您确认此电子邮件地址,我们将用此地址(%1)替换您当前的电子邮件地址。", "welcome.text1": "感谢您注册 %1 帐户!", "welcome.text2": "在您验证您绑定的邮箱地址之后,您的账户才能激活。", - "welcome.text3": "管理员批准了您的注册申请,您现在可以使用您的用户名和密码进行登录了。", + "welcome.text3": "管理员已批准您的注册申请。您现在可以使用用户名/密码登录。", "welcome.cta": "点击这里确认您的电子邮箱地址", "invitation.text1": "%1 邀请您加入 %2", "invitation.text2": "您的邀请将在 %1 天后过期。", "invitation.cta": "点击这里新建账号", - "reset.text1": "很可能是您忘记了密码,我们收到了重置您帐户密码的请求。 如果不是这个情况,请忽略此邮件。", + "reset.text1": "我们收到重置您密码的请求,可能是您忘记了密码。若非如此,请忽略此邮件。", "reset.text2": "如需继续重置密码,请点击下面的链接:", "reset.cta": "点击这里重置您的密码", "reset.notify.subject": "更改密码成功", "reset.notify.text1": "您在 %1 上的密码已经成功修改。", "reset.notify.text2": "如果您没有授权此操作,请立即联系管理员。", - "digest.unread-rooms": "未读房间", + "digest.unread-rooms": "未读聊天室", "digest.room-name-unreadcount": "%1 (%2 未读)", "digest.latest-topics": "来自 %1 的最新主题", - "digest.top-topics": "来自 %1 的关注主题", + "digest.top-topics": "来自 %1 的热门主题", "digest.popular-topics": "来自 %1 的热门主题", "digest.cta": "点击这里访问 %1", "digest.unsub.info": "根据您的订阅设置,为您发送此摘要。", @@ -37,22 +37,22 @@ "digest.title.week": "您的每周摘要", "digest.title.month": "您的每月摘要", "notif.chat.new-message-from-user": "来自 \"%1\" 的新消息", - "notif.chat.new-message-from-user-in-room": "来自 %1 在房间 %2 中的新消息", + "notif.chat.new-message-from-user-in-room": "来自 %1 的新消息,在聊天室 %2 ", "notif.chat.cta": "点击这里继续会话", "notif.chat.unsub.info": "根据您的订阅设置,为您发送此聊天提醒。", "notif.post.unsub.info": "根据您的订阅设置,为您发送此回帖提醒。", - "notif.post.unsub.one-click": "或者通过点击来取消订阅邮件", + "notif.post.unsub.one-click": "或者,您可以点击此处取消订阅此类邮件", "notif.cta": "点击这里前往论坛", "notif.cta-new-reply": "查看帖子", "notif.cta-new-chat": "查看聊天", "notif.test.short": "测试通知", - "notif.test.long": "这是一个测试的通知邮件。", + "notif.test.long": "这是对通知邮件功能的测试。快来帮忙!", "test.text1": "这是一封测试邮件,用来验证 NodeBB 的邮件配置是否设置正确。", "unsub.cta": "点击这里修改这些设置", "unsubscribe": "退订", "unsub.success": "您将不再收到来自%1邮寄名单的邮件", "unsub.failure.title": "无法取消订阅", - "unsub.failure.message": "很不幸,我们不能将您从邮件列表里取消订阅,因为这个链接有问题。不过,您可以到您的用户设置里修改邮件偏好。

(错误:%1)", + "unsub.failure.message": "很遗憾,由于链接出现问题,我们未能成功为您取消邮件订阅。不过您可通过 用户设置 页面调整邮件偏好选项。

(错误:%1)", "banned.subject": "您在 %1 的账户已被封禁", "banned.text1": "您在 %2 的用户 %1 已被封禁。", "banned.text2": "本次封禁将在 %1 结束。", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index e91848b78d..39fabf9c1b 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -39,7 +39,7 @@ "email-not-confirmed": "您需要验证您的邮箱后才能在版块或主题中发布帖子,请点击此处以发送验证邮件。", "email-not-confirmed-chat": "您的电子邮箱尚未确认,无法聊天,请点击这里确认您的电子邮箱。", "email-not-confirmed-email-sent": "您的邮箱尚未验证,请检查您的收件箱以找到验证邮件。在您的邮箱被验证前,您可能不能在某些版块发布帖子或进行聊天。", - "no-email-to-confirm": "您的账号未设置电子邮箱。对于找回账号、聊天以及在版块中发布帖子这几项操作,电子邮箱是必需的。请点击此处输入电子邮箱。", + "no-email-to-confirm": "您的账号尚未设置电子邮箱。电子邮箱是账号找回的必要条件,在某些分类中进行聊天和发帖时也可能需要。请点击此处输入电子邮箱。", "user-doesnt-have-email": "用户“%1”还没有设置邮箱。", "email-confirm-failed": "我们无法确认您的电子邮箱,请重试", "confirm-email-already-sent": "确认邮件已发出,如需重新发送请等待 %1 分钟后再试。", @@ -105,7 +105,7 @@ "still-uploading": "请等待上传完成", "file-too-big": "上传文件的大小限制为 %1 KB - 请缩减文件大小", "guest-upload-disabled": "未登录用户不允许上传", - "cors-error": "由于CORS配置错误,无法上传图片。", + "cors-error": "由于CORS配置错误,无法上传图片", "upload-ratelimit-reached": "您在短时间内上传了过多的文件,请稍后再试", "upload-error-fallback": "无法上传图片 — %1", "scheduling-to-past": "请选择一个未来的日期。", @@ -120,7 +120,7 @@ "cant-mute-other-admins": "您不能禁言其他管理员!", "user-muted-for-hours": "您已被禁言,您在 %1 小时后才能发布内容", "user-muted-for-minutes": "您已被禁言,您在 %1 分钟后才能发布内容", - "cant-make-banned-users-admin": "您不能让被禁止的用户成为管理员。", + "cant-make-banned-users-admin": "您无法将被封禁的用户设为管理员。", "cant-remove-last-admin": "您是唯一的管理员。在删除您的管理员权限前,请添加另一个管理员。", "account-deletion-disabled": "账号删除功能已禁用", "cant-delete-admin": "在删除此账号之前,请先移除其管理权限。", @@ -222,7 +222,7 @@ "no-groups-selected": "没有用户组被选中", "invalid-home-page-route": "无效的首页路径", "invalid-session": "无效的会话", - "invalid-session-text": "您的登录会话似乎不再处于活动状态。请刷新此页面。", + "invalid-session-text": "您的登录会话似乎已失效。请刷新此页面。", "session-mismatch": "会话不匹配", "session-mismatch-text": "您的登录会话似乎与服务器不再匹配。请刷新此页面。", "no-topics-selected": "没有主题被选中!", @@ -247,7 +247,7 @@ "cant-set-self-as-parent": "无法将自身设置为父版块", "api.master-token-no-uid": "收到一个在请求体中没有对应 `_uid` 的主令牌", "api.400": "您传入的请求某些地方出错了。", - "api.401": "找不到有效的登录会话。请登录后再试。", + "api.401": "未找到有效的登录会话。请登录后重试。", "api.403": "您没有权限使用此调用", "api.404": "无效 API 调用", "api.426": "Write API 的请求需要 HTTPS,请用 HTTPS 重新发送您的请求", diff --git a/public/language/zh-CN/global.json b/public/language/zh-CN/global.json index a98d700a71..0a9fbbfff8 100644 --- a/public/language/zh-CN/global.json +++ b/public/language/zh-CN/global.json @@ -3,14 +3,14 @@ "search": "搜索", "buttons.close": "关闭", "403.title": "禁止访问", - "403.message": "您似乎碰到了一个您没有访问权限的页面。", - "403.login": "请您尝试登录后再试", + "403.message": "您似乎意外访问了一个您无权访问的页面。", + "403.login": "或许您应该先尝试登录?", "404.title": "未找到", - "404.message": "你似乎偶然发现了一个不存在的页面。
回到主页
", + "404.message": "您似乎偶然访问了一个不存在的页面。
请返回主页
", "500.title": "内部错误", "500.message": "哎呀!看来是哪里出错了!", "400.title": "错误的请求", - "400.message": "看起来这个链接是畸形的,请仔细检查并重新尝试。
回到主页
", + "400.message": "该链接格式似乎有误,请重新检查后再次尝试。
返回主页
", "register": "注册", "login": "登录", "please-log-in": "请登录", @@ -46,7 +46,7 @@ "header.notifications": "通知", "header.search": "搜索", "header.profile": "设置", - "header.account": "账户", + "header.account": "账号", "header.navigation": "导航", "header.manage": "管理", "header.drafts": "草稿", @@ -60,7 +60,7 @@ "alert.warning": "警告", "alert.info": "信息", "alert.banned": "已封禁", - "alert.banned.message": "您已被禁止,您当前的访问受到限制。", + "alert.banned.message": "您已被封禁,您当前的访问受到限制。", "alert.unbanned": "已解封", "alert.unbanned.message": "你的封禁已被解除。", "alert.unfollow": "您已取消关注 %1!", @@ -68,8 +68,8 @@ "users": "用户", "topics": "主题", "posts": "帖子", - "x-posts": "%1 个帖子", - "x-topics": "%1 个主题", + "x-posts": "%1 个帖子", + "x-topics": "%1 个主题", "x-reputation": "%1声望", "best": "最佳", "controversial": "有争议的", diff --git a/public/language/zh-CN/groups.json b/public/language/zh-CN/groups.json index 74b4fbb097..3f9ba5a0f7 100644 --- a/public/language/zh-CN/groups.json +++ b/public/language/zh-CN/groups.json @@ -23,7 +23,7 @@ "details.members": "成员列表", "details.pending": "待加入成员", "details.invited": "已邀请成员", - "details.has-no-posts": "此用户组的成员尚未发表任何帖子。", + "details.has-no-posts": "此群组的成员尚未发表任何帖子。", "details.latest-posts": "最新帖子", "details.private": "私有", "details.disableJoinRequests": "禁用申请加入群组", diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json index d9ae48b598..6d29de3341 100644 --- a/public/language/zh-CN/modules.json +++ b/public/language/zh-CN/modules.json @@ -1,15 +1,15 @@ { - "chat.room-id": "房间 %1", - "chat.chatting-with": "与聊天", + "chat.room-id": "聊天室 %1", + "chat.chatting-with": "与...聊天", "chat.placeholder": "在此处输入聊天信息,拖放图片", "chat.placeholder.mobile": "输入聊天信息", "chat.placeholder.message-room": "消息 #%1", - "chat.scroll-up-alert": "转到最近的信息", + "chat.scroll-up-alert": "转到最近的消息", "chat.usernames-and-x-others": "%1 和 %2 其他人", - "chat.chat-with-usernames": "与聊天", + "chat.chat-with-usernames": "与 %1 聊天", "chat.chat-with-usernames-and-x-others": "与%1 & %2 和其他人聊天", "chat.send": "发送", - "chat.no-active": "暂无聊天", + "chat.no-active": "您当前没有活跃的聊天。", "chat.user-typing-1": "%1 正在输入...", "chat.user-typing-2": "%1%2 正在输入...", "chat.user-typing-3": "%1%2%3 正在输入...", @@ -57,26 +57,26 @@ "chat.add-user-help": "在这里查找更多用户。被选中的用户会被添加到聊天中。新用户不能他们被加入对话前的聊天消息。只有聊天室所有者()可以从聊天室中移除用户。", "chat.confirm-chat-with-dnd-user": "该用户已将其状态设置为 DnD(请勿打扰)。 您仍希望与其聊天吗?", "chat.room-name-optional": "房间名称(可选)", - "chat.rename-room": "重命名房间", - "chat.rename-placeholder": "在这里输入房间名字", - "chat.rename-help": "这里设置的房间名字能够被房间内所有人都看到。", + "chat.rename-room": "重命名聊天室", + "chat.rename-placeholder": "在这里输入聊天室名字", + "chat.rename-help": "这里设置的聊天室名字能够被聊天室内所有人都看到。", "chat.leave": "离开", "chat.leave-room": "离开房间", "chat.leave-prompt": "您确定您要离开聊天室?", "chat.leave-help": "离开此聊天会切断您和此聊天以后的联系。如果您未来重新加入了,您将不能看到您重新加入之前的聊天记录。", "chat.delete": "删除", "chat.delete-room": "删除房间", - "chat.delete-prompt": "您确定要删除此聊天室?", - "chat.in-room": "在此房间", + "chat.delete-prompt": "您确定您要删除此聊天室?", + "chat.in-room": "在此聊天室", "chat.kick": "踢出", "chat.show-ip": "显示 IP", "chat.copy-text": "复制文本", "chat.copy-link": "复制链接", - "chat.owner": "房间所有者", + "chat.owner": "聊天室所有者", "chat.grant-rescind-ownership": "给予/撤销所有权", - "chat.system.user-join": "%1 加入了房间", - "chat.system.user-leave": "%1 离开了房间", - "chat.system.room-rename": "%2 已将房间重命名为 \"%1\"", + "chat.system.user-join": "%1 加入了聊天室", + "chat.system.user-leave": "%1 离开了聊天室", + "chat.system.room-rename": "%2 已将此聊天室重命名为“%1” ", "composer.compose": "编写帮助", "composer.show-preview": "显示预览", "composer.hide-preview": "隐藏预览", @@ -85,7 +85,7 @@ "composer.user-said": "%1 说:", "composer.discard": "确定想要取消此帖?", "composer.submit-and-lock": "提交并锁定", - "composer.toggle-dropdown": "标为 Dropdown", + "composer.toggle-dropdown": "切换下拉菜单", "composer.uploading": "正在上传 %1", "composer.formatting.bold": "加粗", "composer.formatting.italic": "倾斜", @@ -106,7 +106,7 @@ "composer.zen-mode": "无干扰模式", "composer.select-category": "选择一个版块", "composer.textarea.placeholder": "在此处输入您的帖子内容,拖放图像", - "composer.post-queue-alert": "Hello👋!
This forum uses a post queue system, since you are a new user your post will be hidden until it is approved by our moderation team.", + "composer.post-queue-alert": "你好👋!
本论坛采用发帖队列系统,由于你是新用户,您的帖子在审核团队批准前将处于隐藏状态。", "composer.schedule-for": "定时主题到", "composer.schedule-date": "日期", "composer.schedule-time": "时间", @@ -114,8 +114,8 @@ "composer.change-schedule-date": "更改日期", "composer.set-schedule-date": "设置日期", "composer.discard-all-drafts": "丢弃所有的草稿", - "composer.no-drafts": "你没有草稿", - "composer.discard-draft-confirm": "你想丢弃这个草案吗?", + "composer.no-drafts": "您没有草稿", + "composer.discard-draft-confirm": "您想丢弃这个草稿吗?", "composer.remote-pid-editing": "编辑远程帖子", "composer.remote-pid-content-immutable": "远程帖子的内容不可编辑。不过,您可以更改主题标题和标签。", "bootbox.ok": "确认", @@ -128,7 +128,7 @@ "cover.saved": "封面照片和位置已保存", "thumbs.modal.title": "管理主题缩略图", "thumbs.modal.no-thumbs": "没有找到缩略图。", - "thumbs.modal.resize-note": "注意:此论坛被配置为缩放主题缩略图到最大值为 %1", + "thumbs.modal.resize-note": "注意:此论坛被配置为缩放主题缩略图到最大值为 %1px", "thumbs.modal.add": "添加缩略图", "thumbs.modal.remove": "移除缩略图", "thumbs.modal.confirm-remove": "您确定您要移除此缩略图吗?" diff --git a/public/language/zh-CN/notifications.json b/public/language/zh-CN/notifications.json index 7df8ab7fd0..533651060d 100644 --- a/public/language/zh-CN/notifications.json +++ b/public/language/zh-CN/notifications.json @@ -71,7 +71,7 @@ "users-csv-exported": "用户列表 CSV 已导出,点击以下载", "post-queue-accepted": "您先前提交的帖子已通过查验,点击这里查看您的帖子。", "post-queue-rejected": "您先前提交的帖子已被拒绝", - "post-queue-notify": "您先前提交的帖子收到了通知:“%1”", + "post-queue-notify": "您先前提交的帖子收到了通知:
“%1”", "email-confirmed": "电子邮箱已确认", "email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已完全激活。", "email-confirm-error-message": "验证的您电子邮箱地址时出现了问题。可能是因为验证码无效或已过期。", diff --git a/public/language/zh-CN/register.json b/public/language/zh-CN/register.json index 9d03e65295..7edf5c6077 100644 --- a/public/language/zh-CN/register.json +++ b/public/language/zh-CN/register.json @@ -20,7 +20,7 @@ "terms-of-use-error": "您必须同意使用条款", "registration-added-to-queue": "您的注册正在等待批准。一旦通过,管理员会发送邮件通知您。", "registration-queue-average-time": "我们通常的注册批准时间为 %1 小时 %2 分钟。", - "registration-queue-auto-approve-time": "您在此论坛的帐号将会在最迟 %1  小时后被完全激活。", + "registration-queue-auto-approve-time": "您在此论坛的帐号将会在最迟 %1 小时后被完全激活。", "interstitial.intro": "我们需要一些额外信息以更新您的账号。", "interstitial.intro-new": "我们需要一些额外信息以创建您的账号。", "interstitial.errors-found": "请检查输入的信息:", diff --git a/public/language/zh-CN/search.json b/public/language/zh-CN/search.json index 8cd7bc355b..ebe9d2db6a 100644 --- a/public/language/zh-CN/search.json +++ b/public/language/zh-CN/search.json @@ -25,7 +25,7 @@ "all": "所有", "any": "任何", "posted-by": "发表", - "posted-by-usernames": "被发布:%1", + "posted-by-usernames": "发布者:%1", "type-a-username": "输入用户名", "search-child-categories": "搜索子版块", "has-tags": "有标签", @@ -49,20 +49,20 @@ "three-months": "三个月", "six-months": "六个月", "one-year": "一年", - "time-newer-than-86400": "时间:比昨天更早", - "time-older-than-86400": "时间:比昨天更晚", + "time-newer-than-86400": "时间:比昨天更新", + "time-older-than-86400": "时间:比昨天更久远", "time-newer-than-604800": "时间:一周以内", - "time-older-than-604800": "时间:一周前", + "time-older-than-604800": "时间:超过一周", "time-newer-than-1209600": "时间:两周以内", - "time-older-than-1209600": "时间:两周前", + "time-older-than-1209600": "时间:超过两周", "time-newer-than-2592000": "时间:一个月以内", - "time-older-than-2592000": "时间:一个月前", + "time-older-than-2592000": "时间:超过一个月", "time-newer-than-7776000": "时间:三个月以内", - "time-older-than-7776000": "时间:三个月前", + "time-older-than-7776000": "时间:超过三个月", "time-newer-than-15552000": "时间:六个月以内", - "time-older-than-15552000": "时间:六个月前", + "time-older-than-15552000": "时间:超过六个月", "time-newer-than-31104000": "时间:一年以内", - "time-older-than-31104000": "时间:一年前", + "time-older-than-31104000": "时间:超过一年", "sort-by": "排序", "sort": "排序", "last-reply-time": "最后回复时间", diff --git a/public/language/zh-CN/themes/persona.json b/public/language/zh-CN/themes/persona.json index 18a9f2080f..fe468bb4a6 100644 --- a/public/language/zh-CN/themes/persona.json +++ b/public/language/zh-CN/themes/persona.json @@ -1,6 +1,6 @@ { "settings.title": "主题设置", - "settings.intro": "你可以在这里定制你的主题设置。设置是以每个设备为基础存储的,所以你能够在不同的设备上有不同的设置(手机、平板电脑、桌面等)。", + "settings.intro": "您可以在这里自定义主题设置。设置将按设备单独存储,因此您可以在不同设备(手机、平板、台式机等)上使用不同的设置。", "settings.mobile-menu-side": "移动端导航菜单切换到另一侧", "settings.autoHidingNavbar": "滚动时自动隐藏导航条", "settings.autoHidingNavbar-xs": "非常小的屏幕(如纵向模式的手机)。", diff --git a/public/language/zh-CN/topic.json b/public/language/zh-CN/topic.json index a9830d1883..4a68353e43 100644 --- a/public/language/zh-CN/topic.json +++ b/public/language/zh-CN/topic.json @@ -66,16 +66,16 @@ "user-queued-post-ago": "%1 篇 已排队 待审批的帖子 %3", "user-queued-post-on": "在 %3 中 已排队 %1 篇待审批的帖子", "user-referenced-topic-ago": "%1 被引用 于这个主题 %3", - "user-referenced-topic-on": "%1 在 %3 中 引用了 这个主题", + "user-referenced-topic-on": "%1 在 %3 引用了 此主题", "user-forked-topic-ago": "%1 分支于 这个主题 %3", - "user-forked-topic-on": "%1 这个主题的分支在 %3", + "user-forked-topic-on": "%1 在 %3 上 分支了 这个主题", "bookmark-instructions": "点击阅读本主题帖中的最新回复", "flag-post": "举报这个帖子", "flag-user": "举报此用户", "already-flagged": "已举报", "view-flag-report": "查看举报报告", "resolve-flag": "解决举报", - "merged-message": "此主题已合并到%2", + "merged-message": "此主题已合并到 %2", "forked-message": "此主题由 %2 分支而来", "deleted-message": "此主题已被删除。只有拥有主题管理权限的用户可以查看。", "following-topic.message": "当有人回复此主题时,您会收到通知。", diff --git a/public/language/zh-CN/user.json b/public/language/zh-CN/user.json index e8eafaad63..80358d9096 100644 --- a/public/language/zh-CN/user.json +++ b/public/language/zh-CN/user.json @@ -20,8 +20,8 @@ "unmute-account": "解除账号禁言", "delete-account": "删除帐号", "delete-account-as-admin": "删除账号", - "delete-content": "删除账号内容", - "delete-all": "删除账号和内容", + "delete-content": "删除账号 内容", + "delete-all": "删除 账号内容", "delete-account-confirm": "您确定要匿名化您的所有帖子并删除账号吗?
此操作不可撤销,您将无法恢复您的任何数据

请输入您的密码,以确认您要删除这个账号。", "delete-this-account-confirm": "您确定您要删除此账号同时保留其发布的内容吗?
此操作不可逆,帖子将被匿名化,而且您将无法恢复帖子和被删除账号的联系

", "delete-account-content-confirm": "您确定要删除账户内容(帖子/主题/上传)吗?
此操作不可逆,而且您无法恢复任何数据

", @@ -204,10 +204,10 @@ "browser-version-on-platform": "%1 %2 在 %3", "consent.title": "您的权利与许可", "consent.lead": "本论坛将会收集与处理您的个人信息。", - "consent.intro": "我们收集这些信息将仅用于个性化您于本社区的体验,和关联您的用户账号与您所发表的帖子。在注册过程中您需要提供一个用户名和邮箱地址,您也可以选择是否提供额外的个人信息,以完善您的用户资料。

在您的用户账号有效期内,我们将保留您的信息。您可以在任何时候通过删除您的账号,以撤回您的许可。您可以在任何时候通过您的权力与许可页面,获取一份您对本论坛的贡献的副本。

如果您有任何疑问,我们鼓励您与本论坛管理团队联系。", - "consent.email-intro": "我们有时可能会向您的注册邮件地址发送电子邮件,以向您提供有关于您的新动态和/或新活动。您可以通过您的用户设置页面自定义(包括直接禁用)社区摘要的发送频率,以及选择性地接收哪些类型的通知。", - "consent.digest-frequency": "本社区默认每 %1 发送一封摘要邮件,除非您在用户设置中明确更改了此项。", - "consent.digest-off": "本社区默认不发送摘要邮件,除非您在用户设置中明确更改了此项。", + "consent.intro": "我们严格使用这些信息来个性化您在本社区的体验,并将您发布的帖子关联至您的用户账号。注册时您需提供用户名和电子邮箱地址,也可选择性提供其他信息以完善本网站的用户资料。

我们将保存这些信息直至您的用户账号终止,您可随时通过注销账号撤回授权。您可随时通过“权利与授权”页面申请获取您在本网站的贡献内容副本。

如有任何疑问或顾虑,欢迎联系本论坛管理团队。", + "consent.email-intro": "我们可能会不定期向您注册的电子邮件地址发送邮件,以便提供更新信息和/或通知您与您相关的新动态。您可通过用户设置页面自定义社区摘要的接收频率(包括完全停用该功能),并选择希望通过邮件接收的通知类型。", + "consent.digest-frequency": "除非在您的用户设置中明确更改,否则本社区默认每 %1 发送一次邮件摘要。", + "consent.digest-off": "除非您在用户设置中明确更改,否则本社区不会发送任何邮件摘要。", "consent.received": "您已许可本网站收集与处理您的个人数据。无需其他额外操作。", "consent.not-received": "您未许可本网站收集与处理您的个人数据。本网站的管理团队可能于任何时候删除您的账号,以符合通用数据保护条例的要求。", "consent.give": "授予许可", diff --git a/public/language/zh-TW/admin/manage/categories.json b/public/language/zh-TW/admin/manage/categories.json index 844fc6153a..0989aa7bc7 100644 --- a/public/language/zh-TW/admin/manage/categories.json +++ b/public/language/zh-TW/admin/manage/categories.json @@ -15,9 +15,6 @@ "handle": "Category Handle", "handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.", "description": "版面描述", - "federatedDescription": "Federated Description", - "federatedDescription.help": "This text will be appended to the category description when queried by other websites/apps.", - "federatedDescription.default": "This is a forum category containing topical discussion. You can start new discussions by mentioning this category.", "topic-template": "Topic Template", "topic-template.help": "Define a template for new topics created in this category.", "bg-color": "背景顏色", diff --git a/public/language/zh-TW/admin/settings/uploads.json b/public/language/zh-TW/admin/settings/uploads.json index fc3bddd9ca..a72caffbc2 100644 --- a/public/language/zh-TW/admin/settings/uploads.json +++ b/public/language/zh-TW/admin/settings/uploads.json @@ -22,6 +22,7 @@ "reject-image-height": "圖片最大高度值(單位:像素)", "reject-image-height-help": "高於此數值大小的圖片將會被拒絕", "allow-topic-thumbnails": "允許使用者上傳主題縮圖", + "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "主題縮圖大小", "allowed-file-extensions": "允許的副檔名", "allowed-file-extensions-help": "在此處輸入以逗號分隔的副檔名列表 (例如 pdf,xls,doc )。 為空則表示允許所有副檔名。", diff --git a/public/src/client/infinitescroll.js b/public/src/client/infinitescroll.js index 6c3819245c..74c69d21dd 100644 --- a/public/src/client/infinitescroll.js +++ b/public/src/client/infinitescroll.js @@ -21,7 +21,7 @@ define('forum/infinitescroll', ['hooks', 'alerts', 'api'], function (hooks, aler previousScrollTop = $(window).scrollTop(); $(window).off('scroll', startScrollTimeout).on('scroll', startScrollTimeout); if ($body.height() <= $(window).height() && ( - ajaxify.data.pagination || ajaxify.data.pagination.pageCount > 1 + ajaxify.data.pagination && ajaxify.data.pagination.pageCount > 1 )) { callback(1); } diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 5ee1f91f60..bdebc50264 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -206,6 +206,12 @@ define('forum/topic', [ }); } }); + + $('[component="topic/thumb/list/expand"]').on('click', function () { + const btn = $(this); + btn.parents('[component="topic/thumb/list"]').removeClass('thumbs-collapsed'); + btn.remove(); + }); } function addBlockQuoteHandler() { diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index 896d0b485b..aa1825adda 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -25,6 +25,8 @@ module.exports = function (utils, Benchpress, relative_path) { userAgentIcons, buildAvatar, increment, + lessthan, + greaterthan, generateWroteReplied, generateRepliedTo, generateWrote, @@ -33,6 +35,7 @@ module.exports = function (utils, Benchpress, relative_path) { shouldHideReplyContainer, humanReadableNumber, formattedNumber, + isNumber, txEscape, uploadBasename, generatePlaceholderWave, @@ -328,6 +331,14 @@ module.exports = function (utils, Benchpress, relative_path) { return String(value + parseInt(inc, 10)); } + function lessthan(a, b) { + return parseInt(a, 10) < parseInt(b, 10); + } + + function greaterthan(a, b) { + return parseInt(a, 10) > parseInt(b, 10); + } + function generateWroteReplied(post, timeagoCutoff) { if (post.toPid) { return generateRepliedTo(post, timeagoCutoff); @@ -376,6 +387,10 @@ module.exports = function (utils, Benchpress, relative_path) { return utils.addCommas(number); } + function isNumber(value) { + return utils.isNumber(value); + } + function txEscape(text) { return String(text).replace(/%/g, '%').replace(/,/g, ','); } diff --git a/src/activitypub/contexts.js b/src/activitypub/contexts.js index 9d6c948a5e..2e346facc0 100644 --- a/src/activitypub/contexts.js +++ b/src/activitypub/contexts.js @@ -81,6 +81,10 @@ Contexts.getItems = async (uid, id, options) => { } if (items) { + if (options.returnRootId) { + return items.pop(); + } + items = await Promise.all(items .map(async item => (activitypub.helpers.isUri(item) ? parseString(uid, item) : parseItem(uid, item)))); items = items.filter(Boolean); diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js index b8bf765bef..262010b7da 100644 --- a/src/activitypub/feps.js +++ b/src/activitypub/feps.js @@ -3,6 +3,7 @@ const nconf = require('nconf'); const posts = require('../posts'); +const topics = require('../topics'); const utils = require('../utils'); const activitypub = module.parent.exports; @@ -13,8 +14,18 @@ Feps.announce = async function announce(id, activity) { if (String(id).startsWith(nconf.get('url'))) { ({ id: localId } = await activitypub.helpers.resolveLocalId(id)); } - const cid = await posts.getCidByPid(localId || id); - if (cid === -1 || !utils.isNumber(cid)) { // local cids only + + /** + * Re-broadcasting occurs on + * - local cids (for all tids), and + * - local tids (posted to remote cids) only + */ + const tid = await posts.getPostField(localId || id, 'tid'); + const cid = await topics.getTopicField(tid, 'cid'); + const localCid = utils.isNumber(cid) && cid > 0; + const addressed = activitypub.helpers.addressed(cid, activity); + const shouldAnnounce = localCid || (utils.isNumber(tid) && !addressed); + if (!shouldAnnounce) { // inverse conditionals can kiss my ass. return; } @@ -25,39 +36,45 @@ Feps.announce = async function announce(id, activity) { } return memo; }, []); - const followers = await activitypub.notes.getCategoryFollowers(cid); + const followers = localCid ? await activitypub.notes.getCategoryFollowers(cid) : [cid]; const targets = relays.concat(followers); if (!targets.length) { return; } const { actor } = activity; - if (actor && !actor.startsWith(nconf.get('url'))) { + if (localCid && actor && !actor.startsWith(nconf.get('url'))) { targets.unshift(actor); } const now = Date.now(); + const to = [localCid ? `${nconf.get('url')}/category/${cid}/followers` : cid]; + const cc = [activitypub._constants.publicAddress]; + if (localCid) { + cc.unshift(actor); + } + if (activity.type === 'Create') { const isMain = await posts.isMain(localId || id); if (isMain) { activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing plain object (${activity.id}) to followers of cid ${cid} and ${relays.length} relays`); - await activitypub.send('cid', cid, targets, { - id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now}`, + await activitypub.send('cid', localCid ? cid : 0, targets, { + id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${localCid ? `cid/${cid}` : 'uid/0'}`, type: 'Announce', - actor: `${nconf.get('url')}/category/${cid}`, - to: [`${nconf.get('url')}/category/${cid}/followers`], - cc: [actor, activitypub._constants.publicAddress], + actor: localCid ? `${nconf.get('url')}/category/${cid}` : `${nconf.get('url')}/actor`, + to, + cc, object: activity.object, }); } } activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing ${activity.type} (${activity.id}) to followers of cid ${cid} and ${relays.length} relays`); - await activitypub.send('cid', cid, targets, { - id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now + 1}`, + await activitypub.send('cid', localCid ? cid : 0, targets, { + id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now}`, type: 'Announce', - actor: `${nconf.get('url')}/category/${cid}`, - to: [`${nconf.get('url')}/category/${cid}/followers`], - cc: [actor, activitypub._constants.publicAddress], + actor: localCid ? `${nconf.get('url')}/category/${cid}` : `${nconf.get('url')}/actor`, + to, + cc, object: activity, }); }; diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index e6eb2e1c08..6628b3ec55 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -225,7 +225,7 @@ Helpers.resolveActor = (type, id) => { case 'category': case 'cid': { - return `${nconf.get('url')}/category/${id}`; + return `${nconf.get('url')}${id > 0 ? `/category/${id}` : '/actor'}`; } default: @@ -526,3 +526,20 @@ Helpers.generateDigest = (set) => { return result.toString('hex'); }); }; + +Helpers.addressed = (id, activity) => { + // Returns Boolean for if id is found in addressing fields (to, cc, etc.) + if (!id || !activity || typeof activity !== 'object') { + return false; + } + + const combined = new Set([ + ...(activity.to || []), + ...(activity.cc || []), + ...(activity.bto || []), + ...(activity.bcc || []), + ...(activity.audience || []), + ]); + + return combined.has(id); +}; diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 9b5f85db69..d7dbcf6834 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -13,6 +13,7 @@ const notifications = require('../notifications'); const messaging = require('../messaging'); const flags = require('../flags'); const api = require('../api'); +const utils = require('../utils'); const activitypub = require('.'); const socketHelpers = require('../socket.io/helpers'); @@ -78,6 +79,78 @@ inbox.add = async (req) => { } }; +inbox.remove = async (req) => { + const { actor, object, target } = req.body; + + const isContext = activitypub._constants.acceptable.contextTypes.has(object.type); + if (!isContext) { + return; // don't know how to handle other types + } + + const mainPid = await activitypub.contexts.getItems(0, object.id, { returnRootId: true }); + const fromCid = target || object.audience; + const exists = await posts.exists(mainPid); + if (!exists || !fromCid) { + return; // post not cached; do nothing. + } + + // Ensure that cid is same-origin as the actor + const tid = await posts.getPostField(mainPid, 'tid'); + const cid = await topics.getTopicField(tid, 'cid'); + if (utils.isNumber(cid) || cid !== fromCid) { + // remote removal of topic in local cid, or resolved cid does not match + return; + } + const actorHostname = new URL(actor).hostname; + const cidHostname = new URL(cid).hostname; + if (actorHostname !== cidHostname) { + throw new Error('[[error:activitypub.origin-mismatch]]'); + } + + activitypub.helpers.log(`[activitypub/inbox/remove] Removing topic ${tid} from ${cid}`); + await topics.tools.move(tid, { + cid: -1, + uid: 'system', + }); +}; + +inbox.move = async (req) => { + const { actor, object, origin, target } = req.body; + + const isContext = activitypub._constants.acceptable.contextTypes.has(object.type); + if (!isContext) { + return; // don't know how to handle other types + } + + const mainPid = await activitypub.contexts.getItems(0, object.id, { returnRootId: true }); + const fromCid = origin; + const toCid = target || object.audience; + const exists = await posts.exists(mainPid); + if (!exists || !toCid) { + return; // post not cached; do nothing. + } + + // Ensure that cid is same-origin as the actor + const tid = await posts.getPostField(mainPid, 'tid'); + const cid = await topics.getTopicField(tid, 'cid'); + if (utils.isNumber(cid)) { + // remote removal of topic in local cid, or resolved cid does not match + return; + } + const actorHostname = new URL(actor).hostname; + const toCidHostname = new URL(toCid).hostname; + const fromCidHostname = new URL(fromCid).hostname; + if (actorHostname !== toCidHostname || actorHostname !== fromCidHostname) { + throw new Error('[[error:activitypub.origin-mismatch]]'); + } + + activitypub.helpers.log(`[activitypub/inbox/remove] Moving topic ${tid} from ${fromCid} to ${toCid}`); + await topics.tools.move(tid, { + cid: toCid, + uid: 'system', + }); +}; + inbox.update = async (req) => { const { actor, object } = req.body; const isPublic = publiclyAddressed([...(object.to || []), ...(object.cc || [])]); @@ -188,18 +261,18 @@ inbox.delete = async (req) => { throw new Error('[[error:invalid-pid]]'); } } - const pid = object.id || object; + const id = object.id || object; let type = object.type || undefined; // Deletes don't have their objects resolved automatically let method = 'purge'; try { if (!type) { - ({ type } = await activitypub.get('uid', 0, pid)); + ({ type } = await activitypub.get('uid', 0, id)); } if (type === 'Tombstone') { - method = 'delete'; + method = 'delete'; // soft delete } } catch (e) { // probably 410/404 @@ -208,27 +281,41 @@ inbox.delete = async (req) => { // Deletions must be made by an actor of the same origin const actorHostname = new URL(actor).hostname; - const objectHostname = new URL(pid).hostname; + const objectHostname = new URL(id).hostname; if (actorHostname !== objectHostname) { return reject('Delete', object, actor); } - const [isNote/* , isActor */] = await Promise.all([ - posts.exists(pid), + const [isNote, isContext/* , isActor */] = await Promise.all([ + posts.exists(id), + activitypub.contexts.getItems(0, id, { returnRootId: true }), // ⚠️ unreliable, needs better logic (Contexts.is?) // db.isSortedSetMember('usersRemote:lastCrawled', object.id), ]); switch (true) { case isNote: { - const cid = await posts.getCidByPid(pid); + const cid = await posts.getCidByPid(id); const allowed = await privileges.categories.can('posts:edit', cid, activitypub._constants.uid); if (!allowed) { return reject('Delete', object, actor); } - const uid = await posts.getPostField(pid, 'uid'); - await activitypub.feps.announce(pid, req.body); - await api.posts[method]({ uid }, { pid }); + const uid = await posts.getPostField(id, 'uid'); + await activitypub.feps.announce(id, req.body); + await api.posts[method]({ uid }, { pid: id }); + break; + } + + case !!isContext: { + const pid = isContext; + const exists = await posts.exists(pid); + if (!exists) { + activitypub.helpers.log(`[activitypub/inbox.delete] Context main pid (${pid}) not found locally. Doing nothing.`); + return; + } + const { tid, uid } = await posts.getPostFields(pid, ['tid', 'uid']); + activitypub.helpers.log(`[activitypub/inbox.delete] Deleting tid ${tid}.`); + await api.topics[method]({ uid }, { tids: [tid] }); break; } @@ -238,7 +325,7 @@ inbox.delete = async (req) => { // } default: { - activitypub.helpers.log(`[activitypub/inbox.delete] Object (${pid}) does not exist locally. Doing nothing.`); + activitypub.helpers.log(`[activitypub/inbox.delete] Object (${id}) does not exist locally. Doing nothing.`); break; } } diff --git a/src/activitypub/index.js b/src/activitypub/index.js index e64dd66d6b..343c0bc778 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -68,6 +68,7 @@ ActivityPub.instances = require('./instances'); ActivityPub.feps = require('./feps'); ActivityPub.rules = require('./rules'); ActivityPub.relays = require('./relays'); +ActivityPub.out = require('./out'); ActivityPub.startJobs = () => { ActivityPub.helpers.log('[activitypub/jobs] Registering jobs.'); @@ -206,7 +207,7 @@ ActivityPub.getPrivateKey = async (type, id) => { if (type === 'uid') { keyId = `${nconf.get('url')}${id > 0 ? `/uid/${id}` : '/actor'}#key`; } else { - keyId = `${nconf.get('url')}/category/${id}#key`; + keyId = `${nconf.get('url')}${id > 0 ? `/category/${id}` : '/actor'}#key`; } return { key: privateKey, keyId }; @@ -537,7 +538,7 @@ ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) { * - Builds a list of targets for activitypub.send to consume * - Extends to and cc since the activity can be addressed more widely * - Optional parameters: - * - `cid`: includes followers of the passed-in cid (local only) + * - `cid`: includes followers of the passed-in cid (local only, can also be an array) * - `uid`: includes followers of the passed-in uid (local only) * - `pid`: includes post announcers and all topic participants */ @@ -555,12 +556,15 @@ ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) { } if (cid) { - const cidFollowers = await ActivityPub.notes.getCategoryFollowers(cid); - followers = followers.concat(cidFollowers); - const followersUrl = `${nconf.get('url')}/category/${cid}/followers`; - if (!to.has(followersUrl)) { - cc.add(followersUrl); - } + cid = Array.isArray(cid) ? cid : [cid]; + await Promise.all(cid.map(async (cid) => { + const cidFollowers = await ActivityPub.notes.getCategoryFollowers(cid); + followers = followers.concat(cidFollowers); + const followersUrl = `${nconf.get('url')}/category/${cid}/followers`; + if (!to.has(followersUrl)) { + cc.add(followersUrl); + } + })); } const targets = new Set([...followers, ...to, ...cc]); diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 0dce187268..46a2caaba6 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -320,7 +320,7 @@ Mocks.category = async (actors) => { const payload = { cid, name, - handle: preferredUsername, + handle: `${preferredUsername}@${hostname}`, slug: `${preferredUsername}@${hostname}`, description: summary, descriptionParsed: posts.sanitize(summary), @@ -411,7 +411,10 @@ Mocks.message = async (object) => { mid: object.id, uid: object.attributedTo, content: object.sourceContent || object.content, - // ip: caller.ip, + + _activitypub: { + attachment: object.attachment, + }, }; return message; @@ -521,11 +524,11 @@ Mocks.actors.user = async (uid) => { }; Mocks.actors.category = async (cid) => { - let { + const { name, handle: preferredUsername, slug, - descriptionParsed: summary, federatedDescription, backgroundImage, + descriptionParsed: summary, backgroundImage, } = await categories.getCategoryFields(cid, - ['name', 'handle', 'slug', 'description', 'descriptionParsed', 'federatedDescription', 'backgroundImage']); + ['name', 'handle', 'slug', 'description', 'descriptionParsed', 'backgroundImage']); const publicKey = await activitypub.getPublicKey('cid', cid); let icon; @@ -546,10 +549,6 @@ Mocks.actors.category = async (cid) => { }; } - // Append federated desc. - const fallback = await translator.translate('[[admin/manage/categories:federatedDescription.default]]'); - summary += `

${federatedDescription || fallback}

\n`; - return { '@context': [ 'https://www.w3.org/ns/activitystreams', diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 973e97a7b6..21aebb4c63 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -15,6 +15,7 @@ const notifications = require('../notifications'); const user = require('../user'); const topics = require('../topics'); const posts = require('../posts'); +const api = require('../api'); const utils = require('../utils'); const activitypub = module.parent.exports; @@ -106,7 +107,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { if (options.cid && cid === -1) { // Move topic if currently uncategorized - await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); + await api.topics.move({ uid: 'system' }, { tid, cid: options.cid }); } const exists = await posts.exists(chain.map(p => p.pid)); @@ -214,7 +215,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`); if (!hasTid) { - const { to, cc, attachment } = mainPost._activitypub; + const { to, cc } = mainPost._activitypub; const tags = await Notes._normalizeTags(mainPost._activitypub.tag || []); try { @@ -243,7 +244,6 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { id: tid, path: mainPost._activitypub.image, }) : null, - posts.attachments.update(mainPid, attachment), ]); if (context) { @@ -252,23 +252,24 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { } } - for (const post of unprocessed) { - const { to, cc, attachment } = post._activitypub; + await Promise.all(unprocessed.map(async (post) => { + const { to, cc } = post._activitypub; try { - // eslint-disable-next-line no-await-in-loop await topics.reply(post); - // eslint-disable-next-line no-await-in-loop - await Promise.all([ - Notes.updateLocalRecipients(post.pid, { to, cc }), - posts.attachments.update(post.pid, attachment), - ]); + await Notes.updateLocalRecipients(post.pid, { to, cc }); } catch (e) { activitypub.helpers.log(`[activitypub/notes.assert] Could not add reply (${post.pid}): ${e.message}`); } - } + })); await Notes.syncUserInboxes(tid, uid); + + if (!hasTid && options.cid) { + // New topic, have category announce it + activitypub.out.announce.topic(tid); + } + return { tid, count }; } catch (e) { winston.warn(`[activitypub/notes.assert] Could not assert ${id} (${e.message}).`); @@ -338,6 +339,17 @@ Notes.assertPrivate = async (object) => { const payload = await activitypub.mocks.message(object); + // Naive image appending (using src/posts/attachments.js is likely better, but not worth the effort) + const attachments = payload._activitypub.attachment; + if (attachments && Array.isArray(attachments)) { + const images = attachments.filter((attachment) => { + return attachment.mediaType.startsWith('image/'); + }).map(({ url, href }) => url || href); + images.forEach((url) => { + payload.content += `

`; + }); + } + try { await messaging.checkContent(payload.content, false); } catch (e) { diff --git a/src/api/activitypub.js b/src/activitypub/out.js similarity index 58% rename from src/api/activitypub.js rename to src/activitypub/out.js index 7e4ae7da18..6a267a36bc 100644 --- a/src/api/activitypub.js +++ b/src/activitypub/out.js @@ -1,34 +1,34 @@ 'use strict'; /** - * DEVELOPMENT NOTE + * This method deals unilaterally with federating activities outward. + * There _shouldn't_ be any activities sent out that don't go through this file + * This _should_ be the only file that calls activitypub.send() * - * THIS FILE IS UNDER ACTIVE DEVELOPMENT AND IS EXPLICITLY EXCLUDED FROM IMMUTABILITY GUARANTEES - * - * If you use api methods in this file, be prepared that they may be removed or modified with no warning. + * YMMV. */ -const nconf = require('nconf'); const winston = require('winston'); +const nconf = require('nconf'); const db = require('../database'); const user = require('../user'); const categories = require('../categories'); const meta = require('../meta'); const privileges = require('../privileges'); -const activitypub = require('../activitypub'); -const posts = require('../posts'); const topics = require('../topics'); +const posts = require('../posts'); const messaging = require('../messaging'); const utils = require('../utils'); +const activitypub = module.parent.exports; -const activitypubApi = module.exports; +const Out = module.exports; function enabledCheck(next) { - return async function (caller, params) { + return async function (...args) { if (meta.config.activitypubEnabled) { try { - await next(caller, params); + await next.apply(null, args); } catch (e) { winston.error(`[activitypub/api] Error\n${e.stack}`); } @@ -36,7 +36,7 @@ function enabledCheck(next) { }; } -activitypubApi.follow = enabledCheck(async (caller, { type, id, actor } = {}) => { +Out.follow = enabledCheck(async (type, id, actor) => { // Privilege checks should be done upstream const acceptedTypes = ['uid', 'cid']; const assertion = await activitypub.actors.assert(actor); @@ -73,8 +73,341 @@ activitypubApi.follow = enabledCheck(async (caller, { type, id, actor } = {}) => } }); -// should be .undo.follow -activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => { +Out.create = {}; + +Out.create.note = enabledCheck(async (uid, post) => { + if (utils.isNumber(post)) { + post = (await posts.getPostSummaryByPids([post], uid, { stripTags: false })).pop(); + if (!post) { + return; + } + } + const { pid } = post; + const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating creation of pid ${pid} to the fediverse due to privileges.`); + return; + } + + const { activity, targets } = await activitypub.mocks.activities.create(pid, uid, post); + + await Promise.all([ + activitypub.send('uid', uid, Array.from(targets), activity), + activitypub.feps.announce(pid, activity), + // utils.isNumber(post.cid) ? activitypubApi.add(caller, { pid }) : undefined, + ]); +}); + +Out.create.privateNote = enabledCheck(async (messageObj) => { + const { roomId } = messageObj; + let targets = await messaging.getUidsInRoom(roomId, 0, -1); + targets = targets.filter(uid => !utils.isNumber(uid)); // remote uids only + + const object = await activitypub.mocks.notes.private({ messageObj }); + + const payload = { + id: `${object.id}#activity/create/${Date.now()}`, + type: 'Create', + actor: object.attributedTo, + to: object.to, + object, + }; + + await activitypub.send('uid', messageObj.fromuid, targets, payload); +}); + +Out.update = {}; + +Out.update.profile = enabledCheck(async (uid, actorUid) => { + // Local users only + if (!utils.isNumber(uid)) { + return; + } + + const [object, targets] = await Promise.all([ + activitypub.mocks.actors.user(uid), + db.getSortedSetMembers(`followersRemote:${uid}`), + ]); + + await activitypub.send('uid', actorUid || uid, targets, { + id: `${object.id}#activity/update/${Date.now()}`, + type: 'Update', + actor: object.id, + to: [activitypub._constants.publicAddress], + cc: [], + object, + }); +}); + +Out.update.category = enabledCheck(async (cid) => { + // Local categories only + if (!utils.isNumber(cid)) { + return; + } + + const [object, targets] = await Promise.all([ + activitypub.mocks.actors.category(cid), + activitypub.notes.getCategoryFollowers(cid), + ]); + + await activitypub.send('cid', cid, targets, { + id: `${object.id}#activity/update/${Date.now()}`, + type: 'Update', + actor: object.id, + to: [activitypub._constants.publicAddress], + cc: [], + object, + }); +}); + +Out.update.note = enabledCheck(async (uid, post) => { + // Only applies to local posts + if (!utils.isNumber(post.pid)) { + return; + } + + const object = await activitypub.mocks.notes.public(post); + const { to, cc, targets } = await activitypub.buildRecipients(object, { pid: post.pid, uid: post.user.uid }); + object.to = to; + object.cc = cc; + + const allowed = await privileges.posts.can('topics:read', post.pid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${post.pid} to the fediverse due to privileges.`); + return; + } + + const payload = { + id: `${object.id}#activity/update/${post.edited || Date.now()}`, + type: 'Update', + actor: object.attributedTo, + to, + cc, + object, + }; + + await Promise.all([ + activitypub.send('uid', uid, Array.from(targets), payload), + activitypub.feps.announce(post.pid, payload), + ]); +}); + +Out.update.privateNote = enabledCheck(async (uid, messageObj) => { + if (!utils.isNumber(messageObj.mid)) { + return; + } + + const { roomId } = messageObj; + let uids = await messaging.getUidsInRoom(roomId, 0, -1); + uids = uids.filter(uid => String(uid) !== String(messageObj.fromuid)); // no author + const to = uids.map(uid => (utils.isNumber(uid) ? `${nconf.get('url')}/uid/${uid}` : uid)); + const targets = uids.filter(uid => !utils.isNumber(uid)); // remote uids only + + const object = await activitypub.mocks.notes.private({ messageObj }); + + const payload = { + id: `${object.id}#activity/create/${Date.now()}`, + type: 'Update', + actor: object.attributedTo, + to, + object, + }; + + await activitypub.send('uid', uid, targets, payload); +}); + +Out.delete = {}; + +Out.delete.note = enabledCheck(async (uid, pid) => { + // Only applies to local posts + if (!utils.isNumber(pid)) { + return; + } + + const id = `${nconf.get('url')}/post/${pid}`; + const post = (await posts.getPostSummaryByPids([pid], uid, { stripTags: false })).pop(); + const object = await activitypub.mocks.notes.public(post); + const { to, cc, targets } = await activitypub.buildRecipients(object, { pid, uid: post.user.uid }); + + const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${pid} to the fediverse due to privileges.`); + return; + } + + const payload = { + id: `${id}#activity/delete/${Date.now()}`, + type: 'Delete', + actor: object.attributedTo, + to, + cc, + object: id, + origin: object.context, + }; + + await Promise.all([ + activitypub.send('uid', uid, Array.from(targets), payload), + activitypub.feps.announce(pid, payload), + ]); +}); + +Out.like = {}; + +Out.like.note = enabledCheck(async (uid, pid) => { + const payload = { + id: `${nconf.get('url')}/uid/${uid}#activity/like/${encodeURIComponent(pid)}`, + type: 'Like', + actor: `${nconf.get('url')}/uid/${uid}`, + object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, + }; + + if (!activitypub.helpers.isUri(pid)) { // only 1b12 announce for local likes + await activitypub.feps.announce(pid, payload); + return; + } + + const recipient = await posts.getPostField(pid, 'uid'); + if (!activitypub.helpers.isUri(recipient)) { + return; + } + + await Promise.all([ + activitypub.send('uid', uid, [recipient], payload), + activitypub.feps.announce(pid, payload), + ]); +}); + +Out.announce = {}; + +Out.announce.topic = enabledCheck(async (tid) => { + const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); + + // Only local categories can announce + if (!utils.isNumber(cid) || parseInt(cid, 10) < 1) { + return; + } + + const uid = await posts.getPostField(pid, 'uid'); // author + const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating announce of pid ${pid} to the fediverse due to privileges.`); + return; + } + + const { to, cc, targets } = await activitypub.buildRecipients({ + id: pid, + to: [activitypub._constants.publicAddress], + cc: [`${nconf.get('url')}/category/${cid}/followers`, uid], + }, { cid, uid: utils.isNumber(uid) ? uid : undefined }); + + await activitypub.send('cid', cid, Array.from(targets), { + id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/cid/${cid}`, + type: 'Announce', + actor: `${nconf.get('url')}/category/${cid}`, + to, + cc, + object: pid, + }); +}); + +Out.flag = enabledCheck(async (uid, flag) => { + if (!activitypub.helpers.isUri(flag.targetId)) { + return; + } + const reportedIds = [flag.targetId]; + if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { + reportedIds.push(flag.targetUid); + } + const reason = flag.reason || + (flag.reports && flag.reports.filter(report => report.reporter.uid === uid).at(-1).value); + await activitypub.send('uid', uid, reportedIds, { + id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${uid}`, + type: 'Flag', + actor: `${nconf.get('url')}/uid/${uid}`, + object: reportedIds, + content: reason, + }); + await db.sortedSetAdd(`flag:${flag.flagId}:remote`, Date.now(), uid); +}); + +Out.remove = {}; + +Out.remove.context = enabledCheck(async (uid, tid) => { + // Federates Remove(Context); where Context is the tid + const now = new Date(); + const cid = await topics.getTopicField(tid, 'oldCid'); + + // Only local categories + if (!utils.isNumber(cid) || parseInt(cid, 10) < 1) { + return; + } + + const allowed = await privileges.categories.can('topics:read', cid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating deletion of tid ${tid} to the fediverse due to privileges.`); + return; + } + + const { to, cc, targets } = await activitypub.buildRecipients({ + to: [activitypub._constants.publicAddress], + cc: [], + }, { cid }); + + await activitypub.send('uid', uid, Array.from(targets), { + id: `${nconf.get('url')}/topic/${tid}#activity/remove/${now.getTime()}`, + type: 'Remove', + actor: `${nconf.get('url')}/uid/${uid}`, + to, + cc, + object: `${nconf.get('url')}/topic/${tid}`, + target: `${nconf.get('url')}/category/${cid}`, + }); +}); + +Out.move = {}; + +Out.move.context = enabledCheck(async (uid, tid) => { + // Federates Move(Context); where Context is the tid + const now = new Date(); + const { cid, oldCid } = await topics.getTopicFields(tid, ['cid', 'oldCid']); + + // This check may be revised if inter-community moderation becomes real. + const isLocal = id => utils.isNumber(id) && parseInt(id, 10) > 0; + if (isLocal(oldCid) && !isLocal(cid)) { // moving to remote/uncategorized + return Out.remove.context(uid, tid); + } else if ( + (isLocal(cid) && !isLocal(oldCid)) || // stealing, or + [cid, oldCid].every(id => !isLocal(id)) // remote-to-remote + ) { + return; + } + + const allowed = await privileges.categories.can('topics:read', cid, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[activitypub/api] Not federating move of tid ${tid} to the fediverse due to privileges.`); + return; + } + + const { to, cc, targets } = await activitypub.buildRecipients({ + to: [activitypub._constants.publicAddress], + cc: [], + }, { cid: [cid, oldCid] }); + + await activitypub.send('uid', uid, Array.from(targets), { + id: `${nconf.get('url')}/topic/${tid}#activity/move/${now.getTime()}`, + type: 'Move', + actor: `${nconf.get('url')}/uid/${uid}`, + to, + cc, + object: `${nconf.get('url')}/topic/${tid}`, + origin: `${nconf.get('url')}/category/${oldCid}`, + target: `${nconf.get('url')}/category/${cid}`, + }); +}); + +Out.undo = {}; + +Out.undo.follow = enabledCheck(async (type, id, actor) => { const acceptedTypes = ['uid', 'cid']; const assertion = await activitypub.actors.assert(actor); if (!acceptedTypes.includes(type) || !assertion) { @@ -139,224 +472,75 @@ activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => { } }); -activitypubApi.create = {}; - -activitypubApi.create.note = enabledCheck(async (caller, { pid, post }) => { - if (!post) { - post = (await posts.getPostSummaryByPids([pid], caller.uid, { stripTags: false })).pop(); - if (!post) { - return; - } - } else { - pid = post.pid; - } - - const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); - if (!allowed) { - activitypub.helpers.log(`[activitypub/api] Not federating creation of pid ${pid} to the fediverse due to privileges.`); +Out.undo.like = enabledCheck(async (uid, pid) => { + if (!activitypub.helpers.isUri(pid)) { return; } - const { activity, targets } = await activitypub.mocks.activities.create(pid, caller.uid, post); - - await Promise.all([ - activitypub.send('uid', caller.uid, Array.from(targets), activity), - activitypub.feps.announce(pid, activity), - // utils.isNumber(post.cid) ? activitypubApi.add(caller, { pid }) : undefined, - ]); -}); - -activitypubApi.create.privateNote = enabledCheck(async (caller, { messageObj }) => { - const { roomId } = messageObj; - let targets = await messaging.getUidsInRoom(roomId, 0, -1); - targets = targets.filter(uid => !utils.isNumber(uid)); // remote uids only - - const object = await activitypub.mocks.notes.private({ messageObj }); - - const payload = { - id: `${object.id}#activity/create/${Date.now()}`, - type: 'Create', - actor: object.attributedTo, - to: object.to, - object, - }; - - await activitypub.send('uid', messageObj.fromuid, targets, payload); -}); - -activitypubApi.update = {}; - -activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => { - // Local users only - if (!utils.isNumber(uid)) { - return; - } - - const [object, targets] = await Promise.all([ - activitypub.mocks.actors.user(uid), - db.getSortedSetMembers(`followersRemote:${caller.uid}`), - ]); - - await activitypub.send('uid', caller.uid, targets, { - id: `${object.id}#activity/update/${Date.now()}`, - type: 'Update', - actor: object.id, - to: [activitypub._constants.publicAddress], - cc: [], - object, - }); -}); - -activitypubApi.update.category = enabledCheck(async (caller, { cid }) => { - // Local categories only - if (!utils.isNumber(cid)) { - return; - } - - const [object, targets] = await Promise.all([ - activitypub.mocks.actors.category(cid), - activitypub.notes.getCategoryFollowers(cid), - ]); - - await activitypub.send('cid', cid, targets, { - id: `${object.id}#activity/update/${Date.now()}`, - type: 'Update', - actor: object.id, - to: [activitypub._constants.publicAddress], - cc: [], - object, - }); -}); - -activitypubApi.update.note = enabledCheck(async (caller, { post }) => { - // Only applies to local posts - if (!utils.isNumber(post.pid)) { - return; - } - - const object = await activitypub.mocks.notes.public(post); - const { to, cc, targets } = await activitypub.buildRecipients(object, { pid: post.pid, uid: post.user.uid }); - object.to = to; - object.cc = cc; - - const allowed = await privileges.posts.can('topics:read', post.pid, activitypub._constants.uid); - if (!allowed) { - activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${post.pid} to the fediverse due to privileges.`); + const author = await posts.getPostField(pid, 'uid'); + if (!activitypub.helpers.isUri(author)) { return; } const payload = { - id: `${object.id}#activity/update/${post.edited || Date.now()}`, - type: 'Update', - actor: object.attributedTo, - to, - cc, - object, + id: `${nconf.get('url')}/uid/${uid}#activity/undo:like/${encodeURIComponent(pid)}/${Date.now()}`, + type: 'Undo', + actor: `${nconf.get('url')}/uid/${uid}`, + object: { + actor: `${nconf.get('url')}/uid/${uid}`, + id: `${nconf.get('url')}/uid/${uid}#activity/like/${encodeURIComponent(pid)}`, + type: 'Like', + object: pid, + }, }; await Promise.all([ - activitypub.send('uid', caller.uid, Array.from(targets), payload), - activitypub.feps.announce(post.pid, payload), - ]); -}); - -activitypubApi.update.privateNote = enabledCheck(async (caller, { messageObj }) => { - if (!utils.isNumber(messageObj.mid)) { - return; - } - - const { roomId } = messageObj; - let uids = await messaging.getUidsInRoom(roomId, 0, -1); - uids = uids.filter(uid => String(uid) !== String(messageObj.fromuid)); // no author - const to = uids.map(uid => (utils.isNumber(uid) ? `${nconf.get('url')}/uid/${uid}` : uid)); - const targets = uids.filter(uid => !utils.isNumber(uid)); // remote uids only - - const object = await activitypub.mocks.notes.private({ messageObj }); - - const payload = { - id: `${object.id}#activity/create/${Date.now()}`, - type: 'Update', - actor: object.attributedTo, - to, - object, - }; - - await activitypub.send('uid', caller.uid, targets, payload); -}); - -activitypubApi.delete = {}; - -activitypubApi.delete.note = enabledCheck(async (caller, { pid }) => { - // Only applies to local posts - if (!utils.isNumber(pid)) { - return; - } - - const id = `${nconf.get('url')}/post/${pid}`; - const post = (await posts.getPostSummaryByPids([pid], caller.uid, { stripTags: false })).pop(); - const object = await activitypub.mocks.notes.public(post); - const { to, cc, targets } = await activitypub.buildRecipients(object, { pid, uid: post.user.uid }); - - const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); - if (!allowed) { - activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${pid} to the fediverse due to privileges.`); - return; - } - - const payload = { - id: `${id}#activity/delete/${Date.now()}`, - type: 'Delete', - actor: object.attributedTo, - to, - cc, - object: id, - origin: object.context, - }; - - await Promise.all([ - activitypub.send('uid', caller.uid, Array.from(targets), payload), + activitypub.send('uid', uid, [author], payload), activitypub.feps.announce(pid, payload), ]); }); -activitypubApi.like = {}; - -activitypubApi.like.note = enabledCheck(async (caller, { pid }) => { - const payload = { - id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`, - type: 'Like', - actor: `${nconf.get('url')}/uid/${caller.uid}`, - object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, - }; - - if (!activitypub.helpers.isUri(pid)) { // only 1b12 announce for local likes - await activitypub.feps.announce(pid, payload); +Out.undo.flag = enabledCheck(async (uid, flag) => { + if (!activitypub.helpers.isUri(flag.targetId)) { return; } - - const uid = await posts.getPostField(pid, 'uid'); - if (!activitypub.helpers.isUri(uid)) { - return; + const reportedIds = [flag.targetId]; + if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { + reportedIds.push(flag.targetUid); } - - await Promise.all([ - activitypub.send('uid', caller.uid, [uid], payload), - activitypub.feps.announce(pid, payload), - ]); + const reason = flag.reason || + (flag.reports && flag.reports.filter(report => report.reporter.uid === uid).at(-1).value); + await activitypub.send('uid', uid, reportedIds, { + id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/undo:flag/${uid}/${Date.now()}`, + type: 'Undo', + actor: `${nconf.get('url')}/uid/${uid}`, + object: { + id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${uid}`, + actor: `${nconf.get('url')}/uid/${uid}`, + type: 'Flag', + object: reportedIds, + content: reason, + }, + }); + await db.sortedSetRemove(`flag:${flag.flagId}:remote`, uid); }); -activitypubApi.announce = {}; - -activitypubApi.announce.note = enabledCheck(async (caller, { tid }) => { - const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); - - // Only remote posts can be announced to real categories - if (utils.isNumber(pid) || parseInt(cid, 10) === -1) { - return; +Out.undo.announce = enabledCheck(async (type, id, tid) => { + if (!utils.isNumber(id) || !['uid', 'cid'].includes(type)) { + throw new Error('[[error:invalid-data]]'); } - const uid = await posts.getPostField(pid, 'uid'); // author - const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); + const exists = await Promise.all([ + topics.exists(tid), + type === 'uid' ? user.exists(id) : categories.exists(id), + ]); + if (!exists.every(Boolean)) { + throw new Error('[[error:invalid-data]]'); + } + + const baseUrl = `${nconf.get('url')}/${type === 'uid' ? 'uid' : 'category'}/${id}`; + const { uid, mainPid: pid } = await topics.getTopicFields(tid, ['uid', 'mainPid']); + const allowed = await privileges.topics.can('topics:read', tid, activitypub._constants.uid); if (!allowed) { activitypub.helpers.log(`[activitypub/api] Not federating announce of pid ${pid} to the fediverse due to privileges.`); return; @@ -365,121 +549,27 @@ activitypubApi.announce.note = enabledCheck(async (caller, { tid }) => { const { to, cc, targets } = await activitypub.buildRecipients({ id: pid, to: [activitypub._constants.publicAddress], - cc: [`${nconf.get('url')}/uid/${caller.uid}/followers`, uid], - }, { uid: caller.uid }); + cc: [`${baseUrl}/followers`, uid], + }, { + uid: type === 'uid' && id, + cid: type === 'cid' && id, + }); - await activitypub.send('uid', caller.uid, Array.from(targets), { - id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/${Date.now()}`, - type: 'Announce', - actor: `${nconf.get('url')}/uid/${caller.uid}`, + + // Just undo the announce. + await activitypub.send(type, id, Array.from(targets), { + id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/undo:announce/${type}/${id}`, + type: 'Undo', + actor: baseUrl, to, cc, - object: pid, - target: `${nconf.get('url')}/category/${cid}`, - }); -}); - -activitypubApi.undo = {}; - -// activitypubApi.undo.follow = - -activitypubApi.undo.like = enabledCheck(async (caller, { pid }) => { - if (!activitypub.helpers.isUri(pid)) { - return; - } - - const uid = await posts.getPostField(pid, 'uid'); - if (!activitypub.helpers.isUri(uid)) { - return; - } - - const payload = { - id: `${nconf.get('url')}/uid/${caller.uid}#activity/undo:like/${encodeURIComponent(pid)}/${Date.now()}`, - type: 'Undo', - actor: `${nconf.get('url')}/uid/${caller.uid}`, object: { - actor: `${nconf.get('url')}/uid/${caller.uid}`, - id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`, - type: 'Like', + id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/${type}/${id}`, + type: 'Announce', + actor: baseUrl, + to, + cc, object: pid, }, - }; - - await Promise.all([ - activitypub.send('uid', caller.uid, [uid], payload), - activitypub.feps.announce(pid, payload), - ]); -}); - -activitypubApi.flag = enabledCheck(async (caller, flag) => { - if (!activitypub.helpers.isUri(flag.targetId)) { - return; - } - const reportedIds = [flag.targetId]; - if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { - reportedIds.push(flag.targetUid); - } - const reason = flag.reason || - (flag.reports && flag.reports.filter(report => report.reporter.uid === caller.uid).at(-1).value); - await activitypub.send('uid', caller.uid, reportedIds, { - id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${caller.uid}`, - type: 'Flag', - actor: `${nconf.get('url')}/uid/${caller.uid}`, - object: reportedIds, - content: reason, }); - await db.sortedSetAdd(`flag:${flag.flagId}:remote`, Date.now(), caller.uid); -}); - -/* -activitypubApi.add = enabledCheck((async (_, { pid }) => { - let localId; - if (String(pid).startsWith(nconf.get('url'))) { - ({ id: localId } = await activitypub.helpers.resolveLocalId(pid)); - } - - const tid = await posts.getPostField(localId || pid, 'tid'); - const cid = await posts.getCidByPid(localId || pid); - if (!utils.isNumber(tid) || cid <= 0) { // `Add` only federated on categorized topics started locally - return; - } - - let to = [activitypub._constants.publicAddress]; - let cc = []; - let targets; - ({ to, cc, targets } = await activitypub.buildRecipients({ to, cc }, { pid: localId || pid, cid })); - - await activitypub.send('cid', cid, Array.from(targets), { - id: `${nconf.get('url')}/post/${encodeURIComponent(localId || pid)}#activity/add/${Date.now()}`, - type: 'Add', - to, - cc, - object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, - target: `${nconf.get('url')}/topic/${tid}`, - }); -})); -*/ -activitypubApi.undo.flag = enabledCheck(async (caller, flag) => { - if (!activitypub.helpers.isUri(flag.targetId)) { - return; - } - const reportedIds = [flag.targetId]; - if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { - reportedIds.push(flag.targetUid); - } - const reason = flag.reason || - (flag.reports && flag.reports.filter(report => report.reporter.uid === caller.uid).at(-1).value); - await activitypub.send('uid', caller.uid, reportedIds, { - id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/undo:flag/${caller.uid}/${Date.now()}`, - type: 'Undo', - actor: `${nconf.get('url')}/uid/${caller.uid}`, - object: { - id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${caller.uid}`, - actor: `${nconf.get('url')}/uid/${caller.uid}`, - type: 'Flag', - object: reportedIds, - content: reason, - }, - }); - await db.sortedSetRemove(`flag:${flag.flagId}:remote`, caller.uid); -}); +}); \ No newline at end of file diff --git a/src/api/categories.js b/src/api/categories.js index 693f8f15ee..476a0d4d9d 100644 --- a/src/api/categories.js +++ b/src/api/categories.js @@ -7,10 +7,9 @@ const events = require('../events'); const user = require('../user'); const groups = require('../groups'); const privileges = require('../privileges'); +const activitypub = require('../activitypub'); const utils = require('../utils'); -const activitypubApi = require('./activitypub'); - const categoriesAPI = module.exports; const hasAdminPrivilege = async (uid, privilege = 'categories') => { @@ -66,7 +65,7 @@ categoriesAPI.update = async function (caller, data) { const payload = {}; payload[cid] = values; await categories.update(payload); - activitypubApi.update.category(caller, { cid }); // background + activitypub.out.update.category(cid); // background }; categoriesAPI.delete = async function (caller, { cid }) { diff --git a/src/api/helpers.js b/src/api/helpers.js index 7df860a569..3422b4a6f9 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -6,6 +6,7 @@ const topics = require('../topics'); const posts = require('../posts'); const privileges = require('../privileges'); const plugins = require('../plugins'); +const activitypub = require('../activitypub'); const socketHelpers = require('../socket.io/helpers'); const websockets = require('../socket.io'); const events = require('../events'); @@ -129,7 +130,6 @@ exports.postCommand = async function (caller, command, eventName, notification, }; async function executeCommand(caller, command, eventName, notification, data) { - const api = require('.'); const result = await posts[command](data.pid, caller.uid); if (result && eventName) { websockets.in(`uid_${caller.uid}`).emit(`posts.${command}`, result); @@ -137,12 +137,12 @@ async function executeCommand(caller, command, eventName, notification, data) { } if (result && command === 'upvote') { socketHelpers.upvote(result, notification); - await api.activitypub.like.note(caller, { pid: data.pid }); + await activitypub.out.like.note(caller.uid, data.pid); } else if (result && notification) { socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, command, notification); } else if (result && command === 'unvote') { socketHelpers.rescindUpvoteNotification(data.pid, caller.uid); - await api.activitypub.undo.like(caller, { pid: data.pid }); + await activitypub.out.undo.like(caller.uid, data.pid); } return result; } diff --git a/src/api/index.js b/src/api/index.js index 18cd8678f1..c454de93a5 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -11,7 +11,6 @@ module.exports = { categories: require('./categories'), search: require('./search'), flags: require('./flags'), - activitypub: require('./activitypub'), files: require('./files'), utils: require('./utils'), }; diff --git a/src/api/posts.js b/src/api/posts.js index 54454e6e52..a7eecaa633 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -151,7 +151,7 @@ postsAPI.edit = async function (caller, data) { if (!editResult.post.deleted) { websockets.in(`topic_${editResult.topic.tid}`).emit('event:post_edited', editResult); setTimeout(() => { - require('.').activitypub.update.note(caller, { post: postObj[0] }); + activitypub.out.update.note(caller.uid, postObj[0]); }, 5000); return returnData; @@ -190,9 +190,12 @@ async function deleteOrRestore(caller, data, params) { if (!data || !data.pid) { throw new Error('[[error:invalid-data]]'); } - const postData = await posts.tools[params.command](caller.uid, data.pid); - const results = await isMainAndLastPost(data.pid); - if (results.isMain && results.isLast) { + const [postData, { isMain, isLast }] = await Promise.all([ + posts.tools[params.command](caller.uid, data.pid), + isMainAndLastPost(data.pid), + activitypub.out.delete.note(caller.uid, data.pid), + ]); + if (isMain && isLast) { await deleteOrRestoreTopicOf(params.command, data.pid, caller); } @@ -205,11 +208,6 @@ async function deleteOrRestore(caller, data, params) { tid: postData.tid, ip: caller.ip, }); - - // Explicitly non-awaited - posts.getPostSummaryByPids([data.pid], caller.uid, { extraFields: ['edited'] }).then(([post]) => { - require('.').activitypub.update.note(caller, { post }); - }); } async function deleteOrRestoreTopicOf(command, pid, caller) { @@ -254,7 +252,7 @@ postsAPI.purge = async function (caller, data) { posts.clearCachedPost(data.pid); await Promise.all([ posts.purge(data.pid, caller.uid), - require('.').activitypub.delete.note(caller, { pid: data.pid }), + activitypub.out.delete.note(caller.uid, data.pid), ]); websockets.in(`topic_${postData.tid}`).emit('event:post_purged', postData); diff --git a/src/api/topics.js b/src/api/topics.js index d44fffae04..91e0882db4 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -8,8 +8,9 @@ const meta = require('../meta'); const privileges = require('../privileges'); const events = require('../events'); const batch = require('../batch'); +const activitypub = require('../activitypub'); +const utils = require('../utils'); -const activitypubApi = require('./activitypub'); const apiHelpers = require('./helpers'); const { doTopicAction } = apiHelpers; @@ -79,7 +80,7 @@ topicsAPI.create = async function (caller, data) { socketHelpers.notifyNew(caller.uid, 'newTopic', { posts: [result.postData], topic: result.topicData }); if (!isScheduling) { - await activitypubApi.create.note(caller, { pid: result.postData.pid }); + await activitypub.out.create.note(caller.uid, result.postData.pid); } return result.topicData; @@ -115,7 +116,7 @@ topicsAPI.reply = async function (caller, data) { } socketHelpers.notifyNew(caller.uid, 'newPost', result); - await activitypubApi.create.note(caller, { post: postData }); + await activitypub.out.create.note(caller.uid, postData); return postData; }; @@ -320,7 +321,16 @@ topicsAPI.move = async (caller, { tid, cid }) => { socketHelpers.emitToUids('event:topic_moved', topicData, notifyUids); if (!topicData.deleted) { socketHelpers.sendNotificationToTopicOwner(tid, caller.uid, 'move', 'notifications:moved-your-topic'); - activitypubApi.announce.note(caller, { tid }); + + if (utils.isNumber(cid) && parseInt(cid, 10) === -1) { + activitypub.out.remove.context(caller.uid, tid); // 7888-style + activitypub.out.delete.note(caller.uid, topicData.mainPid); // threadiverse + // tbd: activitypubApi.undo.announce? // microblogs + } else { + activitypub.out.move.context(caller.uid, tid); + activitypub.out.announce.topic(tid); + } + activitypub.out.undo.announce('cid', topicData.cid, tid); } await events.log({ diff --git a/src/cache/ttl.js b/src/cache/ttl.js index c8ed90af57..61cd4c07f4 100644 --- a/src/cache/ttl.js +++ b/src/cache/ttl.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = function (opts) { - const TTLCache = require('@isaacs/ttlcache'); + const { TTLCache } = require('@isaacs/ttlcache'); const os = require('os'); const winston = require('winston'); const chalk = require('chalk'); diff --git a/src/categories/data.js b/src/categories/data.js index dc4467ffa3..bf2ddac25f 100644 --- a/src/categories/data.js +++ b/src/categories/data.js @@ -117,7 +117,7 @@ function modifyCategory(category, fields) { db.parseIntFields(category, intFields, fields); - const escapeFields = ['name', 'nickname', 'description', 'federatedDescription', 'color', 'bgColor', 'backgroundImage', 'imageClass', 'class', 'link']; + const escapeFields = ['name', 'nickname', 'description', 'color', 'bgColor', 'backgroundImage', 'imageClass', 'class', 'link']; escapeFields.forEach((field) => { if (category.hasOwnProperty(field)) { category[field] = validator.escape(String(category[field] || '')); diff --git a/src/controllers/activitypub/actors.js b/src/controllers/activitypub/actors.js index 850585e379..9ae24ed613 100644 --- a/src/controllers/activitypub/actors.js +++ b/src/controllers/activitypub/actors.js @@ -143,7 +143,7 @@ Actors.topic = async function (req, res, next) { method: posts.getPidsFromSet, page, perPage, - url: `${nconf.get('url')}/topic/${req.params.tid}/posts`, + url: `${nconf.get('url')}/topic/${req.params.tid}`, }), db.getSortedSetMembers(`tid:${req.params.tid}:posts`), ])); @@ -178,7 +178,9 @@ Actors.topic = async function (req, res, next) { } // Convert pids to urls - collection.orderedItems = collection.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); + if (collection.orderedItems) { + collection.orderedItems = collection.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); + } const object = { '@context': 'https://www.w3.org/ns/activitystreams', diff --git a/src/controllers/category.js b/src/controllers/category.js index bf72a8cff0..dd6caeea3b 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -151,7 +151,7 @@ categoryController.get = async function (req, res, next) { categoryData.selectedTags = tagData.selectedTags; categoryData.sortOptionLabel = `[[topic:${validator.escape(String(sort)).replace(/_/g, '-')}]]`; - if (!meta.config['feeds:disableRSS']) { + if (utils.isNumber(categoryData.cid) && !meta.config['feeds:disableRSS']) { categoryData.rssFeedUrl = `${url}/category/${categoryData.cid}.rss`; if (req.loggedIn) { categoryData.rssFeedUrl += `?uid=${req.uid}&token=${rssToken}`; @@ -175,10 +175,15 @@ categoryController.get = async function (req, res, next) { res.set('Link', `<${nconf.get('url')}/category/${cid}>; rel="alternate"; type="application/activity+json"`); // Category accessible - const remoteOk = await privileges.categories.can('read', cid, activitypub._constants.uid); - if (remoteOk) { + const federating = await privileges.categories.can('read', cid, activitypub._constants.uid); + if (federating) { categoryData.handleFull = `${categoryData.handle}@${nconf.get('url_parsed').host}`; } + + // Some remote categories don't have `url`, assume same as id + if (!utils.isNumber(categoryData.cid) && !categoryData.hasOwnProperty('url')) { + categoryData.url = categoryData.cid; + } } res.render('category', categoryData); @@ -247,7 +252,7 @@ function addTags(categoryData, res, currentPage) { }, ]; - if (!categoryData['feeds:disableRSS']) { + if (categoryData.rssFeedUrl && !categoryData['feeds:disableRSS']) { res.locals.linkTags.push({ rel: 'alternate', type: 'application/rss+xml', diff --git a/src/controllers/write/categories.js b/src/controllers/write/categories.js index 8a2a002713..996a9d386a 100644 --- a/src/controllers/write/categories.js +++ b/src/controllers/write/categories.js @@ -2,6 +2,7 @@ const categories = require('../../categories'); const meta = require('../../meta'); +const activitypub = require('../../activitypub'); const api = require('../../api'); const helpers = require('../helpers'); @@ -107,6 +108,7 @@ Categories.setModerator = async (req, res) => { }; Categories.follow = async (req, res, next) => { + // Priv check done in route middleware const { actor } = req.body; const id = parseInt(req.params.cid, 10); @@ -114,11 +116,7 @@ Categories.follow = async (req, res, next) => { return next(); } - await api.activitypub.follow(req, { - type: 'cid', - id, - actor, - }); + await activitypub.out.follow('cid', id, actor); helpers.formatApiResponse(200, res, {}); }; @@ -131,11 +129,6 @@ Categories.unfollow = async (req, res, next) => { return next(); } - await api.activitypub.unfollow(req, { - type: 'cid', - id, - actor, - }); - + await activitypub.out.undo.follow('cid', id, actor); helpers.formatApiResponse(200, res, {}); }; diff --git a/src/controllers/write/users.js b/src/controllers/write/users.js index b884ef93fb..a5cde6dad2 100644 --- a/src/controllers/write/users.js +++ b/src/controllers/write/users.js @@ -5,6 +5,7 @@ const path = require('path'); const crypto = require('crypto'); const api = require('../../api'); +const activitypub = require('../../activitypub'); const user = require('../../user'); const helpers = require('../helpers'); @@ -94,11 +95,7 @@ Users.changePassword = async (req, res) => { Users.follow = async (req, res) => { const remote = String(req.params.uid).includes('@'); if (remote) { - await api.activitypub.follow(req, { - type: 'uid', - id: req.uid, - actor: req.params.uid, - }); + await activitypub.out.follow('uid', req.uid, req.params.uid); } else { await api.users.follow(req, req.params); } @@ -109,11 +106,7 @@ Users.follow = async (req, res) => { Users.unfollow = async (req, res) => { const remote = String(req.params.uid).includes('@'); if (remote) { - await api.activitypub.unfollow(req, { - type: 'uid', - id: req.uid, - actor: req.params.uid, - }); + await activitypub.out.undo.follow('uid', req.uid, req.params.uid); } else { await api.users.unfollow(req, req.params); } diff --git a/src/flags.js b/src/flags.js index 8ae9a6c595..435bc10c42 100644 --- a/src/flags.js +++ b/src/flags.js @@ -5,7 +5,6 @@ const winston = require('winston'); const validator = require('validator'); const activitypub = require('./activitypub'); -const activitypubApi = require('./api/activitypub'); const db = require('./database'); const user = require('./user'); const groups = require('./groups'); @@ -477,8 +476,7 @@ Flags.create = async function (type, id, uid, reason, timestamp, forceFlag = fal const flagObj = await Flags.get(flagId); if (notifyRemote && activitypub.helpers.isUri(id)) { - const caller = await user.getUserData(uid); - activitypubApi.flag(caller, { ...flagObj, reason }); + activitypub.out.flag(uid, { ...flagObj, reason }); } plugins.hooks.fire('action:flags.create', { flag: flagObj }); @@ -531,7 +529,7 @@ Flags.purge = async function (flagIds) { flagData.flatMap( async (flagObj, i) => allReporterUids[i].map(async (uid) => { if (await db.isSortedSetMember(`flag:${flagObj.flagId}:remote`, uid)) { - await activitypubApi.undo.flag({ uid }, flagObj); + await activitypub.out.undo.flag(uid, flagObj); } }) ), @@ -569,7 +567,7 @@ Flags.addReport = async function (flagId, type, id, uid, reason, timestamp, targ ]); if (notifyRemote && activitypub.helpers.isUri(id)) { - await activitypubApi.flag({ uid }, { flagId, type, targetId: id, targetUid, uid, reason, timestamp }); + await activitypub.out.flag(uid, { flagId, type, targetId: id, targetUid, uid, reason, timestamp }); } plugins.hooks.fire('action:flags.addReport', { flagId, type, id, uid, reason, timestamp }); @@ -599,7 +597,7 @@ Flags.rescindReport = async (type, id, uid) => { if (await db.isSortedSetMember(`flag:${flagId}:remote`, uid)) { const flag = await Flags.get(flagId); - await activitypubApi.undo.flag({ uid }, flag); + await activitypub.out.undo.flag(uid, flag); } await db.sortedSetRemoveBulk([ diff --git a/src/messaging/delete.js b/src/messaging/delete.js index 9e0f23c797..c754e4fc9f 100644 --- a/src/messaging/delete.js +++ b/src/messaging/delete.js @@ -2,7 +2,7 @@ const sockets = require('../socket.io'); const plugins = require('../plugins'); -const api = require('../api'); +const activitypub = require('../activitypub'); module.exports = function (Messaging) { Messaging.deleteMessage = async (mid, uid) => await doDeleteRestore(mid, 1, uid); @@ -30,6 +30,6 @@ module.exports = function (Messaging) { plugins.hooks.fire('action:messaging.restore', { message: msgData }); } - api.activitypub.update.privateNote({ uid }, { messageObj: msgData }); + activitypub.out.update.privateNote(uid, msgData); } }; diff --git a/src/messaging/edit.js b/src/messaging/edit.js index 358e73f4be..86d8b07b6c 100644 --- a/src/messaging/edit.js +++ b/src/messaging/edit.js @@ -4,7 +4,7 @@ const db = require('../database'); const meta = require('../meta'); const user = require('../user'); const plugins = require('../plugins'); -const api = require('../api'); +const activitypub = require('../activitypub'); const privileges = require('../privileges'); const utils = require('../utils'); @@ -39,7 +39,7 @@ module.exports = function (Messaging) { }); if (!isPublic && utils.isNumber(messages[0].fromuid)) { - api.activitypub.update.privateNote({ uid: messages[0].fromuid }, { messageObj: messages[0] }); + activitypub.out.update.privateNote(messages[0].fromuid, messages[0]); } } diff --git a/src/messaging/notifications.js b/src/messaging/notifications.js index 58611b6bcf..1c9475608d 100644 --- a/src/messaging/notifications.js +++ b/src/messaging/notifications.js @@ -8,7 +8,7 @@ const db = require('../database'); const notifications = require('../notifications'); const user = require('../user'); const io = require('../socket.io'); -const api = require('../api'); +const activitypub = require('../activitypub'); const plugins = require('../plugins'); const utils = require('../utils'); @@ -83,7 +83,7 @@ module.exports = function (Messaging) { await Promise.all([ sendNotification(fromUid, roomId, messageObj), !isPublic && utils.isNumber(fromUid) ? - api.activitypub.create.privateNote({ uid: fromUid }, { messageObj }) : null, + activitypub.out.create.privateNote(messageObj) : null, ]); } catch (err) { winston.error(`[messaging/notifications] Unabled to send notification\n${err.stack}`); diff --git a/src/middleware/render.js b/src/middleware/render.js index 9741585428..c56a0d526b 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -45,7 +45,7 @@ module.exports = function (middleware) { options.loggedInUser = await getLoggedInUser(req); options.relative_path = relative_path; options.template = { name: template, [template]: true }; - options.url = (req.baseUrl + req.path.replace(/^\/api/, '')); + options.url = options.url || (req.baseUrl + req.path.replace(/^\/api/, '')); options.bodyClass = helpers.buildBodyClass(req, res, options); if (req.loggedIn) { diff --git a/src/posts/create.js b/src/posts/create.js index 656ae68ab0..064e337a94 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -7,7 +7,6 @@ const user = require('../user'); const topics = require('../topics'); const categories = require('../categories'); const groups = require('../groups'); -const privileges = require('../privileges'); const activitypub = require('../activitypub'); const utils = require('../utils'); @@ -18,13 +17,14 @@ module.exports = function (Posts) { const content = data.content.toString(); const timestamp = data.timestamp || Date.now(); const isMain = data.isMain || false; + let hasAttachment = false; if (!uid && parseInt(uid, 10) !== 0) { throw new Error('[[error:invalid-uid]]'); } - if (data.toPid) { - await checkToPid(data.toPid, uid); + if (data.toPid && !utils.isNumber(data.toPid) && !activitypub.helpers.isUri(data.toPid)) { + throw new Error('[[error:invalid-pid]]'); } const pid = data.pid || await db.incrObjectField('global', 'nextPid'); @@ -46,23 +46,25 @@ module.exports = function (Posts) { if (_activitypub.audience) { postData.audience = _activitypub.audience; } - } - // Rewrite emoji references to inline image assets - if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) { - _activitypub.tag - .filter(tag => tag.type === 'Emoji' && - tag.icon && tag.icon.type === 'Image') - .forEach((tag) => { - if (!tag.name.startsWith(':')) { - tag.name = `:${tag.name}`; - } - if (!tag.name.endsWith(':')) { - tag.name = `${tag.name}:`; - } + // Rewrite emoji references to inline image assets + if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) { + _activitypub.tag + .filter(tag => tag.type === 'Emoji' && + tag.icon && tag.icon.type === 'Image') + .forEach((tag) => { + if (!tag.name.startsWith(':')) { + tag.name = `:${tag.name}`; + } + if (!tag.name.endsWith(':')) { + tag.name = `${tag.name}:`; + } - postData.content = postData.content.replace(new RegExp(tag.name, 'g'), ``); - }); + postData.content = postData.content.replace(new RegExp(tag.name, 'g'), ``); + }); + } + + hasAttachment = _activitypub && _activitypub.attachment && _activitypub.attachment.length; } ({ post: postData } = await plugins.hooks.fire('filter:post.create', { post: postData, data: data })); @@ -79,7 +81,8 @@ module.exports = function (Posts) { categories.onNewPostMade(topicData.cid, topicData.pinned, postData), groups.onNewPostMade(postData), addReplyTo(postData, timestamp), - Posts.uploads.sync(postData.pid), + Posts.uploads.sync(pid), + hasAttachment ? Posts.attachments.update(pid, _activitypub.attachment) : null, ]); const result = await plugins.hooks.fire('filter:post.get', { post: postData, uid: data.uid }); @@ -97,19 +100,4 @@ module.exports = function (Posts) { db.incrObjectField(`post:${postData.toPid}`, 'replies'), ]); } - - async function checkToPid(toPid, uid) { - if (!utils.isNumber(toPid) && !activitypub.helpers.isUri(toPid)) { - throw new Error('[[error:invalid-pid]]'); - } - - const [toPost, canViewToPid] = await Promise.all([ - Posts.getPostFields(toPid, ['pid', 'deleted']), - privileges.posts.can('posts:view_deleted', toPid, uid), - ]); - const toPidExists = !!toPost.pid; - if (!toPidExists || (toPost.deleted && !canViewToPid)) { - throw new Error('[[error:invalid-pid]]'); - } - } }; diff --git a/src/posts/delete.js b/src/posts/delete.js index 5668ac3750..ba637ccff5 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -34,6 +34,7 @@ module.exports = function (Posts) { await Promise.all([ topics.updateLastPostTimeFromLastPid(postData.tid), topics.updateTeaser(postData.tid), + isDeleting ? activitypub.notes.delete(pid) : null, isDeleting ? db.sortedSetRemove(`cid:${topicData.cid}:pids`, pid) : db.sortedSetAdd(`cid:${topicData.cid}:pids`, postData.timestamp, pid), @@ -196,20 +197,15 @@ module.exports = function (Posts) { } async function deleteFromReplies(postData) { - const arrayOfReplyPids = await db.getSortedSetsMembers(postData.map(p => `pid:${p.pid}:replies`)); - const allReplyPids = _.flatten(arrayOfReplyPids); - const promises = [ - db.deleteObjectFields( - allReplyPids.map(pid => `post:${pid}`), ['toPid'] - ), - db.deleteAll(postData.map(p => `pid:${p.pid}:replies`)), - ]; + // Any replies to deleted posts will retain toPid reference (gh#13527) + await db.deleteAll(postData.map(p => `pid:${p.pid}:replies`)); + // Remove post(s) from parents' replies zsets const postsWithParents = postData.filter(p => parseInt(p.toPid, 10)); const bulkRemove = postsWithParents.map(p => [`pid:${p.toPid}:replies`, p.pid]); - promises.push(db.sortedSetRemoveBulk(bulkRemove)); - await Promise.all(promises); + await db.sortedSetRemoveBulk(bulkRemove); + // Recalculate reply count const parentPids = _.uniq(postsWithParents.map(p => p.toPid)); const counts = await db.sortedSetsCard(parentPids.map(pid => `pid:${pid}:replies`)); await db.setObjectBulk(parentPids.map((pid, index) => [`post:${pid}`, { replies: counts[index] }])); diff --git a/src/topics/create.js b/src/topics/create.js index 8f347c736a..098fba2d41 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -248,7 +248,7 @@ module.exports = function (Topics) { async function onNewPost({ pid, tid, uid: postOwner }, { uid, handle }) { const [[postData], [userInfo]] = await Promise.all([ - posts.getPostSummaryByPids([pid], uid, {}), + posts.getPostSummaryByPids([pid], uid, { extraFields: ['attachments'] }), posts.getUserInfoForPosts([postOwner], uid), ]); await Promise.all([ diff --git a/src/topics/delete.js b/src/topics/delete.js index e97fd0a98e..466d25a0dd 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -8,6 +8,7 @@ const categories = require('../categories'); const flags = require('../flags'); const plugins = require('../plugins'); const batch = require('../batch'); +const activitypub = require('../activitypub'); const utils = require('../utils'); module.exports = function (Topics) { @@ -24,6 +25,7 @@ module.exports = function (Topics) { deleterUid: uid, deletedTimestamp: Date.now(), }), + activitypub.out.remove.context(uid, tid), ]); await categories.updateRecentTidForCid(cid); diff --git a/src/topics/posts.js b/src/topics/posts.js index 8201bcad02..a8535939e9 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -209,7 +209,7 @@ module.exports = function (Topics) { parentPost.content = foundPost.content; return; } - parentPost = await posts.parsePost(parentPost); + await posts.parsePost(parentPost); })); const parents = {}; @@ -227,7 +227,7 @@ module.exports = function (Topics) { }); postData.forEach((post) => { - if (parents[post.toPid]) { + if (parents[post.toPid] && parents[post.toPid].content) { post.parent = parents[post.toPid]; } }); diff --git a/src/topics/scheduled.js b/src/topics/scheduled.js index 1134ae1dfa..a20109a50d 100644 --- a/src/topics/scheduled.js +++ b/src/topics/scheduled.js @@ -11,7 +11,7 @@ const topics = require('./index'); const categories = require('../categories'); const groups = require('../groups'); const user = require('../user'); -const api = require('../api'); +const activitypub = require('../activitypub'); const plugins = require('../plugins'); const Scheduled = module.exports; @@ -175,7 +175,7 @@ function federatePosts(uids, topicData) { topicData.forEach(({ mainPid: pid }, idx) => { const uid = uids[idx]; - api.activitypub.create.note({ uid }, { pid }); + activitypub.out.create.note(uid, pid); }); } diff --git a/src/topics/thumbs.js b/src/topics/thumbs.js index 22fa2c48bd..3239ab0fdc 100644 --- a/src/topics/thumbs.js +++ b/src/topics/thumbs.js @@ -33,8 +33,9 @@ Thumbs.load = async function (topicData) { const topicsWithThumbs = topicData.filter((tid, idx) => hasThumbs[idx]); const tidsWithThumbs = topicsWithThumbs.map(t => t.tid); - - const thumbs = await loadFromTopicData(topicsWithThumbs); + const thumbs = await loadFromTopicData(topicsWithThumbs, { + thumbsOnly: meta.config.showPostUploadsAsThumbnails !== 1, + }); const tidToThumbs = _.zipObject(tidsWithThumbs, thumbs); return topicData.map(t => (t && t.tid ? (tidToThumbs[t.tid] || []) : [])); diff --git a/src/topics/tools.js b/src/topics/tools.js index 294615b38a..f4ac42e3dd 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -283,9 +283,7 @@ module.exports = function (Topics) { oldCid: oldCid, }), Topics.updateCategoryTagsCount([oldCid, cid], tags), - oldCid !== -1 ? - Topics.events.log(tid, { type: 'move', uid: data.uid, fromCid: oldCid }) : - topicTools.share(tid, data.uid), + Topics.events.log(tid, { type: 'move', uid: data.uid, fromCid: oldCid }), ]); // Update entry in recent topics zset — must come after hash update diff --git a/src/user/categories.js b/src/user/categories.js index 09356eac5b..4248def8bd 100644 --- a/src/user/categories.js +++ b/src/user/categories.js @@ -5,8 +5,8 @@ const _ = require('lodash'); const db = require('../database'); const meta = require('../meta'); const categories = require('../categories'); +const activitypub = require('../activitypub'); const plugins = require('../plugins'); -const api = require('../api'); const utils = require('../utils'); module.exports = function (User) { @@ -30,12 +30,8 @@ module.exports = function (User) { throw new Error('[[error:no-category]]'); } - const apiMethod = state >= categories.watchStates.tracking ? 'follow' : 'unfollow'; - const follows = cids.filter(cid => !utils.isNumber(cid)).map(cid => api.activitypub[apiMethod]({ uid }, { - type: 'uid', - id: uid, - actor: cid, - })); // returns promises + const apiMethod = state >= categories.watchStates.tracking ? activitypub.out.follow : activitypub.out.undo.follow; + const follows = cids.filter(cid => !utils.isNumber(cid)).map(cid => apiMethod('uid', uid, cid)); // returns promises await Promise.all([ db.sortedSetsAdd(cids.map(cid => `cid:${cid}:uid:watch:state`), state, uid), diff --git a/src/user/profile.js b/src/user/profile.js index 3009d0a3d5..ec4a23d3a3 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -11,7 +11,7 @@ const meta = require('../meta'); const db = require('../database'); const groups = require('../groups'); const plugins = require('../plugins'); -const api = require('../api'); +const activitypub = require('../activitypub'); const tx = require('../translator'); module.exports = function (User) { @@ -68,7 +68,7 @@ module.exports = function (User) { fields: fields, oldData: oldData, }); - api.activitypub.update.profile({ uid }, { uid: updateUid }); + activitypub.out.update.profile(updateUid, uid); return await User.getUserFields(updateUid, [ 'email', 'username', 'userslug', diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl index b935638ac4..19b0efa116 100644 --- a/src/views/admin/manage/category.tpl +++ b/src/views/admin/manage/category.tpl @@ -36,16 +36,6 @@ -
- - -

- [[admin/manage/categories:federatedDescription.help]] -

-
-
+
+ + +
+
diff --git a/test/topics.js b/test/topics.js index 7136280339..fb3f9cb835 100644 --- a/test/topics.js +++ b/test/topics.js @@ -314,51 +314,6 @@ describe('Topic\'s', () => { }); }); - it('should fail to create new reply with toPid that has been purged', async () => { - const { postData } = await topics.post({ - uid: topic.userId, - cid: topic.categoryId, - title: utils.generateUUID(), - content: utils.generateUUID(), - }); - await posts.purge(postData.pid, topic.userId); - - await assert.rejects( - topics.reply({ uid: topic.userId, content: 'test post', tid: postData.topic.tid, toPid: postData.pid }), - { message: '[[error:invalid-pid]]' } - ); - }); - - it('should fail to create a new reply with toPid that has been deleted (user cannot view_deleted)', async () => { - const { postData } = await topics.post({ - uid: topic.userId, - cid: topic.categoryId, - title: utils.generateUUID(), - content: utils.generateUUID(), - }); - await posts.delete(postData.pid, topic.userId); - const uid = await User.create({ username: utils.generateUUID().slice(0, 10) }); - - await assert.rejects( - topics.reply({ uid, content: 'test post', tid: postData.topic.tid, toPid: postData.pid }), - { message: '[[error:invalid-pid]]' } - ); - }); - - it('should properly create a new reply with toPid that has been deleted (user\'s own deleted post)', async () => { - const { postData } = await topics.post({ - uid: topic.userId, - cid: topic.categoryId, - title: utils.generateUUID(), - content: utils.generateUUID(), - }); - await posts.delete(postData.pid, topic.userId); - const uid = await User.create({ username: utils.generateUUID().slice(0, 10) }); - - const { pid } = await topics.reply({ uid: topic.userId, content: 'test post', tid: postData.topic.tid, toPid: postData.pid }); - assert(pid); - }); - it('should delete nested relies properly', async () => { const result = await topics.post({ uid: fooUid, title: 'nested test', content: 'main post', cid: topic.categoryId }); const reply1 = await topics.reply({ uid: fooUid, content: 'reply post 1', tid: result.topicData.tid }); @@ -372,7 +327,7 @@ describe('Topic\'s', () => { replies = await apiPosts.getReplies({ uid: fooUid }, { pid: reply1.pid }); assert.strictEqual(replies, null); toPid = await posts.getPostField(reply2.pid, 'toPid'); - assert.strictEqual(toPid, null); + assert.strictEqual(parseInt(toPid, 10), parseInt(reply1.pid, 10)); }); });